在学习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()