【Java SE】抽象类与Object类
文章目录
- 一、 抽象类(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 编译器校验
- 二、 Object类:所有类的根
- 2.1 Object类概述
- 2.2 Object类的重要方法
- 2.2.1 toString()方法
- 2.2.2 equals()方法
- 2.2.3 hashCode()方法
- 2.2.4 clone()方法
- 2.2.5 getClass()方法
- 总结
- 关键要点总结
- 最佳实践建议
- 常见面试问题
在面向对象编程的世界中,抽象是一个核心概念。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(); // 编译错误
二、 Object类:所有类的根
2.1 Object类概述
在Java中,所有的类都直接或间接继承自Object
类。Object
类位于java.lang
包中,不需要显式导入。它提供了一些基本方法,这些方法可以被所有Java对象使用。
2.2 Object类的重要方法
2.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}}
}
2.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}
}
2.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}
}
2.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();}}
}
2.2.5 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}
}
总结
关键要点总结
-
. 抽象类:
- 用于表示"is-a"关系
- 可以包含抽象方法和具体方法
- 可以包含实例变量和构造方法
- 支持单继承
- 适合代码复用和模板方法模式
-
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)时出现意外行为
-
深拷贝和浅拷贝的区别?
- 浅拷贝:只复制对象本身,不复制引用字段指向的对象
- 深拷贝:复制对象本身以及所有引用字段指向的对象