继承(inheritance)
继承的意义
我们首先来看下面两个类:
public class Dog {public String name;public int age;public void eat(){System.out.println(this.name+"正在吃饭");}public void bark(){System.out.println(this.name+"正在汪汪叫");}
}
public class Cat {public String name;public int age;public void eat(){System.out.println(this.name+"正在吃饭");}public void miao(){System.out.println(this.name+"正在喵喵叫");}
}
这两个名为Dog和Cat的类拥有相同的成员变量name、age以及相同的成员方法eat。
在主方法中可以正常调用他们的变量和方法。
public class test {public static void main(String[] args) {Dog dog = new Dog();dog.name="大黄";dog.eat();dog.bark();System.out.println("============");Cat cat = new Cat();cat.name="大橘";cat.eat();cat.miao();}
}
我们可以发现在Dog和Cat中就具有相同的成员,如果每次新建一个动物类时都要加入这些相同成员,未免有些麻烦,于是继承出现了。
如何继承
新建一个Animals类,将动物所具有的共同成员放入其中并删去原来类中的共同成员。
public class Animals {public String name;public int age;public void eat(){System.out.println(this.name+"正在吃饭");}
}
在原来的类名后加上extends Animals。
格式如下:
修饰符 class 子类名 extends 父类名{
}
public class Dog extends Animals{public void bark(){System.out.println(this.name+"正在汪汪叫");}
}
public class Cat extends Animals{public void miao(){System.out.println(this.name+"正在喵喵叫");}
}
这样原来的主函数依然可以正常运行。Animals就称为父类/基类/超类,Dog和Cat就叫做子类或者派生类。通过继承,我们实现了代码的复用。
继承中的访问与调用
子类访问父类成员变量
下面三段代码分别是子类,父类和主方法。
package demo2;public class Derived extends Base{public int c=3;public void func(){System.out.println(this.a);System.out.println(this.b);System.out.println(this.c);}
}
package demo2;public class Base {public int a=1;public int b=2;
}
package demo2;public class test {public static void main(String[] args) {Derived derived =new Derived();derived.func();}
}
运行结果:
子类访问了继承自父类的a和b,并访问了自身的成员变量c。
在子类中加入a=100:
package demo2;public class Derived extends Base{public int c=3;public int a=100;public void func(){System.out.println(this.a);System.out.println(this.b);System.out.println(this.c);}
}
运行结果:
可见当父类和子类中同时出现变量a时,this关键字访问的是子类自身的a。
如何在子类中访问父类中的同名变量a?
使用super关键字。
package demo2;public class Derived extends Base{public int c=3;public int a=100;public void func(){System.out.println(super.a);System.out.println(this.b);System.out.println(this.c);}
}
运行结果:
在子类方法中或者通过子类对象访问成员时:
1.如果访问的成员变量子类中有,优先访问自己的成员变量。
2.如果访问的成员变量子类中无,则访问父类继承下来的,如果父类中也无,则编译报错。
3.如果访问的成员变量与父类中的成员变量同名,则优先访问自己的。
子类访问父类成员方法
当子类中成员方法与父类中成员方法不同名时,可以直接在子类中调用父类成员方法:
public class Base {public int a=1;public int b=2;public void testBase(){System.out.println("testBase---");}
}
public class Derived extends Base{public int c=3;public int a=100;public void testDerived(){System.out.println("testDerived---");}public void test(){testBase();testDerived();}
}
public class test {public static void main(String[] args) {Derived derived =new Derived();derived.test();}
}
运行结果:
当子类中方法与父类中方法重名时,优先就近原则调用子类自己的方法,同时可以构成方法的重载和方法的重写:
方法的重载:
public class Base {public int a=1;public int b=2;public void testA(){System.out.println("testA()---");}
}public class Derived extends Base{public int c=3;public int a=100;public void testA(char a){System.out.println("testA(char a)---");}public void test(){testA();testA('a');}
}public class test {public static void main(String[] args) {Derived derived =new Derived();derived.test();}
}
并且可以使用super关键字调用父类方法:
public class Base {public int a=1;public int b=2;public void testA(){System.out.println("testA(Base)---");}
}public class Derived extends Base{public int c=3;public int a=100;public void testA(){System.out.println("testA(Derived)---");}public void test(){testA();super.testA();}
}public class test {public static void main(String[] args) {Derived derived =new Derived();derived.test();}
}
运行结果:
但是super只能指代父类,不能指代父类的父类玩套娃。
当子类继承父类之后,在不使用构造方法的情况下,java会自动为子类和父类提供构造方法,但当我们为类添加构造方法时,子类需要显式的调用父类的构造方法,帮助父类完成初始化。
public class Animals {public String name;public int age;public void eat(){System.out.println(this.name+"正在吃饭");}public Animals(String name,int age){this.name=name;this.age=age;}
}public class Dog extends Animals {public void bark(){System.out.println(this.name+"正在汪汪叫");}public Dog(String name,int age){super(name,age);}
}public class test {public static void main(String[] args) {Dog dog = new Dog("大黄",3);System.out.println("name:"+dog.name+" age:"+dog.age);}
}
简而言之,当为父类添加构造方法时,需要同时在子类中添加构造方法并在第一行使用super关键字帮助父类初始化。
super和this关键字
1.super()和this()构造方法只能在第一行使用,并且不能同时出现。
2.在非静态成员方法中,this用来访问本类中的方法和属性,super用来访问继承父类中的方法和属性。
3.在构造方法中,this()用于调用本类构造方法,super()用于调用继承父类的构造方法,两种调用不能同时在构造方法中出现。
4.构造方法中一定会有super()的调用,即使用户没有写编译器也会添加,但this()用户不写则没有。
继承中的代码块运行顺序
猜测下列标号代码块的运行顺序:
public class Animals {public String name;public int age;static{System.out.println("static::Animal{}");}//1{System.out.println("实例化代码块 Animal");}//2public Animals(String name,int age){this.name=name;this.age=age;System.out.println("Animal()");}//3
}public class Cat extends Animals {static{System.out.println("static::Cat{}");}//4{System.out.println("实例化代码块 Cat{}");}//5public void miao(){System.out.println(this.name+"正在喵喵叫");}public Cat(String name,int age){super(name,age);System.out.println("Cat()");}//6
}public class test {public static void main(String[] args) {Cat cat=new Cat("大橘",4);}
}
运行结果:
运行顺序是:142356
从中可以得出代码块的运行逻辑:先执行父类,子类的静态代码块,再执行父类的实例化和初始化,再执行子类的实例化和初始化。
继承的权限
private:仅允许本类中调用
default:允许同包中的不同类使用
protected:允许不同包中的子类使用
public:允许不同包的非子类中使用
在继承的权限使用中,我们要尽可能的做到封装:即最大的私密性,隐藏内部细节,只显露出必要的内容给使用者,能够使用private,就尽量不要使用public。有一个暴力的方法,就是将所有的变量设置为private权限,将所有的方法设置为public权限。
final关键字
继承关系不宜有太多层,一般三层为上限。final在java中对变量修饰可以将其变为不可改变的常量,对子类修饰则可以防止这个类被继承,当它被继承时编译器会报错,这个类就被称为密封类。
继承与组合
继承中子类与父类的关系可以表示为子类 is a 父类,而组合可以表示为前者 has a后者,或者后者 is a part of 前者,如汽车和轮胎的关系。
class Student{}class Teacher{}public class school {public Student[] student=new Student[10];public Teacher[] teacher=new Teacher[10];
}
学生和老师是学校的一部分,这就是组合关系。