当前位置: 首页 > news >正文

【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 抽象方法的限制

  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(); // 编译错误

二、 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接口

最佳实践建议

  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. 深拷贝和浅拷贝的区别?

    • 浅拷贝:只复制对象本身,不复制引用字段指向的对象
    • 深拷贝:复制对象本身以及所有引用字段指向的对象
http://www.xdnf.cn/news/1355275.html

相关文章:

  • 51单片机-实现外部中断模块教程
  • SpringBoot3整合dubbo3客户端【最佳实践】
  • 编程刷题-染色题DFS
  • 【C标准库】详解<stdio.h>标准输入输出库
  • CUDA和torch的安装
  • 什么是多元线性回归,系数、自变量、因变量是什么,多元线性回归中的线性是什么
  • 多光谱相机检测石油石化行业的跑冒滴漏的可行性分析
  • 【yocto】Yocto Project 配置层(.conf)文件语法详解
  • calchash.exe和chckhash.exe计算pe文件hash值的两个实用小工具
  • 智慧零售漏扫率↓79%!陌讯多模态融合算法在智能收银与货架管理的实战解析
  • 双目密集匹配(stereo dense matching)
  • stack,queue以及deque的介绍
  • 深度学习中主流激活函数的数学原理与PyTorch实现综述
  • 【字母异位分组】
  • 随机森林1
  • 【机器学习深度学习】多模态学习
  • 【GaussDB】使用MySQL客户端连接到GaussDB的M-Compatibility数据库
  • 【85页PPT】数字化转型LIMS大型企业智能制造之LIMS实验室管理系统产品解决方案(附下载方式)
  • MVC模式在个人博客系统中的应用
  • 简单介绍计算机的工作过程
  • 激光雷达工作原理
  • 算法训练营day59 图论⑨ dijkstra(堆优化版)精讲、Bellman_ford 算法精讲
  • C++初阶(2)C++入门基础1
  • 第1篇:走进日志框架的世界 - 从HelloWorld到企业级应用
  • 为什么在WHERE子句里使用函数,会让索引失效
  • 复杂工业场景误报率↓85%!陌讯多模态火焰识别算法实战解析
  • Codeforces Round 1043 (Div. 3)(A-E)
  • 历史数据分析——半导体
  • 【科研绘图系列】浮游植物的溶解性有机碳与初级生产力的关系
  • 【Game】Powerful——Punch and Kick(12.2)