• 猿创征文 | 【Java进阶】详解抽象类及常用接口


    目录

    一、抽象类

    二、接口

    三、Object类

    3.1 toString()方法

    3.2 hashcode()方法

    3.3 equals()方法

    四、常用接口

    4.1 Comparable接口(比较)

    4.2 Comparator接口(比较)

    4.3 Cloneable接口(拷贝)

    4.4 浅拷贝与深拷贝


    一、抽象类

    在Java中,一个类如果被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法不用给出具体的实现体。

    1. public class TestDemo {
    2.    public static void main(String[] args){
    3.        Circle c = new Circle();
    4.        c.setR(5);
    5.        c.getArea();
    6.        Squre s = new Squre();
    7.        s.setLength(10);
    8.        s.getArea();
    9.   }
    10. }
    11. //抽象类
    12. abstract class Shape{
    13.    private int size;
    14.    //抽象方法
    15.    abstract public void getArea();
    16. }
    17. class Circle extends Shape{
    18.    private int r;
    19.    public int getR() {
    20.        return r;
    21.   }
    22.    public void setR(int r) {
    23.        this.r = r;
    24.   }
    25.    //重写抽象方法
    26.    @Override
    27.    public void getArea() {
    28.        double  area = r*r*r*4.0/3;
    29.        System.out.println("此圆形的面积是: "+area);
    30.   }
    31. }
    32. class Squre extends Shape{
    33.    private int length;
    34.    //重写抽象方法
    35.    @Override
    36.    public void getArea() {
    37.        double area = length*length;
    38.        System.out.println("此正方形的面积是: "+area);
    39.   }
    40.    public int getLength() {
    41.        return length;
    42.   }
    43.    public void setLength(int length) {
    44.        this.length = length;
    45.   }
    46. }
    • 抽象类的特性

      • 抽象类中使用abstract修饰类和抽象方法,这个方法没有具体的实现,抽象类中可以包含普通类所能包含的成员,抽象类所存在的最大意义就是被继承。

      • 抽象类方法不能是私有的,如果一个普通类继承了抽象类,那么必须重写抽象类中的抽象方法,不能被static和final修饰,因为抽象方法要被子类继承。

      • 抽象类中不一定包含抽象方法,但是包含抽象方法的一定是抽象类,抽象类之间的相互继承不需要重写抽象方法。

    二、接口

    • 接口的定义

    接口的定义格式与定义类的格式基本相同,将class关键字换成 interface 关键字,就定义了一个接口。

    • 接口的使用

    1. //接口的定义
    2. interface USB {
    3.   void openDevice();
    4.   void closeDevice();
    5. }
    6. //实现接口
    7. class Mouse implements USB {
    8.   @Override
    9.   public void openDevice() {
    10.       System.out.println("打开鼠标");
    11.   }
    12.   @Override
    13.   public void closeDevice() {
    14.       System.out.println("关闭鼠标");
    15.   }
    16.   public void click(){
    17.       System.out.println("鼠标点击");
    18.   }
    19. }
    20. //实现接口
    21. class KeyBoard implements USB{
    22. //实现接口中的抽象类
    23.   @Override
    24.   public void openDevice() {
    25.       System.out.println("打开键盘");
    26.   }
    27.   @Override
    28.   public void closeDevice() {
    29.       System.out.println("关闭键盘");
    30.   }
    31.   public void inPut(){
    32.       System.out.println("键盘输入");
    33.   }
    34. }
    • 注意事项

      • ❗ 接口不能够直接使用,必须有一个类来实现接口,并实现接口中的所有抽象方法

      c69c97f426be0da6ebdc9a2a85c9f632.png

      • ❗ 子类和父类之间是extends 继承关系,类与接口之间是 implements 实现关系

      • 接口中每一个方法都是public的抽象方法, 即接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错。

      afd73d68710c3f557a1a5e54f7e2b355.png

      • ❗ 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现 ,JDK1.8 开始允许有可以实现的方法,但这个方法只能是default 修饰的,类在实现该接口时,不需要重写该默认方法。

      具体作用: 当我们进行业务扩展时,需要在接口中新增方法。如果新增的这个方法写成普通方法的话,那么需要在该接口所有的实现类中都重写这个方法。如果新增的方法定义为default类型,就不需要在所有的实现类中全部重写该default方法,哪个实现类需要新增该方法,就在哪个类中进行实现

      802a6ec868cc15d52502b778c62dc25c.png

      • 重写接口中方法时,不能使用default访问权限修饰

      • ace1ea2ca219c444abd0e61dd3742f59.png

      • 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量

      • 接口中不能有静态代码块和构造方法

      • 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class

      454fd4f42c7ea05495d4304bbddbd5cc.png

      • 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类

      1d8ec01265da82cf30dccdf035f06327.png

    • 实现多个接口

      • 一个类实现多个接口

      1. interface USB {
      2.    void openDevice();
      3.    void closeDevice();
      4. }
      5. interface ULine{
      6.    void lineInsert();
      7. }
      8. class Mouse implements USB,ULine{
      9.    @Override
      10.    public void openDevice() {
      11.        System.out.println("打开鼠标");
      12.   }
      13.    @Override
      14.    public void closeDevice() {
      15.        System.out.println("关闭鼠标");
      16.   }
      17.    @Override
      18.    public void lineInsert() {
      19.        System.out.println("插入鼠标线");
      20.   }
      21. }

      一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类

      • 一个类继承一个父类,同时实现多个接口

      1. public class TestDemo3 {
      2.    public static void main(String[] args) {
      3.        Duck duck = new Duck("yaya");
      4.        walk(duck);
      5.        Brid brid = new Brid("gugu");
      6.        walk(brid);
      7.   }
      8.    public static void walk(IRunning running) {
      9.        System.out.println("去散步");
      10.        running.run();
      11.   }
      12. }
      13. class Animal {
      14.        protected String name;
      15.        public Animal(String name) {
      16.            this.name = name;
      17.       }
      18. }
      19. interface IFlying {
      20.        void fly();
      21. }
      22. interface IRunning {
      23.      void run();
      24. }
      25. interface ISwimming {
      26.        void swim();
      27. }
      28. class Duck extends Animal implements IFlying,IRunning,ISwimming{
      29.    public Duck(String name) {
      30.        super(name);
      31.   }
      32.    @Override
      33.    public void fly() {
      34.        System.out.println("飞飞飞");
      35.   }
      36.    @Override
      37.    public void run() {
      38.        System.out.println("鸭子嘎嘎跑");
      39.   }
      40.    @Override
      41.    public void swim() {
      42.        System.out.println("游游游");
      43.   }
      44. }
      45. class Brid extends Animal implements IRunning,ISwimming,IFlying{
      46.    public Brid(String name) {
      47.        super(name);
      48.   }
      49.    @Override
      50.    public void  fly() {
      51.        System.out.println("鸟儿飞");
      52.   }
      53.    @Override
      54.    public void run() {
      55.        System.out.println("鸟儿跑");
      56.   }
      57.    @Override
      58.    public void swim() {
      59.        System.out.println("鸟儿游");
      60.   }
      61. }
      • 接口中的多态

      1. public class TestDemo3 {
      2.    public static void main(String[] args) {
      3.        Duck duck = new Duck("yaya");
      4.        walk(duck);
      5.        Brid brid = new Brid("gugu");
      6.        walk(brid);
      7.   }
      8.    public static void walk(IRunning running) {
      9.        System.out.println("去散步");
      10.        running.run();
      11.   }
      12. }

      有了接口之后, 类的使用者就不必关注具体类型,而只关注某个类是否具备某种能力.

    • 接口之间的继承

    一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的。接口可以继承一个接口, 达到复用的效果. 使用 extends 关键字

    1. interface IRing {
    2.   void run();
    3. }
    4. interface ISing {
    5.   void swim();
    6. }
    7. interface IAmphibious extends IRunning, ISwimming {}
    8. class Frog implements IAmphibious {
    9.   @Override
    10.   public void run() {
    11.       System.out.println("跑啊跑");
    12.   }
    13.   @Override
    14.   public void swim() {
    15.       System.out.println("游啊游");
    16.   }
    17. }

    接口间的继承相当于把多个接口合并在一起.

    ✅抽象类和接口的区别??

     区别抽象类(abstract)接口(interface)
    1结构组成普通类+抽象方法抽象方法+全局变量
    2权限各种权限public
    3子类使用使用extends关键字继承抽象类使用implements关键字实现接口
    4关系一个抽象类可以实现若干接口接口不能继承抽象类,但是可以使用extends关键字继承多个接口
    5子类限制一个子类只能继承一个抽象类一个子类可以实现多个接口

    三、Object类

    Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类。即所有类的对象都可以使用Object的引用进行接收。

    1. public class TestDemo5 {
    2.    public static void main(String[] args) {
    3.        function(new Person());
    4.        function(new Student());
    5.   }
    6.    public static void function(Object obj){
    7.        System.out.println(obj);
    8.   }
    9. }
    10. class Person{
    11.    private int age;
    12.    private String name;
    13. }
    14. class Student{
    15.    private int grade;
    16.    private String sno;
    17. }

    Object类中提供的一些默认方法

    3.1 toString()方法

    1. //Object类中的toString()方法实现:
    2. public String toString() {
    3.     return getClass().getName() + "@" + Integer.toHexString(hashCode());
    4. }

    toString()方法一般需要通过重写之后进行使用。

    3.2 hashcode()方法

    • 返回对象的hash代码值

    源码:

    public native int hashCode();

    46a38887ba443f94286814e504c27066.png

    重写hashCode() 方法

    1. class Per{
    2.    public String name;
    3.    public int age;
    4.    public Per(String name, int age) {
    5.        this.name = name;
    6.        this.age = age;
    7.   }
    8.    @Override
    9.    public int hashCode() {
    10.        return Objects.hash(name, age);
    11.   }
    12. }
    13. public class TestDemo6 {
    14.    public static void main(String[] args) {
    15.        Per per1 = new Per("gaobo",20);
    16.        Per per2 = new Per("gaobo", 20);
    17.        System.out.println(per1.hashCode());
    18.       /*
    19.        注意事项:哈希值一样。
    20.        结论:
    21.        1、hashcode方法用来确定对象在内存中存储的位置是否相同
    22.        2、事实上hashCode() 在散列表中才有用,在其它情况下没用。
    23.        在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。
    24.        */
    25.        System.out.println(per2.hashCode());
    26.   }
    27. }

    0c88a1ae1c0ecaa09f90799fc0697f80.png

    3.3 equals()方法

    • 比较的是地址

    1. // Object类中的equals方法
    2. public boolean equals(Object obj){
    3.     return (this == obj);
    4.   // 使用引用中的地址直接来进行比较
    5.  
    6. }

    3650809c54d776d0063333edf4772969.png

    ✅如果要比较对象中内容,必须重写Object中的equals方法,因为equals方法默认也是按照地址比较的

    重写equals()方法

    1. @Override
    2. public boolean equals(Object obj) {
    3.     //判断是否为空
    4.        if (obj == null) {
    5.            return false ;
    6.       } if(this == obj) {
    7.            return true ;
    8.       }
    9.        // 不是Person类对象
    10.        if (!(obj instanceof Per)) {
    11.            return false ;
    12.       }
    13.        Per per = (Per) obj ; // 向下转型,比较属性值
    14.        return this.name.equals(per.name) && this.age==per.age ;
    15.   }
    16.    
    17. /* @Override
    18.    public boolean equals(Object obj) {
    19.        Per per = (Per)obj;
    20.        //String类调用的是自身的equals,
    21.        // s1跟s2两者比较的规则则是按照String类重写后的equals方法来比较,
    22.        //很显然,String类的比较规则是按照值来比较的,因此结果会输出true。
    23.        if(this.name.equals(per.name)&&this.age == per.age){
    24.                return true;
    25.        }
    26.        return false;
    27.    }
    28. }
    29. */

    2faf051342eb646f4836fb00f226de44.png

    编译器自动生成重写的hashcode()和equals()方法

    1. @Override
    2. public boolean equals(Object o) {
    3.        if (this == o) return true;
    4.        if (o == null || getClass() != o.getClass()) return false;
    5.        Per per = (Per) o;
    6.        return age == per.age &&
    7.                Objects.equals(name, per.name);
    8.   }
    9.    @Override
    10.    public int hashCode() {
    11.        return Objects.hash(name, age);
    12.   }

    在object类中,hashcode()方法是本地方法,返回的是对象的地址值,而object类中的equals()方法比较的也是两个对象的地址值,如果equals()相等,说明两个对象地址值也相等,当然hashcode()也就相等了.**但是hashcode() 相同时,equals()不一定相同**

    ✅✅重写equals方法时,也必须重写hashcode()方法吗?

    答:必须,hashCode 和 equals 两个方法是用来协同判断两个对象是否相等的,采用这种方式的原因是可以提高程序插入和查询的速度,当重写equals方法后有必要将hashCode方法也重写,这样做才能保证不违背hashCode方法中“相同对象必须有相同哈希值”的约定。

    ✅✅ == 和 equals 的区别是什么?

    答:

    对于基本类型和引用类型 == 的作用效果是不同的,如下所示:

    • 基本类型:比较的是值是否相同;

    • 引用类型:比较的是引用是否相同

    1. String x = "string";
    2. String y = "string";
    3. String z = new String("string");
    4. System.out.println(x==y); // true
    5. System.out.println(x==z); // false
    6. System.out.println(x.equals(y)); // true
    7. System.out.println(x.equals(z)); // true

    对于equals() 方法,根据源码可以得知 : equals() 的本质上就是true

    1. public boolean equals(Object obj) {
    2. return (this == obj);
    3. }

    所以equals()方法 默认情况下是引用比较,只是很多类重写了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。

    四、常用接口

    4.1 Comparable接口(比较)

    ❓  在学习数组时,Arrays类中的sort方法可以对对象数组进行排序 , 那下面的对象数组能不能用Arrays.sort排序呢?

    1. class Student {  
    2.    String name;
    3.    int age;
    4.    public Student(String name, int age) {
    5.        this.name = name;
    6.        this.age = age;
    7.   }
    8.    @Override  
    9.    public String toString() {
    10.        return "Student{" +
    11.                "name='" + name + '\'' +
    12.                ", age=" + age +
    13.                '}';
    14.   }
    15. }
    16. public class test4 {
    17.    public static void main(String[] args) {
    18.        Student[] students = new Student[] {
    19.              new Student("zhangsan", 13),
    20.              new Student("lisi", 23),
    21.              new Student("able", 17),
    22.       };
    23.        Arrays.sort(students);
    24.        System.out.println(Arrays.toString(students));
    25.       }
    26. }

    475baa61f3c5c7eb944a0e0f46d02f12.png

    此时编译器并不知道到底是按姓名还是年龄进行排序,当sort方法对对象所属的类进行排序时,对象所属的类必须实现Comparable接口,通过参考文档可知,Comparable接口中仅有一个抽象方法。

    ba4251b15058ef5da3768943a7a5ed4c.png

    4cc494133e57617ddbedb60a47c7e972.png

    那么我们就可以实现Comparable接口,并实现和重写compareTo方法

    1. class Student implements Comparable{
    2.   public int age;
    3.   public String name;
    4.    
    5.   public Student(int age, String name) {
    6.       this.age = age;
    7.       this.name = name;
    8.   }
    9.   @Override
    10.   public String toString() {
    11.       return "Student{" +
    12.               "age=" + age +
    13.               ", name='" + name + '\'' +
    14.               '}';
    15.   }
    16.   //重写compareTo方法
    17.   @Override
    18.   public int compareTo(Student o) {
    19.       if (this.age - o.age > 0)
    20.           return 1;
    21.       else
    22.       if (this.age - o.age < 0)
    23.           return -1;
    24.       else
    25.           return 0;
    26.   }
    27.     public static void main1(String[] args) {
    28.       Student student = new Student(16, "liba");
    29.       Student student1 = new Student(13, "zhangsan");
    30.       System.out.println(student.toString());
    31.       System.out.println(student1.toString());
    32.       if (student.compareTo(student1) > 0) {
    33.           System.out.println("student > student1");
    34.       } else {
    35.           System.out.println("student < student1");
    36.       }
    37.   }
    38. }

    此时可以得到按年龄进行排序的结果:

    f5447763cc08571f6851bc981a904281.png

    我们知道在Arrays.sort(students); 中是传了一个学生对象数组,在调用Arrays对对象数组排序的时候,其实就调用了我们的Comparable接口中的compareTo方法对数组的每个元素进行了排序和比较,在Java中对于引用数据类型的比较或者排序,一般都要用到使用Comparable接口中的compareTo() 方法

    按姓名排序时,重写的compareTo方法

    1. @Override
    2. public int compareTo(Student o) {   // this.代表对当前对象的引用,o.代表对参数对的引用
    3.        if (this.name.compareTo(o.name) > 0)//String类中重写了compareTo方法,可直接使用
    4.             return 1;  
    5.        else if (this.name.compareTo(o.name) < 0)
    6.             return -1;
    7.        else
    8.             return 0;
    9.   }
    10.    //如果当前对象应排在参数对象之前, 返回小于 0 的数字;
    11.    //如果当前对象应排在参数对象之后, 返回大于 0 的数字;
    12.    //如果当前对象和参数对象不分先后, 返回 0;

    🎈 缺点:一旦重写了comparable()方法,那么就只能对一种参数类型进行比较,把方法写死了,此时就需要使用Comparator接口 ❗

    4.2 Comparator接口(比较)

    这里是Arrays.sort中只有一个参数的方法

    570fc21245c601b5fde0722e3f6e7ae8.png

    当实现Comparator接口时,可以使用两个参数重载的方法实现排序,包含一个比较器类型的参数

    a253d25576b2f0253edbd1d0a8107e9f.png

    首先通过参考文档了解Comparator接口,我们需要重写的是compare()方法

    d7e99a2397b382ea3adf1c56b066645b.png

    所以就像Comparable 接口一样,我们只要实现了Comparator接口,并重写Comparator里的compare方法就可以实现对学生对象数组的排序

    比如我们上面的年龄比较就可以写成这样

    1. class Student {
    2.    String name;
    3.    int age;
    4.    public Student(String name, int age) {
    5.        this.name = name;
    6.        this.age = age;
    7.   }
    8.    @Override  
    9.    public String toString() {
    10.        return "[" + this.name + ":" + this.age + "]";
    11.   }
    12. }
    13. // 实现Comparator接口中的compare方法
    14. class AgeComparator implements Comparator { // 年龄比较器
    15.    @Override
    16.    public int compare(Student o1, Student o2) {
    17.        return o1.age - o2.age;
    18.    // 反正返回的也是数字,当o1.age>o2.age时返回大于零的数,即o1对象排在o2对象的后面,升序排列,我们之前用Comparable接口时也可以这样简写
    19.   }
    20. }
    21. public class test4 {
    22.    public static void main(String[] args) {
    23.        Student[] students = new Student[]{
    24.                new Student("zhangsan", 13),
    25.                new Student("lisi", 23),
    26.                new Student("able", 17),
    27.       };
    28.        AgeComparator ageComparator = new AgeComparator();
    29.        Arrays.sort(students, ageComparator);
    30.        // 用类Arrays.sort对students数组进行排序,这里传了两个参数(学生对象和所对应的年龄比较器)
    31.        System.out.println(Arrays.toString(students));
    32.   }
    33. }

    同样,当我们按照姓名进行排序时,也可以使用此接口

    1. class Student {
    2.    String name;
    3.    int age;
    4.    public Student(String name, int age) {
    5.        this.name = name;
    6.        this.age = age;
    7.   }
    8.    @Override  
    9.    public String toString() {
    10.        return "[" + this.name + ":" + this.age + "]";
    11.   }
    12. }
    13. class NameComparator implements Comparator { // 姓名比较器
    14.    // 实现Comparator接口中的compare方法
    15.    @Override  
    16.    public int compare(Student o1, Student o2) {
    17.        return o1.name.compareTo(o2.name);
    18. // 因为name是String类型,也是一个引用类型,也要用到compareTo方法,此时的compareTo方法是String类里重写的方法
    19.   }
    20. }
    21. public class test4 {
    22.    public static void main(String[] args) {
    23.        Student[] students = new Student[]{
    24.                new Student("zhangsan", 13),
    25.                new Student("lisi", 23),
    26.                new Student("able", 17),
    27.       };
    28.        NameComparator nameComparator = new NameComparator();
    29.        Arrays.sort(students, nameComparator);
    30.        System.out.println(Arrays.toString(students));
    31.     }
    32. }

    Comparable接口和Comparator接口都是Java中用来比较和排序引用类型数据的接口,要实现比较,就需要实现他们所各种对应的compareTo方法或者compare方法。

    Comparator使用起来更加灵活,所以我更倾向于使用比较器:Comparator

    4.3 Cloneable接口(拷贝)

    • 对象在内存当中的存储

    1. class Student {
    2.    public int age = 15;
    3.    @Override
    4.    public String toString() {
    5.        return "Student{" +
    6.                "id=" + id +
    7.                '}';
    8.   }
    9. }
    10. public class test3 {
    11.    public static void main(String[] args) {
    12.        Student student1 = new Student();
    13.        System.out.println(student1);
    14.   }
    15. }

    此时如果在堆内存中对student1对象拷贝一份,如果使用

    Student student2 = student1;

    这只是我们在栈上重新定义了一个引用变量student2,并指向了堆上的student1对象,并没有对我们的student1实现拷贝,改变student2.age会影响student.age 的值。

    d967060b4a986cd1306a38a212c3b15f.png

    所以我们需要重写Object类中的clone方法进行克隆,在使用clone方法之前,需要实现Cloneable接口

    bb199ab6ae85d003f566429fe792fac5.png

    ecdfec7618fcf7f9a8dce04230349a30.png

    由源码和参考文档可知,Cloneable是一个空接口即标记接口,如果有其他类继承该接口,说明该类的对象是可以被克隆的。

    • 要克隆的这个对象的类必须实现 Cloneable 接口

    • 类中重写 Objectclone() 方法

    • 处理重写clone方法时的异常情况

    • clone方法需要进行强转(比较特殊,先记住就好)

    1. class Student implements Cloneable{
    2.    public int age = 10;
    3.    @Override
    4.    protected Object clone() throws CloneNotSupportedException {
    5.        return super.clone();
    6.   }
    7.    @Override
    8.    public String toString() {
    9.        return "Person{" +
    10.                "age=" + age +
    11.                '}';
    12.   }
    13. }
    14. public class Demo2 {
    15.    public static void main(String[] args) throws CloneNotSupportedException{
    16.        Student student = new Student();
    17.        Student student2 = (Student)student.clone();   //返回值为Object需要进行强制类型转换
    18.        System.out.println(student.age);
    19.        System.out.println(student2.age);
    20.        student2.age = 18;
    21.        System.out.println(student.age);
    22.        System.out.println(student2.age);
    23.   }
    24. }

    此时在内存当中就是这样,student1和student2 中的两个age是相互独立的,student2的age发生改变不会影响student1 的内容。此时我们就成功实现了对象的拷贝

    cb4ff3655cb312d66c6c6bdaaa722b00.png

    4.4 浅拷贝与深拷贝

    • 浅拷贝

    ✅根据上边Cloneable接口使用介绍我们已经详细了解了,此时我们提出了一个问题,如果在Student类当中再定义一个引用类型,那么又该如何拷贝呢?

    8bb514fbd406eefe19326675b9eb7cca.png

    1. class Teacher{
    2.   int number = 20;
    3. }
    4. class Student implements Cloneable{
    5.   public int age = 10;
    6.   Teacher teacher = new Teacher();
    7.   @Override
    8.   protected Object clone() throws CloneNotSupportedException {
    9.       return super.clone();
    10.   }
    11.   @Override
    12.   public String toString() {
    13.       return "Person{" +
    14.               "age=" + age +
    15.               '}';
    16.   }
    17. }
    18. public class Demo2 {
    19.   public static void main(String[] args) throws CloneNotSupportedException{
    20.       Student student = new Student();
    21.       Student student2 = (Student)student.clone();   //返回值为Object需要进行强制类型转换
    22.       System.out.println(student.teacher.number);
    23.       System.out.println(student2.teacher.number);
    24.       student.teacher.number = 100;
    25.        
    26.       System.out.println(student.teacher.number);
    27.       System.out.println(student2.teacher.number);
    28.   }
    29. }

    此时,student 中teacher的改变也引起了 student2中地址的改变,此种拷贝就好像只拷贝了student.teacher.number 的地址,并未重新复制一块内存出来,此种拷贝就叫做浅拷贝

    c536cdb6b7cddff025073265af69ae59.png

    浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

    40c4a2955bdae090f3973c45c94c6d21.png

    • 深拷贝

    刚刚我们通过实现Cloneable接口、重写clone方法对Student类实现了拷贝,那么同理我们也可以用这样的办法对Teacher类对象进行拷贝.

    1. class Teacher implements Cloneable{
    2.    int number = 20;
    3.    @Override
    4.    protected Object clone() throws CloneNotSupportedException {
    5.        return super.clone();
    6.   }
    7. }
    8. class Student implements Cloneable{
    9.    public int age = 10;
    10.    public Teacher teacher = new Teacher();
    11.    @Override
    12.    protected Object clone() throws CloneNotSupportedException {
    13.        // 此时我们在进行 “(Student) student.clone();” 操作,
    14.        // 我们在堆上对student克隆拷贝出来一个新对象,并让引用变量tmp指向新对象
    15.        Student tmp = (Student) super.clone();
    16.        // 用this.teacher.clone()对引用变量teacher所指向的Teacher类对象进行克隆
    17.        tmp.teacher = (Teacher) this.teacher.clone();
    18.        return tmp;
    19.   }
    20.    @Override
    21.    public String toString() {
    22.        return "Person{" +
    23.                "age=" + age +
    24.                '}';
    25.   }
    26. }
    27. public class Demo2 {
    28.    public static void main(String[] args) throws CloneNotSupportedException{
    29.        Student student = new Student();
    30.        // 此时的student.clone返回Student类对象的引用tmp,student2 就指向了原来tmp所指向的对象
    31.        Student student2 = (Student)student.clone();  
    32.        System.out.println(student.teacher.number);
    33.        System.out.println(student2.teacher.number);
    34.        student.teacher.number = 100;
    35.        System.out.println(student.teacher.number);
    36.        System.out.println(student2.teacher.number);
    37.   }
    38. }

    b7ab46577aac6ac1a8ff16c83bc860a5.png

     此时的内存结构图为: 
    

    a3fcc95a7d02a3839fdd352b0e0ff200.png

    上面的拷贝就把引用变量teacher所指向的Teacher类的对象也在堆中拷贝了一份,这就是深拷贝, 深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

    深拷贝:创建一个新对象,然后将当前对象的各种成员属性复制到该新对象,无论该成员属性是值类型的还是引用类型,都复制独立的一份,引用类型也会复制该引用类型所指向的对象。

    ced485cbb11e458d81a746890b32cf3f.gif

     

  • 相关阅读:
    工程管理系统简介 工程管理系统源码 java工程管理系统 工程管理系统功能设计
    ArkUI框架,Flex布局,基础组件及封装,父子组件的双向绑定
    6.吴恩达深度学习--深度卷积模型:案例研究
    做FP还不了解怎样建站?一文讲懂独立站搭建与选择
    万字Numpy教程带你从入门到放弃
    ssm+vue的OA办公管理系统(有报告)。Javaee项目,ssm vue前后端分离项目。
    【Python入门】Numpy基础
    volatile 类型变量提供什么保证?能使得一个非原子操作变成原子操作吗?
    Web前端:JavaScript编程语言有哪些优势?
    MotoGP Ignition:准备好参加 Spotlight 活动!
  • 原文地址:https://blog.csdn.net/m0_56361048/article/details/126822798