精华内容
下载资源
问答
  •   在将一个值赋给变量时,解析器必须确定这个值是基本类型值还是引用类型值,因为两种数据类型不仅存放的位置不同,访问的方式也不同。一、区分数据类型基本数据类型(简单数据类型):String,...

    每篇文章纯属个人经验观点,如有错误疏漏欢迎指正

      ECMAScript 变量可能包含两种不同数据类型的值:基本类型值和引用类型值。基本类型值指的是
    简单的数据段,而引用类型值指那些可能由多个值构成的对象。

      在将一个值赋给变量时,解析器必须确定这个值是基本类型值还是引用类型值,因为两种数据类型不仅存放的位置不同,访问的方式也不同。

    一、区分数据类型

    • 基本数据类型(简单数据类型):String,Number,Boolean,Undefined,Null,Symbol

    • 引用数据类型(复杂数据类型):Object

      引用数据类型只有一种 Object ,但 Object 是一个包罗万象的类型,像 Array,Function 等等不在其它六种简单数据类型之外的数据,都属于 Object

    二、栈和堆

      我们简单理解一下栈和堆的区别:

    • 栈:存放基本类型数据,系统会自动分配内存空间,由系统自动释放,占据固定大小的空间;

    • 堆:存放引用类型数据,系统会动态分配内存空间,系统不会自动释放,且占据的空间大小不定;

      在 JS 中,声明一个引用数据类型时,会在堆内存中保存一个对象,在栈中保存变量名和一个指针(地址),这个指针(地址)指向的是堆内存中对应的对象。

      指针(地址)指的是堆内存中的引用地址,通过这个引用地址可以快速查找到保存中堆内存中的对象。而由于 JS 不可以对堆内存进行直接访问和操作,又延伸出两种访问方式:

    • 按值访问:对于基本类型,可以直接操作保存在栈中的实际值;

    • 按引用访问:对于引用类型,通过栈中的引用地址连接到堆中对象,操作的是堆中的对象而不是栈中的地址。

    三、基本数据类型(简单数据类型)

      基本数据类型均为简单数据,这些原始类型的简单数据在内存中占据的空间是固定的,因此会被保存在栈中,这样存储便于迅速查寻变量的值,可以直接访问。

    var a = 1;var b = a;b = 2;console.log(a);    // 1console.log(b);    // 2

      在声明一个基本数据类型变量时,会直接在栈中的相应位置上创建一个值;如果从一个变量向另一个变量复制基本类型的值时,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。这表明两个变量的值是相互独立的,因此,这两个变量可以参与任何操作而不会相互影响。

      下图演示了上述代码在栈内存中的演化过程:

    4b0db1cb1e3b5694e1a0a676099c28c2.png

      因为基本数据类型都是简单数据段,它们的数据大小是固定的,所以会直接按值存放,并可以直接按值访问,而按值访问就代表着我们可以操作保存在变量中的实际的值。

    四、引用数据类型(复杂数据类型)

       引用数据类型都是 Object,其中包含了 Array,Function,Date 等,也就是说引用类型的内容实际上就是存放在堆内存中的对象,变量保存的是栈内存中一个指向堆内存中对应内容地址的指针。

       这是因为引用类型的数据大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。而引用地址的大小是固定的,所以把它存储在栈中对性能没有负面影响。

    var obj1 = {a: 1};var obj2 = obj1;obj2.a = 2;console.log(obj1);    // {a: 2}console.log(obj2);    // {a: 2}

      我们发现修改 arr2 后,arr1 发生了改变。这是因为这两个引用数据类型指向了同一个堆内存对象。obj1 复制给 obj2,实际上是将 obj1 这个堆内存对象在栈内存的引用地址复制了一份给了 obj2 ,而他们的引用地址共同指向同一个堆内存对象,所以修改 obj2 会导致 obj1 也发生改变。

       也就是说,引用类型在保存时,会在堆内存中保存这个对象,在栈内存中保存变量名和一个指向这个对象的引用地址。在进行复制操作时,复制的是栈内的引用地址,而不是堆内的对象,这将导致两个变量实际上将指向同一个对象。因此,改变其中一个变量,就会影响另一个变量。

      下图演示了上述代码在内存中的演化过程:

    b509c3d4cbb1ccb5a88c3a0074842fc0.png

      简单来说,引用类型的数据在复制时,复制的是栈中的引用地址,而非堆中储存的对象。而两者复制的引用地址指向的是同一个对象,这就导致了无论修改元数据还是复制后的数据,都会给二者造成同样的影响。

      我们再来尝试一下,将复杂数据中的简单数据提取出来:

    var arr1 = [1, 2, 3, 4, 5];var arr2 = arr1;var num1 = arr2[0];arr2[0] = 0;console.log(arr1);    // [0, 2, 3, 4, 5]console.log(arr2);    // [0, 2, 3, 4, 5]console.log(num1);    // 1

      我们修改 arr2 后,arr1 跟着发生了改变,但 num1 并没有发生变化。这是因为 arr2[0] 是一个基本数据类型,num1 复制的是 arr2 堆中对象里的一个基本数据类型,会将这个简单值直接复制到栈内,此时,无论对堆内对象进行任何更改,都不会影响到栈内的值。

    24afcc4c30351f0d34869c119c1bf587.png

    五、按值传递和按引用传递

    • 按值传递:在调用函数时将参数的值复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数;

    • 按引用传递:在调用函数时将参数的引用地址传递到函数中,操作的其实是堆内存中的对象,修改会影响到实际参数;

      按值传递示例:

    var num = 1;function fn(n){  n++;  console.log(n)}fn(num);        // 2console.log(num);    // 1

      我们在看一个例子:

    var obj1 = { a: 1 };function fn(obj) {  obj.b = 2;  console.log(obj);}fn(obj1);        // { a: 1, b: 2 };console.log(obj1);    // { a: 1, b: 2 };

      这里需要注意 JS 中没有按引用传递,所有的参数都是按值传递,但即使这个变量是按值传递的,引用数据类型的参数也会按引用来访问同一个对象。

      我们在上方示例中创建一个对象 obj1 并将其复制后的内容传递到函数 fn 中,然后在函数中为其添加了一个新属性 b ,在这个函数内部,obj 和 obj1 引用的是同一个对象,于是,当在函数内部为 obj 添加属性后,函数外部的 obj1 也发生了改变,因为 obj1 指向的对象在堆内存中只有一个,而且是全局对象。

      有很多开发人员错误地认为:在局部作用域中修改的对象会在全局作用域中反映出来,就说明参数是按引用传递的。为了证明对象是按值传递的,我们再看一看下面这个经过修改的例子:

    var obj1 = { a: 1 };function fn(obj) {  obj.b = 2;  obj = {    c: 3  };  console.log(obj);}fn(obj1)        // { c: 3 }console.log(obj1);    // { a: 1, b: 2 }

      这个示例中,在把 obj1 传递给 fn 后,函数为参数 obj 添加了一个新属性 b ,然后,又将一个全新的对象赋给变量 obj,同时赋予了它一个新属性 c 。

      如果 obj1 是按引用传递的,那么 obj1 就会指向这个新创建的包含属性 c 的对象。但是,当接下来再打印 obj1 时,显示的值仍然是具有 a,b 属性的对象,这说明即使在函数内部修改了参数的值,但原始的引用仍然保持未变。

      实际上,当在函数内部重写 obj 时,这个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后立即被销毁。

    六、总结

      JavaScript 变量可以用来保存两种类型的值:基本数据类型和引用数据类型,除了 Object 为引用数据类型外,其它数据均为基本数据类型。

    基本数据类型:

    • 基本类型是储存在栈中的简单数据段,占据固定大小的空间,系统会自动释放;

    • 基本类型的值可以直接访问到,也就是按值访问;

    • 基本类型的变量在复制时,会将原变量的值复制给新变量,但两个变量是完全独立的,只是值相同;

    • 基本类型作为参数时,会将原始值复制一份作为参数传递,参数和原始值互不影响,也就是按值传递;

    • 区分是否为基本类型数据可以使用 typeof 运算符来判断是 String,Number 等;

    引用数据类型:

    • 引用类型是储存在堆中的对象,栈内存储的是指向堆中对象的指针,内存空间的大小由系统分配,系统不会自动释放;

    • 引用类型的值需要根据栈中存储的路径去堆中查找相应的对象,也就是按引用访问;

    • 引用类型的变量复制时,会将原变量的指针复制给新变量,两个变量指向的是堆中的同一个对象,修改时会互相影响;

    • 引用类型作为参数时,会将引用路径复制一份作为参数传递,因此两个变量最终都指向同一个对象,但这是按值传递而非按引用传递;

    • 区分引用数据的类型可以使用 instanceof 操作符来判断是 Array,Function 等;


    感谢大家的观看及支持,我们下篇博客再见!如果有问题需要老王帮忙或者想看关于某个主题的文章,也可以通过留言等方式来联系老王。
    每篇文章纯属个人经验观点,如有错误疏漏欢迎指正。转载请附带作者信息及出处。您的评论和关注是我更新的动力!
    点击查看原文跳转到 博客          点下再看,少个BUGc8a2a0cf6f8294b8d7c2436af1fc93b9.png
    展开全文
  • 基本数据类型变量获得存储单元的方式是静态的,声明了变量后系统就为变量分配了存储单元,就可以对变量赋值。 2、变量的赋值方式不同:  数组变量的引用赋值,数组变量保存的是数组的引用,(即数组占用的一片...

    1、存储单元分配方式不同:

      数组变量指向的是某一连续的内存的空间地址;

          基本数据类型指向某一内存地址;基本数据类型变量获得存储单元的方式是静态的,声明了变量后系统就为变量分配了存储单元,就可以对变量赋值。

    2、变量的赋值方式不同:

       数组变量的引用赋值,数组变量保存的是数组的引用,(即数组占用的一片连续存储空间的首地址及长度特性)当声明一个数字变量而未申请空间时,变量是未初始化的,没有地址及特性值。只有申请了存储空间,才能以下标表示数组元素。 

     

    转载于:https://www.cnblogs.com/sxudk/p/3336714.html

    展开全文
  • 在C#中值类型的变量直接存储数据,而引用类型的变量持有是数据的引用,数据...在C#中每种类型的存储方式有两种:1)分配在托管栈中;2)分配在托管堆中; 内存分配有CLR管理(即公共语言运行时),这两种方法


    在C#中值类型的变量直接存储数据,而引用类型的变量持有的是数据的引用,数据存储在数据堆中。

    常见的值类型数据有:整值型(整形,浮点型,十进制型),布尔类型,枚举类型;

    引用类型有:接口,数组,Object类型,类,委托,字符串,null类型。

    在C#中每种类型的存储方式有两种:1)分配在托管栈中;2)分配在托管堆中;

    内存的分配有CLR管理(即公共语言运行时),这两种方法的区别是:

    1)分配在托管栈中的变量会在创建它们的方法返回时自动释放,例如在一个方法中声明Char型的变量UserInput=C,当实例化它的方法结束时,UserInput变量在栈上占用的内存就会自动释放;

    2)分配在托管堆中的变量并不会在创建它们的方法结束时释放内存,它们所占用的内存会被CLR中的垃圾回收机制释放。

    看下面的代码:

    1 static void Main(string[] args)
    2         {
    3             //当nStudent声明并赋值是,这时在托管栈上就会开辟一块内存来存储nStudent的值,当实例化nStudent的Main()方法结束时,
    4             //nStudent在托管栈上占用的内存会自动释放。
    5             int nStudent = 0;
    6             //当声明strStuName时,这个时候“小明”存储在托管堆中,而托管栈中存储的是strStuName指向的引用。
    7             string strStuName = "小明";

    9             Console.WriteLine("学生的总数是{0},五号的名字是{1}", nStudent, strStuName);
    10             Console.ReadKey();
    11         }

    装箱和拆箱

    当值类型的数据转换成引用类型时,CLR会先在托管堆配置一块内存,将值类型的数据复制到这块内存,然后再让托管栈上的引用类型的变量指向这块内存,这样的过程称为装箱。相反的话,有引用类型转换成值类型的话就称为拆箱。

    一般情况下,.NET会主动的帮我们完成装箱操作,但是拆箱并非主动,我们必须知道拆箱对象的实力类型,然后明确的去执行拆箱操作。

    1 int BirthdayNum = 1989;
    2             object BoxBirthdayNum = BirthdayNum;//系统自动装箱
    3             int nBirthdayNum = (int)BoxBirthdayNum;//明确数据类型的拆箱

    因为花费了更多的时间,所以装箱和拆箱对程序的性能有一定的影响。

    --------------------------------------------------------------------------------------------------------------------------------------

    类型推断

    在C#中有两种类型的数据,一种是值类型,另一种是引用类型。

    值类型包括:内置值类型、用户自定义值类型、和枚举,如 int,float bool 等,以及struct等。

    引用类型包括接口类型、用户自定义的类、委托等。如 string 、DateTime、数组等。

    值类型是存储在堆栈中,而引用类型是存储在托管堆上,C#程序首先被编译成IL程序,然后在托管执行。值类型直接从堆栈中里面取值,而引用类型必须要先从堆栈里面取出它的地址,再根据这个地址在堆里找到对应的值。


    值类型与饮用类型的本质区别有以下几点:

    1.内存分配: 值类型是分配在栈中的;而引用类型是分配在堆中。

    2.效率: 值类型效率高,不需要地址转换;引用类型效率较低,需要进行地址转换。

    3.内存回收: 值类型使用完后立即回收;引用类型使用完后不立即回收,而是交给GC处理回收。

    4.赋值操作: 值类型是创建一个新对象;引用类型创建一个引用。

    5.类型扩展: 值类型不易扩展,所有值类型都是密封(seal)的,所以无法派生出新的值类型;引用类型具有多态的特性方便扩展。

    这是别人的总结,我在这里拿来用下。

    下面我在说说它们在用法上的区别了,C#之所以要分这两种数据类型的原因是达到更好的性能,把一些基本类型如int、bool规定为值类型,而把包含许多字段的较大类型规定为引用类型,如用户自定义的类。值类型主要是负责存储数据,引用类更多是用在代码的重用性上。

    从C#3.0开始,C#引入了一个隐式类型推断的关键字var,编译器可以通过它的初始值来判断变量的具体类型。var只能用于局部变量的声明,不能用于字段级的变量声明。使用var关键字时,var必须得有初始值,这样编译器才能判断是否是真实变量。

    1 class Program
    2     {
    3         static void Main(string[] args)
    4         {
    5             var i = 10;//隐式类型
    6             int m = 10;//显示类型

    8             var Program=new Program();
    9             Program.nAge = 20;
    10             Program.SayHello();
    11         }
    12 
    13         private int nAge;
    14         public void SayHello()
    15         {
    16             var message = "my age is {0}";
    17             Console.WriteLine(message, nAge);
    18         }
    19     }

    message初始值的变量为字符串类型,因此编译器可以推断其类型为String类型。

    转载链接:http://www.cr173.com/html/17724_1.html


    展开全文
  • 实现方式有哪些? 浅克隆(Shadow Clone)是把原型对象中成员变量为值类型的属性都复制给克隆对象,把原型对象中成员变量为引用类型的引用地址也复制给克隆对象,也就是原型对象中如果有成员变量为引用对象,则...

    深克隆和浅克隆有什么区别?它的实现方式有哪些?

    浅克隆(Shadow Clone)是把原型对象中成员变量为值类型的属性都复制给克隆对象,把原型对象中成员变量为引用类型的引用地址也复制给克隆对象,也就是原型对象中如果有成员变量为引用对象,则此引用对象的地址是共享给原型对象和克隆对象的

    简单来说就是浅克隆只会复制原型对象,但不会复制它所引用的对象,如下图所示

    深克隆(Deep Clone)是将原型对象中的所有类型,无论是值类型还是引用类型,都复制一份给克隆对象,也就是说深克隆会把原型对象和原型对象所引用的对象,都复制一份给克隆对象,如下图所示

    在 Java 语言中要实现克隆则需要实现 Cloneable 接口,并重写 Object 类中的 clone() 方法,实现代码如下

    public class CloneExample {
        public static void main(String[] args) throws CloneNotSupportedException {
            // 创建被赋值对象
            People p1 = new People();
            p1.setId(1);
            p1.setName("Java");
            // 克隆 p1 对象
            People p2 = (People) p1.clone();
            // 打印名称
            System.out.println("p2:" + p2.getName());
        }
        static class People implements Cloneable {
            // 属性
            private Integer id;
            private String name;
            /**
             * 重写 clone 方法
             * @throws CloneNotSupportedException
             */
            @Override
            protected Object clone() throws CloneNotSupportedException {
                return super.clone();
            }
            public Integer getId() {
                return id;
            }
            public void setId(Integer id) {
                this.id = id;
            }
            public String getName() {
                return name;
            }
            public void setName(String name) {
                this.name = name;
            }
        }
    }

    以上程序执行的结果为:p2:Java

     

    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 {@code x}, the expression:
     * <blockquote>
     * <pre>
     * x.clone() != x</pre></blockquote>
     * will be true, and that the expression:
     * <blockquote>
     * <pre>
     * x.clone().getClass() == x.getClass()</pre></blockquote>
     * will be {@code true}, but these are not absolute requirements.
     * While it is typically the case that:
     * <blockquote>
     * <pre>
     * x.clone().equals(x)</pre></blockquote>
     * will be {@code true}, this is not an absolute requirement.
     * <p>
     * By convention, the returned object should be obtained by calling
     * {@code super.clone}.  If a class and all of its superclasses (except
     * {@code Object}) obey this convention, it will be the case that
     * {@code x.clone().getClass() == x.getClass()}.
     * <p>
     * By convention, the object returned by this method should be independent
     * of this object (which is being cloned).  To achieve this independence,
     * it may be necessary to modify one or more fields of the object returned
     * by {@code super.clone} before returning it.  Typically, this means
     * copying any mutable objects that comprise the internal "deep structure"
     * of the object being cloned and replacing the references to these
     * objects with references to the copies.  If a class contains only
     * primitive fields or references to immutable objects, then it is usually
     * the case that no fields in the object returned by {@code super.clone}
     * need to be modified.
     * <p>
     * ......
     */
    protected native Object clone() throws CloneNotSupportedException;

    从源码的注释信息中我们可以看出,Object 对 clone() 方法的约定有三条

    • 对于所有对象来说,x.clone() !=x 应当返回 true,因为克隆对象与原对象不是同一个对象

    • 对于所有对象来说,x.clone().getClass() == x.getClass() 应当返回 true,因为克隆对象与原对象的类型是一样的

    • 对于所有对象来说,x.clone().equals(x) 应当返回 true,因为使用 equals 比较时,它们的值都是相同的

    看 clone() 的实现方法,发现 clone() 是使用 native 修饰的本地方法,因此执行的性能会很高,并且它返回的类型为 Object,因此在调用克隆之后要把对象强转为目标类型才行

     

    Arrays.copyOf()分析

    如果是数组类型,我们可以直接使用 Arrays.copyOf() 来实现克隆,实现代码如下

    People[] o1 = {new People(1, "Java")};
    People[] o2 = Arrays.copyOf(o1, o1.length);
    // 修改原型对象的第一个元素的值
    o1[0].setName("Jdk");
    System.out.println("o1:" + o1[0].getName());
    System.out.println("o2:" + o2[0].getName());

    以上程序的执行结果为

    o1:Jdk
    o2:Jdk

    从结果可以看出,我们在修改克隆对象的第一个元素之后,原型对象的第一个元素也跟着被修改了,这说明 Arrays.copyOf() 其实是一个浅克隆

    因为数组比较特殊,数组本身就是引用类型,因此在使用 Arrays.copyOf() 其实只是把引用地址复制了一份给克隆对象,如果修改了它的引用对象,那么指向它的(引用地址)所有对象都会发生改变,因此看到的结果是,修改了克隆对象的第一个元素,原型对象也跟着被修改了

     

    深克隆实现方式有几种?

    深克隆的实现方式有很多种,大体可以分为以下几类

    • 所有对象都实现克隆方法;

    • 通过构造方法实现深克隆;

    • 使用 JDK 自带的字节流实现深克隆;

    • 使用第三方工具实现深克隆,比如 Apache Commons Lang

    • 使用 JSON 工具类实现深克隆,比如 Gson、FastJSON 等

    接下来分别来实现以上这些方式,在开始之前先定义一个公共的用户类,代码如下

    /**
     * 用户类
     */
    public class People {
        private Integer id;
        private String name;
        private Address address; // 包含 Address 引用对象
        // 忽略构造方法、set、get 方法
    }
    
    /**
     * 地址类
     */
    public class Address {
        private Integer id;
        private String city;
        // 忽略构造方法、set、get 方法
    }

    1.所有对象都实现克隆

    这种方式我们需要修改 People 和 Address 类,让它们都实现 Cloneable 的接口,让所有的引用对象都实现克隆,从而实现 People 类的深克隆,代码如下

    public class CloneExample {
        public static void main(String[] args) throws CloneNotSupportedException {
              // 创建被赋值对象
              Address address = new Address(110, "北京");
              People p1 = new People(1, "Java", address);
              // 克隆 p1 对象
              People p2 = p1.clone();
              // 修改原型对象
              p1.getAddress().setCity("西安");
              // 输出 p1 和 p2 地址信息
              System.out.println("p1:" + p1.getAddress().getCity() +
                      " p2:" + p2.getAddress().getCity());
        }
        /**
         * 用户类
         */
        static class People implements Cloneable {
            private Integer id;
            private String name;
            private Address address;
            /**
             * 重写 clone 方法
             * @throws CloneNotSupportedException
             */
            @Override
            protected People clone() throws CloneNotSupportedException {
                People people = (People) super.clone();
                people.setAddress(this.address.clone()); // 引用类型克隆赋值
                return people;
            }
            // 忽略构造方法、set、get 方法
        }
        /**
         * 地址类
         */
        static class Address implements Cloneable {
            private Integer id;
            private String city;
            /**
             * 重写 clone 方法
             * @throws CloneNotSupportedException
             */
            @Override
            protected Address clone() throws CloneNotSupportedException {
                return (Address) super.clone();
            }
            // 忽略构造方法、set、get 方法
        }
    }

    以上程序的执行结果为

    p1:西安 p2:北京

    从结果可以看出,当我们修改了原型对象的引用属性之后,并没有影响克隆对象,这说明此对象已经实现了深克隆

    2.通过构造方法实现深克隆

    《Effective Java》中推荐使用构造器(Copy Constructor)来实现深克隆,如果构造器的参数为基本数据类型或字符串类型则直接赋值,如果是对象类型,则需要重新 new 一个对象,实现代码如下

    public class SecondExample {
        public static void main(String[] args) throws CloneNotSupportedException {
            // 创建对象
            Address address = new Address(110, "北京");
            People p1 = new People(1, "Java", address);
    
            // 调用构造函数克隆对象
            People p2 = new People(p1.getId(), p1.getName(),
                    new Address(p1.getAddress().getId(), p1.getAddress().getCity()));
    
            // 修改原型对象
            p1.getAddress().setCity("西安");
    
            // 输出 p1 和 p2 地址信息
            System.out.println("p1:" + p1.getAddress().getCity() +
                    " p2:" + p2.getAddress().getCity());
        }
    
        /**
         * 用户类
         */
        static class People {
            private Integer id;
            private String name;
            private Address address;
            // 忽略构造方法、set、get 方法
        }
    
        /**
         * 地址类
         */
        static class Address {
            private Integer id;
            private String city;
            // 忽略构造方法、set、get 方法
        }
    }

    以上程序的执行结果为

    p1:西安 p2:北京

    从结果可以看出,当我们修改了原型对象的引用属性之后,并没有影响克隆对象,这说明此对象已经实现了深克隆

    3.通过字节流实现深克隆

    通过 JDK 自带的字节流实现深克隆的方式,是先将要原型对象写入到内存中的字节流,然后再从这个字节流中读出刚刚存储的信息,来作为一个新的对象返回,那么这个新对象和原型对象就不存在任何地址上的共享,这样就实现了深克隆,代码如下

    import java.io.*;
    
    public class ThirdExample {
        public static void main(String[] args) throws CloneNotSupportedException {
            // 创建对象
            Address address = new Address(110, "北京");
            People p1 = new People(1, "Java", address);
    
            // 通过字节流实现克隆
            People p2 = (People) StreamClone.clone(p1);
    
            // 修改原型对象
            p1.getAddress().setCity("西安");
    
            // 输出 p1 和 p2 地址信息
            System.out.println("p1:" + p1.getAddress().getCity() +
                    " p2:" + p2.getAddress().getCity());
        }
    
        /**
         * 通过字节流实现克隆
         */
        static class StreamClone {
            public static <T extends Serializable> T clone(People obj) {
                T cloneObj = null;
                try {
                    // 写入字节流
                    ByteArrayOutputStream bo = new ByteArrayOutputStream();
                    ObjectOutputStream oos = new ObjectOutputStream(bo);
                    oos.writeObject(obj);
                    oos.close();
                    // 分配内存,写入原始对象,生成新对象
                    ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());//获取上面的输出字节流
                    ObjectInputStream oi = new ObjectInputStream(bi);
                    // 返回生成的新对象
                    cloneObj = (T) oi.readObject();
                    oi.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return cloneObj;
            }
        }
    
        /**
         * 用户类
         */
        static class People implements Serializable {
            private Integer id;
            private String name;
            private Address address;
            // 忽略构造方法、set、get 方法
        }
    
        /**
         * 地址类
         */
        static class Address implements Serializable {
            private Integer id;
            private String city;
            // 忽略构造方法、set、get 方法
        }
    }

    以上程序的执行结果为

    p1:西安 p2:北京

    此方式需要注意的是,由于是通过字节流序列化实现的深克隆,因此每个对象必须能被序列化,必须实现 Serializable 接口,标识自己可以被序列化,否则会抛出异常 (java.io.NotSerializableException)

    4.通过第三方工具实现深克隆

    使用 Apache Commons Lang 来实现深克隆,实现代码如下

    import org.apache.commons.lang3.SerializationUtils;
    
    import java.io.Serializable;
    
    /**
     * 深克隆实现方式四:通过 apache.commons.lang 实现
     */
    public class FourthExample {
        public static void main(String[] args) throws CloneNotSupportedException {
            // 创建对象
            Address address = new Address(110, "北京");
            People p1 = new People(1, "Java", address);
    
            // 调用 apache.commons.lang 克隆对象
            People p2 = (People) SerializationUtils.clone(p1);
    
            // 修改原型对象
            p1.getAddress().setCity("西安");
    
            // 输出 p1 和 p2 地址信息
            System.out.println("p1:" + p1.getAddress().getCity() +
                    " p2:" + p2.getAddress().getCity());
        }
    
        /**
         * 用户类
         */
        static class People implements Serializable {
            private Integer id;
            private String name;
            private Address address;
            // 忽略构造方法、set、get 方法
        }
    
        /**
         * 地址类
         */
        static class Address implements Serializable {
            private Integer id;
            private String city;
            // 忽略构造方法、set、get 方法
        }
    }

    以上程序的执行结果为

    p1:西安 p2:北京

    可以看出此方法和第三种实现方式类似,都需要实现 Serializable 接口,都是通过字节流的方式实现的,只不过这种实现方式是第三方提供了现成的方法,让我们可以直接调用

    5.通过 JSON 工具类实现深克隆

    使用 Google 提供的 JSON 转化工具 Gson 来实现,其他 JSON 转化工具类也是类似的,实现代码如下

    import com.google.gson.Gson;
    
    /**
     * 深克隆实现方式五:通过 JSON 工具实现
     */
    public class FifthExample {
        public static void main(String[] args) throws CloneNotSupportedException {
            // 创建对象
            Address address = new Address(110, "北京");
            People p1 = new People(1, "Java", address);
    
            // 调用 Gson 克隆对象
            Gson gson = new Gson();
            People p2 = gson.fromJson(gson.toJson(p1), People.class);
    
            // 修改原型对象
            p1.getAddress().setCity("西安");
    
            // 输出 p1 和 p2 地址信息
            System.out.println("p1:" + p1.getAddress().getCity() +
                    " p2:" + p2.getAddress().getCity());
        }
    
        /**
         * 用户类
         */
        static class People {
            private Integer id;
            private String name;
            private Address address;
            // 忽略构造方法、set、get 方法
        }
    
        /**
         * 地址类
         */
        static class Address {
            private Integer id;
            private String city;
            // 忽略构造方法、set、get 方法
        }
    }

    以上程序的执行结果为

    p1:西安 p2:北京

    使用 JSON 工具类会先把对象转化成字符串,再从字符串转化成新的对象,因为新对象是从字符串转化而来的,因此不会和原型对象有任何的关联,这样就实现了深克隆,其他类似的 JSON 工具类实现方式也是一样的

     

    克隆设计理念猜想

    对于克隆为什么要这样设计,官方没有直接给出答案,Java 中实现克隆需要两个主要的步骤,一是 实现 Cloneable 空接口,二是重写 Object 的 clone() 方法再调用父类的克隆方法 (super.clone())那为什么要这么做?

    从源码中可以看出 Cloneable 接口诞生的比较早,JDK 1.0 就已经存在了,因此从那个时候就已经有克隆方法了,那我们怎么来标识一个类级别对象拥有克隆方法呢?克隆虽然重要,但我们不能给每个类都默认加上克隆,这显然是不合适的,那我们能使用的手段就只有这几个了

    • 在类上新增标识,此标识用于声明某个类拥有克隆的功能,像 final 关键字一样;

    • 使用 Java 中的注解;

    • 实现某个接口;

    • 继承某个类

    以上方法对比分析:先说第一个,为了一个重要但不常用的克隆功能, 单独新增一个类标识,这显然不合适;再说第二个,因为克隆功能出现的比较早,那时候还没有注解功能,因此也不能使用;第三点基本满足我们的需求,第四点和第一点比较类似,为了一个克隆功能需要牺牲一个基类,并且 Java 只能单继承,因此这个方案也不合适。采用排除法,无疑使用实现接口的方式是那时最合理的方案了,而且在 Java 语言中一个类可以实现多个接口

     

    那为什么要在 Object 中添加一个 clone() 方法呢?

    因为 clone() 方法语义的特殊性,因此最好能有 JVM 的直接支持,既然要 JVM 直接支持,就要找一个 API 来把这个方法暴露出来才行,最直接的做法就是把它放入到一个所有类的基类 Object 中,这样所有类就可以很方便地调用到了

    展开全文
  • js数据类型有哪些

    2020-08-31 17:02:37
    JavaScript一共8种数据类型: 基本数据类型:Undefined、...直接存储在栈中,占据空间小、大小固定,属于希望被频繁引用的数据。 引用数据类型 同时存在栈和堆中,占据空间大,大小不固定。引用数据类型在栈中存放
  • 基本数据类型都储存在栈中(stack),栈是结构的,每个区块都是按照后进先出的方式次序存放,基本类型的数据相对是比较稳定的,占的内存也比较小,所有寻找速度比较快,如果基本类型复制的话,栈中重新开辟个新的...
  • Spring自动装配Bean的方式有哪些

    千次阅读 2019-04-30 10:46:42
    no:默认值 表示没有自动装配,应使用显示bean引用进行装配 byName:根据bean名称进行注入 byType:根据类型进行注入 构造函数:通过构造函数来注入依赖项 需要设置大量参数 autodetect:容器首先通过使用autowire...
  • 浅克隆(Shadow Clone)是把原型对象中成员变量为值类型的属性都复制给克隆对象,把原型对象中成员变量为引用类型的引用地址也复制给克隆对象,也就是原型对象中如果成员变量为引用对象,则此引用对象地址是共享...
  • 使用克隆可以为我们快速地构建出一个已对象的副本,它属于 Java 基础的一部分,也是面试中常被问到的知识点之一。...简单来说就是浅克隆只会复制原型对象,但不会复制它所引用的对象,如下图所示: 深克隆(D
  • 使用克隆可以为我们快速地构建出一个已对象的副本,它属于 Java 基础的一部分,也是面试中常被问到的知识点之一。...简单来说就是浅克隆只会复制原型对象,但不会复制它所引用的对象,如下图所示: 深
  • 首先, python的传参方式叫做 共享传参 (call by object),其实就是引用的副本我们知道,的语言函数的参数,传递的是引用,就是指向这个对象的指针, 的语言传递的是参数的拷贝值。但是python不一样,它传递的是...
  • 浅拷贝是指创建一个对象,这个对象有着原始对象属性值一份精确拷贝。如果属性是基本类型,那么拷贝就是基本类型的值,如果属性是引用类型,那么拷贝就是内存地址...2.2 深拷贝和浅拷贝实现方式分别有哪些? ...
  • 1、JavaScript值类型引用类型有哪些 (1)值类型(基本类型):数值(number)、布尔值(boolean)、null、undefined、string(在赋值传递中会以引用类型的方式来处理)。 (2)引用类型:对象、数组、函数。 2、如何...
  • JS数据类型分为基本数据类型引用数据类型,基本数据类型保存是值,引用类型保存引用地址(this指针)。浅拷贝共用一个引用地址,深拷贝会创建新内存地址。 浅拷贝方法 直接对象复制 Object.assign ...
  • 原型链继承基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。 function SuperType() { this.name = 'Yvette'; this.colors = ['pink', 'blue', 'green']; } SuperType.prototype.getName =.....
  • 5.创建对象的方式有哪些? 6.对象属性名的点表示法和方括号表示法 7.三种基本包装类型 二、概念 JS是弱类型语言,所谓弱类型只是表明该语言在表达式运算中不强制校验运算元的数据类型,而并不表明该语言是否具有...
  • 一、JavaScript值类型引用类型有哪些(1)值类型:数值、布尔值、null、undefined、string(字符串在赋值运算中会按引用类型的方式来处理)。(2)引用类型:对象、数组、函数。 值类型一般是一些固定的字节...
  • 目前主流访问方式有通过句柄和直接指针两种方式。 1.句柄访问 使用句柄访问方式,Java堆将会划分出来一部分内存去来作为句柄池,reference中存储就是对象句柄地址。而句柄中则包含对象实例数据地址和
  • MySQL EXPLAIN 语句 SELECT TYPE有哪些

    千次阅读 2016-05-07 15:19:10
    询中每个select子句的类型,提供了各种表示table列引用的使用方式类型。 (1)SIMPLE 简单的SELECT语句(不包括UNION操作或子查询操作) (2)PRIMARY/UNION PRIMARY:查询中最外层的SELECT(如两表...
  • 展开全部询中每个select子句的类型,提供了各32313133353236313431303231363533e78988e69d8331333363393663种表示table列引用的使用方式类型。(1)SIMPLE简单的SELECT语句(不包括UNION操作或子查询操作)(2)PRIMARY/...
  • 实现多态方法是动态绑定(DynamicBinding),动态绑定指是在执行期间判断所引用对象实际类型,根据其实际的类型调用其相应方法。 在Java语言中,Override(覆盖、重写)是实现多态关键技术,在子类中定义与...
  • Rust中,常会用到3种指针有哪些

    千次阅读 2021-01-31 14:47:12
    如果我们讨论中没有包含指针,那么关于内存管理介绍是不完整,因为它是任何低级语言操作内存主要方式。指针只是指向进程地址空间中内存位置变量。在Rust中,我们主要会用到3种指针。 5.8.1 引用—— ...
  • 2.Map集合以key和value(键值对)的方式存储数据。 key和value都是引用数据类型,存储的都是对象的内存地址。 key起到主导的地位,value是key的一个附属品。 3.Map集合中key是唯一的,即所有键值对的key部分都不相同...
  • 1.先介绍下数据类型有哪些: 基础数据类型包括:string,number,boolean,undefined,null 引用数据类型包含:Object,Array,Date 2.再来介绍下存储方式: 栈(操作系统):由操作系统自动分配释放 ,存放函数参数...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 442
精华内容 176
关键字:

引用的方式有哪些类型