文章目录

  • 一、 抽象类(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 抽象方法的限制

  1. 抽象方法不能是private:因为private方法不能被子类访问和重写
  2. 抽象方法不能是final:因为final方法不能被子类重写
  3. 抽象方法不能是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 接口的特性

  1. 接口不能直接创建对象,必须通过实现类来实例化
  2. 接口中的方法默认是public abstract。即使不显式声明,接口中的方法也是public abstract的:
public interface ExampleInterface {// 以下两种声明方式是等价的void method1(); // 默认是public abstractpublic abstract void method2(); // 显式声明
}
  1. 接口中的变量默认是public static final。接口中只能定义常量,不能定义实例变量:
public interface Constants {// 以下两种声明方式是等价的int MAX_VALUE = 100; // 默认是public static finalpublic static final int MIN_VALUE = 0; // 显式声明
}
  1. 接口支持多继承 解决了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");}
}
  1. 默认方法,被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);}
}
  1. 一个接口对应一个字节码文件
  2. 如果一个类不想实现接口中的方法,那么这个类应该定义为抽象类。但是这个抽象类被继承时,要实现所有未实现的方法。

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 何时使用抽象类

  1. 需要在相关类间共享代码:当多个相关类有共同的行为和状态时
  2. 需要定义非public的成员:当需要protected或private的成员时
  3. 需要定义状态:当需要实例变量来维护对象状态时
  4. 需要提供模板方法:当需要定义算法骨架,让子类实现特定步骤时

示例

// 适合使用抽象类的场景:图形类层次结构
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 何时使用接口

  1. 需要定义不相关类的共同行为:当不同类需要实现相同的行为时
  2. 需要实现多重继承:当一个类需要多种能力时
  3. 需要定义数据类型:当想指定一个对象能做什么而不关心如何做时
  4. 需要实现回调机制:当需要实现事件处理或回调时

示例

// 适合使用接口的场景:多种不相关的类需要相同能力
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中被标记为过时。

总结

关键要点总结

  1. 抽象类

    • 用于表示"is-a"关系
    • 可以包含抽象方法和具体方法
    • 可以包含实例变量和构造方法
    • 支持单继承
    • 适合代码复用和模板方法模式
  2. 接口

    • 用于表示"can-do"能力
    • 主要包含抽象方法(Java 8+有默认方法和静态方法)
    • 只能包含常量
    • 支持多实现
    • 适合定义契约、策略模式和回调机制
  3. Object类

    • 所有类的根类
    • 提供基本方法:toString(), equals(), hashCode(), clone()等
    • 重写equals()必须重写hashCode()
    • clone()需要实现Cloneable接口

最佳实践建议

  1. 优先使用接口:当需要定义类型时,优先考虑使用接口,它提供了更大的灵活性。

  2. 合理使用抽象类:当多个相关类需要共享代码时,使用抽象类。

  3. 遵循Liskov替换原则:子类应该能够替换父类而不影响程序正确性。

  4. 合理重写Object方法

    • 总是重写toString()以提供有意义的对象表示
    • 重写equals()时一定要重写hashCode()
    • 谨慎使用clone(),优先考虑复制构造器或工厂方法
  5. 使用组合而非继承:当不确定使用继承是否合适时,优先考虑使用组合。

  6. 接口 segregation原则:创建特定于客户端的接口,而不是通用的大接口。

常见面试问题

  1. 抽象类和接口的区别是什么?

    • 抽象类有构造方法,接口没有
    • 抽象类可以包含具体方法,接口主要包含抽象方法(Java 8前)
    • 类只能单继承抽象类,但可以实现多个接口
    • 抽象类表示"is-a"关系,接口表示"can-do"能力
  2. 什么时候使用抽象类?什么时候使用接口?

    • 使用抽象类:需要代码复用、共享状态、定义模板方法
    • 使用接口:需要多态、定义契约、实现不相关类的共同行为
  3. 为什么重写equals()要重写hashCode()?

    • 为了维护hashCode的一般契约:相等的对象必须有相等的哈希码
    • 防止在使用哈希集合(如HashMap、HashSet)时出现意外行为
  4. 深拷贝和浅拷贝的区别?

    • 浅拷贝:只复制对象本身,不复制引用字段指向的对象
    • 深拷贝:复制对象本身以及所有引用字段指向的对象
  5. Java 8中接口有哪些新特性?

    • 默认方法(default methods)
    • 静态方法(static methods)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/94363.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/94363.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/94363.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Autodl 创建新虚拟环境 python3.9

问题&#xff1a;本人在autodl上保存的环境因为很长时间没有开机&#xff0c;autodl竟然给我删除了。后来看了官网的介绍我才发现&#xff0c;原来15天不开机&#xff0c;autodl就会自动释放实例。 因此&#xff0c;我就自己重新选了一个虚拟环境&#xff0c;从头开始配置。 GP…

应急响应靶机-WindowsServer2022挖矿事件

依旧手痒开局&#xff0c;知攻善防实验室的原创靶机 https://mp.weixin.qq.com/s/URrNHvQSnFKOyefHKXKjQQ 相关账户密码&#xff1a; Administrator/zgsf123 注意&#xff1a;做个原始快照&#xff08;方便日后复习&#xff09;&#xff0c;安装VMware tool&#xff08;安装后图…

PCB电路设计学习3 电路原理图设计 元件PCB封装设计与添加

目录PCB电路设计学习3五、电路原理图设计5.1 32个发光二极管电路5.2 单片机外围电路5.3 供电与程序下载电路5.4 连接各部分网络&#xff0c;绘制边框和说明六、元件PCB封装设计与添加6.1 名词解释6.2 绘制PCB附学习参考网址欢迎大家有问题评论交流 (* ^ ω ^)PCB电路设计学习3 …

redis---常用数据类型及内部编码

Redis 中每种常用数据类型都对应多种内部编码&#xff0c;这些编码会根据数据特征&#xff08;如大小、数量&#xff09;自动切换&#xff0c;以平衡存储效率和操作性能。1.字符串&#xff08;String&#xff09;用途&#xff1a;存储文本、数字或二进制数据&#xff0c;是最基…

crypto.randomUUID is not a function

在本地运行时 crypto.randomUUID 好使&#xff0c;build 后放到服务器上用域名访问就不好使。原因&#xff1a;浏览器策略&#xff0c;浏览器在非https、localhost的环境中访问时&#xff0c;crypto.randomUUID 是不可用的开发时使用的是localhost正常访问 生产临时使用的是htt…

【思考】什么是服务器?什么是服务?什么是部署?

文章目录1 什么是服务器&#xff1f;什么是服务&#xff1f;端口是什么意思&#xff1f;2 什么是部署&#xff1f;1 什么是服务器&#xff1f;什么是服务&#xff1f;端口是什么意思&#xff1f; 服务器本质是一台运行着程序的电脑&#xff0c;它可以运行着很多程序&#xff0c…

自动驾驶导航信号使用方式调研

1 总结 本文调研在给定导航信号后&#xff0c;如何在端到端架构下&#xff0c;利用导航信息引导轨迹生成。 目前主流的方案可以分为2种。一种是将导航作为“前置引导”深度融入轨迹生成过程&#xff08;导航前置型&#xff09;&#xff1b;另一种则是将导航作为“后置评价”标准…

玳瑁的嵌入式日记D21-08020(数据结构)

双向链表double link listtypedef struct dou_node { DATATYPE data; struct dou_node *prev; struct dou_node *next; }DouLinkNode;双向链表&#xff1a;节点 数据 NEXT PREV . 手撕代码(增加删除) 增加&#xff0c;删除的操作&#xff0c; 需要 tmp 停止待操作节点的前一…

Uipath查找元素 查找子元素 获取属性活动组合使用示例

Uipath 查找元素 查找子元素 获取属性组合使用示例使用场景案例介绍项目流程图附加浏览器查找元素查找子元素遍历循环获取属性点击元素使用场景 在实际场景中&#xff0c;有时需RPA自动点击某组范围元素或获取某组范围元素的值&#xff0c;如需获取指定的父元素&#xff0c;再…

【MongoDB与MySQL对比】

MongoDB 与 MySQL 全方位对比分析在现代软件开发中&#xff0c;数据库的选择直接影响系统性能、扩展性和开发效率。MongoDB 和 MySQL 作为两种主流数据库&#xff0c;分别代表了 NoSQL 和关系型数据库的典型&#xff0c;各自在不同场景中发挥着重要作用。本文将抛开代码示例&am…

Spring AI开发指导-对话模型

对话模型接口描述Spring AI基于Spring Cloud的架构体系&#xff0c;定义了一系列可扩展的API接口&#xff0c;支持对接不同类型的AI大模型的核心功能&#xff0c;这些API接口支持同步编程模式或者异步编程模式&#xff1a;接口ModelModel是同步编程模式接口&#xff0c;其参数支…

Win11 下卸载 Oracle11g

目录 1、停止服务 2、启动 Universal install 应用 3、执行 deinstall.bat 脚本 4、删除注册表相关数据 5、删除环境变量中的oracle相关路径 6、删除安装文件 7、删除C盘中的相关Oracle文件 8、删除 Oracle 数据存放目录 9、检查 10、重装oracle可能还会碰到的问题 &…

深入剖析Spring Boot应用启动全流程

目录 前言 启动流程概览 一、第一阶段&#xff1a;初始化SpringApplication 二、第二阶段&#xff1a;运行SpringApplication 三、第三阶段&#xff1a;环境准备 四、第四阶段&#xff1a;创建应用上下文 五、第五阶段&#xff1a;准备应用上下文 六、第六阶段&#xf…

Matplotlib 可视化大师系列(三):plt.bar() 与 plt.barh() - 清晰对比的柱状图

目录Matplotlib 可视化大师系列博客总览Matplotlib 可视化大师系列&#xff08;三&#xff09;&#xff1a;plt.bar() 与 plt.barh() - 清晰对比的柱状图一、 柱状图是什么&#xff1f;何时使用&#xff1f;二、 函数原型与核心参数plt.bar(x, height, ...) - 垂直柱状图plt.ba…

基于 FastAPI 和 OpenFeature 使用 Feature Flag 控制业务功能

模拟业务场景&#xff1a;多租户系统跨域转账&#xff0c;需要控制某租户下某用户是否可以在某域转账 open_feature_util.py import typing from abc import abstractmethod, ABCMeta from typing import Sequencefrom openfeature.evaluation_context import EvaluationContex…

Stm32通过ESP8266 WiFi连接阿里云平台

本文将介绍stm32如何通过WiFi来连接阿里云&#xff0c;上传数据和接收指令。要先与阿里云建立TCP连接&#xff0c;然后再通过MQTT协议交互。 大体流程&#xff1a;1、在阿里云网页上创建产品和设备&#xff1b;2、stm32通过WiFi连接云平台&#xff1b;3、MQTT连接阿里云&#…

北京-测试-入职甲方金融-上班第三天

今日上班时间9-20.18&#xff0c;再加42分钟就可以拿到75块钱了&#xff0c;但我想回家&#xff0c;所以下班今天上午有人事举办的入职培训&#xff0c;下午有业务培训&#xff0c;培训完领导给我安排了两个需求。慌死&#xff0c;吓死&#xff0c;我都不懂&#xff0c;业务和工…

Java基础第2天总结

使用switch时注意事项&#xff1a;表达式类型只能是byte、short、int、char,JDK5开始支持枚举&#xff0c;JDK7开始支持String&#xff0c;不支持double、float、long(精确度问题&#xff0c;小数有点不精确)。case给出的值不允许重复&#xff0c;且只能是字面量&#xff0c;不…

鸿蒙开发中的List组件详解

目录 引言 1.List组件基础 2.List接口参数 1.space 2.initialIndex 3.scroller 3.ListView的属性 1.listDirection 2.lanes 3.divider 4.scrollBar 4.布局与约束 5.ListItem生命周期 1.使用ForEach创建ListItem 2.使用LazyForEach创建ListItem 3…

2026界计算机专业毕业的有福了!(开题报告任务书)

开题报告 我们以基于Java的婚纱店管理系统为案例进行指导。 任务书&#xff1a; 首先是毕设的立题依据&#xff0c;这个主要描写一些简洁大体的大白话&#xff0c;描述一下你为什么要做这个题目的毕设。 那就需要你描述一下现阶段社会面婚纱店的运营情况&#xff0c;写一些…