由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());
    }
}

class Coach implements Cloneable {

    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public Coach(String name) {
        this.name = name;
    }
}

 

输出的结果是:

His name is Duncan,His age is 42,His coach is Kerr
His name is Curry,His age is 30,His coach is Kerr
true
  • 1
  • 2
  • 3

从上面结果可知,克隆出来的Player对象里的name和age是新的,通过setName和setAge方法可以改变,但是coach是和原来的共享的,对克隆对象的coach进行修改同样会影响到原来对象的coach,这就是浅克隆。

到这儿为止,我当时关于clone方法的一些疑问就都已经得到了相应的解答,虽然是比较基础的问题,还是记录一下以供之后参考。

posted @ 2019-12-30 14:25  那些年的代码  阅读(1046)  评论(1编辑  收藏  举报