Java难绷知识02——抽象类中只能有或者必须有抽象方法吗以及有关抽象类的细节探讨
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 及之后,新增的默认方法有了默认实现,实现接口的类可以选择是否重写这些默认方法,这使得接口在扩展时对已有实现类的影响较小,更易于维护和扩展。
上一篇: Java难绷知识01--IO流之对象流
下一篇: Java难绷知识03——包装器类及其自动装箱和拆箱
文章个人编辑较为匆忙,需要大家积极反馈来帮助这篇文章和我的更进一步
QQ:1746928194,是喜欢画画的coder,欢迎来玩!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)