引用类型 订阅
引用类型 由类型的实际值引用(类似于指针)表示的数据类型。如果为某个变量分配一个引用类型,则该变量将引用(或“指向”)原始值。不创建任何副本。引用类型包括类、接口、委托和装箱值类型。 展开全文
引用类型 由类型的实际值引用(类似于指针)表示的数据类型。如果为某个变量分配一个引用类型,则该变量将引用(或“指向”)原始值。不创建任何副本。引用类型包括类、接口、委托和装箱值类型。
信息
外文名
reference type
作    用
是为变量起一个别名
含    义
是c++的一种新的变量类型
中文名
引用类型
引用类型简介
(reference type)“引用”(reference)是c++的一种新的变量类型,是对C的一个重要补充。它的作用是为变量起一个别名。假如有一个变量a,想给它起一个别名,可以这样写:int a;int &b=a;这就表明了b是a的“引用”,即a的别名。经过这样的声明,使用a或b的作用相同,都代表同一变量。在上述引用中,&是“引用声明符”,并不代表地址。不要理解为“把a的值赋给b的地址”。引用类型的数据存储在内存的堆中,而内存单元中只存放堆中对象的地址。声明引用并不开辟内存单元,b和a都代表同一变量单元。注意:在声明引用变量类型时,必须同时使之初始化,即声明它代表哪一变量。在声明一个变量的引用之后,在本函数执行期间,该引用一直与其代表的变量相联系,不能再作为其他变量的别名。下面的用法不对:int a1,a2;int &b=a1;int &b=a2;//企图使b变成a2的别名(引用)是不行的。这样是错误的。我们可以把a2的值赋给b。b=a2;
收起全文
精华内容
下载资源
问答
  • JAVA中的引用四种引用类型

    千次阅读 2020-08-30 10:09:38
    关于值类型和引用类型的话题,C++、JAVA、python、go、C#等等高级语言都有相关的概念,只要理解了其底层工作原理,可以说即使是不同的语言,在面试学习工作实践中都可以信手拈来(不要太纠集语言),当然此处我选择了...

    关于值类型和引用类型的话题,C++、JAVA、python、go、C#等等高级语言都有相关的概念,只要理解了其底层工作原理,可以说即使是不同的语言,在面试学习工作实践中都可以信手拈来(不要太纠集语言),当然此处我选择了JAVA,虽然我是搞C++的,具体原因都懂就不废话了。

    一、值类型与引用类型

      1、变量初始化

    int num=10;
    String str="hello"

      

      2、变量赋值
       从上图可以显而易见,num是int基本类型变量,值就直接保存在变量中。str是String引用类型变量,变量中保存的只是实际对象对应的地址信息,而不是实际对象数据。对于而这特性,如下:

    num=20;
    str="java";

      对于基本类型变量num,赋值运算符将会直接修改变量的值,原来的数据将被覆盖掉,被替换为新的值。对于引用类型变量str,赋值运算符只会改变变量中所保存的对象的地址信息,原来对象的地址被覆盖掉,重新写入新对象的地址数据。但原来的对象本身并不会被改变,只是不再被任何引用所指向的对象,即“垃圾对象”,后续会被垃圾回收器回收。

      3、函数传参

     

    // 基本类型参数,原始value不会被更改
        public void func(int value) {
            value = 100;
        }
    
        // 对于没有提供修改自身的成员方法引用类型,原始str不会被更改
        public void func(String str) {
            str = "hello";
        }
    
        StringBuilder sb = new StringBuilder("test");
    
        // 对于提供修改自身的成员方法引用类型,原始的sBuilder会被更改
        public void func(StringBuilder sBuilder) {
            sBuilder.append("aa");
        }
    
        // 原始的sBuilder不会被更改
        public void test(StringBuilder sBuilder) {
            sBuilder = new StringBuilder("111");
        }

     

    说明:对于第三种情况:

    对于第四种情况:

     

     

    二、数据存储方式

      1、局部变量/方法参数

      对于局部变量和方法传递的参数在jvm中的存储方式是相同的,都是存储在栈上开辟的空间中。方法参数空间在进入方法时开辟,方法退出时进行回收。以32为JVM为例,boolean、byte、short、char、int、float以及对应的引用类型都是分配4字节大小的空间,long、double分配8字节大小空间。对于每一个方法来说,最多占用空间大小是固定的,在编译时就已经确定了。当在方法中声明一个int变量i=0或Object变量obj=null时,此时仅仅在栈上分配空间,不影响到堆空间。当new Object()时,将会在堆中开辟一段内存空间并初始化Object对象。

      2、数组类型引用和对象

      当声明数组时,int[]  arr=new int[2];数组也是对象,arr实际上是引用,栈上占用4个字节大小的存储空间,而是会在堆中开辟相应大小空间进行存储,然后arr变量指向它。当声明一个二维数组时,如:int[][]  arr2=new int[2][4],arr2同样在栈中占用4个字节,在堆内存中开辟长度为2,类型为int[]的数组对象,然后arr2指向这个数组。这个数组内部有两个引用类型(大小为4个字节),分别指向两个长度为4类型为int的数组。内存分布如图:

    所以当传递一个数组给一个方法时,数组的元素在方法内部是可以被修改的,但是无法让数组引用指向新的数组。其实,还可以声明:int [][]  arr3=new int[3][],内存分布如下:

      3、String类型数据

      对于String类型,其对象内部需要维护三个成员变量,char[]  chars,int  startIndex,  int  length。chars是存储字符串数据的真正位置,在某些情况下是可以共用的,实际上String类型是不可变类型。例如:String  str=new String("hello"),内存分布如下:

     

    三、JAVA引用类型

      在JAVA中提供了四种引用类型:强引用、软引用、软引用和虚引用。在四种引用类型中,只有强引用FinalReference类型变量是包内可见的,其他三种引用类型均为public,可以在程序中直接使用。

      1、强引用

      强引用是使用最普遍的引用。如果一个对象具有强引用,那么垃圾回收器绝不会回收它。例如:StringBuilder sb = new StringBuilder("test");变量str指向StringBuffer实例所在的堆空间,通过str可以操作该对象。如下:

      强引用特点:

      • 强引用可以直接访问目标对象
      • 只要有引用变量存在,垃圾回收器永远不会回收。JVM即使抛出OOM异常,也不会回收强引用所指向的对象。
      • 强引用可能导致内存泄漏问

       2、软引用

      软引用是除了强引用外,最强的引用类型。可以通过java.lang.ref.SoftReference使用软引用。一个持有软引用的对象,不会被JVM很快回收,JVM会根据当前堆的使用情况来判断何时回收。当堆使用率临近阈值时,才会去回收软引用的对象。因此,软引用可以用于实现对内存敏感的高速缓存。SoftReference的特点是它的一个实例保存对一个Java对象的软引用,该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对 这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用。一旦垃圾线程回收该Java对象之后,get()方法将返回null。

    Object obj = new Object();
    SoftReference<Object> sf = new SoftReference<Object>(obj);
    obj = null;
    sf.get();//有时候会返回null
    sf是对obj的一个软引用,通过sf.get()方法可以取到这个对象,当这个对象被标记为需要回收的对象时,则返回null;

      软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。使用软引用能防止内存泄露,增强程序的健壮性。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。也就是说,ReferenceQueue中保存的对象是Reference对象,而且是已经失去了它所软引用的对象的Reference对象。当调用它的poll()方法的时候,如果这个队列中不是空队列,那么将返回队列前面的那个Reference对象。在任何时候,都可以调用ReferenceQueue的poll()方法来检查是否有它所关心的非强可及对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。利用这个方法,可以检查哪个SoftReference所软引用的对象已经被回收,于是可以把这些失去所软引用的对象的SoftReference对象清除掉。

      3、弱引用

      弱引用是一种比软引用较弱的引用类型。在系统GC时,只要发现弱引用,不管系统堆空间是否足够,都会将对象进行回收。在java中,可以用java.lang.ref.WeakReference实例来保存对一个Java对象的弱引用。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnQueued方法返回对象是否被垃圾回收器标记。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

    Object obj = new Object();
    WeakReference<Object> wf = new WeakReference<Object>(obj);
    obj = null;
    wf.get();//有时候会返回null
    wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾

      4、虚引用

      虚引用是所有类型中最弱的一个。一个持有虚引用的对象和没有引用几乎是一样的,随时可能被垃圾回收器回收,当试图通过虚引用的get()方法取得强引用时,总是会失败。并且虚引用必须和引用队列一起使用,它的作用在于检测对象是否已经从内存中删除,跟踪垃圾回收过程。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在垃圾回收后,销毁这个对象,将这个虚引用加入引用队列。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

    Object obj = new Object();
    PhantomReference<Object> pf = new PhantomReference<Object>(obj);
    obj=null;
    pf.get();//永远返回null
    pf.isEnQueued();//返回是否从内存中已经删除

     

     

    @Test
        public void test(){
            Map map;
            map = new WeakHashMap<String,Object>();
            for (int i =0;i<10000;i++){
                map.put("key"+i,new byte[i]);
            }
    //        map = new HashMap<String,Object>();
    //        for (int i =0;i<10000;i++){
    //            map.put("key"+i,new byte[i]);
    //        }
        }

     

      使用-Xmx2M限定堆内存,使用WeakHashMap的代码正常运行结束,而使用HashMap的代码段抛出异常:java.lang.OutOfMemoryError: Java heap space。由此可见,WeakHashMap会在系统内存紧张时使用弱引用,自动释放掉持有弱引用的内存数据。但如果WeakHashMap的key都在系统内持有强引用,那么WeakHashMap就退化为普通的HashMap,因为所有的数据项都无法被自动清理。

    展开全文
  • Java四种引用类型

    万次阅读 多人点赞 2019-05-12 19:01:34
    文章目录引用类型简介对象可达性判断GC Roots对象可达性判断可达性状态类型可达性判断规则状态转换Reference类定义状态变化四种引用类型实现强引用FinalReference软引用SoftReference弱引用WeakReference虚引用...

    引用类型简介

    Java开发者肯定都很熟悉java中的4种引用类型,它们从强到弱分别是:

    引用类型对象是否可引用回收时间使用场景
    强引用可以从不回收普遍对象的状态
    软引用可以内存不足时内存敏感的高速缓存
    弱引用可以下一次GC对象缓存
    虚引用不可以下一次GC,不影响对象生命周期必须和引用队列(ReferenceQueue)一起使用,一般用于追踪垃圾收集器的回收动作。相比对象的finalize方法,虚引用的方式更加灵活和安全。

    但可能大多数对4种引用类型的理解都只停留在概念的记忆上,而并不了解四种引用的实现原理。下面我们结合源代码了解下四种引用的实现原理。当然在了解四种引用的实现原理之前,首先得了解下JVM中实现对象可达性判断的原理。

    对象可达性判断

    目前,大多数JVM都是使用可达性分析算法来判断对象的是否可达。可达性分析算法以GC Roots对象作为起始点进行搜索。当一个对象与GC Roots对象没有任何引用链相连时(也即引用有向图中从GC Roots对象到这个对象是不连通的),则表明该对象是不可用的(不可用的对象不一定被判定为可以回收的对象)。当对象与GC Roots对象有引用链相连时,则需要根据引用链的类型来判断对象是否可达。

    不可用的对象不一定被判定为可以回收的对象:判定对象为”死亡”至少需要经历两次标记的过程。第一次标记:对象可达性分析,如果发现对象没有与GC Roots相连接的引用链,且对象需要执行finalize方法,将会被加入F-Queue队列中。第二次标记:由一个优先级低的Finalizer线程去取F-Queue队列的对象,“尝试执行”对象的finalize方法。

    JVM会保证触发满足条件的对象的finalize方法,但是并不承诺会等待方法执行结束。finalize方法是对象逃脱死亡命运的最后一次机会。

    GC Roots对象

    GC Roots对象包含以下四类:

    • 虚拟机栈(栈桢中的本地变量表)中的引用的对象;
    • 方法区中的类静态属性引用的对象;
    • 方法区中的常量引用的对象;
    • 本地方法栈中JNI(Native方法)引用的对象;

    可达性判断

    可达性状态类型

    Java有5种类型的可达性状态:

    • 强可达(Strongly Reachable):与GC Roots对象之间有强引用相连通,则为强可达的;
    • 软可达(Soft Reachable):与GC Roots对象之间没有强引用相连通,但有软引用相连通,则为软可达的;
    • 弱可达(Weak Reachable):与GC Roots对象之间没有强引用或软引用相连通,但有弱引用相连通,则为弱可达的;
    • 虚可达(Phantom Reachable):与GC Roots对象之间没有强引用、软引用或弱引用相连通,然后该对象finalize方法已执行,并且有虚引用相连通,则为虚可达的;
    • 不可达(Unreachable):如果对象finalize方法已执行并且没有任何引用相连通,则对象是不可达的,可以被回收。

    可达性判断规则

    从GC Roots对象到一个对象的引用链可能存在多条,那么此时会依据两个原则来判断对象的可达性:

    • 单个引用链中,以最弱的引用类型为准;
    • 多引用链联合看时,以最强的引用类型为准;

    可达性判断

    首先,单个引用链中,以最弱的引用类型为准:则GC Roots->Obj1->Obj4是软引用连通的,GC Roots->Obj2->Obj4是弱引用连通的,GC Roots->Obj3->Obj5是弱引用连通的。然后多引用链联合看时,以最强的引用类型为准:则GC Roots到Obj4对象的引用联合来看是弱引用连通的。

    状态转换

    对象可达性状态是随着程序运行而不断变化的,对象可达性状态转换图可参考下图。

    • 对象创建后一般是强可达的。
    • 当GC Roots对象到该对象的强引用被清除后:如果剩余引用链最高为软引用,则状态转换为软可达的;反之如果最高为弱引用,则状态转换为弱可达的,反之则把对象标记为可执行finalize方法状态。
    • 当软可达对象重新被强引用连接时,则转换为强可达状态;当软可达对象的软引用被清除后,如果剩余引用链最高为弱引用,则状态转换为弱可达;反之则把对象标记为可执行finalize方法状态。
    • 当弱可达对象重新被强引用或者软引用连接时,则可转换为强可达或者软可达;当弱可达对象的弱引用被清除后,则把对象标记为可执行finalize方法状态。
    • 可对象被标记为可执行finalize方法状态,如果对象finalize从未被执行,则执行finalize方法,并标记对象的finalize方法已经被执行(在finalize方法可能会重新生成强/软/弱引用等,对象状态会重新转换为强/软/弱可达,不过并不推荐这么做,因为可能会导致对象状态紊乱,无法被正常回收);反之当对象有虚引用连接时,则转换为虚可达状态,否则转换为不可达状态。
    • 虚可达对象在垃圾回收后状态转换为不可达(不能通过虚引用获取对象引用,所以对象状态不会再转换为强/软/弱可达);

    可达性状态转换图

    Reference

    Reference类是所有引用类型的基类,定义了reference对象的通用操作,用来保存对象引用及引用的内部状态。Reference抽象类初始化时,会启动一个ReferenceHandler线程。Reference的referent被回收前,垃圾回收器会把reference添加到pending这个链表里(如果注册了ReferenceQueue),然后ReferenceHandler线程不断的读取pending中的reference,把它加入到对应的ReferenceQueue中(如果Reference是Cleaner类的实例,即虚引用对象,则调用其注册的预处理钩子方法)。

    ReferenceQueue提供了两个静态字段NULL,ENQUEUED。这两个字段的主要功能:NULL是当我们构造Reference实例时queue传入null时,会默认使用NULL,这样在enqueue时判断queue是否为NULL,如果为NULL直接返回,入队失败。ENQUEUED的作用是防止重复入队,reference后会把其queue字段赋值为ENQUEUED,当再次入队时会直接返回失败。

    类定义

    Reference对象的基类。该类定义了reference对象的通用操作。因为reference对象是和垃圾回收器密切配合实现的,因此该类不能直接进行子类化。

    public abstract class Reference<T> {
    	// 用于保存对象的引用,GC会特别对待该变量
        private T referent; 
    	// 如果注册了ReferenceQueue(需要通知机制),用来保存对象引用的队列,
        volatile ReferenceQueue<? super T> queue;
    
        // 保存需要由ReferenceHandler处理的引用
        volatile Reference next;
    
    	// 被JVM使用,保存需要被JVM处理的下一个引用
        transient private Reference<T> discovered;
    
        // 同步锁,用于同步pending队列的进队和出队
        static private class Lock { }
        private static Lock lock = new Lock();
    
        // 一个PENDING队列,配合上述next一起使用,实现类单向循环链表的操作
        private static Reference<Object> pending = null;
    
    	// 高优先级线程,用于将pending队列里面的Reference实例依次添加到不同的ReferenceQueue中
    	private static class ReferenceHandler extends Thread {...}
    	...
    }
    

    状态变化

    一个对象引用有四种内部状态:

    • Active: 新创建的实例的状态,当对象引用被垃圾回收器回收前:如果Reference注册了ReferenceQueue,则会切换为Pending,并且Reference会加入pending链表中,如果没有注册ReferenceQueue,会切换为Inactive。
    • Pending: 在pending链表中的Reference的状态,这些Reference等待被ReferenceHandler内部线程加入ReferenceQueue中。
    • Enqueued: 在ReferenceQueue队列中的Reference的状态,如果Reference从队列中移除,会进入Inactive状态。未注册ReferenceQueue的实例不会到达该状态。
    • Inactive: Reference的最终状态,该状态不会再改变。

    其状态转换图如下:

    Reference内部状态转换图

    四种引用类型实现

    不同引用类型的UML类图如下:

    Reference UML类图

    强引用FinalReference

    对象新建后默认为强引用类型的,是普遍对象引用的类型。查看FinalReference在JDK中的源码发现其只有一个空实现,这也说明强引用是“默认引用类型”。

    /**
     * Final references, used to implement finalization
     */
    class FinalReference<T> extends Reference<T> {
        public FinalReference(T referent, ReferenceQueue<? super T> q) {
            super(referent, q);
        }
    }
    

    软引用SoftReference

    软引用是用来描述一些“还有用但是非必须”的对象。软引用的回收策略在不同的JVM实现会略有不同,JVM不仅仅只会考虑当前内存情况,还会考虑软引用所指向的referent最近的使用情况和创建时间来综合决定是否回收该referent。软引用保存了两个变量:

    • timestamp:每次调用get方法都会更新时间戳。JVM可以利用该字段来选择要清除的软引用,但不是必须要这样做。
    • clock:时间锁,由垃圾收集器更新。

    因此,任何GC都可以使用这些字段并定义清除软引用的策略,例如:最后清除最近创建的或最近使用的软引用。

    /**
     * 软引用对象由垃圾收集器根据内存需要决定是否清除。软引用经常用于实现内存敏感的缓存。
     *
     * 假如垃圾收集器在某个时间确定对象是软可达的,此时它可以选择原子地清除
     * 指向该对象的所有软引用,以及从该对象通过强引用链连接的其他软可达对象的所有软引用。
     * 与时同时或者之后的某个时间,它会将注册了reference queues的新清除的软引用加入队列。
     *
     * 在虚拟机抛出OutOfMemoryError异常之前,将保证清除对软可达对象的所有软引用。
     * 不过,并没有对清除软引用的时间以及清除顺序施加强制约束。
     * 但是,鼓励虚拟机实现偏向不清除最近创建或最近使用的软引用。
     *
     * 该类的直接实例可用于实现简单的缓存。
     * 该类或其派生子类也可用于更大的数据结构以实现更复杂的高速缓存。
     * 只要软引用的引用对象还是强可达的,即还在实际使用中,软引用就不会被清除。
     * 因此,复杂的高速缓存可以通过持有对最近使用缓存对象的强引用来防止其被清除,
     * 而不常使用的剩余缓存对象由垃圾收集器决定是否清除。
     */
    public class SoftReference<T> extends Reference<T> {
    
    	// 时间锁,由垃圾收集器更新。
        static private long clock;
    
    	// 每次调用get方法都会更新该时间戳。JVM可能会在选择要清除的软引用时使用该字段,
    	// 但这不是强制必须的。
        private long timestamp;
    	
    	// 返回对象的引用。如果该引用对象已经被程序或者垃圾收集器清除,则返回null。
    	// 把最近一次垃圾回收时间赋值给timestamp
        public T get() {
            T o = super.get();
            if (o != null && this.timestamp != clock)
                this.timestamp = clock;
            return o;
        }
    
    }
    

    弱引用WeakReference

    当一个对象没有被强引用或者软引用连接,但被弱引用连接时,则处于弱可达状态。只要发生GC,弱可达的对象就会被清除,同时会把弱引用加入到注册的引用队列中(如果存在的话)。弱引用对GC几乎是没有影响的,它不影响对应的referent被终结(finalized)和回收(reclaimed)。因此,弱引用最常用于实现规范化映射(canonicalizing mappings),例如哈希表,如果它们在程序中未被引用,则其键和值将从映射中删除。

    /**
     * 弱引用对象不能阻止自身的引用被回收。
     * 弱引用常用于实现规范化映射(对象实例可以在程序的多个地方同时使用)。
     *
     * 假如垃圾收集器在某个时间点确定对象是弱可达的。那时它将原子地清除对该对象的所有弱引用
     * 以及该引用通过强引用或者软引用连接的所有其他弱可达对象的所有弱引用。
     * 同时,它将表明前面所指的所有弱可达对象都可以执行finalize方法。
     * 与此同时或之后某一个时间,它将注册了reference queues的那些新清除弱引用加入队列。
     */
    public class WeakReference<T> extends Reference<T> {
    
    	// 创建没有注册ReferenceQueue的弱引用
        public WeakReference(T referent) {
            super(referent);
        }
    
    	// 创建注册了ReferenceQueue的弱引用
        public WeakReference(T referent, ReferenceQueue<? super T> q) {
            super(referent, q);
        }
    }
    

    虚引用PhantomReference

    虚引用是所有引用类型中最弱的一种。一个对象是否关联到虚引用,完全不会影响该对象的生命周期,也无法通过虚引用来获取一个对象的实例。为对象设置一个虚引用的唯一目的是:能在此对象被垃圾收集器回收的时候收到一个系统通知,它就是利用ReferenceQueue实现的。当referent被gc回收时,JVM自动把虚引用对象本身加入到ReferenceQueue中,表明该reference指向的referent被回收。然后可以通过去queue中取到reference,可以通过这个来做额外的清理工作。可以用虚引用代替对象finalize方法来实现资源释放,这样更加灵活和安全。

    /**
     * 虚引用对象在被垃圾收集器检查到后加入reference queues队列,否则会被回收。
     * 虚引用最常用于实现比Java finalization机制更灵活的安排额外的清理工作。
     *
     * 如果垃圾收集器在某个时间点确定虚引用对象是虚可达的,那么在那个时间或之后某个时间它会将引用加入reference queues队列。
     *
     * 为了确保可回收对象保持不变,虚引用的引用无法使用:虚引用对象的get方法始终返回null。
     *
     * 与软引用和弱引用不同,当虚引用加入reference queues队列后垃圾收集器不会被自动清除。
     * 只通过虚引用可达的对象将保持不变,直到所有此类引用都被清除或自已变为不可达。
     */
    public class PhantomReference<T> extends Reference<T> {
    
        // 由于不能通过虚引用访问对象,因此此方法始终返回null。
        public T get() {
            return null;
        }
    
        // 使用空ReferenceQueue队列创建一个虚引用没有意义:它的get方法总是返回null,
    	// 并且由于它没有注册队列,所以也不会被加入队列有任何清理前的预处理操作。
        public PhantomReference(T referent, ReferenceQueue<? super T> q) {
            super(referent, q);
        }
    }
    
    展开全文
  • golang中的值类型和引用类型

    千次阅读 2019-08-20 20:48:29
    值类型与引用类型 值类型 值类型:这些类型的变量直接指向存在内存中的值,值类型的变量的值存储在栈中。当使用等号=将一个变量的值赋给另一个变量时,如 j = i ,实际上是在内存中将 i 的值进行了拷贝。可以通过 &...

    值类型与引用类型

    不管是Java还是golang中,都有值类型和引用类型的概念。在使用两者时,发现这两种语言之间还是有差异的。

    值类型

    值类型:这些类型的变量直接指向存在内存中的值,值类型的变量的值存储在栈中。当使用等号=将一个变量的值赋给另一个变量时,如 j = i ,实际上是在内存中将 i 的值进行了拷贝。可以通过 &i 获取变量 i 的内存地址。 值拷贝

    引用类型

    引用类型:引用类型拥有更复杂的存储结构:(1)通过make创建并分配内存 (2)初始化一系列属性:指针、长度、哈希分布、数据队列等。一个引用类型的变量r1存储的是r1的值所在的内存地址(数字),或内存地址中第一个元素所在的位置,这个内存地址被称之为指针,这个指针实际上也被存在另外的某一个变量中。

    区别

    值类型和引用类型两者之间的主要区别:拷贝操作和函数传参。

    数组array和切片slice的实例

    1)如下代码片段定义了一个数组a,它是值类型,复制给b是值拷贝copy,当b发生变化后a并不会发生任何变化,程序的执行结果如下所示:

    //由 main 函数作为程序入口点启动
    func main() {
    	a :=[5]int{1,2,3,4,5}    //数组Array类型,带有具体的长度
    	b := a   //值拷贝,等价于a :=[5]int{1,2,3,4,5}, b :=[5]int{1,2,3,4,5}
    	b[2] = 6 //b :=[5]int{1,2,3,4,5},b[2]=8, b = [5]int{1,2,6,4,5}
    	fmt.Println(a, b)
    }
    

    程序运行结果:
    [1 2 3 4 5] [1 2 6 4 5]

    2)切片则不然,由于切片是引用类型,其拷贝(赋值)属于址拷贝,所以其中一个元素的值发生变化,拷贝的另一方也会发生改变。
    //由 main 函数作为程序入口点启动

    func main() {
    	a :=[]int{1,2,3,4,5}  //切片Slice类型,不带长度
    	b := a                //址拷贝
    	b[2] = 6              
    	fmt.Println(a, b)
    }
    

    分析:
    切片的底层数据结构其实是一个指针,切片a和b本质上指向同一个底层数组。
    程序运行结果:
    [1 2 6 4 5] [1 2 6 4 5]

    golang中的struct类型

    Golang中没有class关键字来定义类,对于事物的抽象以struct来定义,因此一个struct变量可以被看做一个类实例。但是这有别于java,因为struct在方法中传参时是值类型而非引用类型,所以当我们需要在方法内改变这个对象的字段值时,应该使用的是struct变量的指针,而非struct变量。例如:

    type Student struct{
             Name string
    }
     
    func (s * Student) Set(name string){
                  s.Name = name
    }
    func (s Student) Get()string{
               return s.Name
    }
    

    原文链接:https://blog.csdn.net/zerro99/article/details/11525757

    由上可知:

    • 如果方法内部会修改当前对象的字段或改变其值,需要用指针。
    • 由于值传递是(内存)复制,因此,如果对象比较大,应该使用指针(地址),避免内存拷贝(值类型等变量指向内存中的值,如果有值类型变量存放大量元素,或造成内存的大量拷贝)。

    注意:不见得使用指针就一定好过传值,因为按照 Go 的内存管理策略,涉及指针和引用的对象会被分配到 GC Heap 上。如果对象很 “小”,显然要比在栈上进行值拷贝 “耗费” 更多。

    与Java差异

    另外与java不同的一点,数组Array在golang里并不被当做引用类型传递,而是值类型传递。按照Golang的说法,Golang中所有的基本类型都属于值类型,但是有几个类型比较特殊,表现出引用类型的特征,分别是slice、map、channel、interface,除赋值以外它们都可以当做引用类型来使用。因此当我们这样做时,可以直接使用变量本身而不用指针:

    type MyMap map[string]string
     
    func (s  MyMap) Set(name,value string){
                  s[name] = value
    }
    
    func (s MyMap) Get(name string)string{
               return s[name]
    }
    

    上面的例子由于MyMap是一个map,而map是引用类型,因此在方法中作为参数使用时可以直接使用MyMap来改变其值,而不用使用MyMap的指针,其实map本身是一个字典的指针。

    展开全文
  • C#基础——值类型和引用类型

    千次阅读 2020-04-07 19:07:18
    1、值类型,引用类型,拆,装箱,常用的引用类型,值类型。 栈:一种先进后出(后进先出)的存储数据的结构体 堆:一块连续的,自由的存储空间。 值类型:变量直接保存其数据。 引用类型:变量保存其数据的引用...

    1、值类型,引用类型,拆,装箱,常用的引用类型,值类型。

    栈:一种先进后出(后进先出)的存储数据的结构体

    堆:一块连续的,自由的存储空间。

    值类型:变量直接保存其数据。

    引用类型:变量保存其数据的引用(地址),而不是具体的数据。

    C#的值类型包括:结构体(数值类型,bool型,用户定义的结构体),枚举,可空类型。

    C#的引用类型包括:数组,用户定义的类、接口、委托,object,字符串。

    数组的元素,不管是引用类型还是值类型,都存储在托管堆上。

    引用类型在栈中存储一个引用,其实际的存储位置位于托管堆。为了方便,本文简称引用类型部署在托管推上。

    值类型总是分配在它声明的地方:作为字段时,跟随其所属的变量(实例)存储;作为局部变量时,存储在栈上。

    值类型在内存管理方面具有更好的效率,并且不支持多态,适合用作存储数据的载体;引用类型支持多态,适合用于定义应用程序的行为。

    应该尽可能地将值类型实现为具有常量性和原子性的类型。

    应该尽可能地确保0为值类型的有效状态。

    应该尽可能地减少装箱和拆箱。

    装箱:把值类型转换成引用类型的过程。把一个值类型赋值给一个引用类型

    拆箱:把引用类型转换成值类型的过程。把一个引用类型赋值给一个值类型(需要用装箱的数据类型转换)

      object obj;

      short  i = 123;

    obj = i;//装箱操作

      int j = (short )obj;//拆箱操作,需要用装箱的数据类型转换

      Console.WriteLine(obj);

    1. 隐式转换和强制转换。

    隐式转换:第一种,把小的赋值给大的。第二种,把派生类(子类)赋值给基类,也会发生隐式转换。第三种、把某一类的对象赋值给实现该类的接口时候发生隐式转换。

    强制转换:大的赋值给小的时候强制转换。拆箱的时候发生强制转换。

    1. 装箱和拆箱的内部原理

    http://images.cnblogs.com/cnblogs_com/hunts/WindowsLiveWriter/C_D645/boxing_thumb2.png

    装箱 是值类型到 object 类型或到此值类型所实现的任何接口类型的隐式转换。对值类型装箱会在堆中分配一个对象实例,并将该值复制到新的对象中。 

     

     

     

     

    http://images.cnblogs.com/cnblogs_com/hunts/WindowsLiveWriter/C_D645/unboxing_thumb.png

    拆箱(取消装箱)是从 object 类型到值类型或从接口类型到实现该接口的值类型的显式转换。取消装箱操作包括:

    检查对象实例,确保它是给定值类型的一个装箱值。(拆箱后没有转成原类型,编译时不会出错,但运行会出错,所以一定要确保这一点。用GetType().ToString()判断时一定要使用类型全称,如:System.String 而不要用String。)

    将该值从实例复制到值类型变量中。

    值类型举例:

     

      int i = 0; 执行,i入栈。

    ChangeAge(i);方法入栈,执行方法i = 10;

    当i = 10执行完后出栈。此时方法执行完,也出栈。

    栈中留下i=0;

    所以输出结果为0

    内存图解如下:

      namespace ConsoleApplication1

     

    {

        class Program

        {

            static void ChangeAge(int i)

            {

                i = 10;

            }

            static void Main(string[] args)

            {

                int i = 0;

                ChangeAge(i);

                Console.WriteLine(i);//输出结果为0

            }

         }

    }

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    引用类型举例:

     

    Student student=new Student();

    在堆中分配一块内存,并将地址赋值到栈中。

    student.Age=0;

    将堆中的age赋值为0;

    Change(student);方法入栈并将student内存地址传到方法内。

    student.Age = 10;将内存地址上对应的对象的Age改变为10;

    然后出栈,方法出栈,再执行   Console.WriteLine(student.Age);输出Age的结果时,Age的值变为10;

     

    namespace ConsoleApplication1

     

    {

        class Student

        {

            public int Age;

        }

        class Program

        {

            static void Change(Student student)

            {

                student.Age = 10;

            }

            static void Main(string[] args)

            {

               Student student=new Student();

                student.Age=0;

                Change(student);

                Console.WriteLine(student.Age);//输出结果为10

            }

        }

    }

     

     

     

     

     

     

     

     

     

     

    C#支持两个预定义的引用类型

    名称 

    CTS类型 

    说明 

    object 

    System.Object 

    根类型,CTS中的其他类型都是从它派生而来的(包括值类型) 

    string 

    System.String 

    Unicode字符 

     

    1.object类型

        许多编程语言和类结构都提供了根类型,层次结构中的其他对象都从它派生而来。C#和.NET也不例外。在C#中,object类型就是最终的父类型,所有内在和用户定义的类型都从它派生而来。这是C#的一个重要特性,它把C#与VB和C++区分开来,但其行为与Java中的非常类似。所有的类型都隐含地最终派生于System.Object类,这样,object类型就可以用于两个目的:

    可以使用object引用绑定任何特定子类型的对象。例如,使用object类型把堆栈中的一个值对象装箱,再移动到堆中。对象引用也可以用于反射,此时,必须有代码来处理未知的特定类型对象。这类似于C++中的void指针或VB中的Variant数据类型。

    object类型执行许多基本的一般用途的方法,包括Equals()、GetHashCode()、GetType()和ToString()。用户定义的类可能需要使用一种面向对象技术--重写,提供其中一些方法的替代执行方法。例如,重写ToString()时,要给类提供一个方法,该方法可以提供类本身的字符串表示。如果类中没有提供这些方法的实现,编译器就会在对象中选择这些实现,它们在类中的执行不一定正确。

    2.string类型

        有C和C++字符串不过是一个字符数组,因此客户机程序员需要做很多工作,才能把一个字符串复制到另一个字符串上,或者连接两个字符串。实际上,对于一般的C++程序员来说,执行包装了这些操作细节的字符串类是一个非常头痛的耗时过程。VB可以使用string类型,Java中的String类在许多方面都类似于C#字符串。

        C#有string关键字,翻译为.NET类时,它就是System.String。

     

    输出结果:

    s1 is a String

    s2 is a String

    s1 is now another string

    s2 is new a String

    改变s1的值对s2没有影响,这与我们期待的引用类型正好相反。当用值 a String 初始化s1时,就在堆上分配了一个新的String对象。在初始化s2时,引用也指向这个对象。所以s2的值也是a string,但是当改变s1的值时,并不会替换原来的值,堆上为新分配一个对象。S2变量仍指向原来的对象,所以它的值没有改变

        string是一个引用类型,String对象保留在堆中,而不是堆栈中。因此当把一个字符串变量赋予另一个字符串时,会得到对内存中同一个字符串的两个引用,但是String与引用类型在常见的操作上有区别。字符串时不可以改变的,修改其中一个字符串,就会创建一个全新的String对象,另一个字符串不发生变化。例:

     

    class Program

        {

            static void Main(string[] args)

            {

                string s1 = "a String";

                string s2 = s1;

                Console.WriteLine("s1 is " + s1);

                Console.WriteLine("s2 is "+s2);

                s1 = "another string";

                Console.WriteLine("s1 is now "+s1);

                Console.WriteLine("s2 is new "+s2);

            }

        }

     

     

    开始bb=alist,此时bb为null,bb指向alist的内存地址,bb=new List<int>(),bb指向内存的地址改变了,不再和alist是同一个地址,

    所以bb无论怎么改alist依然为空。

    关于引用类型需要注意的地方。举例:

     

     一、class Program

        {

            static void Main(string[] args)

            {

                List<int> alist = null;

                List<int> bb = alist;

                bb = new List<int>();

                bb.Add(1);

                if (alist == null)

                {

                    Console.WriteLine("a list is null");

                }

                else

                {

                    Console.WriteLine("a list is not null");//结果a list is null

                }

            }

    }

    二、class Program

        {

            public class Student

            {

                public  Dictionary<int, string> dicValue;

                public void AddDic(Dictionary<int, string> dicValue)

                {

                    if (dicValue == null)

                    {

                        dicValue = new Dictionary<int, string>();

     

    输出结果:发现程序一直为null

    分析原因如下:

      AddDic方法中重新new了一次导致内存地址的指向发生了改变disValue已经和原来的内存地址指向不同了。解决方法:

    第一种方法:声明dicValue的时候将他初始化。

    第二种方法:在AddDic中的dicValue参数前加上outref,重新将地址指给原理的dicValue

                    }

     

                    for (int i = 0; i < 10; i++)

                    {

                        dicValue.Add(i, "ss" + i);

                    }

                }

            }

            static void Main(string[] args)

            {

                Student s = new Student();

                s.dicValue = null;

                s.AddDic(s.dicValue);

                Console.WriteLine(s.dicValue.Count);

            }

        } [C#]

    一个关于null赋值的问题

      class Program

        {

            static void Main(string[] args)

            {

                Children children = new Children();

                SetInstanceNull(children);

                if (children == null)

                {

                    Console.WriteLine("children is null");

                }

                else

                {

                    Console.WriteLine(children.inter);

                }

            }

            static void SetInstanceNull(Children childrenParam)

            {

                childrenParam.inter = 10;

                childrenParam = null;

            }

        }

        class Children

        {

            public int inter = 0;

    }

    程序输出结果:10 

    问题解析:我们知道方法参数如果是引用类型的话,则方法调用时,将把实例对象的地址传递给方法参数,这样在被调用方法中就可以通过实例对象的地址来操作实例对象的数据。故在SetInstanceNull方法中我们能将children实例中inter成员的值改更为10。然而childrenParam = null语句却没有使children为null,而仅仅是把childrenParam值为null。有人说children和chilrenParam是两个不同的变量,所以才有这样的结果。的确,这种原因的产生是因为他们是两个不同的变量导致的,但为什么不同呢?如果我们用object.ReferenceEquals方法去验证两个变量的相等性的话,我们会发现结果是相等的。那这个相等一定表示这两个变量相同吗?答案是否定的。在C#里面,当初始化一个类的时候,系统将使所有的引用引用类型参数引用为空,当遇到实例化一个类的时候,例如:new Children(),系统会在堆上分配一个内存空间存放Children实例,并将该地址返回给引用参数children。这种其实就是指针了。这样引用参数children与刚才实例化的Children实例就建立了一一映射关系。当调用方法SetInstanceNull时,系统将children参数的引用复制给childrenParam参数。这样在SetInstanceNull方法里面就可以操作刚才实例化的Children实例。所以Children实例中的inter成员能够被更改。childrenParam = null中语句只影响到childrenParam而没有影响到children给了我们一点提示,那就是将引用类型参数赋值为null其实是切断参数与实例之间的联系,当没有任何参数与该实例有联系的时候,该实例就会被垃圾回收器给回收。

    展开全文
  • Java引用类型有哪些

    万次阅读 2019-07-05 16:48:32
    其中,引用数据类型在存储堆中对需要引用的对象进行引用,引用是Java面向对象的一个特点,在Java入门中,我们会接触到四种Java的引用类型,接下来就说说这四种Java引用类型有哪些吧: 1、Java中有哪几种引用?...
  • Java 基本类型与引用类型

    千次阅读 多人点赞 2019-03-27 20:55:51
    Java 基本类型与引用类型 一、基本数据类型 java 中一共分为 8 种基本数据类型:byte、short、int、long、float、double、char、boolean, 其中 byte、short、int、long 是整型。float、double 是浮点型,char 是...
  • C#详解值类型和引用类型区别

    万次阅读 多人点赞 2016-04-20 17:59:42
    首先,什么是值类型,什么是引用类型? 在C#中值类型的变量直接存储数据,而引用类型的变量持有的是数据的引用,数据存储在数据堆中。 值类型(value type):byte,short,int,long,float,double,decimal,...
  • java基本类型与引用类型

    万次阅读 多人点赞 2018-01-04 17:30:32
    java基本类型与引用类型 目录 java基本类型与引用类型 目录 一基本数据类型 二引用类型 三基本类型与引用类型的区别 默认值 内存分配 自动装箱自动拆箱 自动装箱拆箱带来的问题 程序的性能 空指针异常 ...
  • 引用类型数组

    千次阅读 2019-03-11 19:48:26
    什么叫引用类型数组? 基本类型数组的元素中放的都是基本数据类型(int等)。 引用类型数组的元素中放的是从基本类型构造而来的类和其他复杂数据类型的地址。 两种类型数组的图解 基本类型数组:arr是引用,放在栈...
  • Python学习系列之值类型与引用类型

    千次阅读 多人点赞 2019-01-15 00:15:24
    Python中的值类型与引用类型 其实各个标准资料中没有说明Python有值类型和引用类型的分类,这个分类一般是C++和Java中的。但是语言是相通的,所以Python肯定也有类似的。实际上Python 的变量是没有类型的,这与以往...
  • js基本类型与引用类型

    千次阅读 多人点赞 2018-06-15 11:21:22
    引用类型: Object首先了解一个概念:栈内存与堆内存---这是两种不同的内存分配方法一般代码逻辑,简单变量,结构体都是放在栈中;而对象,以及被装箱的数据放在堆中栈内存中存放地址指向堆内存中的对象基本类型就是...
  • 【从零开始学习Go语言】Go语言的数组与切片引用类型与值类型一.数组二.多维数组三.切片四.值类型与引用类型 一.数组 go语言的数组在之前的一些例子中有引用过,go的数组在创建时需要声明存储数据的类型,长度,...
  • 【js中的基本类型和引用类型有哪些,有什么区别?】 每篇分享文从 【背景介绍】【知识剖析】【常见问题】【解决方案】【编码实战】【扩展思考】【更多讨论】【参考文献】 八个方面深度解析前端知识/技能,本篇...
  • 值类型和引用类型

    千次阅读 热门讨论 2019-01-02 20:18:16
    注:引用类型当声明一个类时,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其分配堆上的内存空间,当使用new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。...
  • unity中的值类型和引用类型

    千次阅读 2019-09-03 21:26:24
    一、值类型和引用类型概念 值(value)类型 :Unity中的vector3、quaternion是structs,它们是值。 引用(feference)类型 :Unity中的transform、gameobject是类类型,所以它们是引用。引用类型是对数据存储位置的...
  • 一,c#中的值类型和引用类型  众所周知在c#中有两种基本类型,它们分别是值类型和引用类型;而每种类型都可以细分为如下类型:    什么是值类型和引用类型 什么是值类型: 进一步研究文档,你会发现所有的...
  • golang 深入理解map引用类型

    千次阅读 2019-05-28 13:07:35
    指针是一种特殊的类型,指针变量的值是一个地址,该地址指向一个“指针类型”的变量 指针声明 var 指针变量名 *指针类型 示例 package main import( "fmt" ) func main() { var int_value = 10 var int_ptr = &...
  • 值类型和引用类型的区别

    千次阅读 2017-10-25 10:07:08
    一、定义引用类型表示你操作的数据是同一个,也就是说当你传一个参数给另一个方法时,你在另一个方法中改变这个变量的值,那么调用这个方法是传入的变量的值也将改变。值类型表示复制一个当前变量传给方法,当你在这...
  • Java 基本数据类型与引用类型 变量就是申请内存来存储值。也就是说,当创建变量的时候,需要在内存中申请空间。 内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来储存该类型数据。 因此,通过...
  • Java中引用类型的参数也是值传递

    千次阅读 2019-03-14 20:28:13
    Java的引用类型作为参数传递给方法的时候,发生的仅仅是将引用类型变量的值复制一份给方法,让方法中的参数变量也指向内存中的对象区域。
  • Java基本数据类型和引用类型的区别

    千次阅读 2020-08-27 23:30:52
    新语言Kotlin已经抛弃了Java里的基本数据类型,而是采用引用类型去声明一个变量,那么怎么区分基本数据类型和引用类型呢? 文字概念 基本数据类型:byte、short、int、long、float、double、char、boolean引用类型:...
  • C#中值类型与引用类型的区别

    千次阅读 2018-09-28 12:09:01
    值类型是直接存储一个数值,而引用类型是存储对值的引用,这两种类型分别存储在不用的内存区域。而从内存上看,值类型是在栈中的操作,而引用类型是在堆中的操作。值类型是具体的那个数值所占用的空间大小,而引用...
  •   JS1997年标准化以后,定义了六种内置类型,包括五种原始(Primitive)类型以及一种引用类型,ES6又新增了一种原始类型-----symbol,接下来咱们一一来分析下这七种内置类型: 六种原始类型 分别是: number string ...
  • 基本类型和引用类型区别

    千次阅读 2018-06-01 14:30:53
    1.基本类型和引用类型在内存中的保存 Java中数据类型分为两大类,基本类型和对象类型。相应的,变量也有两种类型:基本类型和引用类型。 基本类型的变量保存原始值,即它代表的值就是数值本身; 而引用类型的变量...
  • 形参的基本类型和引用类型的调用

    千次阅读 2019-06-10 17:29:24
    引用类型:形式参数的改变直接影响实际参数。 数据类型:基本数据类型,接口,数组,类。 形式参数是基本类型: class Demo{ public int sum(int a,int b){ return a + b; } } 形式参数是引用类型...
  • Java引用类型

    千次阅读 2016-03-16 19:12:55
    博主最近在整理Java集合框架时,在整理到WeakHashMap的时候,觉得有必要先阐述一下Java的引用类型,故此先整理的这篇文章,希望各位多提提意见。   闲话不多说,直接进入主题。Java中提供了4个级别的引用:强引用...
  • 浅谈java中的值类型和引用类型

    千次阅读 2018-07-23 11:19:22
    1、值类型 (1)也就是基本的数据类型分为:四类八种 (2)四类:整型、浮点型、字符型、布尔型 (3)八种:整型4种--...除了四类八种的基本数据类型之外,所有的类型都是引用类型(数组、字符串、类、接口等)...
  • Go语言中的值类型和引用类型

    千次阅读 2018-05-04 20:50:10
    一、值类型和引用类型值类型:int、float、bool和string这些类型都属于值类型,使用这些类型的变量直接指向存在内存中的值,值类型的变量的值存储在栈中。当使用等号=将一个变量的值赋给另一个变量时,如 j = i ,...
  • 基本类型,或者叫做内置类型,是JAVA中不同于类的特殊类型。它们是我们编程中使用最频繁的类型。java是一种强类型语言,第一次申明变量必须说明数据类型,第一次变量赋值称为变量的初始化。 1. Java的简单类型及其...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,962,210
精华内容 784,884
关键字:

引用类型