Fork me on GitHub

面向对象基础

面向对象三大特征:封装、继承、多态。

类与对象

基本知识

类的修饰符:public、final、abstract或完全省略

类的成员:构造器、成员变量、方法、初始化块、内部类(接口、枚举类)

类的作用:

  • 定义变量
  • 创建对象
  • 调用类的类方法或访问类的类变量

static修饰的成员不能访问没有static修饰的成员。

static修饰的成员表明它属于这个类本身,而不属于该类的单个实例,因此通常把static修饰的成员变量和方法称为类变量、类方法。

类是一种引用数据类型,引用变量存放在栈内存中,真正的对象存放在堆内存中。

当程序访问引用变量的成员变量或方法时,实际上访问的是该变量所引用的对象的成员变量或方法。

如果一个java源文件里定义了一个public修饰的类(接口、枚举类),则这个源文件的文件名必须与public修饰的类(接口、枚举类)的名称相同。

对象的this引用

this出现的两种情形:

  • 构造器中引用该构造器正在初始化的对象
  • 在方法中引用调用该方法的对象

当this出现在某个方法体中时,它所代表的对象是不确定的,但它的类型是确定的,即只能是当前类。只有当这个方法被调用时,它所代表的对象才确定下来,谁在调用这个方法,this就代表谁。

1
2
3
public void run(){
this.jump();
}

java允许对象的一个成员直接调用另一个成员,可以省略this前缀。

1
2
3
public void run(){
jump();
}

static修饰的方法中不能使用this引用。

如果调用static修饰的成员时省略了前面的主调,则默认使用该类作为主调;如果调用没有static修饰的成员时省略了前面的主调,则默认使用this作为主调。

如果方法(构造器)中有个局部变量与成员变量同名,但程序又需要在该方法(构造器)里访问这个被覆盖的成员变量,则必须使用this前缀。

如果在某个方法中把this当作返回值,则可以多次连续调用同一个方法,从而使代码更简洁,但可能造成实际意义的模糊。

1
2
3
4
5
6
7
8
9
10
11
12
public class ReturnThis{
public int age;
public ReturnThis grow(){
age++;
return this;
}
public static void main(String[] args){
ReturnThis a = new ReturnThis();
a.grow().grow().grow();
System.out.println(a.age);//输出3
}
}

成员变量与局部变量

mark

成员变量的修饰符:public、protected、private、static、final

其中public、protected、private最多出现一个。

类变量从该类的准备阶段开始存在,直到系统完全销毁这个类,类变量的作用域与这个类的生存范围相同,而实例变量则从实例被创建起开始存在,直到系统完全销毁这个实现,作用域与对应的实例的生存范围相同。

局部变量除了形参外,都必须显式初始化。

局部变量保存在其所在方法的栈内存中,成员变量保存在其所在对象的堆内存中。

方法

方法的修饰符:public、protected、private、static、final、abstract

其中public、protected、private最多出现一个,final、abstract最多出现一个。

java中方法的参数传递方式只有一个:值传递。

形参个数可变的方法

在最后一个形参的类型后加三个点,表明该形参可以接受多个参数值,多个参数值被当成数组传入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class OverloadVarargs{
public static void main(String[] args){
OverloadVarargs olv = new OverloadVarargs();

olv.test();
olv.test("aa","bb");
olv.test("aa");
olv.test(new String[]{"aa"});
//若想调用形参可变的方法,又只想传入一个参数,可传入字符串数组
}

public void test(String msg){
System.out.println(111111111);
}

public void test(String... books){
System.out.println(222222222);
}
}
/*输出:
222222222
222222222
111111111
222222222
*/

这个长度可变的参数既可传入多个参数,也可传入一个数组。

数组形式的形参可以处于形参列表的任何位置,但个数可变的形参只能处于形参列表的最后。一个方法中最多只能有一个长度可变的形参。

方法重载

两同一不同:同一个类中方法名相同,但形参列表不同。

方法的其他部分,如返回值类型、修饰符等,与方法重载无任何关系。

构造器

构造器的修饰符:public、protected、private三者之一。

构造器既不能定义返回类型,也不能使用void声明没有返回值。(构造器的返回值总是当前类,是隐式的,无须也无法指定)。

构造器是一个类创建对象的根本途径,如果一个类没有构造器,则这个类通常无法创建对象。

如果程序员没有为一个类编写构造器,则系统会为该类提供一个默认的构造器;一旦程序员为一个类提供了构造器,系统将不再为该类提供构造器。

java通过new来调用构造器,从而返回该类的实例。

构造器名必须与类名相同。

初始化块

相同类型的初始化块之间有顺序,前面定义的初始化块先执行,后面定义的初始化块后执行。

初始化块的修饰符只能是static。

普通初始化块、声明实例变量指定的默认值都可认为是对象的初始化代码,它们的执行顺序与源程序中的排列顺序相同。

静态初始化块与声明类变量时所指定的初始值都是该类的初始化代码,它们的执行顺序与源程序中的排列顺序相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class InstanceInitTest{

static int c = 5;
static {
c = 8;
d = 7;
}
static int d = 0;

int b = 3;
{
a = 6;
b = 4;
}
int a = 9;

public static void main(String[] args){
System.out.println(new InstanceInitTest().a);//输出9
System.out.println(new InstanceInitTest().b);//输出4
System.out.println(InstanceInitTest.c);//输出8
System.out.println(InstanceInitTest.d);//输出0
}
}

普通初始化块负责对对象执行初始化,静态初始化块负责对类进行初始化。

静态初始化块总是比普通初始化块优先执行。(因为它们是在类初始化阶段加载的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class Root{
static{
System.out.println("Root的静态初始化块");
}

{
System.out.println("Root的普通初始化块");
}

public Root(){
System.out.println("Root的无参数构造器");
}
}

class Mid extends Root{
static{
System.out.println("Mid的静态初始化块");
}

{
System.out.println("Mid的普通初始化块");
}

public Mid(){
System.out.println("Mid的无参数构造器");
}

public Mid(String msg){
System.out.println("Mid的带参数构造器,其初始值为:" + msg);
}
}

class Leaf extends Mid{
static{
System.out.println("Leaf的静态初始化块");
}

{
System.out.println("Leaf的普通初始化块");
}

public Leaf(){
super("疯狂java讲义");
//先执行super指定的父类构造器,再执行普通初始化块,最后执行子类的构造器
System.out.println("执行Leaf的构造器");
}
}

public class Test{
public static void main(String[] args){
new Leaf();
System.out.println("-------------");
new Leaf();
}
}
/*输出:
Root的静态初始化块
Mid的静态初始化块
Leaf的静态初始化块
Root的普通初始化块
Root的无参数构造器
Mid的普通初始化块
Mid的带参数构造器,其初始值为:疯狂java讲义
Leaf的普通初始化块
执行Leaf的构造器
-------------
Root的普通初始化块
Root的无参数构造器
Mid的普通初始化块
Mid的带参数构造器,其初始值为:疯狂java讲义
Leaf的普通初始化块
执行Leaf的构造器
*/

封装

将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作与访问。

访问级别:mark

通常使用protected来修饰一个方法表示希望其子类来重写该方法。

通常要将实例变量设为private,再提供对应的getter、setter方法来访问。

包名全部是小写字母。

位于包中的类,在文件系统中也必须有与包名层次相同的目录结构。

package语句必须作为源文件的第一条非注释语句。

父包与子包在用法上不存在任何关系,如果父包中的类需要使用子包中的类,则必须使用子包的全名,而不能省略父包部分。

import可以向某个java文件中导入指定包层次下的某个类或全部类(使用*),从而省略写包名。

import static可以导入指定类的某个静态成员变量、方法或全部(使用*),从而省略写类名。

*只能代表类,不能代表包,若需导入子包则需另外导入。

继承

java没有多继承,不过可以通过实现多接口来近似代替。

java子类不能获得父类的构造器。

java.lang.Object是所有类的父类。

方法重写:子类包含与父类同名的方法。

两同两小一大:

  • 方法名、形参列表相同
  • 子类方法的返回值类型与父类方法的返回值类型相等或更小
  • 子类方法声明抛出的异常类与父类方法声明抛出的异常类相等或更小
  • 子类方法的访问权限与父类方法的访问权限相等或更大

如果需要在子类方法中调用父类中被覆盖的方法,则可以使用super(实例方法)或父类类名(类方法)作为调用者。

如果父类方法具有private访问权限,则子类无法访问与重写该方法,但可以重新定义一个相同或相似的新方法。

如果子类定义了一个与父类方法有相同的方法名,但参数列表不同的方法,就会形成父类方法与子类方法的重载。

系统在创建子类对象时,依然会为父类中定义的、被隐藏的变量分配内存空间。

super不能出现在static修饰的方法中。

调用父类的构造器

super调用的是其父类的构造器,而this调用的是同一个类中重载的构造器,因此,使用super调用父类构造器也必须出现在子类构造器执行体的第一行,所以super与this不会同时出现。

子类继承父类,会继承到父类中的数据,所以要看父类是如何对自己的数据进行初始化的,所以子类在进行对象初始化之前必须先调用父类的构造器。

故:创建任何java对象,最先执行的总是java.lang.Object类的构造器。

如果父类中没有空参数的构造器,则子类的构造器内必须通过super语句指定要访问父类中的哪一个构造器。

使用private修饰这个类的构造器,则子类无法调用该类的构造器,也就无法继承该类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Creature{
public Creature(){
System.out.println("Creature无参数构造器");
}
}

class Animal extends Creature{
public Animal(String name){
System.out.println("Animal带一个参数的构造器,该动物的name为" + name);
}

public Animal(String name, int age){
this(name);
System.out.println("Animal带两个参数的构造器,该动物的name为" + name + ",其age为" + age);
}
}

public class Wolf extends Animal{
public Wolf(){
super("灰太狼",3);
System.out.println("Wolf的无参数构造器");
}

public static void main(String[] args){
new Wolf();
}
}
/*输出:
Creature无参数构造器
Animal带一个参数的构造器,该动物的name为灰太狼
Animal带两个参数的构造器,该动物的name为灰太狼,其age为3
Wolf的无参数构造器
*/

如果父类构造器调用了被其子类重写的方法,则变成调用被子类重写后的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Base{
public Base(){
test();
}

public void test(){
System.out.println("将被子类重写的方法");
}

}

public class Sub extends Base{
private String name;
public void test(){
System.out.println("子类重写父类的方法,其name字符串长度为" + name.length());
}

public static void main(String[] args){
Base s = new Sub();
}
}
//引发空指针异常

多态

java引用变量有两种类型,一个是编译时类型,一个是运行时类型,前者由声明该变量时所用的类型决定,后者由实际赋值给该变量的对象来决定。若编译时类型与运行时类型不同则会出现多态。

多态:相同类型的变量、调用同一方法时呈现出不同的行为特征。

对象的实例变量不具有多态性。

只能调用父类中有的方法和成员变量,若父类中的方法在子类中被重写,则调用重写后的方法,若子类中有同名变量,忽略!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class BaseClass{
public int book = 9;
public void base(){
System.out.println("父类的普通方法");
}
public void test(){
System.out.println("父类被覆盖当的方法");
}
}

public class SubClass extends BaseClass{
public String book = "疯狂java讲义";
public int b = 4;
public void test(){
System.out.println("子类覆盖父类的方法");
}
public void sub(){
System.out.println("子类的普通方法");
}

public static void main(String[] args){
BaseClass a = new SubClass();
System.out.println(a.book);//输出9
// System.out.println(a.b);错误: 找不到符号
a.base();//输出父类的普通方法
a.test();//输出子类覆盖父类的方法
// a.sub();错误: 找不到符号

BaseClass c = new BaseClass();
System.out.println(a instanceof SubClass);//输出true
System.out.println(a instanceof BaseClass);//输出true
System.out.println(a instanceof Object);//输出true
System.out.println(c instanceof SubClass);//输出false
System.out.println(c instanceof BaseClass);//输出true
System.out.println(c instanceof Object);//输出true
}
}

通过引用变量来访问其包含的实例变量时,系统总是试图访问它编译类型所定义的成员变量,而不是它运行类型所定义的成员变量。

引用变量的强制类型转换

数值类型和布尔类型之间不能进行类型转换。

引用类型之间的转换必须在具有继承关系的两个类型之间进行。

如果试图把一个父类实例转换为子类类型,则这个对象必须是实际上的子类实例才行,否则会引发ClassCastException异常。

可以使用instanceof运算符判断是否可以成功转换,保证程序更健壮。

instanceof:前一个操作数为一个引用类型变量,后一个操作数为一个类(接口),判断前者的对象是后者的实例吗/前者的对象实现了后者的接口吗?

instanceof运算符前面的操作数的编译时类型要么与后面的子类相同,要么与后面的类具有父子关系,否则会编译错误。

后面的类可以是前面的对象的类的子类,不过会返回false。

例子见上。

-------------本文结束感谢您的阅读-------------
undefined