zoukankan      html  css  js  c++  java
  • 由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方法的一些疑问就都已经得到了相应的解答,虽然是比较基础的问题,还是记录一下以供之后参考。

  • 相关阅读:
    方法和参数
    【转】priority_queue优先队列
    【转】主席树学习
    【转】树链剖分
    【转】线段树完全版~by NotOnlySuccess
    【转】树状数组
    【转】最大流EK算法
    【转】POJ题目分类推荐 (很好很有层次感)
    【转】原根
    【转】Polya定理
  • 原文地址:https://www.cnblogs.com/zhuyeshen/p/12119538.html
Copyright © 2011-2022 走看看