前言:
目前各位老铁们对“抽象类继承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类是Shape的permits允许列表中的一个,属于白名单中的类。但如果是别的不在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类没有出现在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面向对象三大特征之——封装
java 如何实现短函数调用?
在java中String类为什么要设计成final?Java面试常见问题
标签: #抽象类继承java