Java难绷知识02——抽象类中只能有或者必须有抽象方法吗以及有关抽象类的细节探讨
标题长的像轻小说
首先回答标题抛出的问题——False
显然,有抽象方法的类是抽象类,但是,抽象类中只能有或者必须有抽象方法吗? 抽象类可以包罗抽象方法,也可以包罗具体方法
假如一个类包罗至少一个抽象方法,用abstract关键字修饰,那么这个类必须被声明为抽象类。
抽象类除了可以有抽象方法外,还可以包罗具体的方法,即有方法体的方法。
一个类用abstract修饰,那它就是抽象类了,但是不是说抽象类中必须有抽象方法,假如一个类像接口那样全部方法都有具体的实现,也可以用abstract修饰为抽象类
那么反过来想,抽象方法只能写在抽象类中,但是抽象类中不肯定要有抽象方法
一个包罗了抽象方法和具体方法的抽象类- public abstract class Shape {
- // 抽象方法
- public abstract double getArea();
- // 具体方法
- public void displayInfo() {
- System.out.println("This is a shape.");
- }
- }
复制代码- abstract class AbstractParent {
- // 具体方法
- public void printMessage() {
- System.out.println("This is a message from AbstractParent.");
- }
- }
- // 子类继承抽象类,非抽象子类需要实现抽象方法
- class Child extends AbstractParent {
- // 子类可以直接使用父类的具体方法,根据需要重写
- @Override
- public void printMessage() {
- System.out.println("This is a message from Child, overriding the parent method.");
- }
- }
复制代码 另外,假如一个抽象类并没有包罗任何抽象方法,那么它的子类纵然不实现任何方法也是非抽象类
总之,抽象类是一种特殊的类,它的存在主要是为了提供一个通用的框架或基类,让其他子类去继承和实现具体的功能,抽象类可以包罗抽象方法和具体方法,但是有抽象方法的类必须是抽象类
假如我这篇博客就说这一点东西就有种,“因为这点事就把大伙叫过来?”的蹩脚感觉
于是我们接着探讨一些抽象类中的各种令人很难绷的住的细节
为什么要有抽象类
这个问题尽管乍看之下对实际开发的直接引导作用并非立竿见影,但深入剖析其中缘由,能让我们对 Java 语言的设计理念和面向对象编程的本质有更为透彻的明白(也能让我说出更多东西)
抽象类和抽象方法的产生是为了维护继承链的逻辑,即抽象类相对于那些普通的类处于继承树的根部。抽象类和抽象方法的诞生,很大程度上是为了维护继承体系的逻辑完备性与合理性
首先,类用于形貌实际生活中一类事物。类中有属性、方法等成员
那么,抽象类中,有用的就是方法的声明,方法主体存在的意义被弱化,这种情况下十分适合用于取规范一个子类应该具备一个怎样的方法,既可以界说必要子类去实现的抽象举动,以满足差别子类的个性化需求,又能实现一些通用的举动或属性,让子类能够共享这些功能,从而提高代码的复用性和可维护性。
但是,抽象类中的非抽象方法如同在非抽象类中一样,正常继承利用。
所以,抽象类通常用于作为其他类的父类,用来界说一些必要子类去实现的抽象举动
主要目标是为了阻止其他类直接实例化这个类,同时为其子类提供一个通用的基类框架,用于实现一些通用的举动或属性。
抽象类可以设计模板模式,其中的某些步调由抽象方法表示,具体的实现留给子类,大大增加了可读性和简便程度。
抽象类可以用于界说一组相干类的通用接口规范,通过抽象类,我们可以确保全部相干类都具有特定的举动,同时又允许它们根据自身特性进行个性化实现。
抽象方法
抽象方法只有方法声明,没有方法体(有爆Error),并且必须利用 abstract 关键字修饰。- public abstract class Shape {
- public abstract double getSquare();
- }
复制代码 抽象方法的特点
- 抽象方法必须存在于抽象类中
- 包罗抽象方法的类必须被声明为抽象类。如许做的目标是为了确保抽象方法不会被不测调用,因为抽象方法本身没有实际的执行代码,实例化该类时就可能调用到没有实现的方法,这会导致运行时错误。
- 全部子类都必要实现抽象类中的抽象方法,除非子类本身也是抽象类。
- 抽象类界说了一种抽象的概念,其中的抽象方法是这种概念下未完成的举动。子类继承抽象类,就继承了这种抽象概念及其未完成的举动。假如子类不实现抽象方法,那么这个子类仍然是不完备的,因为它没有完成抽象类中界说的举动。
- 强制子类实现
- 这个没啥好说的,当一个类继承自包罗抽象方法的抽象类时,除非子类本身也是抽象类,否则它必须实现父类中的全部抽象方法。这是包管举动一致性的重要本事。
- abstract class Shape {
- public abstract double getArea();
- }
- // 子类本身是抽象类,可以不实现getArea方法
- abstract class ThreeDShape extends Shape {
- // 这里没有实现getArea方法,因为ThreeDShape是抽象类
- }
- class Sphere extends ThreeDShape {
- private double radius;
- public Sphere(double radius) {
- this.radius = radius;
- }
- @Override
- public double getArea() {
- return 4 * Math.PI * radius * radius;
- }
- }
复制代码
- 抽象方法不能用private、final、static、native修饰。
- 抽象方法不能被声明为 static,静态方法属于类本身,而不是类的实例,而抽象方法的实现依赖于具体的子类实例。假如将抽象方法声明为静态,就无法通过子类实例来提供具体的实现。来回的限定区域错误会导致无法通过子类实例来提供具体的实现
- private修饰的成员只能在本类中访问,抽象方法的设计目标是为了让子类去实现,矛盾
- final修饰的方法不能被子类重写。假如一个方法同时被abstract和final修饰,就会产生矛盾
- native方法表示该方法的实现是由当地代码提供的,Java 本身不包罗该方法的实现。抽象方法没有实现,就会出现冲突
抽象类的一些各种细节
1、抽象类不能被实例化
首先抽象类是不能实例化的类
抽象类存在的意义是作为一种抽象概念,为子类提供一个通用的框架,可能包罗一些尚未具体实现的抽象方法,这些方法必要子类去实现
假如抽象类可以被实例化,就可能调用到未实现的抽象方法,导致运行时错误- abstract class Galgame {
- public abstract void run();
- }
复制代码 假如允许Galgame被实例化,如Galgame galgame = new Galgame();
当调用galgame.run()时,由于run方法没有具体实现,就会出现问题
2、abstract不能static
抽象方法可以利用 public 或 protected 作为访问修饰符,但是static不可
再次夸大,抽象方法是一种只有声明没有实现的方法,必要子类去重写实现。它依赖于具体的子类实例来确定具体的举动。
而静态方法属于类本身的方法,不依赖于任何类的实例,是通过类名直接调用。在类加载时就已经确定,其生命周期与类的生命周期相同。
假如一个方法同时被abstract和static修饰,就会产生矛盾。
因为抽象方法没有具体实现,不能直接调用。只有在创建了子类的实例,并将其赋值给父类范例的引用变量后,通过该引用变量调用抽象方法时,才会执行子类中重写的具体实现。而静态方法不依赖于实例,在类加载时就可调用,如许会导致内存的次序出现问题,因为静态方法不依赖于对象实例,而抽象方法却依赖子类实例来提供具体实现
反证法,假如是静态抽象方法,那么在类加载时就可调用,但此时由于它是抽象的,没有具体实现,调用必然出错。
3、抽象类的构造器
抽象类可以有构造器
虽然抽象类不能被直接实例化,但它的构造器用于被子类调用
因为抽象类中是可以有成员变量的
当创建子类对象时,会先调用抽象类的构造器,然后再调用子类的构造器。这确保了在子类对象初始化之前,从抽象类继承的部分已经被正确初始化。
4、可以有成员变量
抽象类可以有成员变量的原因是抽象类中的成员变量可以用于存储子类共享或必要继承的状态信息,假如子类必要,抽象父类可以提供
成员变量的访问控制:与普通类一样,抽象类中的成员变量可以有差别的访问修饰符- abstract class Vehicle {
- private int wheels;
- protected String color;
- public int speed;
- Vehicle(int wheels, String color, int speed) {
- this.wheels = wheels;
- this.color = color;
- this.speed = speed;
- }
- public int getWheels() {
- return wheels;
- }
- public abstract void move();
- }
- class Car extends Vehicle {
- public Car(int wheels, String color, int speed) {
- super(wheels, color, speed);
- }
- @Override
- public void move() {
- System.out.println("The " + color + " car is moving.");
- }
- }
复制代码 5、继承抽象类的抽象子类问题
假如一个类继承自抽象类,但它本身也是抽象类,那么它可以选择不实现父类的抽象方法- abstract class Shape {
- public abstract double getArea();
- }
- abstract class ThreeDShape extends Shape {
- // 可以不实现getArea方法,因为ThreeDShape本身是抽象类
- }
- // 而 Sphere 类继承自 ThreeDShape,作为具体类,它必须实现 getArea 方法。
- class Sphere extends ThreeDShape {
- private double radius;
- public Sphere(double radius) {
- this.radius = radius;
- }
- @Override
- public double getArea() {
- return 4 * Math.PI * radius * radius;
- }
- }
复制代码 抽象类跟接口
包罗我而言,很多人不禁疑惑,为什么有了抽象类,还要干出一个接口来??
这俩都是肯定程度上规范子类的方法,功能一眼看过去差不多
这个我打算写一篇详细的文章,但是算了,没啥时间))我还要推gal,而且这不是一个必要长篇大论的问题,在这里和大家简单探讨一下
其实是很简单的问题,但凡遇到了两种东西,功能很相似,通常情况就是这几种:利用场景差别,产生的最闭幕果差别,兼容性与更优性问题。
突破继承限定与灵活性
在 Java 中,一个类只能继承一个直接父类。这是为了避免多重继承带来的诸如继承混乱问题(什么哈斯图,我正好在看离散)
而接口则打破了这种限定,一个类可以实现多个接口。这使得类能够从多个差别的 “举动聚集” 中获取规范,灵活性极大up,使得子类更好的拥有多种举动能力。
功能侧重点差异
抽象类虽然可以包罗抽象方法,但也能有具体的属性和方法实现。这意味着抽象类在肯定程度上仍然生存了对具体实现细节的形貌。这是根据子类的必要,抽象类并不特殊死的限定会带来很多方便和意想不到的特殊之处
接口是一种完全抽象的范例,它只包罗抽象方法(Java 8 及之后版本可包罗默认方法和静态方法,但是本质绝对不会因为这个改动而改变),没有任何成员变量,只关注举动的界说,不涉及任何细节,只做出最少最必要的方法约束,更加纯粹地表现了一种举动规范,而且不消不可。
场景差异
当多个类之间存在一些共同的属性和举动,并且这些共同部分可以在抽象类中进行部分实现时,适合利用抽象类。也就是对于很多个类中,我抽象出了一些共有属性实现了一个类,之后的符合该属性的类,就按照抽象类的规定来实现。与举动约束相比,更偏向共性总结。
而当必要为不相干的类添加一些通用举动时,接口更为符合。也就是接口这是一个规矩,有什么类必要实现这个规矩,我就用接口来规范它。
扩展性差异
假如在抽象类中添加新的方法,可能必要在全部子类中实现该方法,这对于已经存在的大量子类来说,维护成本较高,一个个改会比较累。
对于接口,假如添加新的方法,(在 Java 8 之前,实现该接口的类不会受到影响(除非强制要求实现新方法)),那么实现该接口的类会分为两种,一个是完全实现了接口的类,一个是未完全实现了接口的类。在 Java 8 及之后,新增的默认方法有了默认实现,实现接口的类可以选择是否重写这些默认方法,这使得接口在扩展时对已有实现类的影响较小,更易于维护和扩展。
文章个人编辑较为匆忙,必要大家积极反馈来帮助这篇文章和我的更进一步
QQ:1746928194,是喜欢画画的coder,欢迎来玩!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |