Java集合学习笔记(三)—— java数组的四种拷贝方式

1、循环赋值 —— 浅拷贝

速度相对比较慢

效率:System.arraycopy > clone > Arrays.copyOf > for循环
循环拷贝其实没什么好说的啦,就是用一个for循环进行元素的逐个拷贝,进行深拷贝或者浅复制这个大家可以自己把握。

	import java.util.Arrays;
	//for循环拷贝数组
	public class KaoBei2 {
	   public static void main(String[] args) {
	       //一维数组的拷贝
	        int[] array1={1,2,3,4,5,6,7};
	        int[] array2=new int[array1.length];
	        for(int i = 0;i < array1.length;i++){
	            array2[i] = array1[i];
	        }
	       System.out.println(Arrays.toString(array2));
	       
	       //二维数组的拷贝
	       int[][] array3= {{1,2,3},{4,5,6}};
	       int[][] array4 = new int[2][3];	
	        for(int i = 0;i < array3.length;i++){
	            for(int j = 0;j < array3[i].length;j++){
	                array4[i][j] = array3[i][j];
	            }
	        }
	      System.out.println(Arrays.deepToString(array4));
	    }
	    }

运行结果:在这里插入图片描述

2、System.arraycopy() —— 浅拷贝

JVM 提供的数组拷贝实现方式,浅拷贝,也就是说对于非基本类型而言,它拷贝的是对象的引用,而不是去新建一个新的对象。

	public static native void arraycopy(Object src, int srcPos, 
										Object dest, int destPos, 
										int length)

参数:
src - 源数组。
srcPos - 源数组中的起始位置。
dest - 目标数组。
destPos - 目标数据中的起始位置。
length - 要复制的数组元素的数量

通过源代码我们可以看到,关键字native说明它不是用java语言写的,而是底层用c或者c++实现的,因而速度会比较System.arraycopy为什么快

应用实例:

public class Main{
    public static void main(String[] args) {
         int[] a1={1,2,3,4,5,6};
         int[] a2={11,12,13,14,15,16};
         System.arraycopy(a1, 2, a2, 3, 2);
         System.out.print("copy后结果:");
         for(int i=0;i<a2.length;i++){
             System.out.print(a2[i]+" ");
         }
    }
}

运行结果:
在这里插入图片描述

3、Arrays.copyOf() —— 浅拷贝

Arrays.copyOf() 方法理解
有十种重载方法,复制指定的数组,返回原数组的副本。
用于复制指定的数组内容以达到扩容的目的,该方法对不同的基本数据类型都有对应的重载方法,详见 java api:
在这里插入图片描述
java.util.Arrays.Arrays.copyOf(源数组,新数组长度);
java.util.Arrays.copyOfRange(源数组,开始拷贝位置,结束拷贝位置);

本文只介绍其中的copyOf(U[] original, int newLength, Class<? extends T[]> newType)方法,基本类型的重载方法比该方法少一个 Class 类型的入参而已,该方法源码如下:

	/**
	 * @Description 复制指定的数组, 如有必要用 null 截取或填充,以使副本具有指定的长度
	 * 对于所有在原数组和副本中都有效的索引,这两个数组相同索引处将包含相同的值
	 * 对于在副本中有效而在原数组无效的所有索引,副本将填充 null,当且仅当指定长度大于原数组的长度时,这些索引存在
	 * 返回的数组属于 newType 类	 
	 * @param original 要复制的数组
	 * @param newLength 副本的长度
	 * @param newType 副本的类
	 * 
	 * @return T 原数组的副本,截取或用 null 填充以获得指定的长度
	 * @throws NegativeArraySizeException 如果 newLength 为负
	 * @throws NullPointerException 如果 original 为 null
	 * @throws ArrayStoreException 如果从 original 中复制的元素不属于存储在 newType 类数组中的运行时类型
	 */
	public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
	    T[] copy = ((Object)newType == (Object)Object[].class)
	        ? (T[]) new Object[newLength]
	        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
	    System.arraycopy(original, 0, copy, 0,
	                     Math.min(original.length, newLength));
	    return copy;
	}

从代码可知,数组拷贝时调用的是本地方法 System.arraycopy()
Arrays.copyOf()方法返回的数组是新的数组对象,原数组对象仍是原数组对象,不变,该拷贝不会影响原来的数组。所以也是浅拷贝。

4、clone() —— 可能深拷/浅拷贝

  • 1.java.lang.Object类的clone()的方法为protected类型(使用native关键字修饰),不可直接调用,我们完成克隆需要重写该方法。
  • 2.重写的clone方法一个要实现Cloneable接口。虽然这个接口并没有什么方法,但是必须实现该标志接口。 如果不实现将会在运行期间抛出:CloneNotSupportedException异常
  • 3.Object中本地clone()方法,默认是浅拷贝

首先被克隆的类实现Cloneable接口;然后在该类中覆盖clone()方法,并且在该clone()方法中调用super.clone();这样,super.clone()便可以调用java.lang.Object类的clone()方法。

//被克隆的类要实现Cloneable接口  
class Cat implements Cloneable
{
    private String name;
    private int age;
    public Cat(String name,int age)
    {
        this.name=name;
        this.age=age;
    }
    //重写clone()方法  
    protected Object clone()throws CloneNotSupportedException{
        return super.clone() ;
    }
}
public class Clone {
    public static void main(String[] args) throws CloneNotSupportedException {
        Cat cat1=new Cat("xiaohua",3);
        System.out.println(cat1);
        //调用clone方法  
        Cat cat2=(Cat)cat1.clone();
        System.out.println(cat2);
    }
}

java对象clone()方法
java中clone方法的理解(深拷贝、浅拷贝)

clone()比较特殊,对于对象而言,它是深拷贝,但是对于数组而言,它是浅拷贝。

对象拷贝(深拷贝)

首先讲一下对象的拷贝,它是深拷贝,大家可以用对象去测试一下。下面我们看一下它的源代码:

protected native Object clone() throws CloneNotSupportedException;

这里也有native关键字,所以也是底层实现的。
还要注意的是,这里修饰符是protected,也就是说,我们创建了一个Object类以后,是不能直接调用这个clone()方法的,因为protected关键字只允许同一个包内的类和它的子类调用,所以我们声明一个object类时,肯定不是同一个包内,所以就不能去调用它。

要调用这个方法,就需要我们写一个类,然后声明实现cloneable接口就好了,不需要去显示地声明继承于object,因为java中的类如果不显示说明父类的话,默认父类就是object。然后我们继承这个方法:

@Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

这里需要是,为了能够在不同包内去调用这个方法,我们需要把这个权限升级为public。现在我们就可以调用这个类的clone()方法去拷贝我们的类了。

数组拷贝(浅拷贝)

对于数组而言,它不是简单的将引用赋值为另外一个数组引用,而是创建一个新的数组。但是我们知道,对于数组本身而言,它它的元素是对象的时候,本来数组每个元素中保存的就是对象的引用,所以,拷贝过来的数组自然而言也是对象的引用,所以对于数组对象元素而言,它又是浅拷贝。我们用以下代码验证一下:

class Aby implements Cloneable{
    public int i;
    public Aby(int i) {
        this.i = i;
    }
    @Override
    public Object clone() throws CloneNotSupportedException {
        // TODO Auto-generated method stub
        return super.clone();
    }
}

public class Solution {

    public static void main(String[] args) throws CloneNotSupportedException {
        Aby aby1 = new Aby(1);
        Aby aby2 = (Aby) aby1.clone();
        aby1.i = 2;
        System.out.println(aby1.i); //2
        System.out.println(aby2.i); //1

        Aby[] arr = {aby1,aby2};

        Aby[] arr2 = arr.clone();
        arr2[0].i = 3;
        System.out.println(arr[0].i);   //3
        System.out.println(arr2[0].i);  //3
    }
}

5、深拷贝(Shallow Copy)与浅拷贝(Deep Copy)

深拷贝与浅拷贝详解

数据类型

数据分为基本数据类型(String, Number, Boolean, Null, Undefined,Symbol)和对象数据类型。

1、基本数据类型的特点:直接存储在栈(stack)中的数据

2、引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里

引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

图解

浅拷贝只是拷贝了源对象的地址,所以源对象的值发生变化时,拷贝对象的值也会发生变化。而深拷贝则是拷贝了源对象的所有值,所以即使源对象的值发生变化时,拷贝对象的值也不会改变。如下图描述:
示意图
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
举例:
深拷贝与浅拷贝

常见的几种深拷贝范式

Java如何对一个对象进行深拷贝?

  1. 构造函数方式
  2. 重写clone方式
  3. Apache Commons Lang序列化
  4. Gson序列化
  5. Jackson序列化

6、总结

  1. 在对象中赋值运算符 = 实现的是浅拷贝,只拷贝对象的引用值
  2. 递归是做复杂深拷贝比较合理的方法;
  3. JSON深拷贝只能是对象中没有function时可以使用
  4. 数组的深拷贝方法较多,但是大多是只能进行第一层的深拷贝
  5. 哪一种方法才是最好的呢?最简单的判断就是根据拷贝的类(包括其成员变量)是否提供了深拷贝的构造函数、是否实现了Cloneable接口、是否实现了Serializable接口、是否实现了默认的无参构造函数来进行选择。如果需要详细的考虑,则可以参考下面的表格:
    总结
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值