【java提高】---细则(3)

HashSet 与TreeSet和LinkedHashSet的区别

         今天项目开发,需要通过两个条件去查询数据库数据,同时只要满足一个条件就可以取出这个对象。所以通过取出的数据肯定会有重复,所以要去掉重复项。

 如果用list集合接收两次的返回对象,那么肯定是有重复对象在list集合中,一开始我想到的是TreeSet,但知道TreeSet存放对象,一定要重写compareto方法,进行排序规则。

而我仅仅是去重,并不需要排序。 所以我就用了HashSet,下面也就缕一缕有关set集合的一些知识点。

 

一、Set接口

      1.Set不允许包含相同的元素,如果试图把两个相同元素加入同一个集合中,add方法返回false。

      2.Set判断两个对象相同不是使用==运算符,而是根据equals方法。也就是说,只要两个对象用equals方法比较返回true,Set就不会接受这两个对象。

      HashSet与TreeSet都是基于Set接口的实现类。其中TreeSet是Set的子接口SortedSet的实现类。Set接口及其子接口、实现类的结构如下所示:

               |——SortedSet接口——TreeSet实现类

Set接口——|——HashSet实现类                

                |——LinkedHashSet实现类

 

    二、HashSet

     1.不能保证元素的排列顺序,顺序有可能发生变化

      2. 不是同步的

      3. 集合元素可以是null,但只能放入一个null

      当向HashSet结合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置。

 简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值相等

       注意,如果要把一个对象放入HashSet中,重写该对象对应类的equals方法,也应该重写其hashCode()方法。其规则是如果两个对象通过equals方法比较返回true时,

其   hashCode也应该相同。另外,对象中用作equals比较标准的属性,都应该用来计算 hashCode的值。

 

三、TreeSet

       TreeSet类型是J2SE中唯一可实现自动排序的类型

        TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。向  TreeSet中加入的应该是同一个类的对象。

        TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0

自然排序

       自然排序使用要排序元素的CompareTo(Object obj)方法来比较元素之间大小关系,然后将元素按照升序排列。

      Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现了该接口的对象就可以比较大小。

      obj1.compareTo(obj2)方法如果返回0,则说明被比较的两个对象相等,如果返回一个正数,则表明obj1大于obj2,如果是 负数,则表明obj1小于obj2。

      如果我们将两个对象的equals方法总是返回true,则这两个对象的compareTo方法返回应该返回0
定制排序

      自然排序是根据集合元素的大小,以升序排列,如果要定制排序,应该使用Comparator接口,实现 int compare(To1,To2)方法

 

 四、LinkedHashSet

   LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起 来像是以插入顺 序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。

     LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。

queue集合

什么是Queue集合?

答:Queue用于模拟队列这种数据结构。队列通常是指“先进先出(FIFO)”的容器。队列的头部保存在队列中存放时间最长的元素,尾部保存存放时间最短的元素。

新元素插入到队列的尾部,取出元素会返回队列头部的元素。通常,队列不允许随机访问队列中的元素。

 一、认识queue

1、Queue 方法介绍

     从上面来看,Queue(队列)接口继承自Collection,用来表示内部元素具有先后顺序的集合。除了基本的集合操作外,队列还提供了其他插入、删除和检查操作。Queue接口定义如下:

复制代码
public interface Queue<E> extends Collection<E> {
        E element();
        boolean offer(E e);
        E peek();
        E poll();
        E remove();
  }
复制代码

       每一个队列相关方法都提供了两种形式:一种如果操作失败抛出异常,另一种如果操作失败返回一个特殊值(null或false)。

 Queue接口结构如下图所示:

操作

抛出异常

返回特殊值

插入

add(e)

offer(e)

移除

remove()

poll()

检查

element()

peek()

 

 

 

 

 

 

 

(1).add(E), offer(E) 在尾部添加:

       他们的共同之处是建议实现类禁止添加 null 元素,否则会报空指针 NullPointerException;

不同之处在于 add() 方法在添加失败(比如队列已满)时会报 一些运行时错误 错;而 offer() 方法即使在添加失败时也不会奔溃,只会返回 false。

(2)remove(), poll() 删除并返回头部

       当队列为空时 remove() 方法会报 NoSuchElementException 错; 而 poll() 不会奔溃,只会返回 null。

(3)element(), peek() 获取但不删除

      当队列为空时 element() 抛出异常;peek() 不会奔溃,只会返回 null。

2、其它

(1)虽然 LinkedList 没有禁止添加 null,但是一般情况下 Queue 的实现类都不允许添加 null 元素,因为 poll(), peek() 方法在异常的时候会返回 null,你添加了 null 以后,当获取时不好分辨究竟是否正确返回。

(2)Queue 一般都是 FIFO 的,但是也有例外,比如优先队列 priority queue(它的顺序是根据自然排序或者自定义 comparator 的);再比如 LIFO 的队列(跟栈一样,后来进去的先出去)。

(3)不论进入、出去的先后顺序是怎样的,使用 remove(),poll() 方法操作的都是 头部 的元素;而插入的位置则不一定是在队尾了,不同的 queue 会有不同的插入逻辑。

 

 二、PriorityQueue实现类

        PriorityQueue是一个比较标准的队列实现类。PriorityQueue保存队列元素的顺序并不是按加入队列的顺序,而是按队列元素的大小进行重新排列。因此当调用peek()方法或者poll()方法取出队

列中的元素时,并不是取出最先进入队列的元素,而是取出队列中最小的元素。

1、PriorityQueue的排序方式

  PriorityQueue中的元素可以默认自然排序(也就是数字默认是小的在队列头,字符串则按字典序排列)或者通过提供的Comparator(比较器)在队列实例化时指定的排序方式。

(1)小案例

复制代码
PriorityQueue<Integer> qi = new PriorityQueue<Integer>();
        qi.offer(5);
        qi.offer(2);
        qi.offer(1);
        qi.offer(10);
        qi.offer(3);
        while (!qi.isEmpty()){
          System.out.print(qi.poll() + ",");
        }
        System.out.println();
        //采用降序排列的方式,越小的越排在队尾
        Comparator<Integer> cmp = new Comparator<Integer>() {
          public int compare(Integer e1, Integer e2) {
            return e2 - e1;
          }
        };
        //这里是初始容量3,当我们超过3个会自动扩容,所以说它是个无边界容器
        PriorityQueue<Integer> q2 = new PriorityQueue<Integer>(3,cmp);
        q2.offer(2);
        q2.offer(8);
        q2.offer(9);
        q2.offer(1);
        while (!q2.isEmpty()){
              System.out.print(q2.poll() + ",");
            }
复制代码

输出结果:

由此可以看出,默认情况下PriorityQueue采用自然排序。指定Comparator的情况下,PriorityQueue采用指定的排序方式。

注意:当PriorityQueue中没有指定Comparator时,加入PriorityQueue的元素必须实现了Comparable接口(即元素是可比较的),否则会导致 ClassCastException。

(比如你放入的是对象,那么必须指定Comparator,因为对象是无法比较的)

2、 PriorityQueue特性 

   (1)队列元素根据自然排序或者根据具体的比较器排序

   (2)实例化时若未指定初始容量,默认容量为11

   (3)自动扩容。如果容量小于64,两倍增长扩容;否则增长50%

   (4)无边界容器

   (5)不支持null元素

   (6)非线程安全

   (7)支持被序列化

   (8)入队出队的时间复杂度O(log(n))

有关Dueue接口与ArrayDeque实现类和LinkedList实现类以后用到了再写他们。

现在只要知道:

(1)Deque接口是Queue接口的子接口,它代表一个双端队列。

(2)LinkedList是List接口的实现类,因此它可以是一个集合,可以根据索引来随机访问集合中的元素。此外,它还是Duque接口的实现类,因此也可以作为一个双端队列,或者栈来使用。

java反射机制

 

一、概述

1、什么是反射机制

       反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

2、反射机制能做什么

反射机制主要提供了以下功能:

        1) 在运行时判断任意一个对象所属的类;

        2) 在运行时构造任意一个类的对象;

        3) 在运行时判断任意一个类所具有的成员变量和方法;

        4) 在运行时调用任意一个对象的方法;

        5) 生成动态代理。

3、反射的优点和缺点

为什么要用反射机制?直接创建对象不就可以了吗,这就涉及到了动态与静态的概念:

        静态编译:在编译时确定类型,绑定对象,即通过。

        动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性。

一句话,反射机制的优点就是可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中它的灵活性就表现的十分明显。

比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如 这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能的更新,

而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功 能。 

它的缺点是对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它 满足我们的要求。这类操作总是慢于只直接执行相同的操作。 

4、反射的原理

        JAVA语言编译之后会生成一个.class文件,这些 Class 对象承载了这个类型的父类、接口、构造函数、方法、属性等原始信息,这些 class 文件在程序运行时会被 ClassLoader 加载到虚拟机中。

反射就是通过字节码文件找到某一个类、类中的方法以及属性等。

5、反射的应用场景

个人觉得使用反射机制的一些地方:

      1) 工厂模式:Factory类中用反射的话,添加了一个新的类之后,就不需要再修改工厂类Factory了

      2) 动态代理模式

      3) 数据库JDBC中通过Class.forName(Driver).来获得数据库连接驱动

      4) 分析类文件:毕竟能得到类中的方法等等

 

二、反射常用方法

利用反射机制能获得类的所有信息 ,类中有什么信息,它就可以获得什么信息。

反射的实现主要借助以下四个类

       Class:类的对象

       Constructor:类的构造方法

       Field:类中的属性对象

       Method:类中的方法对象

我们知道所有类的对象其实都是Class的实例,所以要获得对象,首先要获得class实例

1、获得class实例三种方法

Class<?> demo1=Class.forName("com.jincou.study.Demo"); //一般推荐这种
Class<?> demo2=new Demo().getClass();
Class<?> demo3=Demo.class;

获得class对象后,我们就可以通过class对象获得实际对象

Demo obj=(Demo)demo1.newInstance();//创建对象的实例,这里需要一个无参的构造函数 

OK,有了对象就什么都好办了,想要什么信息就有什么信息了。

2、获得构造函数

Constructor getConstructor(Class[] params)//根据指定参数获得public构造器
Constructor[] getConstructors()           //获得public的所有构造器
Constructor getDeclaredConstructor(Class[] params)//根据指定参数获得public和非public的构造器
Constructor[] getDeclaredConstructors()   //获得public的所有构造器

3、获得类方法

Method getMethod(String name, Class[] params)         //根据方法名,参数类型获得方法
Method[] getMethods()         //获得所有的public方法
Method getDeclaredMethod(String name, Class[] params) //根据方法名和参数类型,获得public和非public的方法
Method[] getDeclaredMethods() //获得所以的public和非public方法 

4、获得类中属性

Field getField(String name)  //根据变量名得到相应的public变量
Field[] getFields()          //获得类中所以public的方法
Field getDeclaredField(String name)//根据方法名获得public和非public变量
Field[] getDeclaredFields()  //获得类中所有的public和非public方法

 

三、综合小案例

上面的都是讲解理论,主要是日后可以当做自己的查找的API来用,下面来一个综合小案例来加深对它的理解:

复制代码
package com.jincou.study;

import java.lang.reflect.Constructor;  
import java.lang.reflect.Field;  
import java.lang.reflect.Method;  

public class Main {  
    /** 
     * 为了看清楚Java反射部分代码,所有异常我都最后抛出来给虚拟机处理! 
     */  
    public static void main(String[] args) throws Exception {  

        System.out.println("Demo1===============================================");    
        //Demo1.  通过Java反射机制得到类的包名和类名  
        Demo1();  
        
        System.out.println("Demo2===============================================");  
        //Demo2.  验证所有的类都是Class类的实例对象  
        Demo2();  
       
        System.out.println("Demo3===============================================");     
        //Demo3.  通过Java反射机制,用Class 创建类对象[这也就是反射存在的意义所在],无参构造  
        Demo3();  
       
        System.out.println("Demo4===============================================");  
        //Demo4:  通过Java反射机制得到一个类的构造函数,并实现构造带参实例对象  
        Demo4();  
          
        System.out.println("Demo5===============================================");    
        //Demo5:  通过Java反射机制操作成员变量, set 和 get  
        Demo5();  
        
        System.out.println("Demo6===============================================");     
        //Demo6: 通过Java反射机制得到类的一些属性: 继承的接口,父类,函数信息,成员信息,类型等  
        Demo6();  
        
        System.out.println("Demo7===============================================");    
        //Demo7: 通过Java反射机制调用类中方法  
        Demo7();  
       
        System.out.println("Demo8===============================================");   
        //Demo8: 通过Java反射机制获得类加载器  
        Demo8();  
                    
    }  
      
    /** 
     * Demo1: 通过Java反射机制得到类的包名和类名 
     */  
    public static void Demo1()  
    {  
        Person person = new Person();  
        System.out.println("包名:" + person.getClass().getPackage().getName());  
        System.out.println("完整类名:" + person.getClass().getName()); 
    }  
      
    /** 
     * Demo2: 验证所有的类都是Class类的实例对象 
     */  
    public static void Demo2() throws ClassNotFoundException  
    {  
        //定义两个类型都未知的Class , 设置初值为null, 看看如何给它们赋值成Person类  
        Class<?> class1 = null;  
        Class<?> class2 = null;  
          
        //写法1, 可能抛出 ClassNotFoundException [多用这个写法]  
        class1 = Class.forName("com.jincou.study.Person");  
        System.out.println("(写法1) 包名: " + class1.getPackage().getName() + ","   
                + "完整类名: " + class1.getName());  
          
        //写法2  
        class2 = Person.class;  
        System.out.println("(写法2) 包名: " + class2.getPackage().getName() + ","   
                + "完整类名: " + class2.getName());  
    }  
      
    /** 
     * Demo3: 通过Java反射机制,用Class 创建类对象[这也就是反射存在的意义所在]   
     */  
    public static void Demo3() throws Exception 
    {  
        Class<?> class1 = null;  
        class1 = Class.forName("com.jincou.study.Person");  
        //由于这里不能带参数,所以你要实例化的这个类Person,一定要有无参构造函数哈~  
        Person person = (Person) class1.newInstance();  
        person.setAge(20);  
        person.setName("张三");  
        System.out.println("name: " + person.getName() + " age: " + person.getAge());  
    }  
      
    /** 
     * Demo4: 通过Java反射机制得到一个类的构造函数,并实现创建带参实例对象  
     */  
    public static void Demo4() throws Exception  
    {  
        Class<?> class1 = null;  
        Person person1 = null;  
        Person person2 = null;  
          
        class1 = Class.forName("com.jincou.study.Person");  
        //得到一系列构造函数集合  
        Constructor<?>[] constructors = class1.getConstructors();           
        //无参构造函数
        person1 = (Person) constructors[0].newInstance();  
        person1.setAge(30);  
        person1.setName("李四");
        //有参构造函数
        person2 = (Person) constructors[1].newInstance(20,"王五");  
          
        System.out.println("name: " + person1.getName() + " age: " + person1.getAge() );  
        System.out.println("name: " + person2.getName() + " age: " + person2.getAge() );     
    }  
      
    /** 
     * Demo5: 通过Java反射机制操作成员变量, set 和 get 
     */  
    public static void Demo5() throws Exception
    {  
        Class<?> class1 = null;  
        class1 = Class.forName("com.jincou.study.Person");  
        Object obj = class1.newInstance();  
        
        //获取com.jincou.study.Person.name的属性名
        Field personNameField = class1.getDeclaredField("name");  
        personNameField.setAccessible(true);  
        personNameField.set(obj, "张三");  
                   
        System.out.println("修改属性之后得到属性变量的值:" + personNameField.get(obj));        
    }  
      
    /** 
     * Demo6: 通过Java反射机制得到类的一些属性: 继承的接口,父类,函数信息,成员信息,类型等  
     */  
    public static void Demo6() throws ClassNotFoundException  
    {  
        Class<?> class1 = null;  
        class1 = Class.forName("com.jincou.study.SuperMan");  
          
        //取得父类名称  
        Class<?>  superClass = class1.getSuperclass();  
        System.out.println("SuperMan类的父类名: " + superClass.getName());  
          
        //取得属性信息         
        Field[] fields = class1.getDeclaredFields();  
        for (int i = 0; i < fields.length; i++) {  
            System.out.println("类中的成员: " + fields[i]);  
        }  
            
        //取得类方法  
        Method[] methods = class1.getDeclaredMethods();  
       
       // 取得SuperMan类的方法
            System.out.println("函数名:" + methods[0].getName());   
            System.out.println("函数代码写法: " + methods[0]);  
                      
        //取得类实现的接口,因为接口类也属于Class,所以得到接口中的方法也是一样的方法得到哈  
        Class<?> interfaces[] = class1.getInterfaces();  
        for (int i = 0; i < interfaces.length; i++) {  
            System.out.println("实现的接口类名: " + interfaces[i].getName() );  
        }  
          
    }  
      
    /** 
     * Demo7: 通过Java反射机制调用类方法 
     */  
    public static void Demo7() throws Exception  
    {  
        Class<?> class1 = null;  
        class1 = Class.forName("com.jincou.study.SuperMan");  
          
       //调用fly()方法
        Method method = class1.getMethod("fly");  
        method.invoke(class1.newInstance());  

        //调用walk(int m)方法  
        method = class1.getMethod("walk",int.class);  
        method.invoke(class1.newInstance(),100);  
    }  
      
    /** 
     * Demo8: 通过Java反射机制得到类加载器信息 
     */  
    public static void Demo8() throws ClassNotFoundException  
    {  
        Class<?> class1 = null;  
        class1 = Class.forName("com.jincou.study.SuperMan");  
        String nameString = class1.getClassLoader().getClass().getName();  
          
        System.out.println("类加载器类名: " + nameString);  
    }  
                
}  
/**
 * Person类 
 */  
class  Person{  
    private int age;  
    private String name;  
    public Person(){  
          
    }  
    public Person(int age, String name){  
        this.age = age;  
        this.name = name;  
    }  
  
    public int getAge() {  
        return age;  
    }  
    public void setAge(int age) {  
        this.age = age;  
    }  
    public String getName() {  
        return name;  
    }  
    public void setName(String name) {  
        this.name = name;  
    }  
}  
 
 //SuperMan类
class SuperMan extends Person implements ActionInterface  
{  
    private boolean BlueBriefs;  
      
    public void fly()  
    {  
        System.out.println("我是fly方法......");  
    }  
      
    public boolean isBlueBriefs() {  
        return BlueBriefs;  
    }  
    public void setBlueBriefs(boolean blueBriefs) {  
        BlueBriefs = blueBriefs;  
    }  
  
    @Override  
    public void walk(int m) {  
        System.out.println("我是walk方法,我的int值为:" + m );  
    }  
} 
//ActionInterface接口
interface ActionInterface{  
    public void walk(int m);  
}  
复制代码

查看运行结果:

 

java深浅拷贝

一、前言

为什么会有深浅拷贝这个概念?

我觉得主要跟JVM内存分配有关,对于基本数据类型,只存在栈内存,所以它的拷贝不存在深浅拷贝这个概念。而对于对象而言,一个对象的创建会在内存中分配两块空间,一个在栈

内存存对象的引用指针,一个在堆内存存放对象。这个时候会有一个问题,你拷贝的只是这个引用指针还是拷贝两块内存一起拷贝,这个时候就会有深浅拷贝一说。

还有之前我认为Arrays.copyOf()是深度拷贝,亲测后发现原来它也是浅拷贝。下面进行具体说明。

 

二、数据类型

数据分为基本数据类型(int, boolean, double, byte, char等)和对象数据类型。
基本数据类型的特点:直接存储在栈(stack)中的数据.
引用数据类型的特点:在栈内存存储对象引用,真实的数据存放在堆内存里
引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

 

三、什么是浅拷贝和深拷贝

首先需要明白,深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。那先来看看浅拷贝和深拷贝的概念。

在 Java 中,除了基本数据类型(元类型)之外,还存在 类的实例对象 这个引用数据类型。而一般使用 =号做赋值操作的时候。对于基本数据类型,实际上是拷贝的它的值,

但是对于对象而言,其实赋值的只是这个对象的引用,将原对象的引用传递过去,他们实际上还是指向的同一个对象。

浅拷贝:如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有真实的创建一个新的对象。

深拷贝:在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量。

深拷贝和浅拷贝的示意图大致如下:

 

具体接下来代码演示。

 

四、代码演示

1、浅拷贝

Person

public class Person {
    public String name;
    public Integer age;
    public String sex;
    /**
     * 提供get和set方法和全参构造函数
     */
}

Test

    public static void main(String[] args) throws Exception {
        Person person = new Person("小小",3,"女");
        //将person值赋值给person1
        Person person1 = person;
        System.out.println(person);
        System.out.println(person1);
        person1.setName("小小她爸");
        System.out.println("person 中 name为:"+person.getName());
        System.out.println("person1 中 name为:"+person.getName());
    }

查看运行结果

从图片中我们可以很明显看出,它们指向的内存地址是一致的,同样我改变person1的属性值时发现person的属性值也改变了。

说明:对于对象用 "=" 赋值 其实只是引用指针的复制,这两个引用还是指向同一个对象。

2、深拷贝

如果要实现深拷贝就会比较复杂点

Student

/**
 * 如果对象要实现深拷贝 那么实体需要做两步
 * 1、实体实现Cloneable接口
 * 2、重写 clone()方法
 */
public class Student implements Cloneable {

    public String name;
    public Integer age;
    public String sex;
    //这也是个实体
    public Address address;
    /**
     * 提供get和set方法和全参构造函数
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

Test

    public static void main(String[] args) throws Exception {
        Student student = new Student("小小", 3, "女", null);
        //将person值赋值给person1
        Student student1 = (Student) student.clone();
        System.out.println(student);
        System.out.println(student1);
        student1.setName("小小她爸");
        System.out.println("person 中 name为:" + student.getName());
        System.out.println("person1 中 name为:" + student1.getName());
        }

这里可以已经是两个不同的对象了。但是这里需要注意的是,如果对象中含有对象,这个对象还是浅拷贝。

Address

public class Address  {
    public String  city;
    public  int phone;
    /**
     * 提供get和set方法和全参构造函数
     */
    }

Test

    public static void main(String[] args) throws Exception {
        Address address = new Address("杭州", 1888888888);
        Student student2 = new Student("小小", 3, "女", address);
        //将person值赋值给person1
        Student student3 = (Student) student2.clone();
        address.setCity("北京天安门");
        System.out.println("person2 中 city为:" + student2.getAddress().getCity());
        System.out.println("person3 中 city为:" + student3.getAddress().getCity());

    }

我们发现虽然Student是实现了深拷贝,但Address却还是浅拷贝,那如何让Adress也实现深拷贝呢。
Address修改

public class Address implements Cloneable {
    public String  city;
    public  int phone;
   /**
     * 提供get和set方法和全参构造函数
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

Student修改

 //修改clone方法
   @Override
    protected Object clone() throws CloneNotSupportedException {
        Student s = (Student) super.clone();
        s.address = (Address) address.clone();
        return s;
    }

弊端: 这里我们Person 类只有一个 Address 引用类型,而 Address 类没有,所以我们只用重写 Address 类的clone 方法,但是如果 Address 类也存在一个引用类型,
那么我们也要重写其clone 方法,这样下去,有多少个引用类型,我们就要重写多少次,如果存在很多引用类型,那么代码量显然会很大,所以这种方法不太合适。
所以还有另一种实现深拷贝方法。

序列化实现深拷贝

//序列化实现深拷贝
public Object deepClone() throws Exception{
    // 序列化
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(this);
    // 反序列化
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);
    return ois.readObject();
}
 //因为序列化产生的是两个完全独立的对象,所有无论嵌套多少个引用类型,序列化都是能实现深拷贝的。

 

五、Arrays.copyOf()

之前我误以为Arrays.copyOf()为深拷贝,那只是因为我用的是基本数据类型作为数组,而基本数据类型上面已经说过它没有深浅拷贝这个概念,可以把他理解成只有深拷贝。

 public static void main(String[] args) {

        //1、基本数据类型
        int[] a = {0, 1, 2, 3};
        // Arrays.copyOf拷贝
        int[] copy = Arrays.copyOf(a, a.length);
        a[0] = 1;
        System.out.println(Arrays.toString(copy));
        System.out.println(Arrays.toString(a));

        //2、对象数组
        Student[]  stuArr = {new Student("小小", 3, "女"),new Student("小小爸", 29, "男"),new Student("小小妈", 27, "女")};
        // Arrays.copyOf拷贝
        Student[] copyStuArr = Arrays.copyOf(stuArr, stuArr.length);
        copyStuArr[0].setName("小小爷爷");
        System.out.println(Arrays.toString(stuArr));
        System.out.println(Arrays.toString(copyStuArr));
        

    }

运行结果:

 

posted @ 2022-02-25 16:29  hanease  阅读(36)  评论(0编辑  收藏  举报