Java接口(二)
1. 接口使用实例
给对象数组排序
// 步骤1:定义学生类(需要排序的对象)
class Student {
String name; // 学生姓名
int age; // 学生年龄
double score; // 考试成绩
// 构造方法:创建学生对象时初始化数据
public Student(String name, int age, double score) {
this.name = name; // 给name字段赋值
this.age = age;// 给age字段赋值
this.score = score;// 给score字段赋值
}
// 重写toString方法:方便打印学生信息
@Override
public String toString() {
return "[" + this.name + "|" + this.age + "|" + this.score + "]";
}
}
再给定一个学生对象数组, 对这个对象数组中的元素进行排序(按分数降序).
Student[] students = {
new Student("张三", 20, 88.5),
new Student("李四", 19, 92.0),
new Student("王五", 22, 85.0),
new Student("赵六", 25, 96.5)
};
数组我们有一个现成的 sort 方法, 能否直接使用这个方法排序呢?
Arrays.sort(students);
System.out.println(Arrays.toString(students));
// 运行出错, 抛出异常.
Exception in thread "main" java.lang.ClassCastException: Student cannot be cast to java.lang.Comparable
发现不能,和普通的整数不一样, 两个整数是可以直接比较的, 大小关系明确. 而两个学生对象的大小关系怎么确定?
方法一:额外让Student实现Comparator接口,并实现其中的compare方法。
// 步骤1:定义学生类(需要排序的对象)
class Student {
String name; // 学生姓名
int age; // 学生年龄
double score; // 考试成绩
// 构造方法:创建学生对象时初始化数据
public Student(String name, int age, double score) {
this.name = name; // 给name字段赋值
this.age = age;// 给age字段赋值
this.score = score;// 给score字段赋值
}
// 重写toString方法:方便打印学生信息
@Override
public String toString() {
return "[" + this.name + "|" + this.age + "|" + this.score + "]";
}
}
// 步骤2:创建比较器(实现Comparator接口)
// 比较器1:年龄比较器,按年龄从小到大排序
import java.util.Comparator;
class AgeComparator implements Comparator<Student> {
//实现Comparator接口,必须要实现compare方法
@Override
public int compare(Student s1, Student s2) {
//compare方法 核心比较逻辑,返回负/零/正数决定排序顺序
/* 比较规则说明:
如果s1的年龄 < s2的年龄 → 返回负数 → s1排在前面
如果s1的年龄 > s2的年龄 → 返回正数 → s2排在前面
如果年龄相同 → 返回0 */
// 年龄差比较法(升序)
// 示例:s1.age=20,s2.age=19 → 20-19=1(正数)→ s1排在s2后面
return s1.age - s2.age; // 直接相减实现升序
}
}
// 比较器2:成绩比较器,按成绩从高到低排序
import java.util.Comparator;
class ScoreComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
/* 比较规则说明:
使用Double.compare比较两个double值
Double.compare(a, b) 返回:
-1 如果a < b
1 如果a > b
0 如果相等
添加负号使排序方向反转 */
// 使用Double类自带的比较方法
// 例如:Double.compare(88.5, 92.0) → 返回-1
// 添加负号反转顺序 → 返回1 → 实现降序
return -Double.compare(s1.score, s2.score);
}
}
// 比较器3:姓名比较器,按姓名拼音顺序排序
import java.util.Comparator;
class NameComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
// 使用String的compareTo方法
// "张三".compareTo("李四") → 正数("张"的拼音在"李"之后)
return s1.name.compareTo(s2.name);
}
}
测试类
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
// 初始化学生数组
Student[] students = {
new Student("张三", 20, 88.5),
new Student("李四", 19, 92.0),
new Student("王五", 22, 85.0),
new Student("赵六", 25, 96.5)
};
// 打印原始数据
System.out.println("========== 原始数据 ==========");
printStudents(students);
// 使用不同比较器进行排序演示
System.out.println("\n====== 按年龄升序排列 ======");
Arrays.sort(students, new AgeComparator());//创建比较器对象 new AgeComparator()
//Arrays.sort() 通用排序方法,通过传入不同的Comparator实现不同排序规则
//允许通过接口类型(Comparator)操作不同的比较器实现类 --多态
printStudents(students);
System.out.println("\n===== 按成绩降序排列 =====");
Arrays.sort(students, new ScoreComparator());
printStudents(students);
System.out.println("\n===== 按姓名拼音排序 =====");
Arrays.sort(students, new NameComparator());
printStudents(students);
}
// 辅助方法:打印学生数组
private static void printStudents(Student[] students) {
for (Student s : students) {
System.out.println(s);// 自动调用toString()方法
}
}
}
/*
========== 原始数据 ==========
[张三|20|88.5]
[李四|19|92.0]
[王五|22|85.0]
[赵六|25|96.5]
====== 按年龄升序排列 ======
[李四|19|92.0]
[张三|20|88.5]
[王五|22|85.0]
[赵六|25|96.5]
===== 按成绩降序排列 =====
[赵六|25|96.5]
[李四|19|92.0]
[张三|20|88.5]
[王五|22|85.0]
===== 按姓名拼音排序 =====
[张三|20|88.5]
[李四|19|92.0]
[王五|22|85.0]
[赵六|25|96.5]
*/
方法二额外指定Student 类实现 Comparable 接口, 并实现其中的 compareTo 方法。
// 学生类实现Comparable接口
class Student implements Comparable<Student>{
String name; // 学生姓名
int age; // 学生年龄
double score; // 考试成绩
// 构造方法:创建学生对象时初始化数据
public Student(String name, int age, double score) {
this.name = name; // 给name字段赋值
this.age = age;// 给age字段赋值
this.score = score;// 给score字段赋值
}
// 重写toString方法:方便打印学生信息
@Override
public String toString() {
return "[" + this.name + "|" + this.age + "|" + this.score + "]";
}
// 实现Comparable接口的compareTo方法
@Override
public int compareTo(Student o) {
return this.age - o.age;
}
}
测试类
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
Student[] students = {
new Student("张三", 20, 88.5),
new Student("李四", 19, 92.0),
new Student("王五", 22, 85.0),
new Student("赵六", 25, 96.5)
};
Arrays.sort(students);// 调用排序
for (Student s : students) {
System.out.println(s);// 打印每个学生信息
}
}
}
/*输出:
[李四|19|92.0]
[张三|20|88.5]
[王五|22|85.0]
[赵六|25|96.5]*/
混合使用两种方式
class Student implements Comparable<Student> {
String name; // 学生姓名
int age; // 学生年龄
double score; // 考试成绩
// 构造方法:创建学生对象时初始化数据
public Student(String name, int age, double score) {
this.name = name; // 给name字段赋值
this.age = age;// 给age字段赋值
this.score = score;// 给score字段赋值
}
// 重写toString方法:方便打印学生信息
@Override
public String toString() {
return "[" + this.name + "|" + this.age + "|" + this.score + "]";
}
@Override
// 实现Comparable接口的compareTo方法
public int compareTo(Student o) {
return this.age - o.age;
}
}
// 额外定义成绩比较器
import java.util.Comparator;
class ScoreComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
return -Double.compare(s1.score, s2.score);
}
}
测试类
public class Main {
public static void main(String[] args) {
Student[] students = {
new Student("张三", 20, 88.5),
new Student("李四", 19, 92.0),
new Student("王五", 22, 85.0),
new Student("赵六", 25, 96.5)
};
System.out.println("-- 自然排序(年龄升序)--");
Arrays.sort(students); // 使用Comparable
print(students);
System.out.println("\n-- 自定义排序(成绩降序)--");
Arrays.sort(students, new ScoreComparator()); // 使用Comparator
print(students);
}
public static void print(Student[] students) {
for (Student s : students) {
System.out.println(s);// 自动调用toString()方法
}
}
}
/*
运行:
-- 自然排序(年龄)--
[李四|19|92.0]
[张三|20|88.5]
[王五|22|85.0]
[赵六|25|96.5]
-- 自定义排序(成绩)--
[赵六|25|96.5]
[李四|19|92.0]
[张三|20|88.5]
[王五|22|85.0]
*/
两种方式区别
对比 | Comparable | Comparator |
---|---|---|
接口位置 | 在类内部实现 | 独立的外部比较器类 |
排序方法 | compareTo(Student o) | compare(Student s1, Student s2) |
调用方式 | Arrays.sort(数组) | Arrays.sort(数组,比较器实例) |
排序规则数量 | 只能定义一种自然排序规则 | 可以定义多个不同的排序规则 |
Comparable:内置于被比较类中
class Student implements Comparable<Student> { ... }
Comparator:独立于被比较类外
class ScoreComparator implements Comparator<Student> { ... }
Comparable 的 compareTo 方法:
public int compareTo(Student other) {
// 比较当前对象(this)和另一个对象(other)
}
Comparator 的 compare 方法:
public int compare(Student s1, Student s2) {
// 比较两个独立对象s1和s2
}
2. Clonable 接口和浅拷贝深拷贝
Java 中内置了一些很有用的接口, Clonable 就是其中之一。Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 “拷贝”. 但是要想合法调用 clone 方法, 必须要先实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常.
2.1 浅拷贝
【定义】
- 复制对象本身及其基本类型字段。
- 新旧对象共享同一引用对象。
- 修改引用字段会影响原对象。
// Person类实现Cloneable接口(允许克隆)
class Person implements Cloneable {
String name;// 基本类型字段(直接复制值)
String[] hobbies; // 引用类型字段
public Person(String name, String[] hobbies) {
this.name = name;
this.hobbies = hobbies;
}
// 重写 clone() 方法实现浅拷贝
@Override
protected Object clone() throws CloneNotSupportedException {
// 调用Object类的默认克隆方法(浅拷贝)
// 仅复制Person对象本身,hobbies数组仍共享同一引用
return super.clone();
}
@Override
public String toString() {
return name + "-" + Arrays.toString(hobbies);
}
}
测试类
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
String[] hobbies = {"打球","阅读"};
// 原始对象
Person original = new Person("赵六",hobbies);
System.out.println("原始对象:"+ original);
//克隆对象
Person cloned = (Person) original.clone();
System.out.println("克隆对象:" + cloned);
// 修改克隆对象的字段
cloned.name = "王五"; // 修改基本类型字段(仅影响克隆对象)
cloned.hobbies[0] = "打游戏";// 修改引用类型字段(影响原始对象)
System.out.println("----------修改克隆对象后----------");
System.out.println("原始对象:"+ original);
System.out.println("修改克隆对象: " + cloned);
}
}
/*
输出:
原始对象:赵六-[打球, 阅读]
克隆对象:赵六-[打球, 阅读]
----------修改克隆对象后----------
原始对象:赵六-[打游戏, 阅读]
修改克隆对象: 王五-[打游戏, 阅读]
*/
【关键问题】
修改 cloned.hobbies 导致 original.hobbies 同步变化
原因:数组 hobbies 是引用类型,浅拷贝仅复制其内存地址。修改引用类型字段会影响所有共享该引用的对象。
2.2 深拷贝
【定义】
- 递归复制对象及其所有引用指向的子对象,生成完全独立的对象树。
- 克隆对象与原始对象不共享任何引用类型数据。
- 修改拷贝后的对象不会影响原对象。
// 地址类(实现Cloneable接口)
class Address implements Cloneable {
String city;
String street;
public Address(String city, String street) {
this.city = city;
this.street = street;
}
// 重写浅拷贝方法
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); // Address类只有基本类型字段,浅拷贝已足够
}
@Override
public String toString() {
return city + "市" + street + "路";
}
}
class DeepPerson implements Cloneable {
String name; // 基本类型字段(直接复制值)
String[] hobbies; // 引用类型字段(数组)
Address address; // 嵌套引用类型字段(对象)
public DeepPerson(String name, String[] hobbies, Address address) {
this.name = name;
this.hobbies = hobbies;
this.address = address;
}
//实现深拷贝
@Override
protected Object clone() throws CloneNotSupportedException {
// 1. 先调用Object.clone()进行浅拷贝(复制name字段)
DeepPerson cloned = (DeepPerson) super.clone();
// 2. 深拷贝数组:创建新数组并复制元素
cloned.hobbies = Arrays.copyOf(this.hobbies, this.hobbies.length);
// 3. 深拷贝嵌套对象:调用Address的clone方法
cloned.address = (Address) this.address.clone();
return cloned;
}
@Override
public String toString() {
return name + "- 爱好:" + String.join(",", hobbies) + "" + "-" + "地址:" + address;
}
}
测试类
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
// 初始化数据
String[] hobbies = {"打球", "阅读"};
Address address = new Address("北京", "长安");
// 创建原始对象
DeepPerson original = new DeepPerson("赵六", hobbies, address);
System.out.println("原始对象:" + original);
// 执行深拷贝
DeepPerson cloned = (DeepPerson) original.clone();
System.out.println("克隆对象:" + cloned);
// 修改克隆对象的所有类型字段
cloned.name = "王五";// 修改基本类型字段(不影响原对象)
cloned.hobbies[0] = "游戏";// 修改数组字段(不影响原对象)
cloned.address.city = "上海"; // 修改嵌套对象字段(不影响原对象)
System.out.println("----------修改克隆对象后----------");
System.out.println("原始对象: " + original);
System.out.println("修改克隆对象: " + cloned);
}
}
/*
输出:
原始对象:赵六- 爱好:打球,阅读-地址:北京市长安路
克隆对象:赵六- 爱好:打球,阅读-地址:北京市长安路
----------修改克隆对象后----------
原始对象: 赵六- 爱好:打球,阅读-地址:北京市长安路
修改克隆对象: 王五- 爱好:游戏,阅读-地址:上海市长安路*/
【相对于浅拷贝关键改进】
- 修改 cloned.hobbies 和 cloned.address 不会影响原对象
- 原因:数组和 Address 对象均被完全复制
2.3 一个简单易懂区别浅拷贝深拷贝示例
【浅拷贝】
// Money类:包含一个公有字段的简单数据类
class Money {
// 公有字段,存储金额数值
public double m = 88.8; // 初始值为88.8
}
// Person类:实现Cloneable接口表示支持克隆
class Person implements Cloneable {
// 公有字段,持有Money对象的引用
public Money money = new Money(); // 初始化时创建Money对象
// 重写clone方法(浅拷贝)
@Override
protected Object clone() throws CloneNotSupportedException {
// 调用Object类的clone方法(浅拷贝)
// 仅复制Person对象本身,不会复制其引用的Money对象
return super.clone();
}
}
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
// 创建原始Person对象
// person1.money指向Money对象A(地址假设0x100)
Person person1 = new Person();
// 克隆对象(浅拷贝)
// person2.money仍指向Money对象A(地址0x100)
Person person2 = (Person) person1.clone();
// 打印克隆操作前的数据
System.out.println("通过person2修改前的结果");
System.out.println(person1.money.m); // 输出Money对象A的值:88.8
System.out.println(person2.money.m); // 输出Money对象A的值:88.8
// 修改克隆对象的数值
person2.money.m = 99.9; // 修改的是Money对象A的值
// 打印修改后的数据
System.out.println("通过person2修改后的结果");
System.out.println(person1.money.m); // 输出Money对象A的新值:99.9
System.out.println(person2.money.m); // 输出Money对象A的新值:99.9
}
}
发现:修改 person2.money.m 会影响 person1.money.m
【深拷贝】
// Money类:包含一个公有字段的简单数据类
class Money {
// 公有字段,存储数值
public double m = 88.8; // 初始值为 88.8
}
// Person类:实现Cloneable接口表示支持克隆
class Person implements Cloneable {
public String name;//基本类型字段
public Money money = new Money(); // 引用类型字段
// 构造函数:初始化姓名
public Person(String name) {
this.name = name;
}
// 重写clone方法(实现深拷贝)
@Override
protected Object clone() throws CloneNotSupportedException {
// 1. 调用Object.clone()进行浅拷贝:复制name字段(基本类型),money字段(引用类型)仍指向原对象
Person cloned = (Person) super.clone();
// 2. 深拷贝核心操作--处理每个引用类型字段:为克隆对象创建新的Money实例
cloned.money = new Money(); // 创建新的Money对象
cloned.money.m = this.money.m; // 复制数值(值相同)
return cloned;//返回深拷贝后的对象
}
}
测试类
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
// 创建原始Person对象
// person1.money指向内存中的Money对象A(地址假设为0x100)
Person person1 = new Person("张三");
// 克隆对象(触发深拷贝)
// person2.money指向新Money对象B(地址0x200)
Person person2 = (Person) person1.clone();
// 打印克隆操作前的数据
System.out.println("通过person2修改前的结果");
System.out.println(person1.money.m); // 输出原对象A的值:88.8
System.out.println(person2.money.m); // 输出新对象B的值:88.8(值复制)
// 修改克隆对象数值
person2.money.m = 99.9;//修改对象B的值
// 验证深拷贝结果
System.out.println("通过person2修改后的结果");
System.out.println(person1.money.m); // 输出:88.8 --原对象A,person1保持原值88.8
System.out.println(person2.money.m); // 输出:99.9 --克隆对象B,person2变为99.9
}
}
2.4 深拷贝与浅拷贝的区别
区别 | 浅拷贝 | 深拷贝 |
---|---|---|
复制范围 | 仅复制对象本身 | 递归复制对象及所有引用对象 |
内存关系 | 引用类型字段共享 | 所有字段独立 |
适用场景 | 不可变对象或无需独立副本时 | 需要完全独立副本的场景 |
代码实现 | 简单(super.clone() ) | 复杂(需要手动处理所有引用字段) |
3. 抽象类和接口的区别
抽象类和接口都是 Java 中多态的常见使用方式。
核心区别: 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中的抽象方法, 子类必须重写所有的抽象方法。
区别 | 抽象类 | 接口 |
---|---|---|
实现 | 单继承(一个类只能继承一个抽象类) | 多实现(一个类可实现多个接口,弥补了单继承,类似于多继承) |
设计原则 | 是什么(is - a 关系) | 能做什么(has - a 或 can-do关系) |
子类使用 | 使用extends关键字继承抽象类 | 使用implements关键字实现接口 |
成员变量 | 可定义普通成员变量 | 只能定义常量(默认public static final) |
构造方法 | 可以定义 | 不能定义 |
关系 | 一个抽象类可以实现若干接口 | 接口不能继承抽象类,但是接口可以使用extends关键字继承多个父接口 |
适用场景 | 为子类提供通用代码实现和共性行为 | 定义行为规范(多态扩展) |
【抽象类和接口混合使用】
抽象类为基,管理公共属性和基础行为
接口为翼,通过多实现扩展多样化能力
下面代码的 Animal被设置为抽象类,是因为Animal类中包含一个 age 这样的属性, 这个属性在任何子类中都是存在的, 因此此处的 Animal 只能作为一个抽象类, 而不应该成为一个接口。
抽象类存在的意义是为了让编译器更好的校验, 像Animal 这样的类一般不会直接使用, 而是使用它的子类,万一不小心创建了 Animal 的实例, 编译器会及时提醒我们。
// 抽象类:定义动物的公共属性(age)和基础行为eat();
public abstract class Animal {
public int age;// 公共属性,所有子类共享
//抽象类的构造方法(强制子类必须初始化age属性)
public Animal(int age) {
this.age = age;
}
// 强制子类实现的基础行为
public abstract void eat();
}
// 接口:定义飞行能力(特殊能力)
public interface Flyable {
void fly();
}
// 接口:定义跑步能力(特殊能力
public interface Runable {
void run();
}
// 接口:定义游泳能力(特殊能力)
public interface Swimmable {
void swim();
}
// Bird:继承Animal的公共属性,实现Flyable能力
public class Bird extends Animal implements Flyable{
// 必须显式调用父类构造方法(抽象类控制子类初始化过程)
public Bird(int age) {
super(age);
}
// 实现抽象方法:定义鸟类具体的进食方式
@Override
public void eat() {
System.out.println("鸟吃鸟粮");
}
// 实现接口方法:定义飞行能力的具体实现
@Override
public void fly() {
System.out.println("鸟有飞行能力");
}
}
// Duck:继承Animal的公共属性,实现Swimmable和Runnable能力
public class Duck extends Animal implements Swimmable, Runable {
//必须显式调用父类构造方法,因为是抽象类父类Animal控制子类构造过程
public Duck(int age) {
super(age);
}
// 实现抽象方法:定义鸭子具体的进食方式
@Override
public void eat() {
System.out.println("鸭子吃饲料");
}
// 实现Swimmable接口方法:游泳能力的具体实现
@Override
public void swim() {
System.out.println("鸭子有游泳能力");
}
@Override
public void run() {
System.out.println("鸭子可以跑步");
}
}
测试类
public class Main {
// 面向接口编程:接收任何具有飞行能力的对象
public static void flying(Flyable flyable) {
flyable.fly();// 多态调用
}
// 面向接口编程:接收任何具有游泳能力的对象
public static void swimming(Swimmable swimmable) {
swimmable.swim();
}
// 面向接口编程:接收任何具有跑步能力的对象
public static void running(Runable runnable) {
runnable.run();
}
public static void main(String[] args) {
// 创建具体动物实例
Bird bird = new Bird(2);
Duck duck = new Duck(5);
// 调用基础行为方法
bird.eat();//输出:鸟吃鸟粮(继承自Animal)
duck.eat();//输出:鸭子吃饲料(继承自Animal)
// 通过接口调用特殊能力
flying(bird);//输出:鸟有飞行能力(Flyable接口)
//flying(duck); // 编译错误:Duck未实现Flyable接口
swimming(duck);//输出:鸭子有游泳能力(Swimmable接口)
running(duck);//输出:鸭子可以跑步(Runnable接口)
}
}
如果再添加一个机器人类,这个类不继承Animal
//机器人类:体现了接口在跨类层次定义行为的能力
public class Robot implements Runable {
//Runnable接口不再局限于Animal继承体系。无论是生物(如Duck)还是非生物(如Robot),只要实现Runnable接口即可具备跑步能力。
@Override
public void run() {
System.out.println("机器人有跑步能力");
}
}
测试类调用
//因为running()方法原本设计为接收Runnable接口类型参数
//所以新增的Robot可直接调用,无需任何调整
Robot robot = new Robot();
running(robot); // 输出:机器人有跑步能力