接口
2.1 接口的概念
接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。在Java
中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
比如:
电脑的USB
口上,可以插:U
盘、鼠标、键盘…所有符合USB
协议的设备
电源插座插孔上,可以插:电脑、电视机、电饭煲…所有符合规范的设备
2.2 语法
使用关键字interface
来修饰
interface IShape {
}
注意:接口的命名习惯第一个字母为大写I
开头
-
成员方法:接口当中的成员方法,都只能是抽象方法,所有的方法都是默认
public abstract
修饰的interface IShape { //成员方法 public abstract void func(); }
我们可以看到,
public abstract
是灰色的,这代表可以不用写,直接写void func()
即可虽然可以不写,但是这不代表可以不这样写。如果我们故意写错访问权限修饰符(
public
写成protected
),还是会报错 -
成员变量:接口当中的成员变量,默认都是
public static final
的,且必须要进行初始化interface IShape { //成员变量 public static final int a; }
正确写法:
interface IShape { //成员变量 public static final int a = 10; }
同样,我们看到
public static final
也是标灰的,这代表我们也可以直接写int a
的形式。 -
接口里面的方法,默认是不能够进行实现的
interface IShape { //成员方法 public abstract void func() { System.out.println("接口里面的方法是实现不了的"); } }
如果我们想要去实现接口里面的方法的话,我们需要借助
default
关键字interface IShape { //成员方法 //注意:这个时候应该把public abstract删掉,default才能好使 default void func() { System.out.println("加了default之后,接口里面的方法可以实现了"); } }
-
接口里面的静态方法也可以有具体实现
interface IShape { public static void staticFunc() { System.out.println("接口里面的静态方法是可以实现的"); } }
-
接口不能进行实例化(即不能
new 接口名()
)interface IShape { //成员变量 public static final int a = 10; //成员方法 default void func() { System.out.println("加了default之后,接口里面的方法可以实现了"); } public static void staticFunc() { System.out.println("接口里面的静态方法是可以实现的"); } } public class TestDemo { public static void main(String[] args) { IShape iShape = new IShape(); } }
-
接口里面不能有静态代码块、实例代码块以及构造方法
interface IShape { //接口里面不能有静态代码块 static { } //接口里面不能有实例代码块 { } //接口里面不能有构造方法 public IShape() { } }
-
接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是
.class
-
如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类(即如果你不想实现接口的方法的话,那你就在这个类前面加
abstract
)
2.3 接口的使用
一个普通的类,可以通过关键字implement
来实现这个接口,这个普通的类需要重写接口里面的抽象方法
interface IShape {
//成员变量
public static final int a = 10;
//成员方法
default void func() {
System.out.println("加了default之后,接口里面的方法可以实现了");
}
public static void staticFunc() {
System.out.println("接口里面的静态方法是可以实现的");
}
void draw();//没写public abstract,默认是抽象方法,需要重写
}
class A implements IShape {
@Override
public void draw() {
System.out.println("重写了接口IShape里面的抽象方法");
}
}
其实我们也可以重写func()
方法,这里我们就不重写了
注意:子类和父类之间是extends
继承关系,类与接口之间是 implements
实现关系
那么main
方法中我们虽然不能直接实例化,但是我们可以通过向上转型的方式进行实例化,看例子
public class TestDemo {
public static void main(String[] args) {
IShape iShape = new A();//向上转型:不过ishape还是由Ishape(父类)创建的,还是只能调用父类的成员
iShape.draw();//可以调用抽象方法
iShape.func();//可以调用default修饰的默认的方法
IShape.staticFunc();//静态方法的调用:类名.的方式
}
}
那么我们通过接口的方式再来实现一遍之前的Shape
类吧
interface Ishape {
void draw();
}
class Cycle implements Ishape {
@Override
public void draw() {
System.out.println("●");
}
}
class Rect implements Ishape {
@Override
public void draw() {
System.out.println("♦");
}
}
class Triangle implements Ishape {
@Override
public void draw() {
System.out.println("▲");
}
}
public class TestDemo {
//主函数也还可以使用多态的思想
public static void draw(Ishape ishape) {
ishape.draw();
}
public static void main(String[] args) {
Cycle cycle = new Cycle();
Rect rect = new Rect();
Triangle triangle = new Triangle();
draw(cycle);
draw(rect);
draw(triangle);
}
}
第二个例子:
请实现笔记本电脑使用USB
鼠标、USB
键盘的例子
USB
接口:包含打开设备、关闭设备功能- 笔记本类:包含开机功能、关机功能、使用
USB
设备功能 - 鼠标类:实现
USB
接口,并具备点击功能 - 键盘类:实现
USB
接口,并具备输入功能
interface USB {
//抽象方法:省略了public abstract
void openDevice();
void closeDevice();
}
class Mouse implements USB {
@Override
public void openDevice() {
System.out.println("打开鼠标");
}
@Override
public void closeDevice() {
System.out.println("关闭鼠标");
}
//还可以有自己的方法
public void click() {
System.out.println("点击鼠标");
}
}
class KeyBoard implements USB {
@Override
public void openDevice() {
System.out.println("打开键盘");
}
@Override
public void closeDevice() {
System.out.println("关闭键盘");
}
public void input() {
System.out.println("输入内容");
}
}
class Computer {
public void powerOn() {
System.out.println("开机");
}
public void powerOff() {
System.out.println("关机");
}
public void useDevice(USB usb) {
usb.openDevice();
if(usb instanceof Mouse) {//如果传过来的是鼠标,那么我们就向下转型成鼠标
Mouse mouse = (Mouse)usb;
mouse.click();
} else if (usb instanceof KeyBoard) {//如果传过来的是键盘,那么我们就向下转型成键盘
KeyBoard keyBoard = (KeyBoard)usb;
keyBoard.input();
}
usb.closeDevice();
}
}
public class TestDemo2 {
public static void main(String[] args) {
Computer computer = new Computer();
computer.powerOn();//开机
computer.useDevice(new Mouse());
computer.useDevice(new KeyBoard());
computer.powerOff();
}
}
2.4 实现多个接口
一个类,不仅可以继承抽象类,还可以实现多个接口,接口之间用,
隔开
先继承,后实现接口
那么我们来思考这样一个场景:我们有一个动物类Animal
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(this.name + "正在吃");
}
}
如果我们还想增加一些动作,比如飞、跑、游泳,那我可以在Animal
这个类里面实现方法吗?
这显然是不行的。假如狗继承了
Animal
这个类,那么狗也不会飞啊。
那么我们可以实现飞、跑、游泳这三个对应的类啊,然后让狗继承这三个类
当然是不行的,
Java
不支持多继承,但是我们之前提到过,Java
中用接口完美地替代了这个问题
那么我们可以实现这三个接口
interface IFlying {
void fly();//接口里面的方法是不能实现的
}
interface IRunning {
void run();
}
interface ISwimming {
void swim();
}
那么我们的狗类就可以继承Animal
类,然后与飞、跑、游泳这三个类实现接口
class Dog extends Animal implements IRunning,ISwimming {
//1.先要构造父类
public Dog(String name) {
super(name);
}
//2.要重写接口的方法
@Override
public void run() {
System.out.println(this.name + "is a dog,正在跑");
}
@Override
public void swim() {
System.out.println(this.name + "is a dog,正在游泳");
}
}
我再添加一个Cat
类与Duck
类,最终代码如下
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(this.name + "正在吃");
}
}
interface IFlying {
void fly();//接口里面的方法是不能实现的
}
interface IRunning {
void run();
}
interface ISwimming {
void swim();
}
class Dog extends Animal implements IRunning,ISwimming {
//1.先要构造父类
public Dog(String name) {
super(name);
}
//2.要重写接口的方法
@Override
public void run() {
System.out.println(this.name + "is a dog,正在跑步");
}
@Override
public void swim() {
System.out.println(this.name + "is a dog,正在游泳");
}
}
class Cat extends Animal implements IRunning {
public Cat(String name) {
super(name);
}
@Override
public void eat() {
System.out.println(this.name + "is a cat,正在吃饭");
}
@Override
public void run() {
System.out.println(this.name + "is a cat,正在跑步");
}
}
class Duck extends Animal implements IRunning,ISwimming,IFlying {
public Duck(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(this.name + "is a duck,正在飞翔");
}
@Override
public void run() {
System.out.println(this.name + "is a duck,正在跑步");
}
@Override
public void swim() {
System.out.println(this.name + "is a duck,正在游泳");
}
}
因为狗,猫,鸭子既继承了Animal
,又实现了不同的接口,我们的main
方法就可以实现向上转型的操作
public static void main(String[] args) {
//父类引用 引用 子类对象
Animal animal1 = new Cat("小猫");
Animal animal2 = new Dog("小狗");
Animal animal3 = new Duck("小鸭");
//接口也可以实现向上转型,代表这个动物也具备这种功能
IRunning iRunning = new Cat("小猫");
IRunning iRunning1 = new Dog("小狗");
IRunning iRunning2 = new Duck("小鸭");
ISwimming iSwimming = new Dog("小狗");
ISwimming iSwimming1 = new Duck("小鸭");
IFlying iFlying = new Duck("小鸭");
}
还可以进行多态
public class TestDemo {
public static void run(IRunning iRunning) {
iRunning.run();
}
public static void swim(ISwimming iSwimming) {
iSwimming.swim();
}
public static void fly(IFlying iFlying) {
iFlying.fly();
}
public static void main(String[] args) {
Dog dog = new Dog("Timi");
run(dog);
//还可以这样写
run(new Cat("杰西卡"));
run(new Duck("布鲁斯"));
}
}
那么此时,run
这个方法只要是能跑的,就可以传过来进行多态(即传过来的对象在类的实现阶段就对IRunning
接口进行实现的,就可以实现多态),我们还可以在创建一个机器人类,让他对IRunning
进行接口,虽然他不是动物,但是他实现了IRunning
接口就可以进行多态
class Robot implements IRunning {
@Override
public void run() {
System.out.println("机器人在跑步");
}
}
public static void run(IRunning iRunning) {
iRunning.run();
}
public static void main(String[] args) {
Dog dog = new Dog("Timi");
run(dog);
//还可以这样写
run(new Cat("杰西卡"));
run(new Duck("布鲁斯"));
//机器人虽然没有继承Animal类,但是他实现了IRunning接口,就可以进行多态
run(new Robot());
}
那么没有实现对应接口的,那么他就完成不了对应接口进行的多态
public class TestDemo {
public static void fly(IFlying iFlying) {
iFlying.fly();
}
public static void main(String[] args) {
fly(new Dog("不会飞的狗狗"));
}
2.5 接口之间的继承
类和接口之间是implements
,接口和接口之间是extends
,在这里为扩展的意思
interface A {
void funcA();
}
interface B {
void funcB();
}
//代表C具备了A B的功能
interface C extends A,B {
void funcC();
}
那么我们要是用一个普通的类去实现C
接口,就需要重写A
,B
,C
的功能(因为C
实现了A
,B
接口,具备了他们俩的功能)
我们有一个快捷键:定位在错误行按下Alt+回车
,编译器会帮我们重写所有需要重写的方法
class AA implements C {
@Override
public void funcA() {
}
@Override
public void funcB() {
}
@Override
public void funcC() {
}
}
2.6 三个重要的接口
我们之前学习了Arrays.sort()
这个操作,这个操作是用来对数组排序的
import java.util.Arrays;
public class TestDemo1 {
public static void main(String[] args) {
int[] array = {1,34,43,22,2};
Arrays.sort(array);
System.out.println(Arrays.toString(array));
}
}
那么假如我们的数组不仅仅是个简单的数组呢,比如我们创建了一个学生数组,我们想要去进行比较
import java.util.Arrays;
class Student {
public String name;
public int age;
public double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
public class TestDemo1 {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student("叶澜依",20,98.5);
students[1] = new Student("齐妃",39,79.4);
students[2] = new Student("华妃",19,89.4);
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}
这里报错了,因为我们根本没指定我们到底要比较什么,所以默认会比较地址,但是在Java
中是不支持引用比较的,所以就会报错。
比如这样:
public static void main(String[] args) {
Student student1 = new Student("张三",20,39);
Student student2 = new Student("李四",30,49);
//Java里面不支持这样的大于小于比较
if(student1 > student2) {
}
}
我们可以看见报错信息里面有一个Comparable
,这是什么呢,我们查询,就知道了,这是个构造器
那么接口就会有他的抽象方法
接下来,我们就深入学习构造器
2.6.1 构造器(Comparable 接口)
构造器的使用方法是
-
在类的后面加上
implements Comparable<T>
,T
代表的是比较对象(即前面的类名)class Student implements Comparable<student> { }
-
要重写
comparaTo
方法我们
ctrl
+单击Comparable
这个接口往下翻,我们会发现
Comparable
有个抽象方法comparaTo
,这代表我们继承Comparable
接口就需要重写comparaTo
这个方法
那么,针对于刚才的例子,我们现在把类实现了构造类接口,并把里面的方法重写了
class Student implements Comparable<Student> {
public String name;
public int age;
public double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
@Override
public int compareTo(Student o) {
}
}
现在,假如我们需要比较学生的年龄,我们就可以重写compareTo
方法
class Student implements Comparable<Student> {
public String name;
public int age;
public double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
@Override
public int compareTo(Student o) {
//返回大于0的数表示this.age>o.age
//返回小于0的数表示o.age>this.age
//返回等于0的数表示this.age = o.age
return this.age - o.age;
}
}
public class TestDemo1 {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student("叶澜依",20,98.5);
students[1] = new Student("齐妃",39,79.4);
students[2] = new Student("华妃",19,89.4);
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}
如果我们想要实现从大到小,那我们可以return o.age-this.age
class Student implements Comparable<Student> {
public String name;
public int age;
public double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
@Override
public int compareTo(Student o) {
return o.age - this.age;
}
}
public class TestDemo1 {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student("叶澜依",20,98.5);
students[1] = new Student("齐妃",39,79.4);
students[2] = new Student("华妃",19,89.4);
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}
当我们只有两个对象比较的时候,我们还可以这么写
class Student implements Comparable<Student> {
public String name;
public int age;
public double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
@Override
public int compareTo(Student o) {
return this.age - o.age;
}
}
public class TestDemo1 {
public static void main(String[] args) {
Student student1 = new Student("张三",20,39);
Student student2 = new Student("李四",30,49);
if(student1.compareTo(student2) > 0) {
System.out.println(student1.name + "年龄大");
} else {
System.out.println(student2.name + "年龄大");
}
}
}
那么比较姓名以及比较分数同理,就不再详细讲解了
比较姓名
class Student implements Comparable<Student> {
public String name;
public int age;
public double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
@Override
public int compareTo(Student o) {
return this.name.compareTo(o.name);//字符串的比较要用compareTo方法
}
}
public class TestDemo1 {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student("zhangsan",20,98.5);
students[1] = new Student("lisi",39,79.4);
students[2] = new Student("wangwu",19,89.4);
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}
比较成绩
class Student implements Comparable<Student> {
public String name;
public int age;
public double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
@Override
public int compareTo(Student o) {
return (int)(this.score - o.score);//注意要强转成int,因为compareTo的源码就是int类型的
}
}
public class TestDemo1 {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student("zhangsan",20,98.5);
students[1] = new Student("lisi",39,79.4);
students[2] = new Student("wangwu",19,89.4);
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}
如果我们比较的成绩假如都是98.x
这种的,就会排序失败
我们就需要对方法重写里面实现的逻辑进行更改
import java.util.Arrays;
class Student implements Comparable<Student> {
public String name;
public int age;
public double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
/*@Override
public int compareTo(Student o) {
return this.age - o.age;
}*/
/*@Override
public int compareTo(Student o) {
return this.name.compareTo(o.name);//字符串的比较要用compareTo方法
}*/
@Override
public int compareTo(Student o) {
if(this.score - o.score > 0) {
return 1;
} else if(this.score - o.score < 0) {
return -1;
} else {
return 0;
}
//return (int)(this.score - o.score);//注意要强转成int
}
}
public class TestDemo1 {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student("zhangsan",20,98.5);
students[1] = new Student("lisi",39,98.4);
students[2] = new Student("wangwu",19,98.6);
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}
要注意的是,构造器只能同时比较一种对象,比如比较成绩的时候就比较不了年龄,那么我们想要既比较成绩,又比较年龄的话,那么我们就需要使用到比较器
2.6.2 比较器(Comparator 接口)
比较器的使用方法是:
定义一个类,在类的后面加上implements Comparator<T>
,T
为要比较的类
class AgeComparator implements Comparator<Student> {
}
我们ctrl+单击
Comparator
可以看见他的抽象方法
所以,我们重写之后的代码是这样的
import java.util.Arrays;
import java.util.Comparator;
class Student2 {
public String name;
public int age;
public double score;
public Student2(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student2{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
class AgeComparator implements Comparator<Student2> {
@Override
public int compare(Student2 o1, Student2 o2) {
return o1.age - o2.age;
}
}
然后在main
方法,我们需要创建一个比较器对象,通过Arrays.sort(要排序的数组,比较器)
进行比较(Arrays.sort
可以有第二个参数的)
public class TestDemo2 {
public static void main(String[] args) {
Student2[] student2 = new Student2[3];
student2[0] = new Student2("zhangsan",20,87.6);
student2[1] = new Student2("lisi",30,67.5);
student2[2] = new Student2("wangwu",40,98.3);
//创建一个比较器对象
AgeComparator ageComparator = new AgeComparator();
//Arrays.sort也可以有两个参数,一个是要排序的数组,一个是比较器
Arrays.sort(student2,ageComparator);
System.out.println(Arrays.toString(student2));
}
}
那么这样,我们还可以创建成绩比较器、姓名比较器,只需要针对不同的比较实现不同的重写方法即可
import java.util.Arrays;
import java.util.Comparator;
class Student2 {
public String name;
public int age;
public double score;
public Student2(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student2{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
class AgeComparator implements Comparator<Student2> {
@Override
public int compare(Student2 o1, Student2 o2) {
return o1.age - o2.age;
}
}
class ScoreComparator implements Comparator<Student2> {
@Override
public int compare(Student2 o1, Student2 o2) {
return (int)(o1.score - o2.score);
}
}
class NameComparator implements Comparator<Student2> {
@Override
public int compare(Student2 o1, Student2 o2) {
return o1.name.compareTo(o2.name);
}
}
public class TestDemo2 {
public static void main(String[] args) {
Student2[] student2 = new Student2[3];
student2[0] = new Student2("zhangsan",20,87.6);
student2[1] = new Student2("lisi",30,67.5);
student2[2] = new Student2("wangwu",40,98.3);
//AgeComparator ageComparator = new AgeComparator();
//ScoreComparator scoreComparator = new ScoreComparator();
NameComparator nameComparator = new NameComparator();
//Arrays.sort(student2,ageComparator);
//Arrays.sort(student2,scoreComparator);
Arrays.sort(student2,nameComparator);
System.out.println(Arrays.toString(student2));
}
}
运行截图:
2.6.3 Clonable接口和深拷贝
在讲解这个知识点之前,我们先引出个例子
class Person {
public int id = 1234;
@Override
public String toString() {
return "Person{" +
"id=" + id +
'}';
}
}
public class TestDemo1 {
public static void main(String[] args) {
Person person1 = new Person();
}
}
我们来画一下他的内存分配图
现在我们想拷贝这个引用所指向的对象,就需要进行克隆
class Person {
public int id = 1234;
@Override
public String toString() {
return "Person{" +
"id=" + id +
'}';
}
}
public class TestDemo1 {
public static void main(String[] args) {
Person person1 = new Person();
Person person2 = person1.clone();//克隆必须调用克隆方法
//Person person2 = new Person();//这样是不行的
}
}
那么接下来我来给大家介绍一下满足克隆需要两个前提:
-
这个对象是可以被克隆的
自定义类型想要克隆,类名后面需要加上
implements Cloneable
那么我们加上之后,
class Person implements Cloneable { public int id = 1234; @Override public String toString() { return "Person{" + "id=" + id + '}'; } }
那么我们就看一看
Cloneable
到底是怎么回事,ctrl+单击
Cloneable
,我们可以发现里面有一个空接口这个接口里面只有一个空接口,没有任何一个方法。这个空接口(也叫标记接口)代表当前类可以被克隆,但是目前还不能被克隆
我们还需要在
Person
类里面重写Object
的clone
方法 -
重写
Object
的克隆方法我们还要注意的是,代码自动为我们抛出了异常,我们暂时不需要去关注,知道有这么一回事就行了
代码如下:
class Person implements Cloneable { public int id = 1234; @Override protected Object clone() throws CloneNotSupportedException { return super.clone();//调用父类的clone方法->帮助我们克隆 } @Override public String toString() { return "Person{" + "id=" + id + '}'; } }
那么这时候,我们再去
.
,就可以了那么怎么解决报错呢?
这里大家就记下来吧
class Person implements Cloneable { public int id = 1234; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public String toString() { return "Person{" + "id=" + id + '}'; } } public class TestDemo1 { public static void main(String[] args) throws CloneNotSupportedException { Person person1 = new Person(); Person person2 = (Person)person1.clone(); System.out.println(person2); } }
运行结果:
内存分配图:
那么我们上面讲解的是基本数据类型,那涉及到引用数据类型呢?怎么解决
我们来对刚才的那个类进行修改
class Money {
public double money = 19.9;
}
class Person implements Cloneable {
public int id = 1234;
public Money m = new Money();
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
'}';
}
}
public class TestDemo1 {
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person();
Person person2 = (Person)person1.clone();
System.out.println(person1.m.money);
System.out.println(person2.m.money);
System.out.println("------------------");
person2.m.money = 99.9;
System.out.println(person1.m.money);
System.out.println(person2.m.money);
}
}
我们新增加了一个Money
类,作为Person
类的一个数据成员,这就代表了Money
类还包括了引用成员。在通过clone
之后,我们先来看一下是否克隆成功以及person1
person2
都有多少钱,这时候我们修改Person2
的钱,再去打印他们俩的钱
这其实就是浅拷贝,我们画一下内存分配图大家就理解了
其实就是调用clone
方法,把中间部分照抄一遍,发现里面的引用变量m
里面的地址没换,还是0x55
,还是连接到了Person1
的money
上了
那么接下来,给大家讲解一下深拷贝。
深拷贝的目的就是:
Person2.m.money = 99.9
,让这条语句只能去修改克隆出来的person2
,而不去修改Person1
要想达到深拷贝,我们也需要把money
(最右边的圈)拷贝一份,所以我们也需要让Money
类去继承Cloneable
接口
class Money implements Cloneable {
public double money = 19.9;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
那么我们运行程序,发现还是浅拷贝
因为Person
类里面的clone
方法返回的还是super.clone()
,我们需要在这里改变一下返回的策略
@Override
protected Object clone() throws CloneNotSupportedException {
Person tmp = (Person)super.clone();//先把拷贝出来的放到一个tmp中(把中间的方框先保存到tmp中)
tmp.m = (Money)this.m.clone();//中间方框里面的框框以及后面的圆圈也拷贝到tmp中
return tmp;//返回tmp这个临时的空间,用person2接收
}
整个代码如下:
class Money implements Cloneable {
public double money = 19.9;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Person implements Cloneable {
public int id = 1234;
public Money m = new Money();
@Override
protected Object clone() throws CloneNotSupportedException {
Person tmp = (Person)super.clone();//先把拷贝出来的放到一个tmp中(把中间的方框先保存到tmp中)
tmp.m = (Money)this.m.clone();//中间方框里面的框框以及后面的圆圈也拷贝到tmp中
return tmp;//返回tmp这个临时的空间,用person2接收
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
'}';
}
}
public class TestDemo1 {
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person();
Person person2 = (Person)person1.clone();
System.out.println(person1.m.money);
System.out.println(person2.m.money);
System.out.println("------------------");
person2.m.money = 99.9;
System.out.println(person1.m.money);
System.out.println(person2.m.money);
}
}
内存分配如下:
那么我们来分析:深拷贝,是从代码层次上来实现的,而不是说某个方法是深拷贝
要想达到深拷贝,那么每个对象中,如果有引用的话,那么这个对象的引用所指的对象也需要进行拷贝。
拷贝完成之后,通过这个引用修改其指向的数据,原来的不会发生改变,这就是深拷贝
深拷贝要考虑的两个方向:
- 要拷贝的数据类型(简单类型)
- 拷贝的代码