精华内容
下载资源
问答
  • JAVA对象引用

    2015-10-13 11:43:05
    根据《JAVA对象结构》介绍了JAVA对象的存储结构,但要如何判断一对象是否存活 引用计数算法: 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器...

    根据《JAVA对象结构》介绍了JAVA对象的存储结构,但要如何判断一对象是否存活

    引用计数算法
             给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
    优点:实现简单,效率高(在每个引用丢失的时候判断下计数器,为0就直接回收)
    缺点:当对象A成员引用对象B,B对象引用A(循环引用),然后把AB都置空,可他们的计数器仍为1,造成内存泄漏。
     

    可达性分析算法
             GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,下图对象object5, object6, object7虽然有互相判断,但它们到GC Roots是不可达的,所以它们将会判定为是可回收对象。

    那么那些点可以作为GC Roots呢?
    全局的引用(常量和静态属性)和执行上下文(栈桢中的本地变量表) 
    1.虚拟机栈(栈桢中的本地变量表)中的引用的对象
    2.方法区中的类静态属性引用的对象
    3.方法区中的常量引用的对象
    4.本地方法栈中JNI(Native方法)的引用的对象

     

    JAVA对象4大引用

    根据可达性分析算法,可以很轻松的判断对象是不是已死。但万物没有绝对性,对象也一样,有些对象死活可根据场景判断。
    如下左图是对象引用等级的划分,下右图是在内存中的状态关系(软引用和弱引用内存结构是差不多的,因此只画出一种)

    从图中得知,JVM 在GC 的时候会判断引用情况

    1.如 Object是被强引用着,则不回收

    2. 如果通SoftReference对象引用着自己,则判断内存是不是 不足了,不足则清,

    3.如果是 WeakReference 对象引用着,则直接回收,

    4.如果是 PhantomReference,则直接被清(与ReferenceQueue后面会讲)
     

    对象状态监视ReferenceQueue

    Reference是JAVA中对引用表现类。对象拥有4种状态    

    1. Active(当对象被创始时)
    2. Pending(如果对象有注册ReferenceQueue,当它确认为引用不可达的时候,引用为被改这状态,等待Reference-handler 线程入Queue)
    3. Enqueued(对象已被加入到ReferenceQueue)
    4. Inactive(当实例被移出ReferenceQueue中时,这个状态永远都不会再被改变 )

    从上面的状态很容易得出ReferenceQueue,是对象的监听对象,只有加入此对象的才可以在回收时被通知。

    Object.finalize也是通个ReferenceQueue,JAVA自动实现的,当你重写finalize,则会把此CLASS创建的对象,都扔进一个默认ReferenceQueue中,Finalizer类中有写

    展开全文
  • Java对象引用

    千次阅读 2011-09-10 13:49:18
    在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及(reachable)状态,程序才能使用它。从JDK 1.2版本开始,把对象引用分为4种级别,从而使程序能...
    1.对象的强、软、弱和虚引用
    在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及(reachable)状态,程序才能使用它。从JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。图1为对象应用类层次。
    图1
    ⑴强引用(StrongReference)
    强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
     
    ⑵软引用(SoftReference)
    如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存(下文给出示例)。
    软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
     
    ⑶弱引用(WeakReference)
    弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
    弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
     
    ⑷虚引用(PhantomReference)
    “虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
    虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。
    ReferenceQueue queue = new ReferenceQueue ();
    PhantomReference pr = new PhantomReference (object, queue);
    程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
    2.对象可及性的判断
        在很多时候,一个对象并不是从根集直接引用的,而是一个对象被其他对象引用,甚至同时被几个对象所引用,从而构成一个以根集为顶的树形结构。如图2所示
        在这个树形的引用链中,箭头的方向代表了引用的方向,所指向的对象是被引用对象。由图可以看出,从根集到一个对象可以由很多条路径。比如到达对象5的路径就有①-⑤,③-⑦两条路径。由此带来了一个问题,那就是某个对象的可及性如何判断:
    ◆单条引用路径可及性判断:在这条路径中,最弱的一个引用决定对象的可及性。
    ◆多条引用路径可及性判断:几条路径中,最强的一条的引用决定对象的可及性。
        比如,我们假设图2中引用①和③为强引用,⑤为软引用,⑦为弱引用,对于对象5按照这两个判断原则,路径①-⑤取最弱的引用⑤,因此该路径对对象5的引用为软引用。同样,③-⑦为弱引用。在这两条路径之间取最强的引用,于是对象5是一个软可及对象。
    3.使用软引用构建敏感数据的缓存
    3.1 为什么需要使用软引用
       首先,我们看一个雇员信息查询系统的实例。我们将使用一个Java语言实现的雇员信息查询系统查询存储在磁盘文件或者数据库中的雇员人事档案信息。作为一个用户,我们完全有可能需要回头去查看几分钟甚至几秒钟前查看过的雇员档案信息(同样,我们在浏览WEB页面的时候也经常会使用“后退”按钮)。这时我们通常会有两种程序实现方式:一种是把过去查看过的雇员信息保存在内存中,每一个存储了雇员档案信息的Java对象的生命周期贯穿整个应用程序始终;另一种是当用户开始查看其他雇员的档案信息的时候,把存储了当前所查看的雇员档案信息的Java对象结束引用,使得垃圾收集线程可以回收其所占用的内存空间,当用户再次需要浏览该雇员的档案信息的时候,重新构建该雇员的信息。很显然,第一种实现方法将造成大量的内存浪费,而第二种实现的缺陷在于即使垃圾收集线程还没有进行垃圾收集,包含雇员档案信息的对象仍然完好地保存在内存中,应用程序也要重新构建一个对象。我们知道,访问磁盘文件、访问网络资源、查询数据库等操作都是影响应用程序执行性能的重要因素,如果能重新获取那些尚未被回收的Java对象的引用,必将减少不必要的访问,大大提高程序的运行速度。
     
    3.2 如果使用软引用
    SoftReference的特点是它的一个实例保存对一个Java对象的软引用,该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用。另外,一旦垃圾线程回收该Java对象之后,get()方法将返回null。
    看下面代码:
    MyObject aRef = new  MyObject();
    SoftReference aSoftRef=new SoftReference(aRef);
        此时,对于这个MyObject对象,有两个引用路径,一个是来自SoftReference对象的软引用,一个来自变量aReference的强引用,所以这个MyObject对象是强可及对象。
    随即,我们可以结束aReference对这个MyObject实例的强引用:
    aRef = null;
    此后,这个MyObject对象成为了软可及对象。如果垃圾收集线程进行内存垃圾收集,并不会因为有一个SoftReference对该对象的引用而始终保留该对象。Java虚拟机的垃圾收集线程对软可及对象和其他一般Java对象进行了区别对待:软可及对象的清理是由垃圾收集线程根据其特定算法按照内存需求决定的。也就是说,垃圾收集线程会在虚拟机抛出OutOfMemoryError之前回收软可及对象,而且虚拟机会尽可能优先回收长时间闲置不用的软可及对象,对那些刚刚构建的或刚刚使用过的“新”软可反对象会被虚拟机尽可能保留。在回收这些对象之前,我们可以通过:
    MyObject anotherRef=(MyObject)aSoftRef.get();
        重新获得对该实例的强引用。而回收之后,调用get()方法就只能得到null了。
     
    3.3 使用ReferenceQueue清除失去了软引用对象的SoftReference
    作为一个Java对象,SoftReference对象除了具有保存软引用的特殊性之外,也具有Java对象的一般性。所以,当软可及对象被回收之后,虽然这个SoftReference对象的get()方法返回null,但这个SoftReference对象已经不再具有存在的价值,需要一个适当的清除机制,避免大量SoftReference对象带来的内存泄漏。在java.lang.ref包里还提供了ReferenceQueue。如果在创建SoftReference对象的时候,使用了一个ReferenceQueue对象作为参数提供给SoftReference的构造方法,如:
    ReferenceQueue queue = new  ReferenceQueue();
    SoftReference  ref=new  SoftReference(aMyObject, queue);
        那么当这个SoftReference所软引用的aMyOhject被垃圾收集器回收的同时,ref所强引用的SoftReference对象被列入ReferenceQueue。也就是说,ReferenceQueue中保存的对象是Reference对象,而且是已经失去了它所软引用的对象的Reference对象。另外从ReferenceQueue这个名字也可以看出,它是一个队列,当我们调用它的poll()方法的时候,如果这个队列中不是空队列,那么将返回队列前面的那个Reference对象。
    在任何时候,我们都可以调用ReferenceQueue的poll()方法来检查是否有它所关心的非强可及对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。利用这个方法,我们可以检查哪个SoftReference所软引用的对象已经被回收。于是我们可以把这些失去所软引用的对象的SoftReference对象清除掉。常用的方式为:
    SoftReference ref = null;
    while ((ref = (EmployeeRef) q.poll()) != null) {
        // 清除ref
    }
    理解了ReferenceQueue的工作机制之后,我们就可以开始构造一个Java对象的高速缓存器了。
     
    3.4通过软可及对象重获方法实现Java对象的高速缓存
        利用Java2平台垃圾收集机制的特性以及前述的垃圾对象重获方法,我们通过一个雇员信息查询系统的小例子来说明如何构建一种高速缓存器来避免重复构建同一个对象带来的性能损失。我们将一个雇员的档案信息定义为一个Employee类:
    public class Employee {
        private String id;// 雇员的标识号码
        private String name;// 雇员姓名
        private String department;// 该雇员所在部门
        private String Phone;// 该雇员联系电话
        private int salary;// 该雇员薪资
        private String origin;// 该雇员信息的来源
     
        // 构造方法
        public Employee(String id) {
           this.id = id;
           getDataFromlnfoCenter();
        }
     
        // 到数据库中取得雇员信息
        private void getDataFromlnfoCenter() {
           // 和数据库建立连接井查询该雇员的信息,将查询结果赋值
           // 给name,department,plone,salary等变量
           // 同时将origin赋值为"From DataBase"
        }
    ……
    这个Employee类的构造方法中我们可以预见,如果每次需要查询一个雇员的信息。哪怕是几秒中之前刚刚查询过的,都要重新构建一个实例,这是需要消耗很多时间的。下面是一个对Employee对象进行缓存的缓存器的定义:
    import java.lang.ref.ReferenceQueue;
    import java.lang.ref.SoftReference;
    import java.util.Hashtable;
    public class EmployeeCache {
        static private EmployeeCache cache;// 一个Cache实例
        private Hashtable<String,EmployeeRef> employeeRefs;// 用于Chche内容的存储
        private ReferenceQueue<Employee> q;// 垃圾Reference的队列
     
        // 继承SoftReference,使得每一个实例都具有可识别的标识。
        // 并且该标识与其在HashMap内的key相同。
        private class EmployeeRef extends SoftReference<Employee> {
           private String _key = "";
     
           public EmployeeRef(Employee em, ReferenceQueue<Employee> q) {
               super(em, q);
               _key = em.getID();
           }
        }
     
        // 构建一个缓存器实例
        private EmployeeCache() {
           employeeRefs = new Hashtable<String,EmployeeRef>();
           q = new ReferenceQueue<Employee>();
        }
     
        // 取得缓存器实例
        public static EmployeeCache getInstance() {
           if (cache == null) {
               cache = new EmployeeCache();
           }
           return cache;
        }
     
        // 以软引用的方式对一个Employee对象的实例进行引用并保存该引用
        private void cacheEmployee(Employee em) {
           cleanCache();// 清除垃圾引用
           EmployeeRef ref = new EmployeeRef(em, q);
           employeeRefs.put(em.getID(), ref);
        }
     
        // 依据所指定的ID号,重新获取相应Employee对象的实例
        public Employee getEmployee(String ID) {
           Employee em = null;
           // 缓存中是否有该Employee实例的软引用,如果有,从软引用中取得。
           if (employeeRefs.containsKey(ID)) {
               EmployeeRef ref = (EmployeeRef) employeeRefs.get(ID);
               em = (Employee) ref.get();
           }
           // 如果没有软引用,或者从软引用中得到的实例是null,重新构建一个实例,
           // 并保存对这个新建实例的软引用
           if (em == null) {
               em = new Employee(ID);
               System.out.println("Retrieve From EmployeeInfoCenter. ID=" + ID);
               this.cacheEmployee(em);
           }
           return em;
        }
     
        // 清除那些所软引用的Employee对象已经被回收的EmployeeRef对象
        private void cleanCache() {
           EmployeeRef ref = null;
           while ((ref = (EmployeeRef) q.poll()) != null) {
               employeeRefs.remove(ref._key);
           }
        }
     
    展开全文
  • 一、Java对象及其引用 &amp;amp;amp;amp;amp;nbsp; &amp;amp;amp;amp;amp;nbsp;&amp;amp;amp;amp;amp;nbsp;&amp;amp;amp;amp;amp;nbsp;&amp;amp;amp;amp;amp;nbsp; 初学Java,总是会自觉或不...

    假如说你想复制一个简单变量。很简单:

    int apples = 5;  
    int pears = apples;  
    

    不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适用于该类情况。

    但是如果你复制的是一个对象,情况就有些复杂了。

    假设说我是一个beginner,我会这样写:

    class Student {  
        private int number;  
      
        public int getNumber() {  
            return number;  
        }  
      
        public void setNumber(int number) {  
            this.number = number;  
        }  
          
    }  
    public class Test {  
          
        public static void main(String args[]) {  
            Student stu1 = new Student();  
            stu1.setNumber(12345);  
            Student stu2 = stu1;  
              
            System.out.println("学生1:" + stu1.getNumber());  
            System.out.println("学生2:" + stu2.getNumber());  
        }  
    }
    

    结果:

    学生1:12345
    学生2:12345

    这里我们自定义了一个学生类,该类只有一个number字段。

    我们新建了一个学生实例,然后将该值赋值给stu2实例。(Student stu2 = stu1;)

    再看看打印结果,作为一个新手,拍了拍胸腹,对象复制不过如此,

    难道真的是这样吗?

    我们试着改变stu2实例的number字段,再打印结果看看:

    stu2.setNumber(54321);  
      
    System.out.println("学生1:" + stu1.getNumber());  
    System.out.println("学生2:" + stu2.getNumber());  
    

    结果:

    学生1:54321  

    学生2:54321  

    这就怪了,为什么改变学生2的学号,学生1的学号也发生了变化呢?

    原因出在(stu2 = stu1) 这一句。该语句的作用是将stu1的引用赋值给stu2,

    这样,stu1和stu2指向内存堆中同一个对象。如图:

    那么,怎样才能达到复制一个对象呢?

    是否记得万类之王Object。它有11个方法,有两个protected的方法,其中一个为clone方法。

    在Java中所有的类都是缺省的继承自Java语言包中的Object类的,查看它的源码,你可以把你的JDK目录下的src.zip复制到其他地方然后解压,里面就是所有的源码。发现里面有一个访问限定符为protected的方法clone():

    /*
    Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
    The general intent is that, for any object x, the expression:
    1) x.clone() != x will be true
    2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
    3) x.clone().equals(x) will be true, this is not an absolute requirement.
    */
    protected native Object clone() throws CloneNotSupportedException;
    

    仔细一看,它还是一个native方法,大家都知道native方法是非Java语言实现的代码,供Java程序调用的,因为Java程序是运行在JVM虚拟机上面的,要想访问到比较底层的与操作系统相关的就没办法了,只能由靠近操作系统的语言来实现。

    1. 第一次声明保证克隆对象将有单独的内存地址分配。
    2. 第二次声明表明,原始和克隆的对象应该具有相同的类类型,但它不是强制性的。
    3. 第三声明表明,原始和克隆的对象应该是平等的equals()方法使用,但它不是强制性的。

    因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问。

    要想对一个对象进行复制,就需要对clone方法覆盖。

    为什么要克隆?

      大家先思考一个问题,为什么需要克隆对象?直接new一个对象不行吗?

      答案是:克隆的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠clone方法了。那么我把这个对象的临时属性一个一个的赋值给我新new的对象不也行嘛?可以是可以,但是一来麻烦不说,二来,大家通过上面的源码都发现了clone是一个native方法,就是快啊,在底层实现的。

      提个醒,我们常见的Object a=new Object();Object b;b=a;这种形式的代码复制的是引用,即对象在内存中的地址,a和b对象仍然指向了同一个对象。

      而通过clone方法赋值的对象跟原来的对象时同时独立存在的。

    如何实现克隆

    先介绍一下两种不同的克隆方法,浅克隆(ShallowClone)深克隆(DeepClone)

    在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制,下面将对两者进行详细介绍。

    一般步骤是(浅克隆):

    1. 被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)

    2. 覆盖clone()方法,访问修饰符设为public方法中调用super.clone()方法得到需要的复制对象。(native为本地方法)

    下面对上面那个方法进行改造:

    class Student implements Cloneable{  
        private int number;  
      
        public int getNumber() {  
            return number;  
        }  
      
        public void setNumber(int number) {  
            this.number = number;  
        }  
          
        @Override  
        public Object clone() {  
            Student stu = null;  
            try{  
                stu = (Student)super.clone();  
            }catch(CloneNotSupportedException e) {  
                e.printStackTrace();  
            }  
            return stu;  
        }  
    }  
    public class Test {  
        public static void main(String args[]) {  
            Student stu1 = new Student();  
            stu1.setNumber(12345);  
            Student stu2 = (Student)stu1.clone();  
              
            System.out.println("学生1:" + stu1.getNumber());  
            System.out.println("学生2:" + stu2.getNumber());  
              
            stu2.setNumber(54321);  
          
            System.out.println("学生1:" + stu1.getNumber());  
            System.out.println("学生2:" + stu2.getNumber());  
        }  
    }
    

    结果:

    学生1:12345  

    学生2:12345  

    学生1:12345  

    学生2:54321

    如果你还不相信这两个对象不是同一个对象,那么你可以看看这一句:

    System.out.println(stu1 == stu2); // false  
    

    上面的复制被称为浅克隆。

    还有一种稍微复杂的深度复制:

    我们在学生类里再加一个Address类。

    class Address  {
        private String add;
    
        public String getAdd() {
            return add;
        }
    
        public void setAdd(String add) {
            this.add = add;
        }
    
    }
    
    class Student implements Cloneable{
        private int number;
    
        private Address addr;
    
        public Address getAddr() {
            return addr;
        }
    
        public void setAddr(Address addr) {
            this.addr = addr;
        }
    
        public int getNumber() {
            return number;
        }
    
        public void setNumber(int number) {
            this.number = number;
        }
    
        @Override
        public Object clone() {
            Student stu = null;
            try{
                stu = (Student)super.clone();
            }catch(CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return stu;
        }
    }
    public class Test {
    
        public static void main(String args[]) {
    
            Address addr = new Address();
            addr.setAdd("杭州市");
            Student stu1 = new Student();
            stu1.setNumber(123);
            stu1.setAddr(addr);
    
            Student stu2 = (Student)stu1.clone();
    
            System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
            System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
        }
    }
    

    结果:

    学生1:123,地址:杭州市  

    学生2:123,地址:杭州市  

     

    乍一看没什么问题,真的是这样吗?

    我们在main方法中试着改变addr实例的地址。

    addr.setAdd("西湖区");  
      
    System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());  
    System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); 
    

    结果:

    学生1:123,地址:杭州市  
    学生2:123,地址:杭州市  
    学生1:123,地址:西湖区  
    学生2:123,地址:西湖区  
    

    这就奇怪了,怎么两个学生的地址都改变了?

    原因是浅复制只是复制了addr变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象。

    所以,为了达到真正的复制对象,而不是纯粹引用复制。我们需要将Address类可复制化,并且修改clone方法,完整代码如下:

    package abc;
    
    class Address implements Cloneable {
        private String add;
    
        public String getAdd() {
            return add;
        }
    
        public void setAdd(String add) {
            this.add = add;
        }
    
        @Override
        public Object clone() {
            Address addr = null;
            try{
                addr = (Address)super.clone();
            }catch(CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return addr;
        }
    }
    
    class Student implements Cloneable{
        private int number;
    
        private Address addr;
    
        public Address getAddr() {
            return addr;
        }
    
        public void setAddr(Address addr) {
            this.addr = addr;
        }
    
        public int getNumber() {
            return number;
        }
    
        public void setNumber(int number) {
            this.number = number;
        }
    
        @Override
        public Object clone() {
            Student stu = null;
            try{
                stu = (Student)super.clone();   //浅复制
            }catch(CloneNotSupportedException e) {
                e.printStackTrace();
            }
            stu.addr = (Address)addr.clone();   //深度复制
            return stu;
        }
    }
    public class Test {
    
        public static void main(String args[]) {
    
            Address addr = new Address();
            addr.setAdd("杭州市");
            Student stu1 = new Student();
            stu1.setNumber(123);
            stu1.setAddr(addr);
    
            Student stu2 = (Student)stu1.clone();
    
            System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
            System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
    
            addr.setAdd("西湖区");
    
            System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
            System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
        }
    }
    

    结果:

    学生1:123,地址:杭州市  
    学生2:123,地址:杭州市  
    学生1:123,地址:西湖区  
    学生2:123,地址:杭州市  
    

    这样结果就符合我们的想法了。

     

    最后我们可以看看API里其中一个实现了clone方法的类:

    java.util.Date:

    /** 
     * Return a copy of this object. 
     */  
    public Object clone() {  
        Date d = null;  
        try {  
            d = (Date)super.clone();  
            if (cdate != null) {  
                d.cdate = (BaseCalendar.Date) cdate.clone();  
            }  
        } catch (CloneNotSupportedException e) {} // Won't happen  
        return d;  
    }
    

    该类其实也属于深度复制。

    参考文档:Java如何复制对象

    浅克隆和深克隆

    1、浅克隆

    在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。

    简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。

    浅克隆示意图

    在Java语言中,通过覆盖Object类的clone()方法可以实现浅克隆

    2、深克隆

    在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。

    简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。

    深克隆示意图

    在Java语言中,如果需要实现深克隆,可以通过覆盖Object类的clone()方法实现,也可以通过序列化(Serialization)等方式来实现。

    如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。

    序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。

    扩展
    Java语言提供的Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,
    这种空接口也称为标识接口,标识接口中没有任何方法的定义,
    其作用是告诉JRE这些接口的实现类是否具有某个功能,
    如是否支持克隆、是否支持序列化等。
    

    解决多层克隆问题

    如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。

    public class Outer implements Serializable{
      private static final long serialVersionUID = 369285298572941L;  //最好是显式声明ID
      public Inner inner;
     //Discription:[深度复制方法,需要对象及对象所有的对象属性都实现序列化]
      public Outer myclone() {
          Outer outer = null;
          try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
              ByteArrayOutputStream baos = new ByteArrayOutputStream();
              ObjectOutputStream oos = new ObjectOutputStream(baos);
              oos.writeObject(this);
          // 将流序列化成对象
              ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
              ObjectInputStream ois = new ObjectInputStream(bais);
              outer = (Outer) ois.readObject();
          } catch (IOException e) {
              e.printStackTrace();
          } catch (ClassNotFoundException e) {
              e.printStackTrace();
          }
          return outer;
      }
    }
    

    Inner也必须实现Serializable,否则无法序列化:

    public class Inner implements Serializable{
      private static final long serialVersionUID = 872390113109L; //最好是显式声明ID
      public String name = "";
    
      public Inner(String name) {
          this.name = name;
      }
    
      @Override
      public String toString() {
          return "Inner的name值为:" + name;
      }
    }
    

    这样也能使两个对象在内存空间内完全独立存在,互不影响对方的值。

    总结

    实现对象克隆有两种方式:

      1). 实现Cloneable接口并重写Object类中的clone()方法;

      2). 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。

    注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是优于把问题留到运行时。

    展开全文
  • JAVA 对象引用以及对象赋值

    千次阅读 2013-05-21 11:16:35
    关键字: java对象 引用 Java对象及其引用 ...可是,如果我分不清对象与对象引用,  那实在没法很好地理解下面的面向对象技术。把自己的一点认识写下来,或许能让初学Java的朋友们少走一点弯路。

    关键字: java对象 引用

    Java对象及其引用

    关于对象与引用之间的一些基本概念。

           初学Java时,在很长一段时间里,总觉得基本概念很模糊。后来才知道,在许多Java书中,把对象和对象的引用混为一谈。可是,如果我分不清对象与对象引用,

           那实在没法很好地理解下面的面向对象技术。把自己的一点认识写下来,或许能让初学Java的朋友们少走一点弯路。

           为便于说明,我们先定义一个简单的类:

           class Vehicle {

           int passengers;      

           int fuelcap;

           int mpg;

                       }

    有了这个模板,就可以用它来创建对象:

           Vehicle veh1 = new Vehicle();

    通常把这条语句的动作称之为创建一个对象,其实,它包含了四个动作。

    1)右边的“new Vehicle”,是以Vehicle类为模板,在堆空间里创建一个Vehicle类对象(也简称为Vehicle对象)。

    2)末尾的()意味着,在对象创建后,立即调用Vehicle类的构造函数,对刚生成的对象进行初始化。构造函数是肯定有的。如果你没写,Java会给你补上一个默认的构造函数。

    3)左边的“Vehicle veh 1”创建了一个Vehicle类引用变量。所谓Vehicle类引用,就是以后可以用来指向Vehicle对象的对象引用。

    4)“=”操作符使对象引用指向刚创建的那个Vehicle对象。

    我们可以把这条语句拆成两部分:

    Vehicle veh1;

    veh1 = new Vehicle();

    效果是一样的。这样写,就比较清楚了,有两个实体:一是对象引用变量,一是对象本身。

           在堆空间里创建的实体,与在数据段以及栈空间里创建的实体不同。尽管它们也是确确实实存在的实体,但是,我们看不见,也摸不着。不仅如此,

           我们仔细研究一下第二句,找找刚创建的对象叫什么名字?有人说,它叫“Vehicle”。不对,“Vehicle”是类(对象的创建模板)的名字。

           一个Vehicle类可以据此创建出无数个对象,这些对象不可能全叫“Vehicle”。

           对象连名都没有,没法直接访问它。我们只能通过对象引用来间接访问对象。

           为了形象地说明对象、引用及它们之间的关系,可以做一个或许不很妥当的比喻。对象好比是一只很大的气球,大到我们抓不住它。引用变量是一根绳, 可以用来系汽球。

           如果只执行了第一条语句,还没执行第二条,此时创建的引用变量veh1还没指向任何一个对象,它的值是null。引用变量可以指向某个对象,或者为null

           它是一根绳,一根还没有系上任何一个汽球的绳。执行了第二句后,一只新汽球做出来了,并被系在veh1这根绳上。我们抓住这根绳,就等于抓住了那只汽球。

           再来一句:

           Vehicle veh2;

    就又做了一根绳,还没系上汽球。如果再加一句:

           veh2 = veh1;

    系上了。这里,发生了复制行为。但是,要说明的是,对象本身并没有被复制,被复制的只是对象引用。结果是,veh2也指向了veh1所指向的对象。两根绳系的是同一只汽球。

           如果用下句再创建一个对象:

    veh2 = new Vehicle();

    则引用变量veh2改指向第二个对象。

           从以上叙述再推演下去,我们可以获得以下结论:

    1)一个对象引用可以指向0个或1个对象(一根绳子可以不系汽球,也可以系一个汽球);

    2)一个对象可以有N个引用指向它(可以有N条绳子系住一个汽球)。

           如果再来下面语句:

           veh1 = veh2;

    按上面的推断,veh1也指向了第二个对象。这个没问题。问题是第一个对象呢?没有一条绳子系住它,它飞了。多数书里说,它被Java的垃圾回收机制回收了。

    这不确切。正确地说,它已成为垃圾回收机制的处理对象。至于什么时候真正被回收,那要看垃圾回收机制的心情了。

           由此看来,下面的语句应该不合法吧?至少是没用的吧?

    new Vehicle();

    不对。它是合法的,而且可用的。譬如,如果我们仅仅为了打印而生成一个对象,就不需要用引用变量来系住它。最常见的就是打印字符串:

        System.out.println(“I am Java!”);

    字符串对象“I am Java!”在打印后即被丢弃。有人把这种对象称之为临时对象。

           对象与引用的关系将持续到对象回收。

      

    Java对象及引用

     

    Java对象及引用是容易混淆却又必须掌握的基础知识,本章阐述Java对象和引用的概念,以及与其密切相关的参数传递。

     

    先看下面的程序:

    StringBuffer s;

    s = new StringBuffer("Hello World!");

    第一个语句仅为引用(reference)分配了空间,而第二个语句则通过调用类(StringBuffer)的构造函数StringBuffer(String str)为类生成了一个实例(或称为对象)。这两个操作被完成后,对象的内容则可通过s进行访问——在Java里都是通过引用来操纵对象的。

     

    Java对象和引用的关系可以说是互相关联,却又彼此独立。彼此独立主要表现在:引用是可以改变的,它可以指向别的对象,譬如上面的s,你可以给它另外的对象,如:

    s = new StringBuffer("Java");

    这样一来,s就和它指向的第一个对象脱离关系。

     

    从存储空间上来说,对象和引用也是独立的,它们存储在不同的地方,对象一般存储在堆中,而引用存储在速度更快的堆栈中。

     

    引用可以指向不同的对象,对象也可以被多个引用操纵,如:

    StringBuffer s1 = s;

    这条语句使得s1s指向同一个对象。既然两个引用指向同一个对象,那么不管使用哪个引用操纵对象,对象的内容都发生改变,并且只有一份,通过s1s得到的内容自然也一样,(String除外,因为String始终不变,String s1=”AAAA”; String s=s1,操作s,s1由于始终不变,所以为s另外开辟了空间来存储s,)如下面的程序:

    StringBuffer s;

    s = new StringBuffer("Java");

    StringBuffer s1 = s;

    s1.append(" World");

    System.out.println("s1=" + s1.toString());//打印结果为:s1=Java World

    System.out.println("s=" + s.toString());//打印结果为:s=Java World

      

    上面的程序表明,s1s打印出来的内容是一样的,这样的结果看起来让人非常疑惑,但是仔细想想,s1s只是两个引用,它们只是操纵杆而已,它们指向同一个对象,操纵的也是同一个对象,通过它们得到的是同一个对象的内容。这就像汽车的刹车和油门,它们操纵的都是车速,假如汽车开始的速度是80,然后你踩了一次油门,汽车加速了,假如车速升到了120,然后你踩一下刹车,此时车速是从120开始下降的,假如下降到60,再踩一次油门,车速则从60开始上升,而不是从第一次踩油门后的120开始。也就是说车速同时受油门和刹车影响,它们的影响是累积起来的,而不是各自独立(除非刹车和油门不在一辆车上)。所以,在上面的程序中,不管使用s1还是s操纵对象,它们对对象的影响也是累积起来的(更多的引用同理)。

     

    只有理解了对象和引用的关系,才能理解参数传递。

    一般面试题中都会考Java传参的问题,并且它的标准答案是Java只有一种参数传递方式:那就是按值传递,即Java中传递任何东西都是传值。如果传入方法的是基本类型的东西,你就得到此基本类型的一份拷贝。如果是传递引用,就得到引用的拷贝。

     

    一般来说,对于基本类型的传递,我们很容易理解,而对于对象,总让人感觉是按引用传递,看下面的程序:

    public class ObjectRef {

     

        //基本类型的参数传递

        public static void testBasicType(int m) {

            System.out.println("m=" + m);//m=50

            m = 100;

            System.out.println("m=" + m);//m=100

        }

       

        //参数为对象,不改变引用的值 ??????

        public static void add(StringBuffer s) {

            s.append("_add");

        }

       

        //参数为对象,改变引用的值 ?????

        public static void changeRef(StringBuffer s) {

            s = new StringBuffer("Java");

        }

       

        public static void main(String[] args) {

            int i = 50;

            testBasicType(i);

            System.out.println(i);//i=50

            StringBuffer sMain = new StringBuffer("init");

            System.out.println("sMain=" + sMain.toString());//sMain=init

            add(sMain);

            System.out.println("sMain=" + sMain.toString());//sMain=init_add

            changeRef(sMain);

            System.out.println("sMain=" + sMain.toString());//sMain=init_add

        }

    }

    以上程序的允许结果显示出,testBasicType方法的参数是基本类型,尽管参数m的值发生改变,但并不影响i

          add方法的参数是一个对象,当把sMain传给参数s时,s得到的是sMain的拷贝,所以ssMain指向同一个对象,因此,使用s操作影响的其实就是sMain指向的对象,故调用add方法后,sMain指向的对象的内容发生了改变。

         changeRef方法中,参数也是对象,当把sMain传给参数s时,s得到的是sMain的拷贝,但与add方法不同的是,在方法体内改变了s指向的对象(也就是s指向了别的对象,牵着气球的绳子换气球了),给s重新赋值后,ssMain已经毫无关联,它和sMain指向了不同的对象,所以不管对s做什么操作,都不会影响sMain指向的对象,故调用changeRef方法前后sMain指向的对象内容并未发生改变。

     

    对于add方法的调用结果,可能很多人会有这种感觉:这不明明是按引用传递吗?对于这种问题,还是套用Bruce Eckel的话:这依赖于你如何看待引用,最终你会明白,这个争论并没那么重要。真正重要的是,你要理解,传引用使得(调用者的)对象的修改变得不可预期。

     

     public   class   Test
    {   public int   i,j;  
        public   void   test_m(Test   a)
        {     Test   b   =  new   Test();
              b.i   =   1;
              b.j   =   2;
              a   =   b;
        }
        public   void   test_m1(Test   a   )
        {     a.i   =   1;
            a.j   =   2;
        }
        public   static   void   main(String   argv[])
        {     Test   t=   new   Test();
              t.i   =   5;
              t.j   =   6;
              System.out.println( "t.i   =   "+   t.i   +   "   t.j=   "   +   t.j); //5,6
              t.test_m(t);
              System.out.println( "t.i   =   "+   t.i   +   "   t.j=   "   +   t.j); //5,6,
    at都指向了一个对象,而在test_ms又指向了另一个对象,所以对象t不变!!!

              t.test_m1(t);

              System.out.println( "t.i   =   "+   t.i   +   "   t.j=   "   +   t.j); //1,2

        }

    }

    答案只有一个:Java里都是按值传递参数。而实际上,我们要明白,当参数是对象时,传引用会发生什么状况(就像上面的add方法)?

     

    =========================================================================

    楼主,这样来记这个问题
    如下表达式:
    A a1 = new A();
    它代表A是类,a1是引用,a1不是对象,new A()才是对象,a1引用指向new A()这个对象。

    在JAVA里,“=”不能被看成是一个赋值语句,它不是在把一个对象赋给另外一个对象,它的执行过程实质上是将右边对象的地址传给了左边的引用,使得左边的引用指向了右边的对象。JAVA表面上看起来没有指针,但它的引用其实质就是一个指针,引用里面存放的并不是对象,而是该对象的地址,使得该引用指向了对象。在JAVA里,“=”语句不应该被翻译成赋值语句,因为它所执行的确实不是一个赋值的过程,而是一个传地址的过程,被译成赋值语句会造成很多误解,译得不准确。

     

    再如:
    A a2;
    它代表A是类,a2是引用,a2不是对象,a2所指向的对象为空null;

     

    再如:
    a2 = a1;
    它代表,a2是引用,a1也是引用,a1所指向的对象的地址传给了a2(传址),使得a2和a1指向了同一对象。

    综上所述,可以简单的记为,在初始化时,“=”语句左边的是引用,右边new出来的是对象。
    在后面的左右都是引用的“=”语句时,左右的引用同时指向了右边引用所指向的对象。

    再所谓实例,其实就是对象的同义词。

    展开全文
  • 浅谈java对象引用及对象赋值

    千次阅读 多人点赞 2017-01-05 15:11:46
    一、Java对象及其引用  初学Java,总是会自觉或不自觉地把Java和C++相比较。在学习Java类与对象章节的时候,发现教科书和许多参考书把对象和对象的引用混为一谈。可是,如果分不清对象与对象引用, 那实在没法很...
  • JAVA对象引用和值引用

    千次阅读 2014-01-17 14:02:34
    以前就知道JAVA对象对象引用和值引用,并且还知道8种基础数据类型,即引用时是值引用的数据类型,比如int,short,long,byte,float,double,char,boolean,其它都是对象引用。可是其它的对象引用我一直都以为跟c里面...
  • Java对象引用处理机制

    千次阅读 2013-11-13 11:31:17
    Java引用别名机制(原文为Aliasing,别名,即Java中的多态)意味着多个引用变量可以定位到同一个实际物理对象,而这些引用变量可以是不同的类型. 下面的代码中,S类继承P类, pp 和 ss 分别是P类型 和 S类
  • 当应用程序需管理大量内存对象或者在新的Java对象创建之前需删除原有对象时,Java对象引用应用程序设计接口具有相当大的用途,例如:    ● 基于Web的应用程序常常要求显示大量图片,当用户离开某一Web页时,...
  • java对象引用和回收

    千次阅读 2012-07-21 23:02:37
    Java对象及其引用 摘自http://dev.csdn.net/author/beepbug/f5c70b05579f46d4996ce9da4f75dbc0.html ...可是,如果我分不清对象与对象引用,那实在没法很好地理解下面的面向对象技术。把自己的一点认识写下来,
  • 改变students[i]可以改变s是不是意味着是引用的数组元素地址 2.如果1成立,那为什么students[i]=null,不能是s也未null,从而在打印引起空指针异常 ``` java package com.zhuxl.jdk.sourcecode.java.util.hashmap;...
  • Java对象引用和回收

    2018-08-21 08:57:00
    java虚拟机中使用的是可达性分析算法,并没有使用引用计数算法,因为引用计数算法很难解决对象之间相互循环引用的问题。   1)引用计数算法 给对象加一个引用计数器,每当有一个地方引用对象引用...
  • Java语言里没有指针(地址)的概念,只用引用的概念,但是引用本身就是一个指向new分配的对象地址(希望我没有理解错误),现在问题来了: 1、为什么System.out.println("对象tree的地址:"+tree); 输出的是:[] ...
  • 最近,高级Java技术栈微信群中,有一些猿友在讨论JVM中对象的周期问题,有谈到引用的级别,现在为大家做个总结吧,虽然大多数公司并没有意识或者用到这些引用,但了解这些基本概念对熟悉整个垃圾回收机制和面试是...
  • JAVA对象引用与垃圾收集

    千次阅读 2006-09-09 18:04:00
    影子引用java.lang.ref.PhantomReferencePhantom Reference 主要是用来取代对象的 finalize()。程序员利用 finalize() 来进行释放资源的同时,有可能不小心 让此对象再度拥有 Direct Reference。但是使用 Phantom ...
  • 它代表A是类,a1是引用,a1不是对象,new A()才是对象,a1引用指向new A()这个对象。 在JAVA里,“=”不能被看成是一个赋值语句,它不是在把一个对象赋给另外一个对象,它的执行过程实质上是将右边对象的地址传给
  • 关于java对象的参数传递过程中,确实有些烦乱,在这里做一下总结:首先是一段测试代码 public class Main {    public class AA{  public String i;  public int j;  }  public static void ...
  • 1、首先数据库查询有四条记录:查询视图:select * from tav_user_dept_role where ...2、实际操作中并非如此,返回了4对对象,但是却都引用了同一个内存地址: List userInfoList = swfBiz.getTaViewUserDeptR
  • 什么是Java的对象引用? Java中都有哪些类型的对象引用? Java中提供的Java对象引用主要有什么目的? 通过本文,你就能很清楚得了解Java的对象引用
  • 当应用程序需管理大量内存对象或者在新的Java对象创建之前需删除原有对象时,Java对象引用应用程序设计接口具有相当大的用途,例如: ● 基于Web的应用程序常常要求显示大量图片,当用户离开某一Web页时,...
  • java对象对象引用变量

    千次阅读 多人点赞 2018-07-12 14:47:54
    Java对象及其引用 先搞清楚什么是堆,什么是栈。 Java开辟了两类存储区域,对比二者的特点 存储区域 存储内容 优点 缺点 回收 栈 基本类型的变量和对象的引用变量 存取速度比堆要快,仅次于...
  • 浅谈一下JAVA对象对象引用以及对象赋值

    万次阅读 多人点赞 2013-09-19 00:50:29
    浅谈一下JAVA对象对象引用以及对象赋值   今天有班级同学问起JAVA对象的引用是什么。正好趁着这次机会,自己总结一下JAVA对象对象引用以及对象赋值。自己总结了所看到的网上相关方面的不少帖子,整理汇总形成...
  • Java对象引用变量

    万次阅读 多人点赞 2016-08-31 00:45:30
    对于引用变量的深层含义,未必在初学的时候就能深刻理解, 所以理解好下面这两句话的真正含义非常重要Case cc=new Case();... 基本类型的变量和对象引用变量 存取速度比堆要快,仅次于寄存器,栈
  • 学习Java的过程中,很容易将对象及对象引用混为一谈,而且我们平时在交流时为了方便起见也只是对象怎样、对象怎样,但很多时候我们操作的,根本上来说是对象,但直接操作的却是对象的引用。 先贴一篇博文,非常形象...
  • Java对象对象引用,参数传递

    千次阅读 2018-07-01 19:45:46
    Java中,万物皆对象! 比如定义一个学生类 public class Student { private int id; private String name; private int age; public Student() { // TODO Auto-generated constructor stub su...
  • Java对象引用变量

    千次阅读 2019-08-19 14:59:26
    基本类型的变量和对象引用变量 存取速度比堆要快,仅次于寄存器,栈数据可以共享 存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量 当超过变量的作用域后,Java会自动释放掉...
  • Java引用对象

    千次阅读 2018-12-11 10:09:03
    在写了15年C/C++之后,我于1999年开始写Java。借助指针切换(pointer handoffs)等编码实践或者Purify等工具,我认为自己对C风格的内存管理已经得心应手了,甚至已经不记得上次发生内存泄露是什么时候了。所以起初我...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 83,552
精华内容 33,420
关键字:

java对象引用

java 订阅