【JavaSE】继承 你拿下继承了吗?一篇让你轻松拿下,简明扼要,超详解

1.为什么需要继承?

我们说Java语言对象与对象之间的交互更符合真实的世界,Java使用类来对现实世界中的实体进行描述,但是我们发现,很多事物之间都是有共同点的,比如猫猫和狗狗都有名字,年龄,他们都属于动物类,而猫猫会抓老鼠,狗狗会拆家,这就是他们有区别的地方。
在这里插入图片描述

class Cat {
    String name;
    int age;
    public void mew() {
        System.out.println("喵喵!");
    }
    public void eat() {
        System.out.println(this.name + "吃猫粮!")}
}

class Dog {
    String name;
    int age;
    public void bark() {
        System.out.println("狗叫!");    
    }
    public void eat() {
        System.out.println(this.name + "吃狗粮!");
    }
}

在这里插入图片描述
我们发现,这里Cat类和Dog类中都有String name,int age, 还有eat()方法,如果还有其他动物类的话,这些代码就会重复定义显得冗杂。那该怎么办呢?

2.extends关键字

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。

在Java中如果要表示类之间的继承关系,需要借助extends关键字,单看extends字面意思,是扩展延伸的意思,跟我们所说的扩展增加新功能相符合,具体用法如下:

修饰符 class 子类 extends 父类 {
// ... 
}

我们来对上面的代码进行改造,抽取共性,写一个Animal类。

class Animal {
    String name;
    int age;

    public void eat() {
        System.out.println(this.name + "呲饭饭!");
    }
}

Cat类和Dog类都是动物,继承Animal的属性,来重新写一下这两个类。

class Cat extends Animal {
    String hair;
    public void mew() {
        System.out.println("喵喵!");
    }
}

class Dog extends Animal {
    String breed;
    public void bark() {
        System.out.println("汪汪!");
    }
}

来试试实例化对象。

public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();

        //Dog类和Cat类并没有name变量,这是继承自父类的
        dog.name = "七七";
        cat.name = "九九";
        System.out.println(dog.name);
        System.out.println(cat.name);

        //自己特有的成员变量
        dog.breed = "柴犬";
        cat.hair = "灰白色";
        System.out.println(dog.breed);
        System.out.println(cat.hair);
        
        //调用父类的方法
        dog.eat();
        cat.eat();
        //调用自己特有的方法
        dog.bark();
        cat.mew();
    }
}

运行结果:
在这里插入图片描述

注意:
1.子类会将父类中的成员变量或者成员方法继承到子类中了。
2.被继承的类我们叫做父类/超类/基类,继承者我们称为子类/派生类
3. 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了。

3.父类成员访问

既然子类继承了父类的字段和方法,那子类能否访问父类的成员呢?

3.1 子类访问父类成员变量

3.1.1成员变量名不同

我们还是用上面Animal和Cat举例子,为了便于大家理解,我们简写一下,来看下面的代码:

class Animal {
    String name = "七七";
    int age = 2;

}

class Cat extends Animal {
    String hair = "黄色";

    public void method() {
        //访问父类继承的
        System.out.println(name);
        System.out.println(age);
        //访问自己的
        System.out.println(hair);
    }
}

调用一下method方法看看结果!

public class Test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.method();
    }
}

在这里插入图片描述

3.1.2成员变量名相同时

如果我们在子类中定义了与父类相同的成员变量,当我们在子类中访问时会访问谁的成员呢?
我们马上来测试一下!

class Animal {
    String name = "七七";
    int age = 2;
}

class Cat extends Animal {
    String name = "九九";
    String hair = "黄色";

    public void method() {
        System.out.println(name);
		//调用谁的name?
        System.out.println(age);
        //子类中没有age,会报错吗?
    }
}
public class Test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.method();
    }
}

在这里插入图片描述

我们看到父类和子类中都有name,但在子类中访问时,访问了子类自己的成员,子类将父类的成员隐藏了,子类没有age,子类访问时访问了父类的age。于是我们得到以下结论:

成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找

很容易理解,咱有咱就不麻烦家里人了,没有了再厚着脸皮找家里要吧!

3.2 子类访问父类成员方法

知道了子类是如何访问成员变量的,我们再来试试成员方法的访问,我猜啊,可能差不多嗷!

3.2.1成员方法名不同

说别的没用,上精简代码!

class Animal {
    String name;
    int age;
    public void methodA() {
        System.out.println("父类的methodA方法");
    }
}

class Cat extends Animal {
    String name;
    int age;
    String hair;
    public void methodB() {
        System.out.println("子类的methodB的方法");
    }
    public void method() {
        methodA();
        methodB();
    }
}
public class Test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.method();
    }
}

运行结果如下:
在这里插入图片描述
这个调用很简单,没什么好说的,既可以调用自己的,也能调用父类的private修饰的私有方法当然不行)。

这样想,即使是父类的,虽然咱没有,但咱继承下来了对吧,咱就当在同一个类中用就完事了,老子的东西也是“老子”的!

3.2.2成员方法名相同

成员方法名相同可就有意思了,因为成员方法名相同时,有重载和重写两种。

重载

什么叫方法重载呢?
就是方法名相同,但参数列表不同(包括但不限于类型,顺序,个数),方法返回值不做要求。

方法签名

编译器是怎么区分这些方法的呢? 其实编译器是通过方法签名来判断的,我们用之前说过的javap工具来看看方法签名。点击查看javap工具介绍及其使用
我们在Animal类中写两个重载方法,用javap -s 文件名 来看看。

class Animal {
    String name;
    int age;
    public void methodA() {
        System.out.println("父类无参的methodA方法");
    }
    public void methodA(int a, int b) {
        System.out.println("父类带两个int参数的methodA方法");
    }
}

在这里插入图片描述
methodA()这个方法的方法签名就是 methodA()V , “V”表示返回值为void
methodA(int a, int b) 方法的签名是 methodA(II)V , 括号中II表示有两个参数,第一个是int,第二个是int,“V” 表示返回值为void。其他的同理哦。

重载时子类访问父类

上测试代码!

class Animal {
    String name;
    int age;
    public void methodA() {
        System.out.println("父类无参的methodA方法");
    }
    public void methodA(int a, int b) {
        System.out.println("父类带两个int参数的methodA方法");
    }
}

class Cat extends Animal {
    String name;
    int age;
    String hair;
    public void methodA(int a) {
        System.out.println("子类的带一个int参数的methodA的方法");
    }
    public void method() {
        methodA();
        methodA(1);
        methodA(1, 2);
    }
}

public class Test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.method();
    }
}

运行结果:
在这里插入图片描述

通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法传递的参数选择合适的方法访问,如果没有则报错。

也就是说,编译器根据你所传参数的不同,去匹配调用相应的的方法。

重写

那还有一种情况,子类和父类的方法名,参数列表,返回类型一模一样,只是内部实现不同而已,这就是所谓的重写,总结为一句话就是,外壳相同,核心不同。后面多态部分再详细介绍,我们先来测试代码。

class Animal {
    String name;
    int age;

    public void eat() {
        System.out.println(this.name + "呲饭饭!");
    }
}

class Cat extends Animal {
    String hair;    
    public void eat() {
        System.out.println(this.name + "吃猫粮!");
    }
}

class Dog extends Animal {
    String breed;
    public void eat() {
        System.out.println(this.name + "吃狗粮!");
    }
}
public class Test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        Dog dog = new Dog();
        cat.eat();
        dog.eat();
    }
}

这里我们的eat方法在子类中完成了重写,那么通过子类对象调用eat方法,会执行谁的呢?

看运行结果:
在这里插入图片描述
看!他们各自调用了自己的eat方法,并没有执行父类的方法。
总结一下:

父类和子类同名方法的原型一致(重写),则只能访问到子类的,父类的无法通过子类对象直接访问到.

如果我们就是想在子类中访问父类的同名的方法怎么办?

3.3 super关键字

在开发中,难免有父类子类存在相同成员的情况,那我们非要访问父类的成员的时侯该怎么办呢?直接访问肯定做不到,Java提供了super关键字来访问父类的成员。

class Animal {
    String name ="七七";
    int age = 2;

    public void eat() {
        System.out.println(this.name + "呲饭饭!");
    }
    public void sleep() {
        System.out.println(this.name + "睡觉觉!");
    }
}

class Cat extends Animal {
    String name = "九九";
    int age = 4;
    String hair;
    public void eat() {
        System.out.println(this.name + "吃猫粮!");
    }
    public void method() {
        System.out.println(super.name);
        //调用父类继承的name,输出七七
        System.out.println(this.name);
        //调用自己的name,输出九九
        System.out.println(super.age);
        //调用父类继承的的age,输出2
        System.out.println(this.age);
        //调用自己的age,输出4
        this.eat();
        //调用自己的eat 输出九九吃猫粮!
        super.eat();
        //调用父类的eat 输出七七呲饭饭!
    }
}
//运行下method方法
public class Test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.method();
    }
}

在这里插入图片描述

注意,super关键字只能在非静态方法中访问。
详细用法后面介绍

3.4 子类构造方法

在刚刚的代码中,我们并没有为父类和子类写构造方法,所以编译器在编译时已经帮我们添加了无参构造方法。

我们知道,子类继承了父类的成员,子类成员包含父类的和自己的,那么在实例化子类对象时,父类的成员也是需要创建的,而成员的初始化,是通过调用构造方法的。点击查看调用构造方法如何初始化成员?

两个类构成父子关系,那肯定先有父再有子,所以在构造子类对象时候 ,先要调用父类的构造方法,将从父类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整 。

我们来自己写构造方法试试。

class Animal {
    String name;
    int age;
    Animal() {
        System.out.println("这是父类构造方法");
    }
    public void eat() {
        System.out.println(this.name + "呲饭饭!");
    }
    public void sleep() {
        System.out.println(this.name + "睡觉觉!");
    }
}

class Cat extends Animal {
    String hair;

    Cat() {
        System.out.println("这是子类构造方法 ");
    }
    public void eat() {
        System.out.println(this.name + "吃猫粮!");
    }
}
public class Test {
    public static void main(String[] args) {
        Cat cat = new Cat();
    }
}

看看运行结果:
在这里插入图片描述
我们发现,实例化子类对象时,先执行父类构造方法,再执行子类的构造方法。但是,它是怎么调用父类的构造方法的呢?

实际上,如果父类构造方法是默认构造方法(即我们没有自己实现构造方法)或者我们自己写了无参的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用父类构造方法。
实际代码段是这样的:

class Base {
	//成员
	
	Base() {
		
		
		//内容
	}
}

class Derived extends Base {
	//成员
	
	
	Derived() {
		super();
		//内容
	}
}

如果我们定义的父类构造方法是含有参数的,那我们必须自己来写出super()并为父类构造方法传参来调用父类构造方法。

如果不写,编译器报错。
在这里插入图片描述
因为子类并没有调用父类的构造方法,所以我们必须在子类构造方法中调用父类相应的构造方法。 如下图:
在这里插入图片描述
这样写就没有问题了,注意:
super(…)语句必须写在子类构造方法的第一行,且只能出现一次,不能和this同时出现。 否则都会报错的,大家可以自己试一下。

4.super和this的异同点

相同点

  1. 都是Java中的关键字
  2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
  3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在

不同点

  1. this是当前对象的引用,当前对象即调用实例方法的对象,谁调用this就指向谁super相当于是子类对象中从父类继承下来部分成员的引用.
  2. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
    在这里插入图片描述
  3. this是非静态成员方法的一个隐藏参数,super不是隐藏的参数
  4. 成员方法中直接访问本类成员时,编译之后会将this还原,即本类非静态成员都是通过this来访问的;在子类中如果通过super访问父类成员,编译之后在字节码层面super实际是不存在的(通过字节码文件可以验证)。

在这里插入图片描述
在这里插入图片描述

  1. 构造方法中一定会存在super(…)的调用,用户没有写编译器也会增加(带有参数的需要自己写),但是this(…)用户不写则没有。
  2. 在构造方法中:this(…)用于调用本类构造方法,super(…)用于调用父类构造方法,两种调用不能同时在构造方法中出现。

5.代码块在继承中的执行

没有继承关系时,构造方法和代码块执行顺序是:
静态代码块 -> 实例代码块 -> 构造方法
在继承关系中,执行顺序是怎么样的呢? 我们来写代码测试!万年不变的Animal和Cat

class Animal {
    String name;
    int age;
    
    {
        System.out.println("这是父类中的实例代码块!");
    }
    
    static {
        System.out.println("这是父类中的静态代码块!");
    }
    
    Animal() {
        System.out.println("这是父类构造方法");
    }
    public void eat() {
        System.out.println(this.name + "呲饭饭!");
    }
    public void sleep() {
        System.out.println(this.name + "睡觉觉!");
    }
}

class Cat extends Animal {
    String hair;
    
    {
        System.out.println("这是子类中的实例代码块!");
    }
    static {
        System.out.println("这是子类中的静态代码块!");
    }
    Cat() {
        super();
        System.out.println("这是子类构造方法 ");
    }
    
    public void eat() {
        System.out.println(this.name + "吃猫粮!");
    }
}

public class Test {
    public static void main(String[] args) {
    	//这里我们实例化两个对象,观察静态代码块的执行情况
        Cat cat1 = new Cat();
        System.out.println("=====我是分割线=======");    
        Cat cat2 = new Cat();
    }
}

运行结果:
在这里插入图片描述
这个代码很清晰的表示出了代码块和构造方法的执行顺序:
在这里插入图片描述
总结:

1.静态代码块只执行一次,即在类被加载时加载一次,之后不再执行
2.实例代码块因为被编译器加在了构造方法的前面,所以在构造方法之前执行
3.super()比实例代码块更早执行。

6.protected关键字

再来回顾一下前面说过的这张图
在这里插入图片描述
这张图展现了Java中访问修饰限定符的权限从小到大的排序,可以看到,protected权限仅次于public,被protected修饰的类或成员,除了在其他包中的其他类不能访问外,其他地方都是能访问的。

至于什么时候用哪一种呢?没有硬性要求,我们根据需求尽量隐藏内部细节,只提供必要的信息给类的调用者就可以了要分清楚我们写代码是站在类的创建者角度还是类的调用者角度,类自己使用还是其他类或者子类使用。

7.继承方式

在这里插入图片描述

真实项目中所写的类也会有很多,类之间的关系也会更加复杂。但是即使如此, 我们并不希望类之间的继承层次太复杂,一般我们不希望出现超过三层的继承关系。
如果想从语法上进行限制,就需要用到final关键字。

8.final关键字

final关键可以用来修饰变量、成员方法以及类。

修饰变量

修饰变量或字段,表示常量,不能被修改。

final int a = 1;
a = 2; // 编译出错

修饰类

表示该类不可被继承。

final public class Animal {
...
}
public class Cat extends Animal {
...
}
// 编译出错

例如,我们用的字符串类型,是不能被继承的,按住Ctrl来看下源码。
在这里插入图片描述

修饰方法

修饰方法:表示该方法不能被重写。

9.继承与组合

和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。

继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物
组合表示对象之间是has-a的关系,比如:汽车有发动机,轮胎等

// 轮胎类
class Tire{
	// ...
}
// 发动机类
class Engine{
	// ...
}
// 车载系统类
class VehicleSystem{
	// ...
}
//汽车中有轮胎,有发动机 
class Car{
	private Tire tire; // 可以复用轮胎中的属性和方法
	private Engine engine; // 可以复用发动机中的属性和方法
	private VehicleSystem vs; // 可以复用车载系统中的属性和方法
	// ...
}
// 奔驰是汽车
class Benz extend Car{
// 将汽车中包含的:轮胎、发送机、车载系统全部继承下来
}

组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般情况下我们建议能使用组合尽量使用组合。

=========================================================
码字不易,点个赞再走吧!收藏不迷路! 持续更新中…

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值