多态、抽象类和接口(深拷贝和浅拷贝)

目录​​​​​​​

前言:

多态:

多态的定义:

向上转型: 

方法重写:

再看toString方法: 

动态绑定:

向下转型: 

小练习:

抽象类:

什么是抽象类?

抽象方法:

抽象类:

抽象类的使用: 

小总结:

接口:

接口是什么?

接口中的方法修饰符: 

接口中的成员修饰符:

接口的使用:

接口的定义格式: 

接口中的代码块使用: 

类使用多个接口: 

接口的继承: 

Comparable接口: 

小练习一: 

小练习二: 

小总结: 

克隆:

浅拷贝:

深拷贝:


前言:

       经过之前的学习,我们都已经了解了什么是继承和封装,那么今天我们就来学习面向对象的最后一个特性,多态。

       当然,了解了多态也就需要知道和它相关的知识,抽象类和接口。

多态:

多态的定义:

       多态:多种形态,去完成某个行为,不同对象去完成时产生不同形态。有一种看人说话的感觉,都是说同一种事物,但是根据不同的对象说话的方式不同。

要想实现多态,需要满足几个条件:

    1.继承关系上:向上转型
    2.子类和父类有同名 覆盖/重写 方法
    3.通过父类对象的引用,去调用这个重写的方法。

我们我们来逐一介绍。

       我们先来看一段代码:

class Animal {public String name;public int age;public Animal(String name, int age) {this.name = name;this.age = age;}public void eat() {System.out.println(this.name + "正在吃饭");}
}class Dog extends Animal {public Dog(String name, int age) {super(name, age);}public void bark() {System.out.println(this.name + "汪汪叫");}
}public class New {public static void main(String[] args) {Dog a = new Dog("hehe",12);a.eat();a.bark();System.out.println("=========");Animal lala = new Animal("lala", 19);lala.eat();lala.bark();//这是子类特有方法,只能调用父类自己特有的成员方法或者成员变量}
}

       这里我们用Dog类继承了Animal类,我们定义了一个Animal类的lala对象,去调用了Dog类中的特定的bark方法,因为bark是Dog的方法,毫无疑问,会报错。

向上转型: 

       之前说过,多态涉及3个定义,此时我们就来讲解向上转型和向下转型。 

Dog gege = new Dog("gege", 10);
Animal animal = gege;
//animal这个引用对象 指向了 dog 这个引用对象

       我们把gege这个Dog类的对象转换为其父类的类型,此时就发生了向上转型。我们将这两句代码合并:

Animal animal = new Dog("gege",11);

       这就是向上转型。此时我们调试程序,来观察如何执行:

       可以看到,先去调用了Dog的构造方法。 向上转型有三个情况:

       此时就完成了向上转型。接下来就要讲解方法重写和动态绑定。

方法重写:

       还是否记得我们之前学到的方法重载?对没错,它和方法重写是两个概念。我们先来讲解方法重写,之后再给出区别。

       此时我们在Dog的类里面写入eat方法(Animal中也有eat方法),并让发生向上转型对象调用。 

class Animal {public String name;public int age;public Animal(String name, int age) {this.name = name;this.age = age;}public void eat() {System.out.println(this.name + "正在吃饭");}
}class Dog extends Animal {public Dog(String name, int age) {super(name, age);}public void bark() {System.out.println(this.name + "汪汪叫");}public void eat() {System.out.println(this.name + "正在吃狗粮");}
}public class New {public static void main(String[] args) {Animal animal = new Dog("lele", 10);animal.eat();}
}

       此时你会发现竟然调用了子类的方法。 

       重写也称为覆盖。是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写,返回值和形参都不能改变。

       tips:我们进行方法重写,IDE编译器会出现一个图标来表明发生了方法重写: 

       为了更好的的区分方法进行重写,我们一般重写方法时,都会在上面加上@Override.

@Override//这个注解即发生了方法重写
public void eat() {System.out.println(this.name + "正在吃狗粮");
}

       在Java中,有一种术语叫做注解,比如@Override就是其中的一种,起到提示的作用(有点像C的assert函数)。

       这里也需要注意他们的权限,被重写的方法权限修饰符必须大于等于继承的方法。

       为了大家更好的复习:

        实现重写:

  1. 最基本的返回值,参数列表,方法名必须是一样的
  2. 被重写的方法的访问修饰限定符,在子类中要大于等于父类的方法修饰限定访问符。
  3. 被private修饰的方法是不可以被重写的。
  4. 被static修饰的方法是不可以重写的。
  5. 被重写的方法返回类型可以不同,但必须具有父子关系。
  6. 被final修饰的方法是不可以被重写的。
  7. 构造方法也是不能被重写的。

       此时我们就来看方法重写和方法重载的区别:

再看toString方法: 

       此时我们再次打印对象,并再看toString方法。

       toString方法是Object类中的方法,因为Java中所有的类都默认继承于Object类,所以当Dog类中没有写toString方法,就会调用Object的toString方法。所以我们每次调用时toString时都会发生向上转型。这就是方法重写。

       对于已经投入使用的类,尽量不要进行修改。最好的方式是:当重新定义了一个新的类,来重复利用其中共性的内容,并且添加或者改动新的内容。

动态绑定:

       此时在通过父类的引用进行调用的时候,是调用子类的方法,把这个过程就叫做 动态绑定。 

       方法的重载就是静态绑定;方法的重写就是动态绑定。

       我们执行进行方法重写的文件(我上方的代码,最长的那个),之后我们看编译时是如何发生动态绑定的。

       我们直接在IDEA右击,之后点击explorer就会打开当前文件的目录,之后回退到out目录,之后点进去(用手机看的可以不用操作,不用纠结)。找到编辑的类生成的字节码文件(后缀为.class),之后在当前目录中输入cmd打开控制台。之后再控制台中输入javap -c 文件名:

       我们可以看到在编译时,调用的方法确实是调用的Animal的eat方法,但是运行期间调用的是Dog的eat方法。这就叫做动态绑定,是在运行时绑定;而静态绑定在编译的时候就确定调用谁。

静态绑定:也称前期绑定(早绑定)。
动态绑定:也称后期绑定(晚绑定)。需要在程序运行时,才能确定具体调用哪个类的方法。

       当父类引用,引用的子类对象不一样的时候,调用这个重写的方法,所表现出来的行为不一样时,我们把这种思想就叫做多态。

向下转型: 

       向上转型的优点是让代码更加灵活;缺点是不能调用子类特有的方法。

       我们目前了解了向上转型,接下来我们再来看看什么是向下转型。

       此时只能够强制转换才可以成功。

       此时再多定义一个Cat类,并写入方法。

class Cat extends Animal {public Cat(String name, int age) {super(name, age);}public void miaomiao() {System.out.println(this.name + "喵喵叫");}
}

        此时是类型转换异常,所以向下转换是非常不安全的,此时只能这样运行:

Animal animal = new Dog("yuanyuan", 10);if (animal instanceof Cat) {Cat cat = (Cat)animal;
}else {System.out.println("理解了!");
}

        instanceof是判断是否是其子类(包括该类),所以我们判断一下即可。

小练习:

        我们来看一个代码:

class B {public B() {func();}public void func() {System.out.println("B.func()");}
}class D extends B {//private int num = 1;@Overridepublic void func() {System.out.println("D.func()");}
}public class Again {public static void main(String[] args) {D d = new D();}
}

        这里先去调用父类的构造方法,父类中调用了它的方法,但是子类中有相同的方法,因为动态绑定的关系,所以会去调用子类重写方法。

       此时我们再打印num的值(放开那条注释):

public void func() {System.out.println("D.func() " + num);
}

         会发现并不是1,而是0。此时子类中有该变量,但是还没来得及赋值为1。

抽象类:

什么是抽象类?

        顾名思义,就是抽象的类。哈哈,确实,但是也确实抽象。再讲抽象类之前,我们还是先来看代码。此时我们来打印一些图形,在不使用多态的情况下:

class Shape {public void draw() {System.out.println("画一个图形:");}
}class Rect extends Shape {public void draw() {System.out.println("□!");}
}class Triangle extends Shape {public void draw() {System.out.println("△!");}
}class Cycle extends Shape {public void draw() {System.out.println("○!");}
}public class Test {public static void main(String[] args) {Cycle cycle = new Cycle();Rect rect = new Rect();Triangle triangle = new Triangle();String[] strings = {"cycle","rect","cycle","rect","triangle"};for (String x : strings) {if (x.equals("cycle")) {cycle.draw();}else if (x.equals("rect")) {rect.draw();}else if (x.equals("triangle")) {triangle.draw();}}}
}

       这样写就是不知道多态才会这样写,如果利用多态来写,就可以省去很多代码:

public class Test {public static void main(String[] args) {/*Shape shape1 = new Cycle();Shape shape2 = new Triangle();Shape shape3 = new Rect();*/Shape[] shapes = {new Cycle(), new Rect(), new Cycle(), new Rect(), new Triangle()};for (Shape shape : shapes) {shape.draw();}}
}

       我们发现父类中的draw方法有些累赘,但是又不能不写,因为是其他形状继承的,否则完成不了多态。所以就有了抽象方法。

抽象方法:

       因为父类中的方法是被重写的,不会被调用,所以我们我们可以什么都不写:

class Shape {public void draw() {}
}

       我们可以这样写,但是还是不够完美。此时我们还想再简洁一些。

       所以这不是一个方法,所以只能是抽象方法,使用抽象关键字abstract修饰。

        但是依旧报错,如果此方法为抽象方法,那么这个类也必须是抽象类。

抽象类:

        抽象方法必须在抽象类中使用,此时我们使用了抽象方法,所以要把类描述为抽象类:

abstract class Shape {public abstract void draw();
}

         因为所有对象都是通过类来描述的,但是反过来,并不是所有类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个对象,这样的类就是抽象类。

         因为抽象类中缺少一些关键信息,所以抽象类是不可以实例化的。 

         抽象类当中,可以和普通类一样,定义成员变量和成员方法。

abstract class Shape {public String name;public abstract void draw();
}

抽象类的使用: 

       既然把方法抽象就要声明这个类是抽象类,那么抽象类肯定是继承时才会使用。此时直接继承会报错:

       必须实现抽象类中的抽象方法才可以被继承。

abstract class Shape {public String name;public abstract void draw();
}class Flower extends Shape {@Overridepublic void draw() {System.out.println("❀!");}
}

       所以抽象类的出现本身就是为了被继承。而且抽象方法和成员不能被final修饰(书写顺序无所谓)。

public static void drawMap(Shape shape) {shape.draw();
}
public static void main(String[] args) {Shape shape = new Cycle();//因为抽象类不能实例化,所以只能向上转型drawMap(shape);
}

       抽象类也可以被继承,可以被抽象类继承,也可以被正常的类继承,但是不是 抽象类 继承的抽象类必须实现抽象类中的实例方法(就是正常的类继承了抽象类,就必须实现之前所有抽象类中的方法)。

       所以出来混,迟早还是要还的。

tips:抽象类的图标我们可以观察一下,和其他正常类的图标不一样: 

小总结:

        抽象类也是类,内部可以包含普通的方法和属性,甚至构造方法。

        抽象类不能被private修饰;抽象方法不能被final和static修饰,因为抽象方法要被子类重写。因为可以通过子类来调用父类的构造方法。

        一个类只能继承一个抽象类。

接口:

接口是什么?

        实际生活中,接口就是设备上的(en……编不出来了)。接口属于一种标准,使用时只要符合规范,就可以使用。

        Java中接口可以看成多个类的公共规范,是一种引用数据类型。

        接口是使用interface方法来修饰的,接口当中不能有被实现的方法,意味着只能有抽象方法:

        但是两个方法除外(JDK8引入的):一个是被static修饰的方法,一个是被default修饰的方法。否则只是普通的抽象方法。

接口中的方法修饰符: 

       之后我们来观察接口中的方法修饰符:

       所以接口中的方法默认都是public abstract修饰的,即使前面没写,也是默认加上的。

接口中的成员修饰符:

       再看接口成员修饰符:

        接口中的成员都是 public static final 修饰的,即使前面没写,也是默认加上的,也意味着它是常量。

        也就是说,接口是对抽象类的抽象,抽象的抽象更不能实例化。

接口的使用:

        说了这么多,那么接口到底是如何使用的?比抽象还抽象。

        类和接口之间的关系,可以使用implements来进行关联。

        但是此时为什么报错,是因为接口中的方法没有被重写,和抽象类类似。 

        所以此时我们再来实现之前的打印形状,就可以不使用抽象类了,可以直接使用接口了。

interface S {/*public int a = 1;public static int b = 2;public static final int c =3;*/void draw();}class R implements S {@Overridepublic void draw() {System.out.println("矩形!");}
}class F implements S {@Overridepublic void draw() {System.out.println("❀!");}
}public class Test3 {public static void func(S shape){shape.draw();}public static void main(String[] args) {S shape1 = new R();S shape2 = new F();S[] shapes = {shape1, shape2};for (S sh : shapes) {func(sh);}}
}

       既然接口无法进行实例化,那么我们就可以利用向上转型和动态绑定,通过多态的方法对其进行使用。

接口的定义格式: 

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

       命名规则即规范:创建接口时,接口命名一般使用大写字母I开头。接口命名一般使用“形容词”词性单词。接口中的方法和属性不要添加任何修饰符号(因为默认也会有),保持代码简洁性。

       还是和之前一样,接口中的方法,都是要重写的,因为出来混都是要还的。

接口中的代码块使用: 

       接口中不能有静态代码块、构造代码块和实例代码块。

类使用多个接口: 

       我们可以在一个类中使用多个接口。当我们在一个类中要使用多个接口时,可以使用“,”隔开。

abstract class Animal {public String name;public int age;public Animal(String name, int age) {this.name = name;this.age = age;}
}interface IFly {void fly();
}interface IRun {void Run();
}interface ISwim {void Swim();
}class Dog extends Animal implements IRun {public Dog(String name, int age){super(name, age);}@Overridepublic void Run() {System.out.println(this.name + "正在跑!");}
}class Frog extends Animal implements ISwim,IRun {public Frog(String name, int age) {super(name, age);}@Overridepublic void Swim() {System.out.println(this.name + "蛙泳");}@Overridepublic void Run() {System.out.println(this.name + "跳跳");}
}class Duck extends Animal implements ISwim,IRun,IFly {public Duck(String name, int age) {super(name, age);}@Overridepublic void fly() {System.out.println(this.name + "在飞");}@Overridepublic void Run() {System.out.println(this.name + "双脚跑");}@Overridepublic void Swim() {System.out.println(this.name + "鸭泳");}
}public class Test {public static void running(IRun iRun) {iRun.Run();}public static void flying(IFly iFly) {iFly.fly();}public static void main(String[] args) {running(new Dog("二狗",11));flying(new Duck("唐老鸭",10));}
}

       所以Java使用接口也就实现了多继承问题。 

接口的继承: 

         接口之间,也可以实现继承。如果一个类使用的是继承的接口,则需要重写父类接口和子类接口的所有方法。

interface A {void testA();
}interface  B extends A {void testB();
}class TestDemo1 implements B {@Overridepublic void testA() {}@Overridepublic void testB() {}
}

       所以还是那句话,出来混还是要还的。

Comparable接口: 

       我们如果对一个类进行比较时,需要明确类型。否则就会报错。

class Student {public String name;public int age;public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}public class Test {public static void main(String[] args) {Student student1 = new Student("zhangsan", 10);Student student2 = new Student("lisi", 11);System.out.println(student1 > student2);//报错,因为没有指定类型}
}

       为了实现比较,我们需要使用接口。我们导入一个包,之后利用这个接口(Comparable)进行比较。

       我们点进去这个接口并看里面的内容:

       这里面有一个<>,代表泛型的意思,泛型我们以后再了解。当前代表我们要比较的类型,此时我们传入Student.

class Student implements Comparable<Student>

        我们可以看到这个接口中有一个compareTo的抽象方法。

        因为接口中有一个抽象方法,所以我们使用这个接口要去重写这个compareTo方法。

public int compareTo(Student o) {if (this.age > o.age) {return 1;}else if (this.age == this.age) {return 0;}else {return -1;}
}//主方法调用
System.out.println(student1.compareTo(student2));

       我们目前重写了该方法,但是有弊端,此时再次调用该方法只能比较年龄了。所以我们可以改进。但是我们要根据姓名进行比较时就没办法了。

       此时我们可以进行改进,我们可以使用比较器Comparator这个接口:

       其实就是实现一个类,之后重写compare方法,之后创建这个类的对象之后调用方法。

class AgeCompare implements Comparator<Student> {@Overridepublic int compare(Student o1, Student o2) {return o1.age - o2.age;}
}
public static void main(String[] args) {Student student1 = new Student("zhangsan", 10);Student student2 = new Student("lisi", 11);AgeCompare ageCompare = new AgeCompare();System.out.println(ageCompare.compare(student1, student2));}

        此时我们再比较姓名。创建一个类(比较器),之后实现compare方法,因为是字符串比较,所以用调用compareTo方法。

class NameCompare implements Comparator<Student> {@Overridepublic int compare(Student o1, Student o2) {return o1.name.compareTo(o2.name);}
}
NameCompare nameCompare = new NameCompare();
System.out.println(nameCompare.compare(student1, student2));

       此时是String类型调用的compareTo方法,所以我们先进入该方法。

       此时我们我们看String这个类型,进入源码观察。

       我们可以发现,String类实现了Comparable接口,所以是String类重写了compareTo方法。

       之后我们来比较 比较器 和 方法重写 各自的优缺点:

       此时两种方法都可以写,但是可以根据实际情况来修改,利用比较器灵活性更强。

小练习一: 

       我们定义一个接口,这个接口时USB,它里面有打开设备方法和关闭方法,之后定义三个类,分别是Computer类和Mouse类和KeyBoard类,

package demo2;public class Test {public static void main(String[] args) {Computer computer1 = new Computer();Mouse mouse = new Mouse();computer1.useService(mouse);System.out.println("==========");KeyBoard keyBoard = new KeyBoard();computer1.useService(keyBoard);}
}
package demo2;public class Computer {public void powerOn() {System.out.println("打开电脑");}public void powerOff() {System.out.println("关闭电脑");}public void useService(USB usb) {usb.openDevice();//先打开if (usb instanceof Mouse) {Mouse mouse = (Mouse)usb;mouse.click();}else if (usb instanceof KeyBoard) {KeyBoard keyBoard = (KeyBoard)usb;keyBoard.flap();}usb.closeDevice();//后关闭}
}
public class Mouse implements USB{@Overridepublic void openDevice() {System.out.println("鼠标开始工作");}public void click() {System.out.println("疯狂点击鼠标……");}@Overridepublic void closeDevice() {System.out.println("鼠标结束工作");}
}
public class KeyBoard implements USB{@Overridepublic void openDevice() {System.out.println("插上键盘设备");}public void flap() {System.out.println("疯狂敲击键盘……");}@Overridepublic void closeDevice() {System.out.println("关闭键盘设备");}
}
package demo2;public interface USB {void openDevice();void closeDevice();
}

小练习二: 

       了解了Comparable接口后,我们再来对一组数据进行排序,也是一个类。

class Student {public String name;public int age;public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}}public class Test {public static void main(String[] args) {Student[] students = new Student[3];students[0] = new Student("zhangsan", 11);students[1] = new Student("lisi", 5);students[2] = new Student("wangwu", 7);System.out.println("排序前:" + Arrays.toString(students));Arrays.sort(students);System.out.println("排序后:" + Arrays.toString(students));}
}

       执行此代码会发生错误,那么此时我们就点击这个报错,看是哪里出现了错误:

       和之前举的例子一样,是因为Arrays.sort排序必须指定类型,而且发现了向上转型。Comparable是一个接口,但是此时我们的类和这个接口没有任何关系,所以我们要去使用这个接口。

       使用接口以后,我们发现里面调用了compareTo方法,这个方法是接口的抽象方法,所以我们要重写一遍,并得知是排序的是哪个类型。

class Student implements Comparable<Student> {public String name;public int age;public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic int compareTo(Student o) {return this.age - o.age;}
}

小总结: 

       接口也属于一种类型,可以去引用实现了该接口的具体类型。

       两个特例,静态的方法直接通过接口名调用即可;default方法需要实例化一个对象,通过向上转型才能调用成功。

       和重写抽象类方法一样,重写权限修饰符必须大于等于父类,但是由于接口的方法默认都是 public abstract static 修饰的,所以重写方法只能用public修饰。

       接口相较于抽象类,可以更好的让其他类使用更加灵活。书写顺序不能错。

       2个关系:

  1. 类和接口之间的关系 ->implements 实现
  2. 接口和接口之间的关系 -> extends 拓展

       接口的修饰符可以为abstract,并且默认为其修饰。

克隆:

       克隆也是拷贝,一般编程中分为两种,深拷贝和浅拷贝。

浅拷贝:

       Java中其实也有现成的拷贝方法,我们先来看代码:

class Person {public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}
}

       此时我们有一个学生类,此时我们要创建两个对象,其中一个要去拷贝一个已经实例化的对象。 

public class Test {public static void main(String[] args) {Person person1 = new Person("zhangsan",10);//此时新创将一个对象,并将person1内容拷贝给新对象Person person2 = person1;//此时需要调用克隆方法}
}

        此时就需要调用clone方法。我们要知道所有类都是继承与Object的类,所以我们搜索Object类。

        在Object里面寻找clone方法。

       我们可以看到Object类中有clone方法。但是 .(点) 不出来,这是因为protected的原因。

        我们之前讲过,用protected修饰的成员或者方法,调用时就必须使用super。但是此时主方法是静态方法,不能使用this和super,所以要么再写一个方法,要么直接在子类中调用。

        所以我们重写clone方法(因为默认继承Object类,这个类中有这个方法,所以进行重写),之后通过对象调用。此时我们在子类中重写clone方法。

        但是可能会抛出异常,异常处理我们以后再学,就先按照提示输入即可。 

        还是报错,此时就需要向下转型。

public class Test {public static void main(String[] args) throws CloneNotSupportedException {Person person1 = new Person("zhangsan",10);//此时新创将一个对象,并将person1内容拷贝给新对象Person person2 = (Person) person1.clone();//此时需要调用克隆方法}
}

        此时就完成了克隆。但是注意这是浅拷贝。

class Person {public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}public class Test {public static void main(String[] args) throws CloneNotSupportedException {Person person1 = new Person("zhangsan",10);//此时新创将一个对象,并将person1内容拷贝给新对象Person person2 = (Person) person1.clone();//此时需要调用克隆方法System.out.println(person1);System.out.println(person2);}
}

        当然了,此时你去执行,依旧报错(哈哈哈)。

        这个方法确实重写了,但是当我们要实现克隆时,一定要使用一个Cloneable接口,才能实现克隆。

         我们把它叫做空接口/标记接口,表明当前类是可以被克隆的。之前报错是因为不支持克隆,实现接口以后就可以克隆。

        此时就完成了浅拷贝,那么接下来我们来了解深拷贝。

深拷贝:

        此时就需要用到继承来观察了。观察一下代码:

class Money {public double m = 19.9;
}
class Person implements Cloneable {public String name;public int age;public Money money = new Money();public Person(String name, int age) {this.name = name;this.age = age;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}public class Test {public static void main(String[] args) throws CloneNotSupportedException {Person person1 = new Person("zhangsan",10);//此时新创将一个对象,并将person1内容拷贝给新对象Person person2 = (Person) person1.clone();//此时需要调用克隆方法System.out.println(person1.money.m);System.out.println(person2.money.m);System.out.println("=============");person1.money.m = 200;System.out.println(person1.money.m);System.out.println(person2.money.m);}
}

        注意这里我们只改变了了person1的值,如果拷贝的话不会影响person2的值。但是结果并不是想象中的。

        这就是浅拷贝的弊端。对于嵌套的内容他们指向相同的地址。        此时我们就要来了解深拷贝了。 在Money里面也要使用克隆接口,拷贝时也要把父类拷贝进去(注意要先在Money实现Cloneable接口)。

class Money implements Cloneable {//这里面也要使用克隆接口 和 重写克隆方法public double m = 19.9;@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}

        之后去Person类中拷贝Money。

@Override
protected Object clone() throws CloneNotSupportedException {//为了实现深拷贝Person tmp = (Person)super.clone();//之后tmp中的money再拷贝一次tmp.money = (Money)this.money.clone();return tmp;
}

        此时就正式完成了深拷贝。 

class Money implements Cloneable{public double m = 19.9;@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}class Person implements Cloneable {public String name;public int age;public Money money = new Money();public Person(String name, int age) {this.name = name;this.age = age;}@Overrideprotected Object clone() throws CloneNotSupportedException {Person tmp = (Person)super.clone();tmp.money = (Money)this.money.clone();return tmp;}
}public class Test {public static void main(String[] args) throws CloneNotSupportedException {Person person1 = new Person("zhangsan",10);//此时新创将一个对象,并将person1内容拷贝给新对象Person person2 = (Person) person1.clone();//此时需要调用克隆方法System.out.println(person1.money.m);System.out.println(person2.money.m);System.out.println("============");person1.money.m = 200;System.out.println(person1.money.m);System.out.println(person2.money.m);}
}

        完结了这一篇,希望大佬点点赞。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/613892.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Logstash应用-同步ES(elasticsearch)到HDFS

1.场景分析 现有需求需要将elasticsearch的备份至hdfs存储&#xff0c;根据以上需求&#xff0c;使用logstash按照天级别进行数据的同步 2.重难点 数据采集存在时间漂移问题&#xff0c;数据保存时使用的是采集时间而不是数据生成时间采用webhdfs无法对文件大小进行设置解决…

水产冷链物流行业零下25℃库架一体 海格里斯HEGERLS四向穿梭式冷藏冷库智能密集仓

随着国内外仓储物流整体规模和低温产品消费需求的稳步增长&#xff0c;冷链市场应用潜力不断释放。在传统“货架叉车”的方式下&#xff0c;货物、人员及机械设备不断进出&#xff0c;容易造成温度波动&#xff0c;导致冷量流失。立体冷库则以更高密度、更具成本效益的方式&…

性能测试中TPS上不去的几种原因浅析

昨晚在某个测试群看到有人问了一个问题&#xff1a;压力测试中TPS一直上不去&#xff0c;是什么原因&#xff1f;稍微整理了下思路&#xff0c;列举性的简略回答了他的问题。 这篇博客&#xff0c;就具体说说在实际压力测试中&#xff0c;为什么有时候TPS上不去的原因。如有遗…

C++学习笔记(三十五):c++ 函数指针及lambda表达式

本节介绍c函数指针。在一些源码中经常能看到c函数指针&#xff0c;但之前一直觉着这一块比较复杂&#xff0c;就一直没去仔细研究&#xff0c;终于有时间去仔细研究这一块内容了。 c风格的函数指针 函数指针是指将一个函数赋值给一个变量的方法&#xff0c;可以将函数作为一个参…

Vercel配置自定义域名

首先你需要有一个域名 1.点击部署的项目设置 2.找到Domains 3.输入自己的域名 点击添加之后按要求去域名服务商添加解析即可 4.显示下面内容就设置完成了&#xff0c;

激活/注册navicat15

一、获取软件 链接&#xff1a;https://pan.baidu.com/s/1F_tiLuLvVFMEz8pDfIvDjw?pwdjjfj 提取码&#xff1a;jjfj 二、安装 安装的过程我就不放了&#xff0c;重点如下 安装完不要打开软件&#xff01; 安装完不要打开软件&#xff01; 安装完不要打开软件&#xff01;…

强化学习求解TSP(三):Qlearning求解旅行商问题TSP(提供Python代码)

一、Qlearning简介 Q-learning是一种强化学习算法&#xff0c;用于解决基于奖励的决策问题。它是一种无模型的学习方法&#xff0c;通过与环境的交互来学习最优策略。Q-learning的核心思想是通过学习一个Q值函数来指导决策&#xff0c;该函数表示在给定状态下采取某个动作所获…

回首24考研历程,那些无法忘却的收获

文章目录 毅力与坚持&#x1f44d;知识的力量&#x1f9d0;心态的历练&#x1f913;友谊与互助( •̀ ω •́ )y未来的启示(●◡●) 在这个充满希望与挑战的时刻&#xff0c;我想与你们分享一段关于2023这一年特别的经历——考研之路。这段旅程既有磨砺的痛楚&#xff0c;也有…

小程序基础学习(事件处理)

概述&#xff1a;点击某一个标题&#xff0c;使标题选中增加不同颜色。 <!--pages/four/four.wxml--> <navigation-bar title"牧原" back"{{false}}" color"black" background"#FFF"></navigation-bar> <view c…

2023年全国职业院校技能大赛(高职组)“云计算应用”赛项赛卷⑩

2023年全国职业院校技能大赛&#xff08;高职组&#xff09; “云计算应用”赛项赛卷10 目录 需要竞赛软件包环境以及备赛资源可私信博主&#xff01;&#xff01;&#xff01; 2023年全国职业院校技能大赛&#xff08;高职组&#xff09; “云计算应用”赛项赛卷10 模块…

RT-Thread: ulog 日志 讲解和使用

说明&#xff1a;记录 RT-Thread: ulog 日志功能和使用流程。 官网资料链接&#xff1a; https://docs.rt-thread.org/#/rt-thread-version/rt-thread-standard/programming-manual/ulog/ulog 1.ulog 简介 日志的定义&#xff1a;日志是将软件运行的状态、过程等信息&#x…

海外市场调研为什么要用独享静态代理IP?

独享静态IP在海外市场调研中扮演着至关重要的角色&#xff0c;提供了一系列无可比拟的优势。独享静态代理IP的稳定性和可靠性对于长期的市场调研至关重要&#xff0c;它保证了连接的持续性和数据的准确性。通过这些方面的综合优势&#xff0c;独享静态代理IP成为海外市场调研中…

工业智能网关:HiWoo Box远程采集设备数据

工业智能网关&#xff1a;HiWoo Box远程采集设备数据 在工业4.0和智能制造的浪潮下&#xff0c;工业互联网已成为推动产业升级、提升生产效率的关键。而在这其中&#xff0c;工业智能网关扮演着至关重要的角色。今天&#xff0c;我们就来深入探讨一下工业智能网关。 一、什么…

用Linux的视角来理解缓冲区概念

缓冲区的认识 缓冲区&#xff08;buffer&#xff09;是存储数据的临时存储区域。当我们用C语言向文件中写入数据时&#xff0c;数据并不会直接的写到文件中&#xff0c;中途还经过了缓冲区&#xff0c;而我们需要对缓冲区的数据进行刷新&#xff0c;那么数据才算写到文件当中。…

Java获取IP地址及对应的归属地

目录 前言 一、获取访问的IP地址 二、通过IP地址获取对应的归属地 2.1 Ip2region 2.1.1 高达 99.9 % 的查询准确率 2.1.2 Ip2region V2.0 特性 2.1.3 多语言以及查询客户端的支持 2.2 Ip2region xdb Java 查询客户端实现 2.2.1 引入 Maven 仓库 2.2.2 ip2region.xdb …

【.NET Core】可为null类型详解

【.NET Core】可为null类型详解 文章目录 【.NET Core】可为null类型详解一、概述二、可为空的值类型2.1 声明和赋值2.2 检查可为空值类型2.3 基础类型与可为空的值类型互换2.4 可为空的值类型装箱和取消装箱2.5 如何确定可为空的值类型 三、可为 null 的引用类型 一、概述 nu…

用通俗易懂的方式讲解:在 Langchain 中建立一个多模态的 RAG 管道

写在前面 语言模型的出现彻底改变了我们从文件中提取信息的方式。然而&#xff0c;我们知道图片&#xff0c;通常是图表和表格&#xff0c;经常包含关键信息&#xff0c;但基于文本的语言模型无法处理媒体文件。 例如&#xff0c;我们以前只能使用 PDF 文件中的文本来查找答案…

C#编程-实现线程声明周期

实现线程声明周期 当System.Threading.Thread类的对象被创建的时候,线程的生命周期开始。线程的生命周期在完成任务时结束。在线程的生命周期中有各种状态。这些状态是: 未启动状态可运行状态不可运行状态死亡状态下图显示了线程的各种状态和引起线程从一个状态变为另一个状…

欢乐的周末 - 华为OD统一考试

OD统一考试 题解: Java / Python / C++ 题目描述 小华和小为是很要好的朋友,他们约定周末一起吃饭。 通过手机交流,他们在地图上选择了多个聚餐地点(由于自然地形等原因,部分聚餐地点不可达)。求小华和小为都能到达的聚餐地点有多少个? 输入描述 第一行输入m和n,m代表…

C练习——递归求第n个人年龄

题目&#xff1a; 有n个人坐在一起&#xff0c;第n个人比第n-1个人大2岁&#xff0c;第n-1个人比第n-2个人大2岁&#xff0c;以此类推&#xff0c;……&#xff0c;第1个人是10岁。请问第n个人年龄多大&#xff1f; 解析&#xff1a; 简单循环也能求解 但按题意要求递归求解…