1. 继承的概念
继承是出现是基于对代码复用的需求,在我们写代码时,会发现两个类之间存在大量的代码重复的情况,这个时候继承就排上了用场。继承可以在保持原有类的特性的基础上进行扩展,增加新的字段和方法形成一个新的类。
举个例子,我们想要定义两个类,分别是狗类和猫类,这两个类的内容很相似,都具有name、age等成员变量,也都具有eat()、sleep()等成员方法,但是同时也存在着不同的成员变量或成员方法。那么这个时候,我们可以定义两个完全独立的类分别给出狗类和猫类的全部属性,但是这样就会导致代码重复部分很多。
所以我们引入继承,对两个类共性的内容进行抽取,得到一个动物类,在这个类中有着两个类都存在的内容。这个时候再去定义狗类和猫类就可以直接继承动物类了,在继承了这些共性属性的基础上只需要加入自己特有的属性和方法即可完成定义,大大减少代码量。
在如上继承中,我们将想要继承的类称作子类(派生类),将被继承的类称为父类(基类、超类)。父类是基础,子类继承了父类,具有父类的成员,同时也有自己新增的成员。
2. 继承的使用与访问
2.1 继承的使用
在java中使用关键字extends来表示类之间的继承关系。子类会将父类中的成员变量和成员方法继承下来,可以视为子类中就具有这些成员变量和成员方法,可以对其进行访问。
class A{
private int num;
private String str;
public int getNum(){
return num = 1;
}
public String getStr(){
return str = "hello";
}
}
//B是子类,A是父类
class B extends A{
private char ch = '%';
public char getCh(){
return ch;
}
}
public class Test1 {
public static void main(String[] args) {
B b1 = new B();
System.out.println(b1.getNum());
System.out.println(b1.getStr());
System.out.println(b1.getCh());
}
}
2.2 继承的访问原则
2.2.1 子类优先原则
在通过子类方法中或者是经过子类对象访问成员变量或者方法时,遵循子类优先原则,即当子类和父类的成员名相同时,优先访问子类成员。
class A{
public char a = 'A';
public void func(){
System.out.println("A.func()");
}
}
class B extends A{
public char a = 'B';
public void func(){
System.out.println("B.func()");
}
}
public class Test1 {
public static void main(String[] args) {
B b = new B();
System.out.println(b.a);
b.func();
}
}
B
B.func()
2.2.2 super关键字
在类的使用过程中默认遵循子类优先,这会使得同名时无法访问到父类成员变量和方法。为了解决这个问题,引入了关键字super,super可以在子类方法中访问到父类的成员。
class A{
public char a = 'A';
public void func(){
System.out.println("A.func()");
}
}
class B extends A{
public char a = 'B';
public void func(){
System.out.println("B.func()");
}
public void method(){
System.out.println(this.a);
System.out.println(super.a);
this.func();
super.func();
}
}
public class Test1 {
public static void main(String[] args) {
new B().method(); //匿名对象
}
}
B
A
B.func()
A.func()
重点说明:
①super的用法和this是一样的,可以类比学习。this是调用方法的对象的引用,而super是调用方法的对象中父类部分的引用。
②this和super一样,因为都是引用,所以只能在非静态方法中使用。
③通过①的叙述可以发现,this的范围实际上包含了super,所以super一般在访问子类和父类重名的成员变量或方法的时候才会使用,而访问非重名的成员时使用this就可以了,不需要用到super(但是也可以)。
2.2.3 访问限定符
尽管子类继承了父类,但究其根本不是同一个类,所以在java中子类中访问父类的成员变量和成员方法收到访问限定符的限制。我们再次搬出这个图,分别测试四种访问限定符。
2.2.3.1 同包同类
package xlz;
public class Test2 {
private int a;
int b;
protected int c;
public int d;
//同一个包中的同一类
public void method1(){
a = 1; //private支持同包同类访问
b = 1; //default支持同包同类访问
c = 1; //protected支持同包同类访问
d = 1; //public支持同包同类访问
}
}
2.2.3.2 同包子类
package xlz;
//同一个包中的子类
public class Test3 extends Test2{
public void method2(){
//super.a = 1; //error,private不支持同包不同类访问
super.b = 1; //default支持同包不同类访问
super.c = 1; //protected支持同包不同类访问
super.d = 1; //public支持同包不同类访问
}
}
2.2.3.3 不同包子类
import xlz.Test2;
//不同包中的子类
public class Test1 extends Test2 {
public void method3(){
//super.a = 1; //error,private不支持不同包子类访问
//super.b = 1; //error,default不支持不同包子类访问
super.c = 1; //protected支持不同包子类访问
super.d = 1; //public支持不同包子类访问
}
}
2.2.3.4 不同包非子类
package gdd;
import xlz.Test2;
public class Test4{
public void method3(){
//new Test().a = 1; //error,private不支持不同包非子类访问
//new Test2().b = 1; //error,default不支持不同包非子类访问
//new Test2().c = 1; //error,protected不支持不同包非子类访问
new Test2().d = 1; //public支持不同包非子类访问
}
}
2.3 子类父类的构造和初始化
在定义了一个子类对象后,必然涉及到构造方法的调用和初始化的问题,这就会涉及到构造方法调用方式和调用顺序的问题了。我们综合分析:
package xlz;
public class Test2 {
public Test2(){
System.out.println("Test2()");
}
static{
System.out.println("static{Test2}");
}
{
System.out.println("{Test2}");
}
}
package xlz;
public class Test3 extends Test2{
public Test3(){
System.out.println("Test3()");
}
static {
System.out.println("static{Test3}");
}
{
System.out.println("{Test3}");
}
public static void main(String[] args) {
Test3 t = new Test3();
}
}
static{Test2}
static{Test3}
{Test2}
Test2()
{Test3}
Test3()
通过执行以上代码,我们可以知道在实例化子类对象的时候,会先完成父类的实例化与初始化,然后再完成子类的实例化与初始化。这是因为在子类构造方法第一行有着默认隐含的super()来调用父类的构造。这个隐含的super()只能调用无参构造,当父类不存在无参构造时就需要自己手动传参调用。
public class Test2 {
public Test2(int a){
System.out.println("Test2()");
}
}
public class Test3 extends Test2{
public Test3(){
super(1);
System.out.println("Test3()");
}
}
在此处做以总结:
①当实例化子类对象时,初始化调用顺序:1.父类的静态代码块;2.子类的静态代码块;3.父类的构造代码块;4.父类的构造方法;5.子类的构造代码块;6.子类的构造方法。
②在子类的构造方法中第一行必须是父类的构造方法调用super(),如果没有被显式地写出来也会最后由编译器加上。编译器所加的是super()无参构造方法,这就说明如果父类没有无参构造方法就会出错,所以如果父类定义了带参构造而没有定义无参构造,编译器就不会生成,这时就会出错。所以想调用父类带参构造就需要手动super(参数)进行调用。
③super(...)和this(...)都必须写在第一行,所以就意味着这两个调用不可以同时出现。
④super()不写会自动增加,this()不写则不会。
3.继承的其他要点
3.1 继承方式
在Java中支持:
单继承——一个子类继承一个父类;
多层继承——一个子类继承一个父类,而这个父类也继承了一个父类;
不同类继承同一个类——多个子类继承一个父类;
在Java中,多继承——一个子类继承多个父类,的方式是不支持的。
3.2 final关键字
final关键字可以用来修饰变量、类以及成员方法。
当final修饰变量时,表示是一个常量,只可以被初始化一次,不可以修改。
当final修饰类的时候,说明该类不可以被继承。
public final class Test1 {
public static void main(String[] args) {
final int a = 1;
final int b;
b=2;
//b = 3;
//a = 2; //error,final修饰不可修改
}
}
//class A extends Test1{}//error,final修饰不可继承
对于修饰成员方法的作用,我们在之后的博客中会介绍。
3.3 继承与组合
组合实际上就是一个类对象作为了另一个类的成员变量,这种形式我们并不少见,在以往数据结构中有很多类似形式。
class A{}
class B{}
public class Test1 {
private A a;
private B[] b = new B[5];
}