第5章 继承
5.1 类、超类和子类
关键字extends表明正在构造的新类派生于一个已存在的类。
已存在的类称为超类(superclass)、基类(base class)或父类(parent class)。
派生出来的新类称为子类(subclass)、派生类(derived class)或孩子类(child class)。
覆盖,overriding,也称重写。
发生在继承中,子类重写超类的方法,方法名、参数列表、返回值类型全部要求相同。被重写的方法,不能比超类的访问权限更严格。
注意:要与重载区分开来
子类构造器
- 使用super调用构造器的语句必须是子类构造器的第一条语句。
- 如果子类构造器没有显式调用超类的构造器,则将自动调用超类默认(没有参数)的构造器。如果超类没有无参的构造器,子类也没有调用超类的其它构造器,则Java编译器会报错。
- 一个对象变量可以指示多种实际类型的现象被称为多态。在运行时能够自动选择调用哪个方法的现象称为动态绑定。
继承层次
由一个公共超类派生出来的所有类的集合被称为继承层次。在继承层次中,从某个特定的类到其祖先的路径被称为该类的继承链。
总结
继承的基本概念
扩展父类的功能
Java中使用extends关键字完成继承
格式:
class 子类 extends 父类 { }
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
50class Person{
private String name;
private int age;
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 void say(){
System.out.println("姓名:"+this.getName()+" 年龄:"+this.getAge());
}
}
class Student extends Person{
private int score;
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public void show (){
System.out.println("成绩:"+this.getScore());
}
}
public class ExtendsDemo01 {
public static void main(String[] args) {
Student s = new Student();
s.setName("张三");
s.setAge(20);
s.setScore(99);
s.say();
s.show();
}
}继承的限制
- 在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
38
39
40
41
42
43
44
45
46
47
48class People {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
private void say() {
System.out.println("I'am a father");
}
}
class Worker extends People {
//虽不可对父类的私有成员直接访问,但可以通过其他方式进行访问,如set()和get()方法
//子类实质上是继承了父类的所有成员变量(包括私有成员变量),但私有方法并没有继承
public void tell() {
System.out.println("I'am a worker,I'am "+getAge());
}
}
class PetWorker extends Worker { //只允许单继承,不允许多继承,但可以多层继承
public void tell() {
System.out.println("I'am a petworker,I'am "+getAge());
}
}
public class ExtendsDemo02 {
public static void main(String[] args) {
Worker wor = new Worker();
wor.setAge(30);
wor.tell();
// wor.say(); //私有方法没有继承,无法使用,报错
PetWorker petWor = new PetWorker();
petWor.setAge(45);
petWor.tell();
}
}
//运行结果:
//I'am a worker,I'am 30
//I'am a petworker,I'am 45子类对象实例化过程
在子类对象实例化时,必须先调用父类中的构造方法,之后调用子类构造方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class Father {
public Father(){
System.out.println("调用父类构造方法");
}
}
class Son extends Father{
public Son() {
System.out.println("调用子类构造方法");
}
}
public class ExtendsDemo03 {
public static void main(String[] args) {
Son son = new Son();
}
}
//运行结果:
//调用父类构造方法
//调用子类构造方法
方法重写与super关键字
重写
在继承中,也存在着“重写”的概念,其实就是子类定义了和父类同名的方法。
定义:
方法名称相同,返回值类型相同,参数也相同。
限制:被子类重写的方法不能拥有比父类方法更加严格的访问权限
访问权限:private<default<public
四种修饰符的访问权限范围:
权限 类内 同包 不同包子类 不同包非子类 private √ × × × default √ √ × × protected √ √ √ × public √ √ √ √ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class A {
public void tell() {
System.out.println("我是tell方法");
}
}
class B extends A {
public void tell() { //方法名称相同,返回值类型相同,参数也相同。
super.tell(); //强行调用父类的方法执行
System.out.println("我重写了tell方法");
}
}
public class ExtendsDemo04 {
public static void main(String[] args) {
B b = new B();
b.tell();
}
}
//运行结果:
//我是tell方法
//我重写了tell方法super关键字
- 强行调用父类的方法执行
- super也可以表示那些方法是从父类中继承而来的
多态性
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
38
39
40
41
42
43
44
45
46
47class A {
public void tell1() {
System.out.println("A--tell1");
}
public void tell2() {
System.out.println("A--tell2");
}
}
class B extends A {
public void tell1() {
System.out.println("B--tell1");
}
public void tell3() {
System.out.println("B--tell3");
}
}
public class PolDemo01 {
public static void main(String[] args) {
// 向上转型
// B b = new B();
// A a = b;
// a.tell1(); //B重写了A的tell1方法,打印结果为 B--tell1
// a.tell2(); //打印结果为 A--tell2
// 向下转型
// A a = new A(); //a 和 b 没有明确关系,无法转型
// B b = (B)a; //运行出错
// 向下转型前,一定会有向上转型
A a = new B();
B b = (B)a;
b.tell1();
b.tell2();
b.tell3();
}
}
//运行结果:
//B--tell1
//A--tell2
//B--tell3多态性的应用
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
32class A1 {
public void tell1() {
System.out.println("tell1");
}
}
class B1 extends A1 {
public void tell2() {
System.out.println("tell2");
}
}
class C1 extends A1 {
public void tell3() {
System.out.println("tell3");
}
}
public class PolDemo02 {
public static void main(String[] args) {
say(new B1());
say(new C1());
}
public static void say(A1 a) {
a.tell1();
}
}
//运行结果:
//tell1
//tell1
抽象类与接口
final关键字的使用
- final关键字在Java中被称为完结器,表示最终的意思。
- final能声明类、方法、属性
- 使用final声明的类不能被继承
- 使用final声明的方法不能被重写
- 使用final声明的变量变成常量,常量是不可以被修改的。被修饰的变量名称应全部大写。
抽象类
抽象类概念:包含一个抽象方法的类就是抽象类。
抽象方法:声明而未被实现的方法,抽象方法必须使用abstract关键字声明。
抽象类被子类继承,子类(如果不是抽象类)必须重写抽象类中的所有抽象方法。
定义格式:
1
2
3
4
5
6
7
8//抽象类
abstract class className {
属性
方法
//抽象方法,用abstract修饰,在“()”后直接加上“;”
//eg:
//public abstract void say();
}抽象类不能直接实例化,要通过其子类进行实例化。
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//抽象类
abstract class Abs {
private int age;
public void say() {
}
//抽象方法
public abstract void print();
public abstract void tell();
}
class AbsSon extends Abs {
//重写抽象类中的所有抽象方法
public void print() {
}
public void tell() {
}
}
public class AbsDemo {
public static void main(String[] args) {
// 抽象类不能直接被实例化,要通过其子类进行实例化
// Abs abs = new Abs();
AbsSon abs = new AbsSon();
abs.say();
abs.print();
abs.tell();
}
}
接口的实现
接口是Java中最重要的概念,可以理解为一种特殊的类,里面全部是由全局常量和公共的抽象方法所组成。
接口的格式:
1
2
3
4interface interfaceName {
全局常量
公共抽象方法
}接口的实现也必须通过子类,使用关键字implements,而且接口是可以多实现的。
接口被子类继承,子类(如果不是抽象类)必须重写接口中的所有抽象方法。
一个子类可以同时继承抽象类和实现接口
一个接口不能继承一个抽象类,但是却可以通过extends关键字同时继承多个接口,实现接口的多继承。
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//接口
interface Inter1 {
public static final int AGE = 20;
public abstract void tell() ;
}
interface Inter2 {
public abstract void say();
}
abstract class AbsDemo01 {
public abstract void print();
}
class A extends Abs implements Inter1,Inter2 {
//重写所继承的所有接口中的所有抽象方法
public void tell() {
}
public void say() {
}
//重写所继承的所有抽象类中的所有抽象方法
public void print() {
}
}
//通过extends关键字同时继承多个接口
interface Inter3 extends Inter1,Inter2{
}
public class InterDemo01 {
public static void main(String[] args) {
A a = new A();
a.tell();
a.say();
System.out.println(A.AGE);
a.print();
}
}抽象类和接口的对比
参数 抽象类 接口 默认的方法实现 它可以有默认的方法实现 接口完全是抽象的。它根本不存在方法的实现 实现 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现 构造器 抽象类可以有构造器 接口不能有构造器 与正常Java类的区别 除了不能实例化抽象类之外,它和普通Java类没有任何区别 接口是完全不同的类型 访问修饰符 抽象方法可以有public、protected和default这些修饰符 接口方法默认修饰符是public。你不可以使用其它修饰符。 main方法 抽象方法可以有main方法并且我们可以运行它 接口没有main方法,因此我们不能运行它。 多继承 抽象方法可以继承一个类和实现多个接口 接口只可以继承一个或多个其它接口 速度 它比接口速度要快 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。 添加新方法 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 如果你往接口中添加方法,那么你必须改变实现该接口的类。 什么时候使用抽象类和接口:
- 如果你拥有一些方法并且想让它们中的一些有默认实现,那么使用抽象类吧。
- 如果你想实现多重继承,那么你必须使用接口。由于Java不支持多继承,子类不能够继承多个类,但可以实现多个接口。因此你就可以使用接口来解决它。
- 如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。
5.2 Object所有类的超类
Object类是Java中所有类的始祖,在Java中每个类都是由它扩展而来。
- Object类型的变量可以引用任何类型的对象,但若要对其具体内容进行具体操作,还是需要知道对象的原始类型,并进行相应的转换。
- 在Java中,只有基本类型不是对象,如数值、字符、布尔值等。所有的数组类型,不管是对象数组还是基本类型的数组都扩展了Object类。
equals方法
Object类中的equals方法用于检测一个对象是否等于另一个对象,本质上是比较两个对象是否具有相同的引用。
hashCode方法
散列码(hash code)是由对象导出的一个整型值。散列码是没有规律的。不同的对象,散列码基本不同。
1
2
3
4String s = "OK";
System.out.println(s.hashCode());
//运行结果:
//java.io.PrintStream@15db9742toString方法
- toString方法用于返回表示对象值的字符串。(个人感觉还不如用空字符串连接其它数据,如
int a = 12; String s = ""+a;
)
- toString方法用于返回表示对象值的字符串。(个人感觉还不如用空字符串连接其它数据,如
5.3 泛型数组列表
ArrayList是一个采用类型参数的泛型类。为了指定数组列表保存的元素对象类型,需要用一对尖括号将类名括起来加在后面,
例如
ArrayList<Father> test = new ArrayList<Father>();
注意:从Java SE7开始,可以省去右边的参数类型,即
ArrayList<Father> test = new ArrayList<>();
ArrayList类用起来与数组类似,但在添加或删除元素时,具有自动调节数组容量的功能。也可设置初始容量。
- add(),添加元素到数组列表
- get(),从数组列表取元素
- set(),替换数组列表相应位置已有的元素
- remove(),删除元素
- size(),返回当前数组列表中包含的实际元素数目。
- trimToSize(),将存储区域的大小调整为当前元素数量所需存储空间数目,多余的内存将会被gc回收。
示例
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
39public class ArrList {
public static void main(String[] args) {
// 创建ArrayList实例,并设置初始容量100
ArrayList<Person> human = new ArrayList<>(100);
human.add((new Person("张三",34)));
Person p = human.get(0);
System.out.println("姓名:"+p.name);
System.out.println("年龄:"+p.age);
System.out.println("此时的ArrayList的实际元素数目为"+human.size());
human.remove(human.size()-1);
System.out.println("删除成功!");
System.out.println("此时的ArrayList的实际元素数目为"+human.size());
}
}
class Person {
String name;
int age;
public Person(String name,int age) {
this.name = name;
this.age = age;
}
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;
}
}
5.4 对象包装器与自动装箱
所有的基本类型都有一个与之对应的类。如,Integer类对应基本类型int。Integer、Long、Float、Double、Short、Byte、Character、Boolean、Void均称之为对象包装器类。
ArrayList<Integer> list = new ArrayList<>();
程序调用
list.add(3);
将会自动变成list.add(Integer.valueOf(3));
,这种变换称为“自动装箱”。编译器将
list.add(Integer.valueOf(3));
翻译成list.add(3);
,这种变换称为“自动拆箱”。简单一句话:装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。
5.5 参数数量可变的方法
- printf方法就是参数可变的方法。
- 个人觉得目前用不上,很鸡肋。
5.6 枚举类
枚举类是类!
1 | public enum Size {SMALL,MEDIUM,LARGE,EXTRA_LARGE}; |
5.7 反射
- 反射库提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵Java代码的程序。
- 能够分析类能力的程序称为反射。
- 反射机制极其强大:
- 在运行时分析类的能力。
- 在运行时查看对象。
- 实现通用的数组操纵代码。
- 利用Method对象。
5.8 继承的设计技巧
- 将公共操作和域放在超类
- 不要使用受保护的域
- 使用继承实现“is-a”关系
- 除非所有继承的方法都有意义,否则不要使用继承
- 在覆盖方法时,不要改变预期的行为
- 使用多态,而非类型信息
- 不要过多使用反射