I. 继承概述
1. 什么是继承
子类继承父类的特征和行为,使得子类具有父类的各种属性和方法。或子类从父类继承方法,使得子类具有父类相同的行为。
继承的特点:
* 在继承关系中,父类更通用、子类更具体。父类具有更一般的特征和行为,而子类除了具有父类的特征和行为,还具有一些自己特殊的特征和行为。
* 在继承关系中。父类和子类需要满足is-a的关系。子类是父类。
2. 为什么要继承
使用继承可以有效实现代码复用,避免重复代码的出现。当两个类具有相同的特征(属性)和行为(方法)时,可以将相同的部分抽取出来放到一个类中作为父类,其它两个类继承这个父类。
3.如何实现继承
在Java中,用extends关键字来表示一个类继承了另一个类。在父类中只定义一些通用的属性和方法。子类继承父类的属性和方法,子类中可以定义特定的属性和方法。或子类重新定义父类的属性、重写父类的方法可以获得与父类不同的功能。
接下来以一个例子来说明:
/**
* 继承的演示:
* 工人和学生都是人,
* 他们有共同的属性:年龄和姓名
* 有共同的行为:吃饭
* 他们还有各自特有的属性:工人有公司
* 学生有学校
* 各自特有的行为:工人工作
* 学生学习
*/
/**
* @Date: 2017/11/11
* @Time: 下午9:02
* @Description: 父类
*/
class Person {
String name;
int age;
void eat() {
System.out.println("人可以吃饭");
}
}
/**
* @Date: 2017/11/11
* @Time: 下午9:08
* @Description: 学生类
*/
class Student extends Person {
String school;
void study() {
System.out.println("学生学习");
}
}
/**
* @Date: 2017/11/11
* @Time: 下午9:09
* @Description: 工人类
*/
class Worker extends Person {
String company;
void work() {
System.out.println("工人工作");
}
}
/**
* @Author: 落脚丶
* @Date: 2017/11/11
* @Time: 下午9:02
* @Description: 继承演示
*/
public class InheritanceDemo {
public static void main(String[] args) {
Student student = new Student();
student.age = 20; // 具有父类的属性年龄
student.name = "Jean"; // 具有父类的属性姓名
student.school = "No1 Middle School"; // 子类独有的属性
System.out.println(
"学生的年龄:" + student.age + "\n" +
"学生的姓名:" + student.name +"\n" +
"学生所在学校:" + student.school
);
student.eat(); // 调用父类的方法
student.study(); // 调用子类特有方法
}
}
/**
* Output
* 学生的年龄:20
* 学生的姓名:Jean
* 学生所在学校:No1 Middle School
* 人可以吃饭
* 学生学习
*/
上面仅仅是继承的基本概念,下面我们再来探究一下继承的细节。
II. 继承的细节
- 单继承与多重继承
Java支持单继承,不直接支持多继承。即,一个子类只能有一个直接父类,不能有多个直接父类。
class A {
void show() {
System.out.println("a");
}
}
class B {
void show() {
System.out.println("b");
}
}
class C extends A,B {}
上面这中情况是不允许的,我们也很容易能看出来如果可以这么做带来的问题:如果类C同时继承了类A和类B,那么当C调用show()方法时,就会出现不确定性。
Java支持多重继承,即下面的情况是被允许的。
class A {}
class B extends A {}
class C extends B {}
- 成员变量
由第一个例子我们可以知道,子类继承了父类的成员变量,并且可以直接使用。子类还可以定义自己的成员变量。不过,这里会出现一个特殊的情况:子父类中有同名的成员变量。
看下面的例子:
class A {
int tem = 1;
int num = 4;
}
class B extends A {
int num = 5;
void show() {
System.out.println("num = " + num + "\n" +
"tem = " + tem
);
}
}
public class InheritanceDemo {
public static void main(String[] args) {
B b = new B();
b.show();
}
}
/**
* Output
* num = 5
* tem = 1
*/
由上面例子可以看出,子类B的对象调用show方法时,输出num = 5;首先我们要明确的一点是虽然子类和父类中都有变量num,但是它们不是同一个变量。所以,并不是子类的num重新定义的了父类num的值。这涉及到作用域的问题,子类优先调用子类的变量。如果子类和父类中有同名的变量,我们还想调用父类的变量,可以使用super关键字。
class A {
int tem = 1;
int num = 4;
}
class B extends A {
int num = 5;
void show() {
System.out.println("num = " + super.num + "\n" +
"tem = " + tem
);
}
}
public class InheritanceDemo1 {
public static void main(String[] args) {
B b = new B();
b.show();
}
}
/**
* Output
* num = 4
* tem = 1
*/
这样,就可以使用父类的成员变量了。
- 成员方法
正常情况下,子类可以调用从父类中继承的方法。
class A {
void show1() {
System.out.println("A show() run");
}
}
class B extends A {
void show2() {
System.out.println("B show() run");
}
}
public class InheritanceDemo {
public static void main(String[] args) {
B b = new B();
b.show1();
b.show2();
}
}
/**
* Output
* A show() run
* B show() run
*/
我们现在重点要说的是当子父类中出现相同的方法时,这时就是出现重写(override)。(也称覆盖或覆写,因为这时父类的方法不能再由子类对象通过“.”运算符直接被调用,就像被覆盖了一样。)
方法的重写有三点需要注意:
- 在子类中可以根据需要对从基类中继承来的方法进行重写。
- 重写的方法和被重写的方法必须具有相同方法名称、参数列表和返回类型。
- 重写方法不能使用比被重写的方法更严格的访问权限。
接下来我们依次说明一下这三点。
第一点说明当我们继承父类的方法时,发现父类的方法不能满足我们的要求,我们可以在子类中对父类的方法进行重写。即在子类中写一个和父类一模一样的方法。这里需要注意的一点是,这个方法必须时从父类继承过来的。
class A {
private void show() {
System.out.println("A show() run");
}
}
class B extends A {
public void show() {
System.out.println("B show() run");
}
}
public class InheritanceDemo {
public static void main(String[] args) {
B b = new B();
b.show();
}
}
/**
* Output
* B show() run
*/
我们知道,当方法声明为private权限时,是不能被子类继承的。上面的情况,子类并未从父类中继承show()方法,所以该情况不是重写。
第二点就是我之前讲的,必须是同一个方法。不光要有相同的方法名,还要有相同的参数列表。满足这两个要求的情况下,返回值类型不一样本身在Java中就是不被允许的。所以,同一个函数必须满足这三个要求。
class A {
public void show(int num) {
System.out.println("A show() run" + num);
}
}
class B extends A {
public void show() {
System.out.println("B show() run");
}
}
public class InheritanceDemo {
public static void main(String[] args) {
int a = 0;
B b = new B();
b.show(a);
b.show();
}
}
/**
* Output
* A show() run0
* B show() run
*/
上面这种情况并没有发生重写,可以看到,对象b依然可以调用父类的show方法。
第三点,重写函数的权限不能比父类中的权限更严格。
class A {
public void show() {
System.out.println("A show() run");
}
}
class B extends A {
void show() {
System.out.println("B show() run");
}
}
public class InheritanceDemo1 {
public static void main(String[] args) {
B b = new B();
b.show();
}
}
即上面这种情况是不能被允许的。编译器被报错:
那么如果我们还需要调用父类的show方法,可以在子类中通过super调用。
class A {
public void show() {
System.out.println("A show() run");
}
}
class B extends A {
public void show() {
super.show();
System.out.println("B show() run");
}
}
public class InheritanceDemo1 {
public static void main(String[] args) {
B b = new B();
b.show();
}
}
/**
* A show() run
* B show() run
*/
- 构造方法
先看一个简单的例子:
class A {
A() {
System.out.println("A is running");
}
}
class B extends A {
B() {
System.out.println("B is running");
}
}
public class InheritanceDemo1 {
public static void main(String[] args) {
B b = new B();
}
}
/**
* A is running
* B is running
*/
我们可以看出,当我们初始化子类对象时,父类的构造方法也会被调用。这是为什么呢?原来,在子类的构造方法(不管有没有参数)中会隐式调用父类的空参构造方法。即,实际情况是这样的
class B extends A {
B() {
super();
System.out.println("B is running");
}
}
super();
该语句写不写都是存在的。此处需要注意的是,默认调用的是父类的空参构造方法,如果父类中没有空参构造方法就会报错。即,下面这种情况是不被允许的。
class A {
A(int a) {
System.out.println("A is running");
}
}
class B extends A {
B() {
System.out.println("B is running");
}
}
public class InheritanceDemo1 {
public static void main(String[] args) {
B b = new B();
}
}
其实这也是很容易理解的,子类构造之前为什么要先调用父类的构造方法呢?因为,子类要继承父类的成员,所以在继承之前,应当对父类先初始化。这样我们就明白了,子类构造之前,一定是要先构造父类的。只是在默认情况下调用了空参构造。其实在更多的情况下,应该是由人为指定的。并且super语句必须放在第一行。如果子类的一个构造方法中使用了this语句调用其他构造方法,此时该构造函数中super语句就没有了。
class A {
A(int a) {
System.out.println("A is running" + a);
}
}
class B extends A {
B() {
super(4);
System.out.println("B is running");
}
}
public class InheritanceDemo1 {
public static void main(String[] args) {
B b = new B();
}
}
/**
* A is running4
* B is running
*/
- 子类实例化过程
我们先来看下面这个例子
class A {
A() {
show();
}
void show() {
System.out.println("A show");
}
}
class B extends A {
int num = 8;
B() {
super();
System.out.println("B constructor..." + num);
}
void show() {
System.out.println("B show..." + num);
}
}
public class InheritanceDemo1 {
public static void main(String[] args) {
B b = new B();
b.show();
}
}
/**
* Output
* B show...0
* B constructor...8
* B show...8
*/
这个结果时有的人可能会很奇怪,那我们就来分析一下实例化的过程。
当new一个B的对象时:
- 首先会隐式(默认)初始化num = 0;
- 然后调用B的构造方法B();
- 进入构造方法后,首先执行super(),即调用父类构造方法;
- 父类(其实也有父类Object这里先忽略)的构造方法调用show()方法,注意这个show()方法是子类的show()方法。因为整个过程的主体是子类,调用时,子类的show()方法重写了父类的show()方法。所以会出现第一个输出的结果;
- 当父类的构造方法结束后,接着对子类的变量进行显式初始化,此时num = 8;
- 然后构造方法中再输出num的时候买就会出现第二句输出的情况。
- final关键字与继承
当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让它被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。