文章目录
- 抽象类与接口:Java核心之道(上)
-
- 一、抽象类
-
- [1.1 抽象类概念](#1.1 抽象类概念)
- [1.2 抽象类语法](#1.2 抽象类语法)
- [1.3 抽象类特性](#1.3 抽象类特性)
- [1.4 抽象类的作用](#1.4 抽象类的作用)
- 二、Object类
-
- [2.1 Object类概述](#2.1 Object类概述)
- [2.2 获取对象信息 - `toString()` 方法](#2.2 获取对象信息 -
toString()方法) -
- [2.2.1 示例:重写 `toString()` 方法](#2.2.1 示例:重写
toString()方法)
- [2.2.1 示例:重写 `toString()` 方法](#2.2.1 示例:重写
- [2.3 对象比较 - `equals()` 方法](#2.3 对象比较 -
equals()方法) -
- [2.3.1 默认的 `equals()` 方法](#2.3.1 默认的
equals()方法) - [2.3.2 自定义重写 `equals()` 方法](#2.3.2 自定义重写
equals()方法)
- [2.3.1 默认的 `equals()` 方法](#2.3.1 默认的
- [2.4 `hashCode()` 方法](#2.4
hashCode()方法) -
- [2.4.1 默认的 `hashCode()` 方法](#2.4.1 默认的
hashCode()方法) - [2.4.2 示例:重写 `hashCode()` 方法](#2.4.2 示例:重写
hashCode()方法)
- [2.4.1 默认的 `hashCode()` 方法](#2.4.1 默认的
- [2.5 `hashCode()` 与 `equals()` 方法的关系](#2.5
hashCode()与equals()方法的关系) -
- [2.5.1 示例:重写 `equals()` 和 `hashCode()` 方法](#2.5.1 示例:重写
equals()和hashCode()方法)
- [2.5.1 示例:重写 `equals()` 和 `hashCode()` 方法](#2.5.1 示例:重写
- [2.6 `clone()` 方法(在下一篇文章讲接口时详细讲解)](#2.6
clone()方法(在下一篇文章讲接口时详细讲解)) -
- [2.6.1 示例:使用 `clone()` 方法](#2.6.1 示例:使用
clone()方法)
- [2.6.1 示例:使用 `clone()` 方法](#2.6.1 示例:使用
- [2.7 `getClass()` 方法](#2.7
getClass()方法) -
- [2.7.1 示例:使用 `getClass()` 方法](#2.7.1 示例:使用
getClass()方法)
- [2.7.1 示例:使用 `getClass()` 方法](#2.7.1 示例:使用
- 三、总结与展望
抽象类与接口:Java核心之道(上)
💬 欢迎讨论:如果你对本篇内容有任何疑问或想深入探讨,欢迎在评论区留言交流!
👍 点赞、收藏与分享:觉得内容有帮助就请点赞、收藏并分享给更多学习Java的小伙伴!
🚀 继续学习之旅:本篇文章将详细讲解抽象类的基本概念、实现方式以及实际应用,让你在面向对象的世界中进一步提升Java编程技能!
一、抽象类
1.1 抽象类概念
在面向对象的编程中,所有的对象都是通过类来描绘的。但并不是所有的类都用来描绘具体的对象。如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就称为抽象类。
例如,在继承与多态的文章中我们讲到的绘制图形的例子中,我们发现,父类 Shape 中的 draw() 方法本身并没有具体的实现,主要的绘制工作是由 Shape 的各种子类的 draw() 方法来完成的。像这种没有实际工作的方法,我们可以把它设计成抽象方法 (abstract method)。包含抽象方法的类我们称为抽象类 (abstract class)。


抽象类的主要作用是提供一种公共的接口或模板,让子类根据具体情况进行实现。
1.2 抽象类语法
在Java中,一个类如果被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法不用给出具体的实现体。而是由子类来提供实现。
java
// 抽象类:被 abstract 修饰的类
public abstract class Shape {
// 抽象方法:被 abstract 修饰的方法,没有方法体
abstract public void draw();
abstract void calcArea();
// 抽象类也是类,可以增加普通方法和属性
public double getArea(){
return area;
}
protected double area; // 面积
}
注意:抽象类仍然是类,内部可以包含普通方法、属性,甚至构造方法。
1.3 抽象类特性
- 抽象类不能直接实例化对象
java
Shape shape = new Shape();
// 编译出错
Error:(30, 23) java: Shape是抽象的; 无法实例化
由于抽象类没有完全实现所有方法,因此无法直接创建该类的实例。
- 抽象方法不能是 private 的
java
abstract class Shape {
abstract private void draw();
}
// 编译出错
Error:(4, 27) java: 非法的修饰符组合: abstract和private
抽象方法必须是子类可以访问并重写的方法,因此不能是 private。
- 抽象方法不能被
final和static修饰
抽象方法必须允许子类重写,final和static会阻止重写,因此不能与抽象方法一起使用。
java
public abstract class Shape {
abstract final void methodA();
abstract public static void methodB();
}
// 编译报错:
// Error:(20, 25) java: 非法的修饰符组合: abstract和final
// Error:(21, 33) java: 非法的修饰符组合: abstract和static
- 抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也必须是抽象类
如果子类没有重写父类的抽象方法,那么子类也必须声明为抽象类。
java
// 抽象类 Shape,包含抽象方法 draw() 和 calcArea()
abstract class Shape {
protected double area;
// 抽象方法:绘制形状
public abstract void draw();
// 抽象方法:计算面积
public abstract void calcArea();
public double getArea() {
return area;
}
}
// 矩形类 Rect,继承自 Shape 类,必须实现所有抽象方法
public class Rect extends Shape {
private double length;
private double width;
Rect(double length, double width) {
this.length = length;
this.width = width;
}
@Override
public void draw() {
System.out.println("矩形: length= " + length + " width= " + width);
}
@Override
public void calcArea() {
area = length * width;
}
}
// 圆形类 Circle,继承自 Shape 类,必须实现所有抽象方法
public class Circle extends Shape {
private double r;
final private static double PI = 3.14;
public Circle(double r) {
this.r = r;
}
@Override
public void draw() {
System.out.println("圆:r = " + r);
}
@Override
public void calcArea() {
area = PI * r * r;
}
}
// 三角形类 Triangle,继承自 Shape 类,但仍然是抽象类
public abstract class Triangle extends Shape {
private double a;
private double b;
private double c;
// 三角形绘制方法,所有三角形的绘制方式一致
@Override
public void draw() {
System.out.println("三角形:a = " + a + " b = " + b + " c = " + c);
}
// 由于不同三角形的面积计算不同,所以我们将 calcArea() 设为抽象方法
@Override
public abstract void calcArea();
}
// 直角三角形类,继承自 Triangle 并实现 calcArea() 方法
public class RightAngledTriangle extends Triangle {
private double base;
private double height;
public RightAngledTriangle(double base, double height) {
this.base = base;
this.height = height;
}
@Override
public void calcArea() {
area = (base * height) / 2;
}
}
// 等腰三角形类,继承自 Triangle 并实现 calcArea() 方法
public class EquilateralTriangle extends Triangle {
private double side;
public EquilateralTriangle(double side) {
this.side = side;
}
@Override
public void calcArea() {
area = (Math.sqrt(3) / 4) * side * side; // 等腰三角形的面积公式
}
}
-
抽象类中不一定包含抽象方法,但如果有抽象方法的类一定是抽象类
即使一个抽象类不包含抽象方法,它依然可以包含具体实现的代码。
-
抽象类可以有构造方法
抽象类也可以包含构造方法,供子类在创建对象时初始化父类的成员变量。
1.4 抽象类的作用
抽象类本身不能被实例化,要想使用该抽象类,必须创建其子类,并让子类重写抽象类中的抽象方法。
你可能会疑问,普通的类也可以被继承,普通的方法也可以被重写,为何要使用抽象类和抽象方法呢?
确实如此,但使用抽象类的主要好处在于编译器的校验。
例如,如果父类是抽象类,编译器会强制要求子类重写所有的抽象方法,若子类未重写,编译时就会报错。 这种机制有效避免了程序员的失误,让我们在开发过程中尽早发现问题,提高了代码的可靠性。
抽象类的使用场景通常是:当某些类的实现无法确定或者通用时,可以用抽象类来规定子类必须实现某些方法,并为子类提供一些通用的实现。
很多编程语言中的关键字(如 final)也有类似的作用,它们通过限制不允许修改,帮助我们防止不小心修改了重要的部分。充分利用编译器的校验,可以有效提升代码的质量和稳定性。
二、Object类
2.1 Object类概述
Object 类是 Java 默认提供的一个类。Java 中,除了 Object 类,所有的类都是存在继承关系的,默认会继承 Object 作为父类。也就是说,所有类的对象都可以通过 Object 类型的引用进行接收。
范例:使用 Object 接收所有类的对象
java
class Person {}
class Student {}
public class Test {
public static void main(String[] args) {
function(new Person());
function(new Student());
}
public static void function(Object obj) {
System.out.println(obj);
}
}
执行结果:
java
Person@1b6d3586
Student@4554617c
在上述代码中,Object 类作为参数的最高统一类型,可以接收任何类的对象。通过 Object 类型的引用,我们可以调用对象的 toString() 方法,输出对象的类名和哈希码。
2.2 获取对象信息 - toString() 方法
如果要打印对象中的内容,可以直接重写 Object 类中的 toString() 方法。toString() 方法默认返回的是对象的类名和哈希码。我们可以通过重写这个方法来定义如何输出对象的内容。
2.2.1 示例:重写 toString() 方法
java
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
public class Test {
public static void main(String[] args) {
Person person = new Person("Alice", 25);
System.out.println(person); // 输出:Person{name='Alice', age=25}
}
}
通过重写 toString() 方法,我们可以自定义对象输出的内容,便于调试和打印对象的具体信息。
2.3 对象比较 - equals() 方法
在 Java 中,== 运算符用于比较两个对象时,比较的是它们的引用地址是否相同,而不是对象的内容。如果需要比较对象内容是否相同,必须重写 Object 类中的 equals() 方法。
2.3.1 默认的 equals() 方法
Object 类中的 equals() 方法默认是通过比较对象的内存地址来判断两个对象是否相等。它的实现如下:
java
public boolean equals(Object obj) {
return (this == obj); // 使用引用地址进行比较
}
2.3.2 自定义重写 equals() 方法
java
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && name.equals(person.name);
}
}
public class Test {
public static void main(String[] args) {
Person p1 = new Person("Alice", 25);
Person p2 = new Person("Alice", 25);
System.out.println(p1 == p2); // 输出:false
System.out.println(p1.equals(p2)); // 输出:true
}
}
结论 :比较对象内容时,必须重写 equals() 方法。如果不重写,equals() 方法会比较引用地址。
2.4 hashCode() 方法
hashCode() 方法用于返回对象的哈希值,它是通过对象的内存地址计算出来的。哈希值常常用于散列表等数据结构中,用来确定对象在内存中的位置。hashCode() 方法的默认实现与 equals() 方法类似,比较的是对象的内存地址。
2.4.1 默认的 hashCode() 方法
Object 类中的 hashCode() 方法返回对象的内存地址的哈希码:
java
public native int hashCode();
2.4.2 示例:重写 hashCode() 方法
java
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
return Objects.hash(name, age); // 自定义的哈希值计算方法
}
}
public class Test {
public static void main(String[] args) {
Person p1 = new Person("Alice", 25);
Person p2 = new Person("Alice", 25);
System.out.println(p1.hashCode()); // 输出:根据自定义的哈希值计算
System.out.println(p2.hashCode()); // 输出:根据自定义的哈希值计算
}
}
执行结果:
java
460141958
460141958
结论:
hashCode()方法用于确定对象在内存中的存储位置。- 在散列表等数据结构中,
hashCode()用于快速查找对象的位置。 - 如果重写了
equals()方法,也应该重写hashCode()方法,确保对象的哈希值在比较时一致。
2.5 hashCode() 与 equals() 方法的关系
在 Java 中,equals() 和 hashCode() 方法密切相关。当我们重写 equals() 方法时,也应当重写 hashCode() 方法。因为如果两个对象通过 equals() 方法被认为相等,那么它们的 hashCode() 也应该相等。
2.5.1 示例:重写 equals() 和 hashCode() 方法
java
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && name.equals(person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
public class Test {
public static void main(String[] args) {
Person p1 = new Person("Alice", 25);
Person p2 = new Person("Alice", 25);
System.out.println(p1.hashCode() == p2.hashCode()); // 输出:true
}
}
执行结果:
java
true
在这个例子中,我们重写了 equals() 和 hashCode() 方法,使得 p1 和 p2 在内容相同的情况下,它们的哈希值也相等。
2.6 clone() 方法(在下一篇文章讲接口时详细讲解)
clone() 方法是 Object 类中提供的一个浅拷贝方法。它返回当前对象的副本,使用默认的克隆方式(浅拷贝)创建一个新的对象。
2.6.1 示例:使用 clone() 方法
java
class Person implements Cloneable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); // 调用 Object 类的 clone() 方法
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Person p1 = new Person("Alice", 25);
Person p2 = (Person) p1.clone(); // 克隆 p1 对象
System.out.println(p1 == p2); // 输出:false,说明 p1 和 p2 是两个不同的对象
System.out.println(p1.equals(p2)); // 输出:true,说明内容相同
}
}
执行结果:
java
false
true
结论:
clone()方法用于创建当前对象的副本,默认是浅拷贝。- 需要实现
Cloneable接口才能调用clone()方法。
2.7 getClass() 方法
getClass() 方法返回一个 Class 对象,它描述了当前对象所属类的类型信息。这个方法可以用来获取对象的类名等信息。
2.7.1 示例:使用 getClass() 方法
java
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Test {
public static void main(String[] args) {
Person p1 = new Person("Alice", 25);
System.out.println(p1.getClass()); // 输出:class Person
}
}
执行结果:
java
class Person
结论:
getClass()方法返回对象的类类型信息,可用于反射和类型判断。
三、总结与展望
本篇文章我们介绍了抽象类的定义、语法规则以及关键特性,详细说明了如何利用抽象方法约束子类必须实现某些行为,从而通过编译器的校验提升代码的健壮性。同时,通过多个示例,我们展示了抽象类在设计模板、共享代码实现上的优势。同时我们解析了Object类中的常用方法(如toString、equals、hashCode、clone getClass),阐述了这些方法在对象信息输出、比较、克隆等操作中的重要作用,并通过代码示例帮助你掌握了如何重写和自定义这些方法。
在接下来的内容中,我们将转向接口这一重要的Java编程构造。接口不仅定义了类必须遵循的行为规范,还通过多实现的特性帮助我们构建出更加灵活和模块化的程序架构。相信通过本篇的学习,你已经具备了理解接口设计思想的前提条件。下一篇文章将会带你详细了解接口的概念、语法以及在实际开发中的各种应用场景,从而构建一个更为完善的Java编程知识体系。
以上就是关于的内容啦,各位大佬有什么问题欢迎在评论区指正,或者私信我也是可以的啦,您的支持是我创作的最大动力!❤️
