文章目录
- 一、 抽象类(Abstract Class)
- 1.1 什么是抽象类?
- 1.2 抽象类的语法
- 1.2.1 定义抽象类
- 1.2.2 继承抽象类
- 1.3 抽象类的特性
- 1.3.1 不能直接实例化
- 1.3.2 抽象方法的限制
- 1.3.3 抽象类可以包含构造方法
- 1.3.4 抽象类不一定包含抽象方法
- 1.3.5 抽象类可以实现接口
- 1.4 抽象类的作用与优势
- 1.4.1 代码复用
- 1.4.2 模板方法模式
- 1.4.3 编译器校验
- 二、接口(Interface)
- 2.1 什么是接口?
- 2.2 接口的语法
- 2.2.1 定义接口
- 2.2.2 实现接口
- 2.3 接口的特性
- 2.4 接口的应用场景
- 2.4.1 定义行为契约
- 2.4.3 回调机制
- 2.4.4 策略模式
- 三、 抽象类与接口的比较
- 3.1 核心区别
- 3.2 选择指南
- 3.2.1 何时使用抽象类
- 3.2.2 何时使用接口
- 3.3 组合使用抽象类和接口
- 四、 Object类:所有类的根
- 4.1 Object类概述
- 4.2 Object类的重要方法
- 4.2.1 toString()方法
- 4.2.2 equals()方法
- 4.2.3 hashCode()方法
- 4.2.4 clone()方法
- 4.3 其他Object方法
- 4.3.1 getClass()方法
- 4.3.2 finalize()方法
- 总结
- 关键要点总结
- 最佳实践建议
- 常见面试问题
在面向对象编程的世界中,抽象是一个核心概念。Java作为一门纯粹的面向对象语言,提供了两种重要的抽象机制:抽象类(Abstract Class)和接口(Interface)。这两种机制虽然都用于实现抽象,但在设计理念、语法特性和应用场景上有着显著差异。本文将深入探讨抽象类和接口的各个方面,帮助开发者更好地理解并运用这两种强大的抽象工具。
一、 抽象类(Abstract Class)
1.1 什么是抽象类?
在面向对象编程中,并不是所有的类都是用来直接创建对象的。如果一个类中没有包含足够的信息来描述一个具体的对象,那么它就应该被定义为抽象类。
抽象类是一种介于完全抽象(接口)和完全具体(普通类)之间的类。它可以包含抽象方法(没有实现的方法)和具体方法(有实现的方法),以及成员变量。
类比:
考虑一个"图形"类。图形是一个抽象概念,我们可以定义图形的共同特性(如颜色、位置)和行为(如绘制、计算面积),但无法具体实现"绘制"方法,因为不知道具体是什么图形(圆形、矩形等)。这种情况下,"图形"类就应该被设计为抽象类。
1.2 抽象类的语法
1.2.1 定义抽象类
使用abstract
关键字修饰类定义:
// 抽象类:被abstract修饰的类
public abstract class Shape {// 属性protected String color;protected boolean filled;// 构造方法public Shape(String color, boolean filled) {this.color = color;this.filled = filled;}// 抽象方法:被abstract修饰的方法,没有方法体public abstract double calculateArea();public abstract double calculatePerimeter();// 具体方法public String getColor() {return color;}public void setColor(String color) {this.color = color;}public boolean isFilled() {return filled;}public void setFilled(boolean filled) {this.filled = filled;}// 可以重写Object类的方法@Overridepublic String toString() {return "Shape[color=" + color + ", filled=" + filled + "]";}
}
1.2.2 继承抽象类
子类必须实现父抽象类中的所有抽象方法,否则子类也必须声明为抽象类:
// 圆形类继承抽象图形类
public class Circle extends Shape {private double radius;public Circle(String color, boolean filled, double radius) {super(color, filled);this.radius = radius;}// 实现抽象方法@Overridepublic double calculateArea() {return Math.PI * radius * radius;}@Overridepublic double calculatePerimeter() {return 2 * Math.PI * radius;}// 子类特有的方法public double getRadius() {return radius;}public void setRadius(double radius) {this.radius = radius;}@Overridepublic String toString() {return "Circle[" + super.toString() + ", radius=" + radius + "]";}
}// 矩形类继承抽象图形类
public class Rectangle extends Shape {private double width;private double height;public Rectangle(String color, boolean filled, double width, double height) {super(color, filled);this.width = width;this.height = height;}// 实现抽象方法@Overridepublic double calculateArea() {return width * height;}@Overridepublic double calculatePerimeter() {return 2 * (width + height);}// 子类特有的方法public double getWidth() {return width;}public void setWidth(double width) {this.width = width;}public double getHeight() {return height;}public void setHeight(double height) {this.height = height;}@Overridepublic String toString() {return "Rectangle[" + super.toString() + ", width=" + width + ", height=" + height + "]";}
}
1.3 抽象类的特性
1.3.1 不能直接实例化
抽象类不能直接创建对象,尝试实例化抽象类会导致编译错误:
public class AbstractClassTest {public static void main(String[] args) {// 编译错误:Shape是抽象的; 无法实例化// Shape shape = new Shape("red", true);// 正确:通过子类实例化Shape circle = new Circle("blue", true, 5.0);Shape rectangle = new Rectangle("green", false, 4.0, 6.0);System.out.println("Circle area: " + circle.calculateArea());System.out.println("Rectangle perimeter: " + rectangle.calculatePerimeter());}
}
1.3.2 抽象方法的限制
- 抽象方法不能是private:因为private方法不能被子类访问和重写
- 抽象方法不能是final:因为final方法不能被子类重写
- 抽象方法不能是static:因为static方法属于类而不是实例,不能重写
public abstract class Example {// 编译错误:非法的修饰符组合: abstract和private// abstract private void method1();// 编译错误:非法的修饰符组合: abstract和final// abstract final void method2();// 编译错误:非法的修饰符组合: abstract和static// abstract static void method3();// 正确:抽象方法abstract void method4();
}
1.3.3 抽象类可以包含构造方法
虽然抽象类不能直接实例化,但可以包含构造方法,用于初始化抽象类中定义的属性:
public abstract class Animal {private String name;private int age;// 抽象类的构造方法public Animal(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public int getAge() {return age;}// 抽象方法public abstract void makeSound();
}public class Dog extends Animal {public Dog(String name, int age) {super(name, age); // 调用父类构造方法}@Overridepublic void makeSound() {System.out.println("Woof! Woof!");}
}
1.3.4 抽象类不一定包含抽象方法
一个类可以声明为抽象类而不包含任何抽象方法,这种情况通常是为了防止该类被实例化:
// 没有抽象方法的抽象类
public abstract class UtilityClass {// 只有静态方法public static void utilityMethod1() {// 实现}public static void utilityMethod2() {// 实现}// 防止实例化private UtilityClass() {throw new AssertionError("Cannot instantiate utility class");}
}
1.3.5 抽象类可以实现接口
抽象类可以实现接口,可以选择实现接口中的部分或全部方法:
public interface Drawable {void draw();void resize();
}public abstract AbstractShape implements Drawable {// 实现接口中的一个方法@Overridepublic void draw() {System.out.println("Drawing shape");}// 保留另一个方法为抽象,让子类实现public abstract void resize();
}
1.4 抽象类的作用与优势
1.4.1 代码复用
抽象类允许将公共代码提取到父类中,子类可以复用这些代码:
public abstract class DatabaseAccessor {// 公共的连接数据库方法protected Connection connectToDatabase() throws SQLException {String url = "jdbc:mysql://localhost:3306/mydatabase";String user = "username";String password = "password";return DriverManager.getConnection(url, user, password);}// 公共的关闭连接方法protected void closeConnection(Connection conn) {if (conn != null) {try {conn.close();} catch (SQLException e) {System.err.println("Error closing connection: " + e.getMessage());}}}// 抽象方法,子类必须实现public abstract List<?> fetchData();public abstract void saveData(Object data);
}public class UserDAO extends DatabaseAccessor {@Overridepublic List<User> fetchData() {Connection conn = null;try {conn = connectToDatabase(); // 复用父类方法// 执行查询...return new ArrayList<User>();} catch (SQLException e) {throw new RuntimeException("Database error", e);} finally {closeConnection(conn); // 复用父类方法}}@Overridepublic void saveData(Object data) {// 实现保存逻辑}
}
1.4.2 模板方法模式
抽象类是实现模板方法模式的理想选择:
public abstract class Game {// 模板方法,定义了算法骨架public final void play() {initialize();startPlay();endPlay();}// 具体方法private void initialize() {System.out.println("Game Initialized! Starting game.");}// 抽象方法,子类必须实现protected abstract void startPlay();protected abstract void endPlay();
}public class Cricket extends Game {@Overrideprotected void startPlay() {System.out.println("Cricket Game Started. Enjoy the game!");}@Overrideprotected void endPlay() {System.out.println("Cricket Game Finished!");}
}public class Football extends Game {@Overrideprotected void startPlay() {System.out.println("Football Game Started. Enjoy the game!");}@Overrideprotected void endPlay() {System.out.println("Football Game Finished!");}
}
1.4.3 编译器校验
抽象类提供编译时检查,确保不会意外实例化不完整的类:
public abstract class PaymentProcessor {public abstract boolean validatePayment();public abstract void processPayment();// 如果尝试实例化这个抽象类,编译器会报错
}// 在别处尝试实例化:
// PaymentProcessor processor = new PaymentProcessor(); // 编译错误
二、接口(Interface)
2.1 什么是接口?
接口是一种完全抽象的类型,它定义了一组方法签名(没有实现),任何实现该接口的类都必须提供这些方法的具体实现。
接口表示一种"契约"或"能力",描述了一个类"能做什么",而不是"是什么"。
类比:
考虑USB接口。USB定义了一组规范(数据传输、供电等),任何遵循USB规范的设备(U盘、鼠标、键盘)都可以与计算机交互。计算机不需要知道设备的具体类型,只需要知道设备遵循USB规范。
2.2 接口的语法
2.2.1 定义接口
使用interface
关键字定义接口:
// USB接口示例
public interface USB {// 常量(默认是public static final)double VERSION = 3.0;// 抽象方法(默认是public abstract)void connect();void disconnect();void transferData(byte[] data);// 默认方法default String getVersionInfo() {return "USB " + VERSION;}// 静态方法static boolean isCompatible(USB device) {return device != null;}// 私有方法private void logConnection() {System.out.println("USB connection established");}
}
2.2.2 实现接口
类使用implements
关键字实现接口:
// 鼠标类实现USB接口
public class Mouse implements USB {private boolean connected;@Overridepublic void connect() {this.connected = true;System.out.println("Mouse connected");}@Overridepublic void disconnect() {this.connected = false;System.out.println("Mouse disconnected");}@Overridepublic void transferData(byte[] data) {if (connected) {System.out.println("Mouse data transferred: " + data.length + " bytes");} else {System.out.println("Mouse not connected");}}// 鼠标特有的方法public void click() {System.out.println("Mouse clicked");}public void scroll(int direction) {System.out.println("Mouse scrolled: " + direction);}
}// 键盘类实现USB接口
public class Keyboard implements USB {private boolean connected;@Overridepublic void connect() {this.connected = true;System.out.println("Keyboard connected");}@Overridepublic void disconnect() {this.connected = false;System.out.println("Keyboard disconnected");}@Overridepublic void transferData(byte[] data) {if (connected) {System.out.println("Keyboard data transferred: " + data.length + " bytes");} else {System.out.println("Keyboard not connected");}}// 键盘特有的方法public void pressKey(char key) {System.out.println("Key pressed: " + key);}
}
2.3 接口的特性
- 接口不能直接创建对象,必须通过实现类来实例化
- 接口中的方法默认是
public abstract
。即使不显式声明,接口中的方法也是public abstract的:
public interface ExampleInterface {// 以下两种声明方式是等价的void method1(); // 默认是public abstractpublic abstract void method2(); // 显式声明
}
- 接口中的变量默认是
public static final
。接口中只能定义常量,不能定义实例变量:
public interface Constants {// 以下两种声明方式是等价的int MAX_VALUE = 100; // 默认是public static finalpublic static final int MIN_VALUE = 0; // 显式声明
}
- 接口支持多继承 解决了Java不能多继承父类的问题
public interface Flyable {void fly();
}public interface Swimmable {void swim();
}public interface Runnable {void run();
}// 接口多继承
public interface Amphibious extends Flyable, Swimmable, Runnable {// 可以添加新方法或使用继承的方法
}// 类实现多接口
public class Frog implements Amphibious {@Overridepublic void fly() {System.out.println("Frog gliding through air");}@Overridepublic void swim() {System.out.println("Frog swimming in water");}@Overridepublic void run() {System.out.println("Frog hopping on land");}
}
- 默认方法,被static或default修饰可以有具体方法实现
- 默认方法(Default Methods) 避免破坏现有实现类:
public interface Vehicle {void start();void stop();// 默认方法default void honk() {System.out.println("Beep beep!");}
}public class Car implements Vehicle {@Overridepublic void start() {System.out.println("Car started");}@Overridepublic void stop() {System.out.println("Car stopped");}// 可以选择不重写honk方法,使用默认实现
}public class Truck implements Vehicle {@Overridepublic void start() {System.out.println("Truck started");}@Overridepublic void stop() {System.out.println("Truck stopped");}// 也可以选择重写默认方法@Overridepublic void honk() {System.out.println("HONK HONK!");}
}
- 静态方法(Static Methods)
public interface MathOperations {// 静态方法static int add(int a, int b) {return a + b;}static int multiply(int a, int b) {return a * b;}
}// 使用接口静态方法
public class Calculator {public void calculate() {int sum = MathOperations.add(5, 3);int product = MathOperations.multiply(5, 3);System.out.println("Sum: " + sum + ", Product: " + product);}
}
- 私有方法(Private Methods):用于代码复用:
public interface Logger {default void logInfo(String message) {log("INFO", message);}default void logError(String message) {log("ERROR", message);}// 私有方法private void log(String level, String message) {System.out.println("[" + level + "] " + new Date() + ": " + message);}
}
- 一个接口对应一个字节码文件
- 如果一个类不想实现接口中的方法,那么这个类应该定义为抽象类。但是这个抽象类被继承时,要实现所有未实现的方法。
2.4 接口的应用场景
2.4.1 定义行为契约
接口表示具有某某特性,适合定义一组相关行为,而不关心实现细节:
- 定义抽象类动物,三个表示功能的接口:
//抽象类动物
public abstract class Animal {public String name;public int age;public Animal(String name, int age) {this.name = name;this.age = age;}public abstract void eat();
}
//IRunning接口
public interface IRunning {void run();
}
//ISwimming接口
public interface ISwimming {void swim();
}
//IFly接口
public interface IFly {void ifly();
}
- 三个动物用来实现接口:
public class Dog extends Animal implements IRunning,ISwimming{public Dog(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println(this.name+"正在吃狗粮。。。");}public void bark(){System.out.println(this.name+"正在汪汪汪");}@Overridepublic void run() {System.out.println(this.name+"正在跑");}@Overridepublic void swim() {System.out.println(this.name+"正在游泳");}
}public class Duck extends Animal implements ISwimming,IRunning,IFly{public Duck(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println(this.name+"正在吃鸭粮。。。");}@Overridepublic void ifly() {System.out.println(this.name+"正在飞。。。");}@Overridepublic void run() {System.out.println(this.name+"正在跑。。。");}@Overridepublic void swim() {System.out.println(this.name+"正在吃游泳。。。");}
}public class Fish extends Animal implements ISwimming,IRunning{public Fish(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println(this.name+"正在吃鱼粮");}@Overridepublic void swim() {System.out.println(this.name+"正在游泳");}@Overridepublic void run() {System.out.println(this.name+"正在跑");}
}
- 测试类:在这个 walk 方法内部, 我们并不关注到底是哪种动物, 只要参数是会跑的。 类的使用者就不必关注具体类型,而只关注某个类是否具备某种能力.
public class Test {public static void walk(IRunning iRunning){iRunning.run();}public static void fly(IFly iFly){iFly.ifly();}public static void swim(ISwimming iSwimming){iSwimming.swim();}public static void main(String[] args) {walk(new Dog("旺财",11));walk(new Duck("唐老鸭",10));fly(new Duck("唐老鸭",10));swim(new Dog("旺财",11));}
}
2.4.3 回调机制
接口常用于实现回调机制:
// 回调接口
public interface EventListener {void onEvent(String eventType, String data);
}// 事件生产者
public class EventProducer {private List<EventListener> listeners = new ArrayList<>();public void addListener(EventListener listener) {listeners.add(listener);}public void removeListener(EventListener listener) {listeners.remove(listener);}public void produceEvent(String eventType, String data) {System.out.println("Producing event: " + eventType);for (EventListener listener : listeners) {listener.onEvent(eventType, data);}}
}// 事件消费者
public class EventConsumer implements EventListener {private String name;public EventConsumer(String name) {this.name = name;}@Overridepublic void onEvent(String eventType, String data) {System.out.println(name + " received event: " + eventType + " with data: " + data);}
}// 使用回调
public class CallbackExample {public static void main(String[] args) {EventProducer producer = new EventProducer();EventConsumer consumer1 = new EventConsumer("Consumer1");EventConsumer consumer2 = new EventConsumer("Consumer2");producer.addListener(consumer1);producer.addListener(consumer2);producer.produceEvent("USER_LOGIN", "user123");producer.produceEvent("FILE_UPLOAD", "document.pdf");}
}
2.4.4 策略模式
接口是实现策略模式的理想选择:
// 策略接口
public interface SortingStrategy {void sort(int[] array);
}// 具体策略:冒泡排序
public class BubbleSort implements SortingStrategy {@Overridepublic void sort(int[] array) {System.out.println("Sorting using bubble sort");// 实现冒泡排序...for (int i = 0; i < array.length - 1; i++) {for (int j = 0; j < array.length - i - 1; j++) {if (array[j] > array[j + 1]) {int temp = array[j];array[j] = array[j + 1];array[j + 1] = temp;}}}}
}// 具体策略:快速排序
public class QuickSort implements SortingStrategy {@Overridepublic void sort(int[] array) {System.out.println("Sorting using quick sort");quickSort(array, 0, array.length - 1);}private void quickSort(int[] array, int low, int high) {if (low < high) {int pi = partition(array, low, high);quickSort(array, low, pi - 1);quickSort(array, pi + 1, high);}}private int partition(int[] array, int low, int high) {int pivot = array[high];int i = low - 1;for (int j = low; j < high; j++) {if (array[j] < pivot) {i++;int temp = array[i];array[i] = array[j];array[j] = temp;}}int temp = array[i + 1];array[i + 1] = array[high];array[high] = temp;return i + 1;}
}// 上下文类
public class Sorter {private SortingStrategy strategy;public Sorter(SortingStrategy strategy) {this.strategy = strategy;}public void setStrategy(SortingStrategy strategy) {this.strategy = strategy;}public void sortArray(int[] array) {strategy.sort(array);}
}// 使用策略模式
public class StrategyExample {public static void main(String[] args) {int[] array = {64, 34, 25, 12, 22, 11, 90};Sorter sorter = new Sorter(new BubbleSort());sorter.sortArray(array);printArray("After bubble sort", array);// 切换策略sorter.setStrategy(new QuickSort());sorter.sortArray(array);printArray("After quick sort", array);}private static void printArray(String message, int[] array) {System.out.print(message + ": ");for (int value : array) {System.out.print(value + " ");}System.out.println();}
}
三、 抽象类与接口的比较
3.1 核心区别
特性 | 抽象类 | 接口 |
---|---|---|
方法实现 | 可以包含抽象方法和具体方法 | Java 8前只能包含抽象方法,Java 8+可以包含默认方法和静态方法 |
字段 | 可以包含实例变量和常量 | 只能包含常量(public static final) |
构造方法 | 可以包含构造方法 | 不能包含构造方法 |
继承 | 单继承(一个类只能继承一个抽象类) | 多实现(一个类可以实现多个接口) |
设计理念 | "is-a"关系(是什么) | "has-a"关系或"can-do"能力(能做什么) |
访问修饰符 | 方法可以是public、protected、private | 方法默认是public(Java 9+可以有private方法) |
代码复用 | 更适合代码复用和共享实现 | 更适合定义契约和多态行为 |
3.2 选择指南
3.2.1 何时使用抽象类
- 需要在相关类间共享代码:当多个相关类有共同的行为和状态时
- 需要定义非public的成员:当需要protected或private的成员时
- 需要定义状态:当需要实例变量来维护对象状态时
- 需要提供模板方法:当需要定义算法骨架,让子类实现特定步骤时
示例:
// 适合使用抽象类的场景:图形类层次结构
public abstract class GraphicObject {protected int x, y;protected String color;public GraphicObject(int x, int y, String color) {this.x = x;this.y = y;this.color = color;}// 公共方法public void moveTo(int newX, int newY) {this.x = newX;this.y = newY;}public String getColor() {return color;}public void setColor(String color) {this.color = color;}// 抽象方法public abstract void draw();public abstract void resize();
}public class Circle extends GraphicObject {private double radius;public Circle(int x, int y, String color, double radius) {super(x, y, color);this.radius = radius;}@Overridepublic void draw() {System.out.println("Drawing circle at (" + x + "," + y + ") with radius " + radius);}@Overridepublic void resize() {radius *= 1.1; // 扩大10%}
}
3.2.2 何时使用接口
- 需要定义不相关类的共同行为:当不同类需要实现相同的行为时
- 需要实现多重继承:当一个类需要多种能力时
- 需要定义数据类型:当想指定一个对象能做什么而不关心如何做时
- 需要实现回调机制:当需要实现事件处理或回调时
示例:
// 适合使用接口的场景:多种不相关的类需要相同能力
public interface Loggable {void log(String message);String getLogInfo();
}// 网络连接类实现日志功能
public class NetworkConnection implements Loggable {private String url;public NetworkConnection(String url) {this.url = url;}@Overridepublic void log(String message) {System.out.println("Network[" + url + "]: " + message);}@Overridepublic String getLogInfo() {return "Network connection to " + url;}public void connect() {log("Connecting...");// 连接逻辑...}
}// 用户类实现日志功能
public class User implements Loggable {private String username;private String email;public User(String username, String email) {this.username = username;this.email = email;}@Overridepublic void log(String message) {System.out.println("User[" + username + "]: " + message);}@Overridepublic String getLogInfo() {return "User: " + username + " (" + email + ")";}public void login() {log("User logged in");// 登录逻辑...}
}// 日志管理器,处理所有可日志对象
public class LoggerManager {private List<Loggable> loggables = new ArrayList<>();public void addLoggable(Loggable loggable) {loggables.add(loggable);}public void logAll(String message) {for (Loggable loggable : loggables) {loggable.log(message);}}
}
3.3 组合使用抽象类和接口
在实际开发中,经常同时使用抽象类和接口:
// 接口定义能力
public interface Flyable {void fly();int getMaxAltitude();
}public interface Swimmable {void swim();int getMaxDepth();
}// 抽象类提供公共实现
public abstract class Animal {protected String name;protected int age;public Animal(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public int getAge() {return age;}public abstract void makeSound();public void sleep() {System.out.println(name + " is sleeping");}
}// 具体类继承抽象类并实现接口
public class Duck extends Animal implements Flyable, Swimmable {public Duck(String name, int age) {super(name, age);}@Overridepublic void makeSound() {System.out.println("Quack! Quack!");}@Overridepublic void fly() {System.out.println(name + " is flying");}@Overridepublic int getMaxAltitude() {return 1000; // 鸭子最高飞1000米}@Overridepublic void swim() {System.out.println(name + " is swimming");}@Overridepublic int getMaxDepth() {return 5; // 鸭子最多潜5米}
}// 使用
public class Zoo {public void showAnimalAbilities(Animal animal) {System.out.println(animal.getName() + " says:");animal.makeSound();if (animal instanceof Flyable) {Flyable flyable = (Flyable) animal;flyable.fly();System.out.println("Max altitude: " + flyable.getMaxAltitude() + "m");}if (animal instanceof Swimmable) {Swimmable swimmable = (Swimmable) animal;swimmable.swim();System.out.println("Max depth: " + swimmable.getMaxDepth() + "m");}}
}
四、 Object类:所有类的根
4.1 Object类概述
在Java中,所有的类都直接或间接继承自Object
类。Object
类位于java.lang
包中,不需要显式导入。它提供了一些基本方法,这些方法可以被所有Java对象使用。
4.2 Object类的重要方法
4.2.1 toString()方法
toString()
方法返回对象的字符串表示。默认实现返回类名和哈希码:
public class Person {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}// 重写toString方法@Overridepublic String toString() {return "Person{name='" + name + "', age=" + age + "}";}
}public class ToStringExample {public static void main(String[] args) {Person person = new Person("Alice", 30);System.out.println(person); // 自动调用toString()// 输出: Person{name='Alice', age=30}}
}
4.2.2 equals()方法
- 如果
==
左右两侧是基本类型变量,比较的是变量中值是否相同 - 如果
==
左右两侧是引用类型变量,比较的是引用变量地址是否相同 - 如果要比较对象中内容,必须重写
Object
中的equals
方法,因为equals
方法默认也是按照地址比较:
public class Person {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}// 重写equals方法@Overridepublic boolean equals(Object obj) {// 1. 检查是否是同一个对象if (this == obj) return true;// 2. 检查是否为null或类型不同if (obj == null || getClass() != obj.getClass()) return false;// 3. 类型转换和字段比较Person person = (Person) obj;return age == person.age && Objects.equals(name, person.name);}
}public class Test{public static void main(String[] args) {Person p1 = new Person("Alice", 30);Person p2 = new Person("Alice", 30);Person p3 = new Person("Bob", 25);System.out.println(p1.equals(p2)); // trueSystem.out.println(p1.equals(p3)); // falseSystem.out.println(p1.equals(null)); // false}
}
4.2.3 hashCode()方法
hashCode()
方法返回对象的哈希码,可以理解为内存地址。如果重写了equals()
方法,必须同时重写hashCode()
方法:
public class Person {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic 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 && Objects.equals(name, person.name);}// 重写hashCode方法@Overridepublic int hashCode() {return Objects.hash(name, age);}
}public class Test{public static void main(String[] args) {Person p1 = new Person("Alice", 30);Person p2 = new Person("Alice", 30);System.out.println(p1.hashCode()); // 例如: 123456789System.out.println(p2.hashCode()); // 与p1相同: 123456789System.out.println(p1.hashCode() == p2.hashCode()); // true}
}
4.2.4 clone()方法
clone()
方法创建并返回对象的一个副本。要实现克隆,类必须实现Cloneable
接口:
public class Person implements Cloneable {private String name;private int age;private Address address; // 引用类型字段public Person(String name, int age, Address address) {this.name = name;this.age = age;this.address = address;}// 浅拷贝实现@Overridepublic Object clone() throws CloneNotSupportedException {return super.clone();}// 深拷贝实现public Person deepClone() throws CloneNotSupportedException {Person cloned = (Person) super.clone();cloned.address = (Address) address.clone(); // 假设Address也实现了Cloneablereturn cloned;}
}public class Address implements Cloneable {private String city;private String street;public Address(String city, String street) {this.city = city;this.street = street;}@Overridepublic Object clone() throws CloneNotSupportedException {return super.clone();}
}public class CloneExample {public static void main(String[] args) {try {Address address = new Address("Beijing", "Chang'an Street");Person original = new Person("Alice", 30, address);// 浅拷贝Person shallowCopy = (Person) original.clone();// 深拷贝Person deepCopy = original.deepClone();System.out.println("Original: " + original);System.out.println("Shallow copy: " + shallowCopy);System.out.println("Deep copy: " + deepCopy);} catch (CloneNotSupportedException e) {e.printStackTrace();}}
}
4.3 其他Object方法
4.3.1 getClass()方法
getClass()
方法返回对象的运行时类:
public class GetClassExample {public static void main(String[] args) {String str = "Hello";Integer num = 123;System.out.println(str.getClass()); // class java.lang.StringSystem.out.println(num.getClass()); // class java.lang.IntegerSystem.out.println(str.getClass().getName()); // java.lang.StringSystem.out.println(num.getClass().getSimpleName()); // Integer}
}
4.3.2 finalize()方法
finalize()
方法在对象被垃圾回收前调用,但不推荐使用:
public class FinalizeExample {private String name;public FinalizeExample(String name) {this.name = name;}@Overrideprotected void finalize() throws Throwable {try {System.out.println("Finalizing " + name);} finally {super.finalize();}}public static void main(String[] args) {new FinalizeExample("test");System.gc(); // 建议JVM进行垃圾回收}
}
注意:finalize()
方法已不推荐使用,在Java 9中被标记为过时。
总结
关键要点总结
-
抽象类:
- 用于表示"is-a"关系
- 可以包含抽象方法和具体方法
- 可以包含实例变量和构造方法
- 支持单继承
- 适合代码复用和模板方法模式
-
接口:
- 用于表示"can-do"能力
- 主要包含抽象方法(Java 8+有默认方法和静态方法)
- 只能包含常量
- 支持多实现
- 适合定义契约、策略模式和回调机制
-
Object类:
- 所有类的根类
- 提供基本方法:toString(), equals(), hashCode(), clone()等
- 重写equals()必须重写hashCode()
- clone()需要实现Cloneable接口
最佳实践建议
-
优先使用接口:当需要定义类型时,优先考虑使用接口,它提供了更大的灵活性。
-
合理使用抽象类:当多个相关类需要共享代码时,使用抽象类。
-
遵循Liskov替换原则:子类应该能够替换父类而不影响程序正确性。
-
合理重写Object方法:
- 总是重写toString()以提供有意义的对象表示
- 重写equals()时一定要重写hashCode()
- 谨慎使用clone(),优先考虑复制构造器或工厂方法
-
使用组合而非继承:当不确定使用继承是否合适时,优先考虑使用组合。
-
接口 segregation原则:创建特定于客户端的接口,而不是通用的大接口。
常见面试问题
-
抽象类和接口的区别是什么?
- 抽象类有构造方法,接口没有
- 抽象类可以包含具体方法,接口主要包含抽象方法(Java 8前)
- 类只能单继承抽象类,但可以实现多个接口
- 抽象类表示"is-a"关系,接口表示"can-do"能力
-
什么时候使用抽象类?什么时候使用接口?
- 使用抽象类:需要代码复用、共享状态、定义模板方法
- 使用接口:需要多态、定义契约、实现不相关类的共同行为
-
为什么重写equals()要重写hashCode()?
- 为了维护hashCode的一般契约:相等的对象必须有相等的哈希码
- 防止在使用哈希集合(如HashMap、HashSet)时出现意外行为
-
深拷贝和浅拷贝的区别?
- 浅拷贝:只复制对象本身,不复制引用字段指向的对象
- 深拷贝:复制对象本身以及所有引用字段指向的对象
-
Java 8中接口有哪些新特性?
- 默认方法(default methods)
- 静态方法(static methods)