由Object.clone()方法引出的访问权限问题
在学习Java的过程中,在《Java核心技术》(卷一)中看到这样一句话“因为Object类中的clone()方法是protected,所以不能直接以anObject.clone()这样的形式调用。当时看到的时候有些不解,之前学习的时候,对protected的认识是这样的
protected 修饰的类和属性,对于自己、本包和其子类可见
Object默认是所有类的超类,clone()方法是Object里的方法,那按照这个理解来说,clone()方法是可以被任意一个类调用的,但实际上,当我们试图运行下面这段代码的时候
public class test {
public static void main(String... args){
test2 a = new test2();
a.clone();
}
}
class test2 {
}
编辑器会直接提示
‘clone() has protected access in ‘java.lang.Object’’
出现这种情况的原因实际上是因为之前对protected的理解有些片面,protected的准确理解是
对于protected的成员或方法,要分子类和超类是否在同一个包中。与基类不在同一个包中的子类,只能访问自身从基类继承而来的受保护成员,而不能访问基类实例本身的受保护成员。在相同包时,protected和public是一样的
对于这个例子,也就是说,虽然test和test2都是Object的子类,但是因为clone()方法是protected的,而且test和Object并不在一个包里,因此test里的方法只能访问自身从基类(这个例子中也就是Object)继承而来的受保护成员(也就是clone()方法),而不能访问基类实例本身的受保护成员(也就是试图用a.clone()这样的方式去调用clone()方法)
public class test {
public static void main(String... args){
test a = new test();
a.clone();
}
}
class test2 {
}
实际上,当我们通过上面这种方式去调用clone()方法时,注意此时a是test的实例而非test2的实例,编译器会提示
Unhandled exception:java.lang.CloneNotSupportedException
虽然程序仍然不能正常运行,但这时阻挠程序运行的已经不是之前的权限问题了,这也说明了test是可以调用自身从Object中继承的clone()方法,而至于这个异常的来历,我们去查看一下Object.clone()方法的源码,可以找到下面这段内容
/**
* @return a clone of this instance.
* @throws CloneNotSupportedException if the object's class does not
* support the {@code Cloneable} interface. Subclasses
* that override the {@code clone} method can also
* throw this exception to indicate that an instance cannot
* be cloned.
* @see java.lang.Cloneable
*/
@HotSpotIntrinsicCandidate
protected native Object clone() throws CloneNotSupportedException;
可以看到,Object.clone()方法是一个native方法,简单地讲,一个native方法就是一个java调用非java代码的接口。而一般native方法的速度都要比你自己所写的程序运行速度快很多,这也是为什么当我们想要对一个对象进行克隆操作时,推荐使用Object.clone()方法而非自己通过java代码去实现这样一个功能(虽然也可以达到想要的结果)。除了native关键字外,我们再来关注一下这个方法的一些注解
- @return:表示这个方法返回的一个实例的克隆,这也与我们的预想没有太大出入。
- @throws:实际上,这就是我们刚才的程序不能正常运行的原因了,@throws后面的内容说明了如果一个类没有实现Cloneable接口,或者一个子类试图重写clone方法都会抛出CloneNotSupportedException这个异常
在实际操作中,如果要使用clone()方法,一般会进行以下几个操作:
1.实现Cloneable接口,这里多说一句,实际上Cloneable是一个标记接口,所谓标记接口,顾名思义,是一个用来对类进行标记的接口,也就是说这个接口实际上没有任何内容,我们去查看Cloneable的源码,也可以看到相关解释
/**
* A class implements the <code>Cloneable</code> interface to
* indicate to the {@link java.lang.Object#clone()} method that it
* is legal for that method to make a
* field-for-field copy of instances of that class.
*/
public interface Cloneable {
}
2.将clone()方法重写为public,之前也解释过这个内容,protected的权限不能满足实际需要。
3.调用父类的clone()方法,这里再补充一个内容,实际上Object.clone()方法实现的是浅克隆,而不是深克隆,如果想实现深克隆,需要自己对clone方法进行合理的重写。这里也简单的解释一下两种克隆。
- 浅克隆复制出来的对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。也就是说,克隆之后的对象和之前的对象仍存在一些关联,克隆程度不高,因此也被称为浅克隆。
- 而深克隆复制出来的所有变量都含有与原来的对象相同的值,那些引用其他对象的变量将指向复制出来的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
这里也给一个实现上述方法的简单例子:
public class Player implements Cloneable {
private String name;
private int age;
private Coach coach;
public Player(String name, int age, Coach coach) {
this.name = name;
this.age = age;
this.coach = coach;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public Coach getCoach() {
return coach;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public Player clone() {
Player player = null;
try {
player = (Player) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return player;
}
public static void main(String... args) {
Coach coach = new Coach("Popvinch");
Player duncan = new Player("Duncan", 42, coach);
Player clone = duncan.clone();
clone.getCoach().setName("Kerr");
clone.setName("Curry");
clone.setAge(30);
System.out.println("His name is " + duncan.getName() + ",His age is " + duncan.getAge() + ",His coach is " + duncan.getCoach().getName());
System.out.println("His name is " + clone.getName() + ",His age is " + clone.getAge() + ",His coach is " + clone.getCoach().getName());
System.out.println(duncan.getCoach() == clone.getCoach(