Comparable接口和Cloneable接口

文章目录

  • 1,Comparable接口
  • 2,Clonable接口和深拷贝

1,Comparable接口

以下是一个实现给对象数组排序的程序:

class Student {
	private String name;
	private int score;
	public Student(String name, int score) {
		this.name = name;
		this.score = score;
	}
	@Override
	public String toString() {
		return "[" + this.name + ":" + this.score + "]";
	}
}

再给定一个学生对象数组, 对这个对象数组中的元素进行排序(按分数降序)。

Student[] students = new Student[] {
new Student("张三", 95),
new Student("李四", 96),
new Student("王五", 97),
new Student("赵六", 92),
};

按照我们之前的理解, 数组我们有一个现成的 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 类实现 Comparable 接口, 并实现其中的 compareTo 方法。

class Student implements Comparable {
	private String name;
	private int score;
	public Student(String name, int score) {
		this.name = name;
		this.score = score;
	}
	@Override
	public String toString() {
		return "[" + this.name + ":" + this.score + "]";
	}
	@Override
	public int compareTo(Object o) {
		Student s = (Student)o;
		if (this.score > s.score) {
			return -1;
		} else if (this.score < s.score) {
			return 1;
		} else {
			return 0;
		}
	}
}

然后比较当前对象和参数对象的大小关系(按分数来算)。

如果当前对象应排在参数对象之前, 返回小于 0 的数字。
如果当前对象应排在参数对象之后, 返回大于 0 的数字。
如果当前对象和参数对象不分先后, 返回 0。

再次执行程序, 结果就符合预期了。

// 执行结果
[[王五:97], [李四:96], [张三:95], [赵六:92]]

注意事项: 对于 sort 方法来说, 需要传入的数组的每个对象都是 “可比较” 的, 需要具备 compareTo 这样的能力。通过重写 compareTo 方法的方式, 就可以定义比较规则。
为了进一步加深对接口的理解, 我们可以尝试自己实现一个 sort 方法来完成刚才的排序过程(使用冒泡排序)。

public static void sort(Comparable[] array) {
	for (int bound = 0; bound < array.length; bound++) {
		for (int cur = array.length - 1; cur > bound; cur--) {
			if (array[cur - 1].compareTo(array[cur]) > 0) {
				// 说明顺序不符合要求, 交换两个变量的位置
				Comparable tmp = array[cur - 1];
				array[cur - 1] = array[cur];
				array[cur] = tmp;
			}
		}
	}
}

再次执行代码。

sort(students);
System.out.println(Arrays.toString(students));
// 执行结果
[[王五:97], [李四:96], [张三:95], [赵六:92]]

2,Clonable接口和深拷贝

Java 中内置了一些很有用的接口, Clonable 就是其中之一。Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 “拷贝”, 但是要想合法调用 clone 方法, 必须要先实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常。
如下:

class Animal implements Cloneable {
	private String name;
	@Override
	public Animal clone() {
		Animal o = null;
		try {
			o = (Animal)super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return o;
	}
}
public class Test {
	public static void main(String[] args) {
		Animal animal = new Animal();
		Animal animal2 = animal.clone();
		System.out.println(animal == animal2);
	}
}
// 输出结果
// false

有两个概念:浅拷贝和深拷贝
Cloneable 拷贝出的对象是一份浅拷贝。
观察以下代码:

class Money {
	public double m = 99.99;
}
class Student implements Cloneable{
	String name;
	int age;
	public Money money = new Money();
	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
}
public class demo {
	public static void main(String[] args) throws CloneNotSupportedException {
		Student stduent1 = new Student();
		Student stduent2 = (Student) Student.clone();
		System.out.println("通过stduent2修改前的结果");
		System.out.println(stduent1.money.m);
		System.out.println(stduent2.money.m);
		stduent2.money.m = 13.6;
		System.out.println("通过stduent2修改后的结果");
		System.out.println(stduent1.money.m);
		System.out.println(stduent2.money.m);
	}
}
// 执行结果
通过stduent2修改前的结果
99.99
99.99
通过stduent2修改后的结果
13.6
13.6

如上代码,我们可以看到,通过clone,我们只是拷贝了Student对象。但是Student对象中的Money对象,并没有拷贝。通过stduent2这个引用修改了m的值后,stduent1这个引用访问m的时候,值也发生了改变。其原因就是stduent2拷贝stduent1后,其成员变量money与stduent1的money为同一个地址,这里就是发生了浅拷贝。
如图:
Comparable接口和Cloneable接口_第1张图片
如何避免这种情况?这就需要使用深拷贝,代码如下:

class Money implements Cloneable{
    public double m=25.0;
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
class Student  implements Cloneable {
    public String name;
    public int age;
    public Money money=new Money();
    @Override  //深拷贝,将引用的对象(student1)中的对象也拷贝到目标对象中
    protected Object clone() throws CloneNotSupportedException {
        Student temp=(Student) super.clone();
        temp.money=(Money)this.money.clone();
        return temp;
    }
}
public class demo {
    public static void main(String[] args) throws CloneNotSupportedException {
        Student student1 = new Student();
        Student student2 = (Student) student1.clone();
        System.out.println("通过stduent2修改前的结果");
        System.out.println(student1.money.m);
        System.out.println(student2.money.m);
        student2.money.m = 13.6;
        System.out.println("通过stduent2修改后的结果");
        System.out.println(student1.money.m);
        System.out.println(student2.money.m);
    }
}
//执行结果:
通过stduent2修改前的结果
25.0
25.0
通过stduent2修改后的结果
25.0
13.6

原因如下:Comparable接口和Cloneable接口_第2张图片
深拷贝改变了拷贝过后student2的money的地址,故修改m的值不会影响student1的值。

你可能感兴趣的:(java,开发语言)