1. 继承
1.1 为什么要继承
在Java中我们定义猫类和狗类,如下
public class Cat {
public String name;
public int age;
public String color;
public void eat(){
System.out.println(name + "正在吃饭");
}
public void sleep(){
System.out.println(name + "正在睡觉");
}
public void bark(){
System.out.println(name + "在喵喵叫");
}
}
public class Dog {
public String name;
public int age;
public String color;
public void eat(){
System.out.println(name + "正在吃饭");
}
public void sleep(){
System.out.println(name + "正在睡觉");
}
public void bark(){
System.out.println(name + "在汪汪叫");
}
}
根据图片我们可以发现猫类和狗类有大量重复的代码,我框住的就是重复的地方。
在Java中,于是就提出了继承这个概念,用来抽取共性。
1.2 继承的概念
在Java中,继承是面向对象编程的重要特性之一,它允许一个类(子类)继承另一个类(超类)的属性和方法。通过继承,子类可以重用超类的代码,从而提高代码的可重用性和可维护性,即实现共性的抽取,代码复用。
上述代码中,猫和狗都是动物,我们可以定义一个动物类将共性抽取,再让猫类和狗类都继承这个动物类实现代码复用
1.3 如何继承
继承需要用到 extends关键字
修饰符 class 子类 extends 父类 {
// ...
}
将上述代码用继承实现
public class Animal {
public String name;
public int age;
public String color;
public void eat(){
System.out.println(name + "正在吃饭");
}
public void sleep(){
System.out.println(name + "正在睡觉");
}
}
public class Cat extends Animal {
public void bark(){
System.out.println(name + "在喵喵叫");
}
}
public class Dog extends Animal{
public void bark(){
System.out.println(name + "在汪汪叫");
}
}
子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了
1.4 父类成员的访问
那么子类怎么访问父类的成员变量和方法呢?
1.4.1 子类怎么访问父类的成员变量
子类访问父类的成员变量也分为两种情况,分别为不同名的成员变量如何访问、同名的成员变量如何访问。
1.4.1.1 子类和父类的成员变量不同名
public class Test1 {
public int a = 1;
public int b = 2;
}
public class Test2 extends Test1{
public int c = 3;
public void test(){
System.out.println(a);//从父类中继承下来的a
System.out.println(b);//从父类中继承下来的b
System.out.println(c);//自己类的c
}
}
public class Main {
public static void main(String[] args) {
Test2 test2 = new Test2();
test2.test();
}
}
//运行结果
1
2
3
1.4.1.2 子类和父类的成员变量同名
public class Test1 {
public int a = 1;
public int b = 2;
}
public class Test2 extends Test1{
public int b = 5;
public int c = 3;
public void test(){
System.out.println(a);//从父类继承下来的a
System.out.println(b);//是访问父类继承下来的b还是自己类的b呢
System.out.println(c);//自己类的c
}
}
public class Main {
public static void main(String[] args) {
Test2 test2 = new Test2();
test2.test();
}
}
//执行结果
1
5
3
根据执行结果来看,访问的是自己类的b
如果我们将Test2的代码改动,如下
public class Test2 extends Test1{
public int b = 5;
public int c = 3;
public void test(){
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(d);
}
}
访问父类和子类都没有的成员变量编译器会报错。
结论:
- 如果访问的成员变量子类中有时,就访问子类自己的成员变量。
- 如果访问的成员变量子类中没有时,就访问父类中的成员变量,如果父类中也不存在该成员变量,编译器报错。
- 如果访问的成员变量子类中和父类中同名时,优先访问子类的
即优先访问子类自己的,如果没有再去访问父类的。
1.4.2 子类怎么访问父类的成员方法
同上,分为不同名的成员方法如何访问、同名的成员方法如何访问。
1.4.2.1 子类和父类的成员方法不同名
public class Test1 {
public void f1(){
System.out.println("Test1的方法f1");
}
}
public class Test2 extends Test1{
public void f2(){
System.out.println("Test2方法中的f2");
}
public void test(){
f1();//访问父类中的该方法
f2();//访问子类中该方法
}
}
public class Main {
public static void main(String[] args) {
Test2 test2 = new Test2();
test2.test();
}
}
//运行结果
Test1的方法f1
Test2方法中的f2
1.4.2.2 子类和父类的成员方法同名
public class Test1 {
public void f1(){
System.out.println("Test1的方法f1");
}
public void f2(){
System.out.println("Test1的f1方法");
}
}
public class Test2 extends Test1{
public void f1(int a){
System.out.println("Test2的方法f1");
}
public void f2(){
System.out.println("Test2的方法f2");
}
public void test(){
f1();//没有传参,访问父类中的该方法
f1(1);//传参,访问自己类中的该方法
f2();//访问子类中的该方法
}
}
public class Main {
public static void main(String[] args) {
Test2 test2 = new Test2();
test2.test();
}
}
//执行结果
Test1的方法f1
Test2的方法f1
Test2的方法f2
结论:
- 如果访问的成员方法不同名时,优先在子类中找,找到则访问,找不到在父类中找;如果父类和子类都没有时,则编译器报错。
- 如果访问的成员方法同名时,优先访问自己的;如果父类和子类同名方法的参数列表不同(重载),根据调用
方法适传递的参数选择合适的方法访问。
1.5 super关键字
如上述代码中,出现父类和子类的成员变量或成员方法同名的情况,但是想在子类中访问父类,于是Java提供了super关键字,即在子类中访问父类的成员变量或成员方法。
如下:
public class Test1 {
int a = 1;
int b = 2;
public void f1(){
System.out.println("Test1的方法f1");
}
public void f2(){
System.out.println("Test1的f1方法");
}
}
public class Test2 extends Test1{
int a = 3;
int b = 4;
public void f1(){
System.out.println("Test2的方法f1");
}
public void f2(){
System.out.println("Test2的方法f2");
}
public void test(){
System.out.println(this.a);
System.out.println(super.a);
System.out.println(this.b);
System.out.println(super.b);
f1();
super.f1();
}
}
public class Main {
public static void main(String[] args) {
Test2 test2 = new Test2();
test2.test();
}
}
//执行结果
3
1
4
2
Test2的方法f1
Test1的方法f1
注意:
super关键字与this关键字一样,都不能在静态方法中使用。
1.6 子类的构造方法
在创建子类的对象时,会调用子类的构造方法,在调用子类的构造方法时,会先调用父类的构造方法,然后再调用子类的构造方法。
public class Animal {
public String name;
public int age;
public String color;
public Animal(){
System.out.println("Animal");
}
public void sleep(){
System.out.println(name + "在睡觉");
}
public void eat(){
System.out.println(name + "在吃饭");
}
}
public class Cat extends Animal{
public Cat(){
super();
System.out.println("Cat");
}
public void bark(){
System.out.println(name + "喵喵叫");
}
}
public class Main {
public static void main(String[] args) {
Cat cat = new Cat();
}
}
在之前的代码中,我们并没有在子类中实现构造方法,并没有报错,如果我们在父类中实现有参数的构造方法呢?
当我们在父类中实现带参数的构造方法
public Animal(String name,int age,String color){
this.name = name;
this.age = age;
this.color = color;
System.out.println("Animal");
}
编译器会报错,那为什么在父类中实现有参数的构造方法就报错呢?
我们知道,当我们没有给父类实现构造方法时,Java会自动提供一个无参数的构造方法,而子类也提供了一个无参数的构造方法,并且该构造方法第一行默认有隐含的super调用,所以编译器不会报错;一旦我们在父类中自己实现了一个构造方法,Java便不会提供,上述父类的代码中实现了带参数的构造方法,而子类中Java提供的构造方法没有参数,所以参数列表长度不同,编译器报错。
所以应该向子类的代码中提供一个带参数的构造方法。
完整代码如下:
public class Animal {
public String name;
public int age;
public String color;
public Animal(String name,int age,String color){
this.name = name;
this.age = age;
this.color = color;
System.out.println("Animal");
}
public void sleep(){
System.out.println(name + "在睡觉");
}
public void eat(){
System.out.println(name + "在吃饭");
}
}
public class Cat extends Animal{
public Cat(String name, int age, String color) {
super(name, age, color);
System.out.println("Cat");
}
public void bark(){
System.out.println(name + "喵喵叫");
}
}
public class Main {
public static void main(String[] args) {
Test2 test2 = new Test2();
test2.test();
}
}
注意事项:
- 如果父类中没有定义构造方法或者定义了无参数的构造方法,在子类的构造方法中第一行默认有隐含的super( )来调用父类的构造方法。
- 如果父类中的构造方法是带参数的,子类的构造方法也要是带出参数的,并使用super( )选择合适的父类构造方法调用。
- 在子类的构造方法中,super( )必须在第一行。
- 在子类的构造方法中,super( )只能出现一次,并且不能和 this( )同时出现。
1.7 super和this
super和this的相同点和不同点
相同点
- 都可以访问成员变量和成员方法
- 都不能在静态方法中使用
- 在构造方法中都必须在第一行,但是不能同时出现
不同点
4. this是当前对象的引用,当前对象即调用实例方法的对象,super是子类对象中从父类继承下来部分成员的引用。
5. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
6. 在构造方法中:this(…)用于调用本类构造方法,super(…)用于调用父类构造方法,两种调用不能同时在构造方法中出现
7. 构造方法中一定会存在super(…)的调用,用户没有写编译器也会增加,但是this(…)用户不写则没有
1.8 再谈初始化
还记得在之前的文章中我们讲过的代码块吗,我们知道了静态代码快和实例化代码块以及构造方法的执行顺序,现在我们基于继承的条件下再试一下
代码如下:
public class Animal {
public String name;
public int age;
public String color;
{
System.out.println("Animal的实例化代码块");
}
static {
System.out.println("Animal的静态代码快");
}
public Animal(String name,int age,String color){
this.name = name;
this.age = age;
this.color = color;
System.out.println("Animal的构造方法");
}
}
public class Cat extends Animal{
{
System.out.println("Cat的实例化代码块");
}
static {
System.out.println("Cat的静态代码快");
}
public Cat(String name, int age, String color) {
super(name,age,color);
System.out.println("Cat的构造方法");
}
}
public class Main {
public static void main(String[] args) {
Cat cat1 = new Cat("mimi1",1,"black");
Cat cat2 = new Cat("mimi2",2,"white");
}
}
//执行结果
Animal的静态代码快
Cat的静态代码快
Animal的实例化代码块
Animal的构造方法
Cat的实例化代码块
Cat的构造方法
Animal的实例化代码块
Animal的构造方法
Cat的实例化代码块
Cat的构造方法
结论:
- 先执行父类的静态代码块,然后再执行子类的静态代码块。
- 静态代码快执行完后,再执行父类的实例化代码块,紧接着执行父类的构造方法。
- 父类的执行完后,执行子类的实例化代码块,紧接着执行子类的构造方法。
- 静态代码快只执行一次。第二次实例化子列对象,父类和子类的静态代码块都不会再执行。
1.9 继承的方式
在Java中,分为三种继承,分别为:
public class A{
}
public class B extends A{
}
public class A{
}
public class B extends A{
}
public class C extends B{
}
public class A{
}
public class B extends A{
}
public class C extends A{
}
1.10 final 关键字
final 可以修饰变量、成员方法、类。
- final 修饰变量
final int a = 1;
a = 2;
//编译错误
//java: 无法为最终变量a分配值
被 final 修饰的变量不可以被修改
- final 修饰类
被 final 修饰的类无法被继承
当我们用 final 来修饰 Animal
运行后的编译报错:java: 无法从最终com.company.Animal进行继承
- 被 final 修饰的方法不能被重写