02-封装,继承,多态

1. 封装

  • 概念:尽可能隐藏对象的内部实现细节,控制对象的修改和访问权限
  • 访问修饰符: private (可将属性修饰为私有,仅本类可见)
    • get/set方法是外界访问对象私有属性的唯一通道,方法内部对属性检测和过滤
    • 提供public公共访问方法,以保证数据可以正常录入和访问
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
public class TestEncapsulation2 {
public static void main(String[] args) {
Teacher t1 = new Teacher();

t1.setName("Jack");
t1.setAge(25);
t1.setSex("male");
t1.setSalary(8000.0);
System.out.println(t1.getName());
System.out.println(t1.getAge());
System.out.println(t1.getSex());
System.out.println(t1.getSalary());

}
}
class Teacher {
private String name;
private int age;
private String sex;
private double salary;

public Teacher() {}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}

2. 继承

  • 程序中的继承,是类与类之间特征和行为的一种赠予或获得。
  • 类与类之间必须满足 is a 的关系。

父类的选择

  • 功能越精细,重合点越多的,就越接近直接父类。

父类的抽象

  • 根据程序需要使用到的多个具体类,进行共性提取,进而定义父类。
  • 在一组相同或类似的类中,抽取特征和行为,定义在父类中,实现重用。

继承语法

  • class 子类名 extends 父类名{ } //定义子类时,显式定义父类
  • 产生继承关系之后,子类可以使用父类的属性和方法,也可以定义子类独有的属性和方法。

完整的子类

  • 完整子类 = 父类共性 + 子类独有
  • 好处:提高代码的复用性,又提高代码的可扩展性
  • Java为单继承,一个类只能有一个直接父类,但可以多级继承,属性和方法逐级叠加。

不可继承

  • 构造方法:类中的构造方法,只负责创建本类对象,不可继承;
  • private修饰的属性/方法:仅本类可见,不可继承;
  • 父子类不在同一个package中,default修饰的属性和方法,不可继承;

点击并拖拽以移动

  • 汇总:

    • 本类都可访问
    • public都可访问
    • default同包内可以访问
    • protected非同包子类,有继承关系
  • 跨包访问

    • 写全限定名(包名.类名),eg: java.util.Arrays.copyOf(…);
    • 导包(import),eg: import java.util.Arrays;

方法的覆盖

  • 当父类提供的方法无法满足子类的需求时,可在子类中定义和父类相同的方法进行覆盖(Override)
  • 方法覆盖原则:
    • 方法名称、参数列表、返回值类型必须与父类相同;
    • 访问修饰符应与父类相同或更宽泛;
  • 执行机制:子类覆盖父类方法后,调用时优先执行子类覆盖后的方法;

super 关键字

  • 子类去访问父类重名的属性或方法时(属性遮蔽/方法覆盖),使用super.专项访问
  • super() 默认调用父类无参构造方法,隐式存在
  • super(参数) 指定调用父类有参构造方法,显式调用
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
public class TestSuper {
public static void main(String[] args) {
Son s = new Son();
s.method();
}
}
class Father {
int field = 10;

public void method() {
System.out.println("Father-method()");
}
}
class Son extends Father {
int field = 20;

public void method() {
int field = 30;
System.out.println(field);//30
System.out.println(super.field); //10,子类与父类重名时,super修饰为父类的属性
System.out.println(this.field); //20

super.method(); // 子类与父类方法重名时,super访问父类的同名方法,且不能省略
System.out.println("Son-method()"); // 再叠加额外的功能代码,组成新的功能
}
}
输出:
30
10
20
Father-method()
Son-method()

点击并拖拽以移动

  • this与super

    • 同一个子类构造方法中,super()和this()不可同时存在
    • 当子类构造中使用了this()或this(实参),即不可再同时书写super()或super(实参),会由this()指向的构造方法完成super()的调用
  • 继承关系下的对象创建:

    • 继承关系下构建子类对象时,会先构建父类对象
    • 由”父类共性” + “子类独有” 组合成一个完整的子类对象
  • 继承关系下的对象创建流程

    • ① 构建父类对象
    • ② 初始化自身属性
    • ③ 执行自身构造方法中的逻辑代码
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
/* 继承关系下的对象创建流程 */
public class TestCreateSort { // extends Object 隐式继承存在
public static void main(String[] args) {
new C();

System.out.println("end...");
}
}
class A { // extends Object 隐式继承存在
// [总]第 1 步
String fieldA = "A:field"; // ① 初始化属性

// [总]第 2 步
public A () {
System.out.println(fieldA); // 非null
System.out.println("--- A() ---"); // ② 执行构造方法中的逻辑代码
}
}
class B extends A {
// [总]第 3 步
String fieldB = "B:field"; // ② 初始化属性

// [总]第 4 步
public B () {
super();// ① 调用父类的构造方法(默认调用父类无参构造方法) super()写不写都隐式存在于构造方法的首行
System.out.println(fieldB); // 非null
System.out.println("--- B() ---"); // ③ 执行构造方法中的逻辑代码
}
}
class C extends B {
// [总]第 5 步
String fieldC = "C:field"; // ② 初始化属性

// [总]第 6 步
public C () {
super();// ① 调用父类的构造方法(默认调用父类无参构造方法) super()写不写都隐式存在于构造方法的首行
System.out.println(fieldC); // 非null
System.out.println("--- C() ---"); // ③ 执行构造方法中的逻辑代码
}
}
输出:
A:field
--- A() ---
B:field
--- B() ---
C:field
--- C() ---
end...

3. 多态

  • 概念:父类引用指向子类对象,从而产生多种形态。

​ eg: Animal a = new Dog();

  • 二者具有直接或者间接的继承关系时,父类引用可指向子类对象,形成多态。
  • 父类引用仅可调用父类所声明的属性和方法,不可调用子类独有的属性和方法。

多态的两种应用场景

  • 场景① 父类类型引用作为方法的形参,实现多态,使方法参数的类型更为宽泛 (该父类的任何一个子类均可作为实参传入)
  • 场景② 使用父类类型作为方法返回值,实现多态,使方法可以可以返回不同的子类对象
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
public class TestApplyPolymorphic {
public static void main(String[] args) {
// 普通:自身引用指向自身对象
Car car = new Car("小汽车", 45, 160000, "领克02"); // this.brand = "领克02" 为子类独有属性
car.run();
// 多态:父类引用指向子类对象
Vehicle veh = new Car("小汽车", 50, 180000, "领克01");
veh.run(); // 方法覆盖 优先

System.out.println("\n****************多态①:父类类型引用作为【形参】****************");

Bus bus = new Bus("公交车", 60, 1000000, 55);
Bicycle bic = new Bicycle("自行车", 15, 300, "蓝色");
Employee emp = new Employee("工程师007");
emp.goHome(car);
emp.goHome(bus);
emp.goHome(bic);

System.out.println("\n****************多态②:父类类型引用作为【返回值】****************");
Employee emp2 = new Employee("工程师996");
Vehicle myVeh = emp2.buyVehicle(50); // 价格可能由用户任意输入 >> 多态② >> 多态①
emp2.goHome(myVeh);
System.out.println("\n****************多态[向上造型](装箱):父类引用中保存真实子类对象****************");
// 多态:父类引用指向子类对象
Vehicle veh1 = new Car("小汽车", 80, 500000, "BMW X5");
veh1.run();

System.out.println("\n****************多态[向下造型](拆箱):父类引用强转回子类本身类型****************");
// 取决于 line:24 行价格决定此时访问子类独有时是哪个子类
if (myVeh instanceof Car) {
Car myCar = (Car) myVeh;
System.out.println("汽车独有品牌:"+ myCar.brand);
// Bus myBus = (Bus) myVeh; // 运行则会抛出类型转换异常:java.lang.ClassCastException
// System.out.println("公交独有座数:" + myBus.seatNum);
} else if (myVeh instanceof Bus) {
Bus myBus = (Bus) myVeh;
System.out.println("公交独有座数:" + myBus.seatNum);
} else if (myVeh instanceof Bicycle) {
Bicycle myBic = (Bicycle) myVeh;
System.out.println("自行车独颜色:" + myBic.color);
}
}
}
// 员工
class Employee {
String name;

public Employee() { }

public Employee(String name) {
this.name = name;
}
/**
* 多态场景①:父类类型作为方法的【形参】,实现多态 (该父类任一个子类均可作为实参传入)
* @param 父类类型引用
*/
public void goHome(Vehicle veh) {
System.out.print(name + "乘坐: ");
veh.run(); // 父类依然遵循子类的覆盖方法优先,因此会打出来带子类方法的那句输出
}

/**
* 多态场景②:父类类型作为方法【返回值】,实现多态,使方法可以可以返回不同的子类对象
* @return 父类类型引用
*/
public Vehicle buyVehicle(int money) { //money:万
Vehicle veh = null; // 方法返回值,父类引用可存储不同子类对象的地址

if (money >= 100) {
veh = new Bus("公交车", 66, 1100000, 50);
} else if (money >= 30) {
veh = new Car("小汽车", 55, 150000, "领克03");
} else {
veh = new Bicycle("自行车", 20, 500, "黄色");
}
return veh;
}

/*
public void goHome(Bus bus) { // 耦合高(模块与模块之间的关联程度,即耦合高=很松散)
System.out.print(name + "正在乘坐");
bus.run();
}

// 类内方法重载
public void goHome(Bicyle bic) {
System.out.print(name + "正在乘坐");
bic.run();
}
*/
}
// 交通工具
class Vehicle {
String type; // 小汽车,公交车,自行车
int speed;
double price;

public Vehicle() {}
public Vehicle(String type, int speed, double price) {
super();
this.type = type;
this.speed = speed;
this.price = price;
}
public void run() {
System.out.println("一辆价值" + price + "RMB的" + type + "正在以" + speed + "/H速度前进...");
}
}
class Car extends Vehicle {
String brand;

public Car() {
super();
}
public Car(String type, int speed, double price, String brand) {
super(type, speed, price);
this.brand = brand;
}
public void run() {
System.out.println("一辆价值" + price + "RMB的" + brand + "品牌的" + type + "正在以" + speed + "/H速度前进...");
}
}
class Bus extends Vehicle {
int seatNum;

public Bus() {
super();
}
public Bus(String type, int speed, double price, int seatNum) {
super(type, speed, price);
this.seatNum = seatNum;
}
public void run() {
System.out.println("一辆价值" + price + "RMB的" + seatNum + "座的" + type + "正在以" + speed + "/H速度前进...");
}
}
class Bicycle extends Vehicle {
String color;

public Bicycle() {
super();
}
public Bicycle(String type, int speed, double price, String color) {
super(type, speed, price);
this.color = color;
}
public void run() {
System.out.println("一辆价值" + price + "RMB的" + color + "的" + type + "正在以" + speed + "/H速度前进...");
}
}
输出:
一辆价值160000.0RMB的领克02品牌的小汽车正在以45/H速度前进...
一辆价值180000.0RMB的领克01品牌的小汽车正在以50/H速度前进...
****************多态①:父类类型引用作为【形参】****************
工程师007乘坐: 一辆价值160000.0RMB的领克02品牌的小汽车正在以45/H速度前进...
工程师007乘坐: 一辆价值1000000.0RMB的55座的公交车正在以60/H速度前进...
工程师007乘坐: 一辆价值300.0RMB的蓝色的自行车正在以15/H速度前进...
****************多态②:父类类型引用作为【返回值】****************
工程师996乘坐: 一辆价值150000.0RMB的领克03品牌的小汽车正在以55/H速度前进...
****************多态[向上造型](装箱):父类引用中保存真实子类对象****************
一辆价值500000.0RMB的BMW X5品牌的小汽车正在以80/H速度前进...
****************多态[向下造型](拆箱):父类引用强转回子类本身类型****************
汽车独有品牌:领克03

向上转型(装箱) - <多态核心概念>

  • 父类引用中保存真实子类对象。
  • eg: Animal a = new Dog(); // 对象层面的自动类型转换
  • 仅可调用父类中所声明的属性和方法(遵循属性遮蔽/方法覆盖原则)

向下转型(拆箱)

  • 将父类引用中的真实子类对象,强转回子类本身类型。

  • eg: Animal a = new Dog();

  • ​ Dog dog = (Dog)a; // 对象层面的强制类型转换

    ​ Cat cat = (Cat)a; // Error: 不是真实子类对象new Dog(),编译OK,运行则会抛出类型转换异常:java.lang.ClassCastException

  • 只有转回子类真实类型,才可调用子类独有的属性和方法。

  • 向下转型前应该判断引用中的对象真实类型,保证类型转换的正确性

    • 语法: 父类引用 instanceof 类型 // 返回boolean类型结果
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/* 从动物对象数组中找到目标动物子类 */
public class TestAnimal {
public static void main(String[] args) {
Animal[] as = new Animal[] {
new Dog("Pluto"),
new Cat("Tom"),
new Dog("Snoopy"),
new Cat("Garfield"),
new Dog("XiaoHei"),
new Cat("XiaoPang"),
};

for (int i = 0; i < as.length; i++) {
System.out.println(as[i].getName());
}

System.out.println("-------------------------------------");

Dog[] dogs = getAllDog(as);
for (int i = 0; i < dogs.length; i++) {
System.out.print(dogs[i].getName() + " ");
}
}

/**
* 返回指定相同子类的对象数组
* @param as 父类动物对象数组
* @return Dog[] 一组Dog对象
*/
public static Dog[] getAllDog(Animal[] as) {
// 0.计数器
int count = 0;

// 1.先数一遍要查找的同类子类对象数量
for (int i = 0; i < as.length; i++) {
// 1.1 对数组中元素的类型进行判断
if (as[i] instanceof Dog) {
// 1.2 每找到一个,计数器自增+1
count++;
}
}

// 2.根据计数器结果,创建适合长度的目标数组
Dog[] dogs = new Dog[count]; // 时间与空间的平衡、效率与安全的平衡

// 3.定义一个目标数组有效元素个数
int size = 0;

// 4.将所有的目标同类子类对象,保存在目标数组中
for (int i = 0; i < as.length; i++) {
// 4.1 判断是否为目标子类类型
if (as[i] instanceof Dog) {
// 4.2 保存子类对象到数组中 (多态-向下转型(拆箱)需强转)
dogs[size++] = (Dog) as[i];
//dogs[--count] = (Dog) as[i]; // 也可以,倒序
}
}

// 5.返回目标数组
return dogs;
}
}
class Animal {
private String name;
public Animal() {
super();
}
public Animal(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class Dog extends Animal {
public Dog() {
super();
}

public Dog(String name) {
super(name);
}
}
class Cat extends Animal {
public Cat() {
super();
}

public Cat(String name) {
super(name);
}
}

02-封装,继承,多态
https://janycode.github.io/2016/04/28/02_编程语言/01_Java/01_JavaSE/02_面向对象/02-封装,继承,多态/
作者
Jerry(姜源)
发布于
2016年4月28日
许可协议