Java基础-学习笔记(十一)——类的继承性
1、为什么会有继承性
1 class Person 2 { 3 String name; 4 int age; 5 String getInfo(){...} 6 } 7 class Student 8 { 9 String name; 10 int age; 11 String school; 12 String getInfo(){...} 13 String study(){...} 14 }
在编写代码过程中,可能会出现如上所示的情况,Student类中包含了Person类中的方法和属性。我们针对这种情况,就引入了继承这个概念,只要表明Student类继承了Person类中的所有属性和方法,就不用再在Student中重写Person类中的属性和方法,也就是简化了类的定义,如下所示:
class Person { String name; int age; void getInfo() { System.out.println("name="+name+" "+"age="+age); } } class Student extends Person { String school; void study() { System.out.println("school="+school); } public static void main(String [] args) { s1.name="Jane"; s1.age=23; s1.school="清华"; s1.getInfo(); s1.study(); } } /* F:\java_example\lesson5>java Student name=Jane age=20 school=清华*/
2、继承性的特点
1)可以简化类的定义;
2)java只支持单继承,不允许多重继承。也就是说,一个类不能同时继承多个类;
3)java支持多层继承。即,A类可以继承B类,B类可以继承C类,...,重复再多层也支持。
4)子类只继承父类(或称为基类、超类)所有的成员和方法,不能继承父类的构造函数,需要通过子类构造方法中使用语句super(参数列表)调用父类的构造函数;
1 class Person 2 { 3 String name; 4 int age; 5 Person(String name,int age) 6 { 7 this.name=name; 8 this.age=age; 9 } 10 void getInfo() 11 { 12 System.out.println("name="+name+" "+"age="+age); 13 } 14 } 15 class Student extends Person 16 { 17 String school; 18 Student(String name,int age,String school) 19 { 20 super(name,age); 21 this.school=school; 22 } 23 void study() 24 { 25 System.out.println("school="+school); 26 } 27 public static void main(String [] args) 28 { 29 Student s1=new Student("Jane",20,"清华"); 30 s1.getInfo(); 31 s1.study(); 32 } 33 } 34 /* 35 F:\java_example\lesson5>java Student 36 name=Jane age=20 37 school=清华*/
5)子类对象实例化过程
注意事项: 子类对象创建时需要调用父类的构造函数
如下代码在编译过程中出错
1 class Person 2 { 3 String name; 4 int age; 5 Person(String name,int age) 6 { 7 this.name=name; 8 this.age=age; 9 } 10 //Person(){} 11 void getInfo() 12 { 13 System.out.println("name="+name+" "+"age="+age); 14 } 15 } 16 class Student extends Person 17 { 18 String school; 19 void study() 20 { 21 System.out.println("school="+school); 22 } 23 public static void main(String [] args) 24 { 25 Student s1=new Student(); 26 s1.name="Jane"; 27 s1.age=23; 28 s1.school="清华"; 29 s1.getInfo(); 30 s1.study(); 31 } 32 }
报错如下:
F:\java_example\lesson5>javac lesson5.java
lesson5.java:19: 错误: 无法将类 Person中的构造器 Person应用到给定类型;
class Student extends Person
^
需要: String,int
找到: 没有参数
原因: 实际参数列表和形式参数列表长度不同
1 个错误
解决方法:将无参数的Person构造函数注释去掉。以后只要是定义了类有参数的构造函数,需要把其无参数的构造函数同样写上,避免类似错误。
现象说明:在构造Student类的s1对象的过程中,需要调用父类Person的无参数构造函数,但是由于,父类已存在的构造函数是有参数的,那么编译器不会再给Person自动生成无参数的构造函数,所以s1对象就无法创建成功
1 class Person 2 { 3 String name="unknown"; 4 int age=-1; 5 Person(String name,int age) 6 { 7 this.name=name; 8 this.age=age; 9 } 10 Person() 11 { 12 System.out.println("Is calling"); 13 } 14 void getInfo() 15 { 16 System.out.println("name="+name+" "+"age="+age); 17 } 18 } 19 class Student extends Person 20 { 21 String school="unknown"; 22 Student(String name,int age,String school) 23 { 24 super(name,age); 25 this.school=school; 26 } 27 /*Student(String name,int age) 28 { 29 super(name,age); 30 } 31 Student(String name,int age,String school) 32 { 33 this(name,age); 34 this.school=school; 35 } 36 */ 37 void study() 38 { 39 System.out.println("school="+school); 40 } 41 public static void main(String [] args) 42 { 43 Student s1=new Student("Jane",20,"清华"); 44 s1.getInfo(); 45 s1.study(); 46 } 47 }
这段代码说明了,子类对象创建时会按需选择父类中对应的构造函数。
具体实例化步骤:
第一步 分配成员变量的内存空间并进行默认的初始化,就是在new对象的过程中,会按照系统的默认值给各个成员赋初值,例如,String类型赋值为“null”,int类型为0等等;
第二步 绑定构造方法参数,就是将new Student("Jane",20,"清华")中的值传给对应的构造函数中的形参
第三步 不会立马将构造函数形参的值赋值给实参,而是,检查有没有this()方法调用。如果有,则调用相应的重载构造函数(被调用的重载构造函数,又从第二步开始执行),该过程结束后,回到当前的构造方法,直接转到第六步;如果没有,则执行下一步骤;
将代码中Student的重载构造函数换成如下
Student(String name,int age)
{
super(name,age);
}
Student(String name,int age,String school)
{
this(name,age);
this.school=school;
}
第四步 显示(就是执行super语句)或者隐式追溯调用父类的构造方法(根据继承性,一直追溯到最上层的父类)。在这个过程中,也是从第二步开始,全部结束后,继续下一步骤;
第五步 进行实例变量的显式初始化操作,也就是执行在定义成员变量时对它赋初值的语句。即,将unknown赋给name。没有则跳过该步骤
第六步 执行当前构造方法中的程序代码,即执行this.school=school。super或this已经在之前的步骤中执行过了,注意区别,上文提到的this()和this.school=school语句,前者调用的是构造方法,后者只是一个普通的语句
super()能和this()放同一个函数中么?我们通过this()间接来调用父类的构造方法,作用是和super()一致的,所以没意义,程序也不允许
super()、this()可以放在方法体中任意位置么?NO,只能放在对应构造方法体中的第一句,要不然就和上面的流程冲突了
3、覆盖父类的方法
在子类中可以根据需要更改从父类继承过来的方法---方法的覆盖或重写
覆盖的方法和被覆盖的方法两者具有相同的名称、参数列表和返回值类型
1 class Person 2 { 3 String name="unknown"; 4 int age=-1; 5 public Person(String name,int age) 6 { 7 this.name=name; 8 this.age=age; 9 } 10 public Person() 11 { 12 System.out.println("Is calling"); 13 } 14 public void getInfo() 15 { 16 System.out.println("name="+name+" "+"age="+age); 17 } 18 } 19 class Student extends Person 20 { 21 String school="unknown"; 22 public Student(String name,int age,String school) 23 { 24 super(name,age); 25 this.school=school; 26 } 27 public void getInfo()//覆盖父类中的getInfo() 28 { 29 System.out.println("name="+name+" "+"age="+age+" "+"school="+school); 30 //super.getInfo();可以直接调用父类的getInfo() 31 } 32 public void study() 33 { 34 System.out.println("I'm studing"); 35 } 36 public static void main(String [] args) 37 { 38 Student s1=new Student("Jane",20,"清华"); 39 s1.getInfo(); 40 s1.study(); 41 } 42 } 43 /* 44 F:\java_example\lesson5>java Student 45 name=Jane age=20 school=清华 46 I'm studing
如果将子类的访问修饰符换成protected,那么编译时会报如下错
F:\java_example\lesson5>javac lesson5.java
lesson5.java:27: 错误: Student中的getInfo()无法覆盖Person中的getInfo()
protected void getInfo()//覆盖父类中的getInfo()
^
正在尝试分配更低的访问权限; 以前为public
1 个错误
这说明了,如果要覆盖父类的方法,则子类的该方法的权限修饰符等级要比父类的高或者是同级;但两者均不能用private来进行修饰,super需要调用父类的getInfo()
4、final关键字
a 在java中声明类、属性、方法时,可以用关键字final来修饰
b final标记的类不能被继承
c final标记的方法不能被子类重写
d final标记的变量(成员变量或局部变量)则为常量,只能赋值一次,并且,只能在声明或者该类的所有构造方法中显式赋值再来使用,还有,赋值后的变量只能在该类中直接使用,在类的外部不能直接使用
java中定义常量,常用public static fianl的组合方式进行标识
e 方法中定义的内部类只能访问该方法的final类型的局部变量,用final定义的局部变量相当于是一个常量,它的生命周期超出方法运行的生命周期
1 class Person 2 { 3 public final String;// name=“Jane”; 4 //要么在声明时初始化,要么在所有显式的构造函数中都进行初始化 5 int age=-1; 6 public Person(String name,int age) 7 { 8 this.name="Jane"; 9 this.age=age; 10 } 11 public Person() 12 { 13 this.name="Jane";//如果不写这句,会报错“可能尚未初始化变量name” 14 System.out.println("Is calling"); 15 } 16 public void getInfo() 17 { 18 System.out.println("name="+name+" "+"age="+age); 19 } 20 } 21 class Student extends Person 22 { 23 String school="unknown"; 24 public Student(String name,int age,String school) 25 { 26 super(name,age); 27 this.school=school; 28 } 29 public void getInfo()//覆盖父类中的getInfo() 30 { 31 System.out.println("name="+name+" "+"age="+age+" "+"school="+school); 32 super.getInfo(); 33 } 34 public void study() 35 { 36 System.out.println("I'm studing"); 37 } 38 public static void main(String [] args) 39 { 40 Student s1=new Student("Chen",20,"清华");//name已经是常量,且值为Jane 41 s1.getInfo(); 42 s1.study(); 43 } 44 } 45 /* 46 F:\java_example\lesson5>java Student 47 name=Jane age=20 school=清华 48 I'm studing
如果将public final String name;写成public static final String name;只是替换这一句,程序会报错,因为用static修饰的变量,可以直接用类名来引用。那么就存在了一种隐患,我如果是通过类名来调用name,那我就不用new一个Person对象,我既然不用创建对象,那也不会调用其构造函数,从而,我的name也就没有进行初始化。所以,在通过publicstatic final修饰的变量,只能在声明时赋值