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 Studentname=Jane age=20school=清华*/
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 Person16 {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 Student36 name=Jane age=2037 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 Person17 {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 Person20 {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 Person20 {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 Student45 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() ^ 正在尝试分配更低的访问权限; 以前为public1 个错误这说明了,如果要覆盖父类的方法,则子类的该方法的权限修饰符等级要比父类的高或者是同级;但两者均不能用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 Person22 {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已经是常量,且值为Jane41 s1.getInfo();42 s1.study();43 }44 }45 /*46 F:\java_example\lesson5>java Student47 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修饰的变量,只能在声明时赋值