龙空技术网

JAVA面向对象的三大特征——继承

千锋教育 167

前言:

目前各位老铁们对“抽象类继承java”可能比较注重,你们都想要分析一些“抽象类继承java”的相关文章。那么小编也在网上收集了一些对于“抽象类继承java””的相关内容,希望咱们能喜欢,姐妹们一起来了解一下吧!

前言

在上一篇文章中给大家讲解了面向对象三大特征之一的封装,现在我们还有另外的两个特征没有了解。在今天的这篇文章中,会给大家讲解面向对象的第二大特征--继承!我们之前操作的类一般都是单个的类,还没有怎么同时处理过两个类,而从继承的知识点开始,我们就会处理父子两个类之间的关系了。

全文大约5400】字,不说废话,只讲可以让你学到技术、明白原理的纯干货!本文带有丰富的案例及配图,让你更好地理解和运用文中的技术概念,并可以给你带来具有足够启迪的思考......

一. 继承简介1. 概述

在日常生活中,“继承”是施方的一种赠与,受方的一种获得,是将一方所拥有的东西给予另一方。

2. 概念

开发中的”继承“,其实和我们日常生活中所熟知的含义类似,代表着子类可以从父类中得到的承接。

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

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

3. 优缺点

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

1. 实现了代码共享,减少了创建类的工作量,使子类可以拥有父类的方法和属性;

2. 提高了代码的维护性和可重用性;

3. 提高了代码的可扩展性,更好的实现父类的方法。

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

1. 继承具有侵入性。只要继承,就必须拥有父类的非私有属性和方法;

2. 降低了代码的灵活性。子类拥有父类的属性和方法后就会多了一些约束。

3. 提高了代码的耦合性(应该高内聚低耦合)。父类的常量、变量和方法被修改时,也要考虑对子类进行修改,有可能会导致大段的代码被重构。

4. 使用特性

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

子类继承父类,可以继承父类中的属性和方法,即儿子可以继承爹的特征、遗产;

子类可以拥有自己独有的属性和方法,即儿子可以有自己的个性;

只能单继承,java中一个子类只能继承一个父类,但一个父类可以拥有多个子类。即一个儿子只能有一个亲爹,但一个爹可以有多个儿子;

多重继承结构,父类还可以继承另外一个类。Java中最大的父类是Object,如果一个类没有显式地标明继承自哪个父类,默认都是Object的子类。即儿子有爹,爹也有自己的爹......最终有个老祖宗是Object,这是根!

5. 注意事项

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

构造方法不能被继承,即生成父类对象的方法不能传给儿子,这样就”乱伦“了;

父类的私有属性不能被继承,即爹的私有财产小金库不能继承给儿子;

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

使用final声明的类是最终类,也不能被继承。

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

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

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

二. 代码实现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父类,在这里定义一些共同的属性和方法。

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。子类会从父类中继承共同的属性和方法,但不能继承父类的构造方法和私有属性,子类中可以定义自己特有的属性和方法。

/** *  * 定义子类 */public class Cat extends Animal{		//从父类中继承共同的属性和方法,但不能继承父类的构造方法和私有属性!	//定义独有属性    String color;    //定义独有方法    public void catchMouse() {        System.out.println("抓老鼠...");    }	}

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

4.3 定义子类Dog

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

/** *  * 定义子类 */public class Dog extends Animal{		//定义子类独有属性    String color;    //定义子类独有方法    public void lookHome() {        System.out.println("看家...");    }}
4.4 效果测试

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

/** *  * 测试继承 */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个构造方法的父类,如下所示:

/** * 父类 */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个构造方法的子类,如下所示:

/** *  * 定义子类 */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 测试类

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

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类

/** *  * 定义一个父类的“形状类” *          * 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类

/** *  * 定义一个子类的“矩形类” *  * 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类Shapepermits允许列表中的一个,属于白名单中的类。但如果是别的不在permits列表中的类就会报错。

2.3 定义Circle类

/** *  * 定义一个子类的“矩形类” *  * 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类
/** *  * 定义一个子类的“矩形类” *  * 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类
/** *  * 定义一个子类的“三角形类” *  * 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类没有出现在Shapepermits列表中,就不能继承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面向对象三大特征之——封装

java 如何实现短函数调用?

在java中String类为什么要设计成final?Java面试常见问题

标签: #抽象类继承java