一、继承后的特点——成员变量
当类之间产生了关系后,其中各类中的成员变量会产生影响,分为下面两类来讨论:
1、成员变量不重名
如果子类父类中出现不重名的成员变量,这时候的访问是没有影响的。
Demo:
1 // 父类
2 class Fu {
3 // Fu中的成员变量。
4 int num = 5;
5 }
6 // 子类
7 class Zi extends Fu {
8 // Zi中的成员变量
9 int num2 = 6;
10 // Zi中的成员方法
11 public void show() {
12 // 访问父类中的num,
13 System.out.println("Fu num="+num); // 本类中没有,继承而来,所以直接访问。
14 // 访问子类中的num2
15 System.out.println("Zi num2="+num2); // 本类中有,访问本类中num2
16 }
17 }
18 // 测试类
19 class Test{
20 public static void main(String[] args) {
21 // 创建子类对象
22 Zi z = new Zi();
23 // 调用子类中的show方法
24 z.show();
25 }
26 }
27
28 演示结果:
29 Fu num = 5
30 Zi num2 = 6
2、成员变量重名
如果子类父类中出现了重名的成员变量,这时候的访问是有影响的。
Demo:
1 1 class Fu {
2 2 // Fu中的成员变量。
3 3 int num = 5;
4 4 }
5 5 class Zi extends Fu {
6 6 // Zi中的成员变量
7 7 int num = 6;
8 8 public void show() {
9 9 // 访问父类中的num
10 10 System.out.println("Fu num=" + num);
11 11 // 访问子类中的num
12 12 System.out.println("Zi num=" + num);
13 13 }
14 14 }
15 15 class ExtendsDemo03 {
16 16 public static void main(String[] args) {
17 17 // 创建子类对象
18 18 Zi z = new Zi();
19 19 // 调用子类中的show方法
20 20 z.show();
21 21 }
22 22 }
23 23 演示结果:
24 24 Fu num = 6
25 25 Zi num = 6
通过以上Demo可以看出,子父类中出现了同名的成员变量时,在子类中访问父类是访问不到的。
这时需要另外一个关键字——super 关键字。
在子类中需要访问父类中非私有成员变量时,需要使用 super 关键字,修饰父类成员变量,类似于之前的 this。
使用格式:
super.父类成员变量名
将上面的 Demo 中子类方法进行修改:
1 class Zi extends Fu {
2 // Zi中的成员变量
3 int num = 6;
4 public void show() {
5 //访问父类中的num
6 System.out.println("Fu num=" + super.num);
7 //访问子类中的num
8 System.out.println("Zi num=" + this.num);
9 }
10 }
11 演示结果:
12 Fu num = 5
13 Zi num = 6
Tips:父类中的成员变量是非私有的,子类中可以直接访问。若父类中的成员变量私有了,子类是不能直接访问的。通常,遵循封装的原则,使用 private 修饰成员变量 ,那么访问父类的成员变量就需要使用公开的 setter 和 getter 方法。
二、继承后的特点——成员方法
当类之间产生了关系,其中各类中的成员方法又会产生影响,下面分两种情况来讨论:
1、成员方法不重名
如果子类父类中出现不重名的成员方法,这时调用是没有影响的。对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类(直接父类与间接父类,会一直向上找)中相应的方法。
Demo:
1 class Fu{
2 public void show(){
3 System.out.println("Fu类中的show方法执行");
4 }
5 }
6 class Zi extends Fu{
7 public void show2(){
8 System.out.println("Zi类中的show2方法执行");
9 }
10 }
11 public class ExtendsDemo04{
12 public static void main(String[] args) {
13 Zi z = new Zi();
14 //子类中没有show方法,但是可以找到父类方法去执行
15 z.show();
16 z.show2();
17 }
18 }
2、成员方法重名——重写(Override/overwrite)
如果子类父类中出现 重名 的成员方法,这时的访问是一种特殊情况,叫做方法重写(Override)。
方法重写:子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现。
Demo:
1 class Fu {
2 public void show() {
3 System.out.println("Fu show");
4 }
5 }
6 class Zi extends Fu {
7 //子类重写了父类的show方法
8 public void show() {
9 System.out.println("Zi show");
10 }
11 }
12 public class ExtendsDemo05{
13 public static void main(String[] args) {
14 Zi z = new Zi();
15 // 子类中有show方法,只执行重写后的show方法
16 z.show(); // Zi show
17 }
18 }
3、方法的重写(详解)
(1)定义:在子类中可以根据需要对从父类中继承来的方法进行改造, 也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
(2)重写的规定:
方法的声明: 权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{
//方法体
}
约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法;
(3)重写的要点
① 子类重写的方法的方法名和形参列表必须与父类被重写的方法的方法名和形参列表相同;
② 权限修饰符:子类重写的方法的权限修饰符不能小于父类被重写的方法的权限修饰符;(public > protected > (default) > private)
特殊情况:子类不能重写父类中声明为private权限的方法;
③ 返回值类型:子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型
A、父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void;
B、父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类;
C、父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)
④ 异常类型:子类重写的方法抛出的异常类型不能大于父类被重写的方法抛出的异常类型
父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出。
注意:子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写) 。因为static方法是属于类的,子类无法覆盖父类的方法。
4、重写的应用
重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。
子类可以根据需要,定义特定与自己的行为。既沿袭了父类的功能名称,又根据子类的需要重新实现父类方法,从而进行扩展增强。
Demo:新手机增加来点显示头像的功能。
1 class Phone {
2 public void sendMessage(){
3 System.out.println("发短信");
4 }
5 public void call(){
6 System.out.println("打电话");
7 }
8 public void showNum(){
9 System.out.println("来电显示号码");
10 }
11 }
12 //智能手机类
13 class NewPhone extends Phone {
14 //重写父类的来电显示号码功能,并增加自己的显示姓名和图片功能
15 public void showNum(){
16 //调用父类已经存在的功能使用super
17 super.showNum();
18 //增加自己特有显示姓名和图片功能
19 System.out.println("显示来电姓名");
20 System.out.println("显示头像");
21 }
22 }
23
24 public class ExtendsDemo06 {
25 public static void main(String[] args) {
26 // 创建子类对象
27 NewPhone np = new NewPhone();
28 // 调用父类继承而来的方法
29 np.call();
30 // 调用子类重写的方法
31 np.showNum();
32 }
33 }
Tips:这里进行重写时,用到 super.父类成员方法,表示调用父类的成员方法。
三、继承后的特点——构造方法
当类之间产生了关系,现在来讨论各类中的构造方法的影响。
构造方法的定义格式和作用:
1、构造方法的名字和类名一致的,所以子类是无法继承父类构造方法的。
2、构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化工作,父类成员变量初始化后,才可以给子类使用。
3、子类的构造方法中默认有一个 super() ,默认情况下,表示调用父类的无参构造方法;如果父类没有无参构造,那么在子类的构造方法的首行,必须手动调用父类的有参构造。
Demo:
1 class Fu {
2 private int n;
3 Fu(){
4 System.out.println("Fu()");
5 }
6 }
7 class Zi extends Fu {
8 Zi(){
9 // super(),调用父类构造方法,默认就给提供一个。
10 super();
11 System.out.println("Zi()");
12 }
13 }
14 public class ExtendsDemo07{
15 public static void main (String args[]){
16 Zi zi = new Zi();
17 }
18 }
19 输出结果:
20 Fu()
21 Zi()
小结:
-
- 子类构造方法当中有一个默认隐含的 “super()” 调用,所以一定是先调用的父类构造,后执行的子类构造;
- 子类构造可以通过super关键字来调用父类重载构造;
- super的父类构造调用,必须是子类构造方法的第一个语句。不能一个子类构造调用多次super构造,只能有一个;
- 子类中所有的构造器默认都会访问父类中空参数的构造器;
- 当父类中没有空参数的构造器时, 子类的构造器必须通过 this(参数列表)或者super(参数列表) 语句指定调用本类或者父类中相应的造器。 同时, 只能”二选一”, 且必须放在构造器的首行;
- 如果子类构造器中既未显式调用父类或本类的构造器, 且父类中又没有无参的构造器, 则编译出错。
四、super 关键字
1、super 含义
在Java类中使用 super来调用父类中的指定操作,表示从父类中去查找,引用父类的属性或方法。
注意:super 只能访问在父类中可见的属性、方法、构造器(非 private修饰)
2、用法
(1)super.属性
当子类声明了和父类同名的属性时,可以使用 super.属性来访问父类中定义属性;
(2)super.方法
当在子类中需要调用父类被重写的方法时,可以使用 super.方法,调用父类中定义的成员方法;
(3)super() 或 super(实参列表)
super():调用父类的无参构造;
super(实参列表): 调用父类的有参构造;
用于在子类构造器中调用父类的构造器。
注意:
① super() 或 super(实参列表) 必须在子类构造器的首行;
② 如果子类的构造器中,没有写 super(),它也存在;但是如果子类构造器中写super(实参列表),那么super()就不会存在的;
③ super 的追溯不仅仅限制于直接父类,也可以从间接父类中获取;
④ super 和 this 的用法想象,this 代表本类对象的应用, super 代表父类的内存空间的标识;
五、super 与 this 区别
1、父类空间优先于子类对象产生
在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。
目的在于子类对象中包含了其对应的父类空间,便可以包含其父类的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。
代码体现在子类的构造方法调用时,一定先调用父类的构造方法。
图示:
2、super 与 this 的含义
(1)super:代表父类的存储空间标识(可以理解为父类的引用);
(2)this:代表当前对象的引用(谁调用就代表谁);
3、super 和 this 的用法
a、访问成员
this.成员变量 ‐‐ 本类的
super.成员变量 ‐‐ 父类的
this.成员方法名() ‐‐ 本类的
super.成员方法名() ‐‐ 父类的
Demo:
1 class Animal {
2 public void eat() {
3 System.out.println("animal : eat");
4 }
5 }
6
7 class Cat extends Animal {
8 public void eat() {
9 System.out.println("cat : eat");
10 }
11 public void eatTest() {
12 this.eat(); // this 调用本类的方法
13 super.eat(); // super 调用父类的方法
14 }
15 }
16 public class ExtendsDemo08 {
17 public static void main(String[] args) {
18 Animal a = new Animal();
19 a.eat();
20 Cat c = new Cat();
21 c.eatTest();
22 }
23 }
24 输出结果为:
25 animal : eat
26 cat : eat
27 animal : eat
b、访问构造方法
this(...) ‐‐ 本类的构造方法
super(...) ‐‐ 父类的构造方法
注意:
(1)子类的每个构造方法中均有默认的 super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。 ;
(2)super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
4、super 与 this 的区别
六、继承的特点
1、子类继承了父类,子类不能直接访问父类中私有的属性、方法。可以通过 setter/getter 方法来调用。
2、子类继承父类时,构造器是不能被继承的。
3、子类继承父类时,在子类的构造器中一定要去调用父类的构造器。
默认情况下,调用的是父类的无参构造;如果父类没有无参构造,那么在子类的构造器的首行,必须手动调用父类的有参构造。
4、Java 只支持单继承,即一个 Java 类只能有一个直接父类。
Demo:
//一个类只能有一个父类,不可以有多个父类。
class C extends A{} //ok
class C extends A,B... //error
5、Java 支持多层继承(继承体系)。
Demo:
class A{}
class B extends A{}
class C extends B{}
扩展:顶层父类是 Object 类,所有的类默认继承 Object,作为父类。
6、子类和父类是一种相对的概念。
Demo:
7、
七、子类对象实例化过程
1、子类实例化过程
2、案例
Demo:
1 class Creature {
2 public Creature() {
3 System.out.println("Creature无参数的构造器");
4 }
5 }
6
7 class Animal extends Creature {
8 public Animal(String name) {
9 System.out.println("Animal带一个参数的构造器,该动物的name为" + name);
10 }
11
12 public Animal(String name, int age) {
13 this(name);
14 System.out.println("Animal带两个参数的构造器,其age为" + age);
15 }
16 }
17
18 public class Wolf extends Animal {
19 public Wolf() {
20 super("灰太狼", 3);
21 System.out.println("Wolf无参数的构造器");
22 }
23
24 public static void main(String[] args) {
25 new Wolf();
26 }
27 }
3、小结
(1)从结果上来看:(继承性)
-
-
- 子类继承父类以后,就获取了父类中声明的属性或方法。
- 创建子类的对象,在堆空间,就会加载所有父类中声明的属性。
-
(2)从过程上来看:
当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器。
直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。
注意:虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。