Aprender Java desde cero para explorar las características de la herencia y los permisos sellados para evitar la herencia.

Autor : Sun Yuchang, apodado [ Hermano Yiyi ], y [ Hermano Yiyi ] también soy yo

Experto en blogs de CSDN, bloguero de Wanfan, bloguero experto en la nube de Alibaba, autor de alta calidad de Nuggets

prefacio

En el último artículo, el hermano Yi les explicó la encapsulación de una de las tres características principales de la orientación a objetos. Ahora todavía tenemos otras dos características que no entendemos. En el artículo de hoy, el hermano Yi le explicará la segunda característica principal de la herencia orientada a objetos. Las clases en las que trabajamos antes son generalmente clases individuales, y no hemos tratado con dos clases al mismo tiempo. A partir de los puntos de conocimiento heredados, nos ocuparemos de la relación entre las clases padre e hijo.

------------------------------ Se acabó el juego previo y comienza la diversión ----------- -----------------

¡El texto completo tiene aproximadamente [ 5400] palabras, sin tonterías, solo productos secos puros que le permiten aprender técnicas y comprender principios! Este artículo tiene una gran cantidad de casos y videos con imágenes, para que pueda comprender y utilizar mejor los conceptos técnicos del artículo, y pueda brindarle suficientes ideas esclarecedoras...

1. Introducción a la herencia

1. Información general

En la vida diaria, la "herencia" es una especie de regalo del dador y una especie de adquisición del receptor, que consiste en dar algo de propiedad de una parte a la otra parte.

2. Concepto

"Herencia" en desarrollo es similar al significado con el que estamos familiarizados en nuestra vida diaria, representando la herencia que una subclase puede obtener de una clase padre.

在Java中,继承表示子类能够承接父类的特征和行为,使得子类对象(实例)具有父类的成员属性。或者子类可以从父类继承方法,使得子类具有父类相同的行为,所以继承是类与类之间特征(属性)和行为(方法)的一种赠与或获得。继承能让我们创建出带有等级层次的类, 两个类之间的继承会满足“is a”的关系,如下图所示:

Java中的继承是对已存在的类进行扩展,从而产生新的类。已经存在的类称为 父类、基类或超类 ,而新产生的类称为 子类或派生类 。在子类中,不仅包含父类的属性和方法,还可以增加新的属性和方法。

3. 优缺点

继承能够减少代码的冗余,提高代码复用性,具有以下优点:

  1. 实现了代码共享,减少了创建类的工作量,使子类可以拥有父类的方法和属性;
  2. 提高了代码的维护性和可重用性;
  3. 提高了代码的可扩展性,更好的实现父类的方法。

但继承也并非全是优点,毕竟这个世界上没有十全十美的东西,也有如下一些缺点:

  1. 继承具有侵入性。只要继承,就必须拥有父类的非私有属性和方法;
  2. 降低了代码的灵活性。子类拥有父类的属性和方法后就会多了一些约束。
  3. 提高了代码的耦合性(应该高内聚低耦合)。父类的常量、变量和方法被修改时,也要考虑对子类进行修改,有可能会导致大段的代码被重构。

4. 使用特性

继承在使用时,具有如下特性,需要我们牢牢掌握:

  • 子类继承父类,可以继承父类中的属性和方法,即儿子可以继承爹的特征、遗产;
  • 子类可以拥有自己独有的属性和方法,即儿子可以有自己的个性;
  • 只能单继承,java中一个子类只能继承一个父类,但一个父类可以拥有多个子类。即一个儿子只能有一个亲爹,但一个爹可以有多个儿子;
  • 多重继承结构,父类还可以继承另外一个类。Java中最大的父类是Object,如果一个类没有显式地标明继承自哪个父类,默认都是Object的子类。即儿子有爹,爹也有自己的爹......最终有个老祖宗是Object,这是根!

5. 注意事项

但是我们要注意,虽然子类继承父类时,很多属性和方法都能继承过来,但也有一些内容无法继承,主要是以下几点:

  • 构造方法不能被继承,即生成父类对象的方法不能传给儿子,这样就”乱伦“了;
  • 父类的私有属性不能被继承,即爹的私有财产小金库不能继承给儿子;
  • 父类中使用默认修饰符修饰的属性和方法,在不同包的子类中不能被继承;
  • 使用final声明的类是最终类,也不能被继承。

在继承时,需要考虑父类中的访问修饰符问题。关于访问修饰符,壹哥在之前的文章中就给大家讲过,你还能想起来吗?看看下面这个表格回忆一下吧。

修饰符 本类 本包(不同类) 不同包子类 其他
private ✔️
默认的 ✔️ ✔️
protected ✔️ ✔️ ✔️
public ✔️ ✔️ ✔️ ✔️

类的继承不会改变类成员的访问权限。 也就是说,如果父类的成员是公有的、被保护的或默认的,它的子类仍具有相应的这些特性,且子类不能继承父类的构造方法。

了解了关于继承的这些内容之后,接下来我们再通过一些代码案例来实操一下吧。

二. 代码实现

1. 基本语法

首先我们来看看继承的基本语法。

class 父类 {
    ...
}
 
class 子类 extends 父类 {
    ...
}
复制代码

extends关键字直接跟在子类名称之后,后面是子类要继承的父类名称。

2. extends关键字

Java中的继承主要是通过extends关键字来实现。extends的英文意思是扩展,而不是继承。 extends很好地体现了子类和父类的关系,即子类是对父类的扩展,子类是一种特殊的父类。从这个角度看,使用“继承”这个词来描述子类和父类的关系是错误的,所以用“扩展”更恰当。

Java类的继承是单一继承,即一个子类只能拥有一个父类。 如果一个类没有明确地继承某个别的类,编译器会自动加上extends Object,即默认继承Object(在java.lang包中,不需要手动import导包 ) 祖先类。所以,除了Object之外的任何类,都会继承某个类,只有Object没有父类。

另外我们也可以利用implements关键字实现接口,这其实也是一种变相的继承,壹哥后面会再给大家单独讲解接口的实现。所以很多地方在介绍Java单继承时,会说Java类只能有一个父类,其实严格地说,这种说法是不准确的!应该是一个类只能有一个直接父类,但它可以有多个间接的父类。 比如儿子只能有一个亲爹,但是爷爷、老爷爷、老老爷爷等也是儿子的“父类”,“父辈”,这属于间接父类。

3. 需求分析

接下来壹哥通过一个案例,让大家来看看该如何进行继承的实现。这里我对动物的共性做了一些抽象,如下图所示:

从上图中,我们可以看到这些不同的动物有一些共同的属性,比如“品种、年龄、性别”;也有一些共同的方法,比如吃、睡等。但不同的动物也有会一些个性化的行为或特征,比如鱼可以游泳,鸟会飞,狗会跑,蛇会爬。那么如果让我们来设计一个程序对动物进行描述,就需要对他们的特征和行为进行抽象归纳。

所以根据上图,我们可以总结出一些基本的规律:如果我们想实现继承,需要把使用到的多个具体类,进行共性的抽取,进而定义父类。在一组相同或类似的类中,抽取出共性的特征和行为定义在父类中,实现代码的重用。

4. 代码实现

那么具体该怎么进行代码实现呢?我们来参考下面这些案例吧。

4.1 Animal父类

我们先来定义一个Animal父类,在这里定义一些共同的属性和方法。

/**
 * @author 一一哥Sun
 * 定义父类
 */
public class Animal {
    //定义公共属性
    String name;
    int age;
    String type;

    //定义公共方法
    public void sleep() {
        System.out.println("睡觉...");
    }

    public void eat() {
        System.out.println("吃饭...");
    }
}
复制代码

在OOP面向对象的术语中,我们可以 Animal 称为父类(parent class)、超类(super class)或者基类(base class);把 Cat/Dog等 称为子类(subclass)、扩展类(extended class)。

4.2 定义子类Cat

我们再来定义一个子类Cat。子类会从父类中继承共同的属性和方法,但不能继承父类的构造方法和私有属性,子类中可以定义自己特有的属性和方法。

/**
 * @author 一一哥Sun
 * 定义子类
 */
public class Cat extends Animal{
    //从父类中继承共同的属性和方法,但不能继承父类的构造方法和私有属性!
    //定义独有属性
    String color;

    //定义独有方法
    public void catchMouse() {
        System.out.println("抓老鼠...");
    }
}
复制代码

子类继承父类之后,就具有了父类中的属性和方法,子类不用再重复地编写这些代码。我们只需要为子类编写新增的功能即可,这样代码的可维护性和复用性也就提高了,代码也更加简洁了。

4.3 定义子类Dog

我们再来定义第二个子类Dog。

/**
 * @author 一一哥Sun
 * 定义子类
 */
public class Dog extends Animal{
    //定义子类独有属性
    String color;

    //定义子类独有方法
    public void lookHome() {
        System.out.println("看家...");
    }
}
复制代码

4.4 效果测试

接下来我们在main()方法中对上面的继承关系进行测试。

/**
 * @author 一一哥Sun
 * 测试继承
 */
public class ExtendTest {
    public static void main(String[] args) {
	Dog dog = new Dog();
        //使用父类继承下来的属性
        dog.name = "旺财";
        dog.type = "泰迪";
        dog.age = 3;
        //使用子类独有属性
        dog.color = "黄色";

        System.out.println("姓名为:"+dog.name+",品种为:"+dog.type+",毛色为:"+dog.color);
        //使用父类继承下来的方法
        dog.eat();
        dog.sleep();
        //使用子类独有方法
        dog.lookHome();
    }
}
复制代码

三. 几种不能继承的情况

1. 构造方法不能被继承

子类不能继承父类的构造方法,只能隐式或显式地调用。 如果父类的构造方法带有参数,继承的子类可以在自己的构造方法中,显式地利用 super关键字调用父类的构造方法,并配以适当的参数列表。

如果父类的构造方法没有参数,则子类的构造方法中可以不用 super关键字调用父类的构造方法,系统会自动调用父类的无参构造方法。

接下来我们再通过一个案例来进行说明。

1.1 定义父类

这里定义了一个带有2个构造方法的父类,如下所示:

/**
 * @author 一一哥Sun 
 * 父类
 */
public class Father {
    //私有属性不能被继承
    private String name;
    private int age;
    private String secret;
	
    //公开的属性--姓氏。公开属性可以被继承
    public String familyname;

    //如果在父类中存在有参的构造方法,但没有重载无参的构造方法,那么子类中必须存在有参的构造方法。否则会产生如下异常:
    //Implicit super constructor Father() is undefined. Must explicitly invoke another constructor
    public Father() {
	System.out.println("Father父类的无参构造方法");
    }

    public Father(String name, int age) {
	this.name = name;
	this.age = age;
	System.out.println("Father父类的有参构造方法");
    }
}
复制代码

如果在父类中存在有参的构造方法,但没有重载无参的构造方法, 那么子类中必须显式地调用父类有参的构造方法。 否则会产生如下异常:

Implicit super constructor Father() is undefined. Must explicitly invoke another constructor。

这是因为子类中默认会去调用父类中无参的构造方法,而在父类中如果没有无参的构造方法就会出错。

1.2 定义子类

接着我们定义一个带有2个构造方法的子类,如下所示:

/**
 * @author 一一哥Sun
 * 定义子类
 */
public class Son extends Father{

    //子类自己的私有属性
    private String hobby;
    private int height;
    private String job;

    //如果在父类中存在有参的构造方法,但没有重载无参的构造方法,那么子类中必须显式地调用父类有参的构造方法。否则会产生如下异常:
    //Implicit super constructor Father() is undefined. Must explicitly invoke another constructor
    public Son() {
        //不用显式调用super();方法
        //super();
        //父类中存在有参构造方法,但没有重载无参构造方法,需要显式调用如下方法。
	//super("",11);
	//这里会隐式地调用父类的无参数构造方法
	System.out.println("Son子类的无参构造方法");
    }

    public Son(String name,int age,String job) {
	//super();
	//子类显式地调用父类中带有参数的构造方法
	super(name, age);
	this.job = job;
	System.out.println("Son子类的有参构造方法"+job);
    }
}
复制代码

从这些案例中我们可以知道,子类不会继承父类任何的构造方法,子类默认的构造方法是Java自动生成的,不是继承来的!

1.3 测试类

这里定义一个测试类,测试上面的继承关系,如下所示:

/**
 * @author 一一哥Sun
 */
public class FatherTest {
    public static void main(String[] args) {
	//创建第一个子类对象
	Son son1 = new Son();
		
	//创建第二个子类对象
	Son son2 = new Son("小棒",38,"盗窃");
    }
}
复制代码

执行结果如下图所示:

2. 私有属性不能被继承

父类中的私有属性不能被子类继承,公开的属性是可以的,如下图所示:

但private私有的修饰符,有可能会使得继承的作用被削弱。所以有时候为了让子类可以访问父类的某些字段,我们可以把private改为protected关键词,用protected修饰的字段可以被子类访问。protected 关键字可以把字段和方法的访问权限控制在继承树的内部,一个 protected 字段和方法可以被其子类,以及子类的子类所访问。

另外父类中使用默认修饰符修饰的属性和方法,在不同包的子类中也不能被继承。

3. final类不能被继承

假如我们把上面的父类进行调整,用final关键字修饰Father类,如下图所示:

此时子类就会出现如下图所示的提示信息:

“The type Son cannot subclass the final class Father”,即子类不能继承final类

四. 新特性(拓展)--sealed+permits阻止继承

1. 概述

一般情况下,只要一个类没有被 final 修饰,那么任何类都可以继承该类。 但从JDK 15开始,允许使用sealed(密封)关键字来修饰class,并利用permits(许可)关键字明确写出能够从该类继承的子类名称。

2. 示范案例

2.1 定义Shape类

/**
 * @author 一一哥Sun 
 * 定义一个父类的“形状类”
 *         
 * Permitted class Triangle does not declare demo14.Shape as direct super class
 */
public sealed class Shape permits Rect, Circle, Triangle {
    ...
}
复制代码

Shape类是一个被sealed修饰的类,它指定了3个类Rect/Circle/Triangle可以继承它,我们是利用permits关键字实现允许继承。sealed 类主要用于一些框架中,防止继承被滥用!

2.2 定义Rect类

/**
 * @author 一一哥Sun
 * 定义一个子类的“矩形类”
 * 
 * The class Rect with a sealed direct superclass or a sealed direct superinterface Shape 
 * should be declared either final, sealed, or non-sealed
 */
public final class Rect extends Shape{
}
复制代码

Rect类可以继承Shape类,因为 Rect类 Shape permits允许 列表中的一个,属于白名单中的类。但如果是别的不在permits列表中的类就会报错。

2.3 定义Circle类

/**
 * @author 一一哥Sun
 * 定义一个子类的“矩形类”
 * The class Rect with a sealed direct superclass or a sealed direct superinterface Shape 
 * should be declared either final, sealed, or non-sealed
 */
public final class Circle extends Shape{
    ...
}
复制代码

2.4 定义Triangle类

/**
 * @author 一一哥Sun
 * 定义一个子类的“矩形类”
 * 
 * The class Rect with a sealed direct superclass or a sealed direct superinterface Shape 
 * should be declared either final, sealed, or non-sealed
 */
public final class Triangle extends Shape{
    ...
}
复制代码

2.5 定义Ellipse类

/**
 * @author 一一哥Sun
 * 定义一个子类的“三角形类”
 * 
 * The type Ellipse extending a sealed class Shape should be a permitted subtype of Shape
 */
public final class Ellipse extends Shape{
    //Compile error: class is not allowed to extend sealed class: Shape
    ...
}
复制代码

Ellipse类没有出现在Shape的permits列表中,就不能继承Shape类,否则就会报错:The type Ellipse extending a sealed class Shape should be a permitted subtype of Shape。Compile error: class is not allowed to extend sealed class: Shape

------------------------------正片已结束,来根事后烟----------------------------

五. 结语

至此,壹哥就把Java里的继承给大家讲解完毕了,现在你知道继承有什么特点了吗?关于继承,有如下几个要点:

  • 继承是面向对象编程的一种强大的代码复用方式;
  • Java只允许单继承,所有类最终的根类都是 Object, C++可以有多重继承(即一个子类有多个直接父类)
  • 父类中的 private 成员在子类中是不可见的,子类中不能直接使用它们;
  • protected 允许子类访问父类的字段和方法;
  • 在子类的构造方法中可以通过 super() 调用父类的构造方法;
  • 子类一般比父类包含更多的属性和方法;
  • 子类和父类的关系是is a,has关系不能用继承,但也并不是所有符合“is-a”关系的都应该用继承。 如正方形是一个矩形,但不能让正方形类来继承矩形类,因为正方形不能从矩形扩展得到任何东西。正确的继承关系是正方形类继承图形类。

关于继承的内容其实还有很多,比如super关键字的详情、父子类之间的转型问题等,更多关于继承的内容,壹哥会在后面的文章中专门进行讲解。另外如果你独自学习觉得有很多困难,可以加入壹哥的学习互助群,大家一起交流学习。

六. 配套视频

如果你不习惯阅读技术文章,或是对文中的技术概念不能很好地理解,可以来看看壹哥帮你筛选出的视频教程。与本文配套的Java学习视频,链接如下:

player.bilibili.com/player.html…

七. 今日作业

1. 第一题

设计一个Person类和Teacher类,理顺两者之间的关系,说说子类对象的实例化过程。

本文正在参加「金石计划」

Supongo que te gusta

Origin juejin.im/post/7214880895092719671
Recomendado
Clasificación