java抽象类和接口

抽象类

抽象类的概念

在面向对象的概念中,所有的对象都是通过类来描绘的,但是并不是所有的类都是用来描述对象的,如果一个类中没有足够的信息来描绘一个具体的对象,这样的类就是抽象类。
例如我们 上篇文章 中的代码例子中的 Animal 类中并没有实际意义的方法,在之后的子类都进行了重写,而且该类并不能直接表示某种动物,例如狗叫是汪汪汪,猫叫是喵喵喵,并不能表示直接具体的动物,所以该类可以设计为抽象类。另一个例子 shape 类画图也是同样可以设计为抽象类。
像draw这种没有实际工作的方法,我们可以将其设置为抽象方法,包含抽象方法的类我们称为抽象类。

抽象类语法

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

//抽象类:被 abstract 修饰的类
public abstract class Shape {
    //抽象方法:被abstract修饰的方法,没有方法体
    public abstract void draw();
    public abstract void calcArea();

    //抽象类也是类,可以增加普通方法和属性
    protected double Area;      //面积
    
    public double getArea(){
        return Area;
    }
}

注意:抽象类也是类,内部可以包含普通方法和属性,甚至构造方法,抽象类和普通类的区别在于抽象类中可以包含抽象方法,如果这个类中有抽象方法,那这个类一定是抽象类。

抽象类特性

  1. 抽象类不能直接实例化对象。
public class Test {
    public static void main(String[] args) {
        Shape shape = new Shape();  //编译报错
    }
}
  1. 抽象方法不能是 private 修饰
public abstract class Shape {
    private abstract void draw();	//编译报错
}
  1. 抽象方法不能被final 和 static修饰,因为抽象方法要在子类中重写
public abstract class Shape {
    public final abstract void draw();				//编译报错
    public static abstract void calcArea();			//编译报错
}
  1. 抽象类必须被继承,并且继承后子类必须要重写父类中的抽象方法,否则子类也应该是抽象类,必须使用abtract修饰
public class Rect extends Shape{
    private double length;
    private double width;

    public Rect(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override
    public void draw() {
        System.out.println("矩形:length = " + length + "width = " + width);
    }

    @Override
    public void calcArea() {
        Area = length * width;
    }
}
  1. 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
  2. 抽象类可以有构造方法,供子类创建对象时,初始化父类成员变量

抽象类的作用

抽象类本身不能被实例化,要想使用,只能创建该抽象类的子类,然后让子类来重写抽象类中的方法。
但是普通的类也可以被继承,普通的方法也可以被重写,为什么非得用抽象类和抽象方法呢?
使用抽象类相当于多了一重编译器的校验。
使用抽象类场景的代码如上面所示,实际工作不应该由父类来完成,而应由子类来完成。此时如果不小心使用成父类,使用普通类编译器是不会报错的,但是如果父类是抽象类的话,就会在实例化的时候提示错误,让我们尽早发现问题。

接口

接口的概念

现实生活中接口的例子有很多,如笔记本上的USB接口,电源插座等等。
笔记本上的USB接口可以插:U盘,鼠标,键盘···所有符合USB协议的的设备
电源插座可以插:电脑,电视机,电饭煲···所有符合规范的设备
通过上述例子可以看出:接口就是公共的行为规范标准,大家在实现时只要符合规范标准就可以通用
在java中,接口可以看成:多个类的公共规范,是一种引用数据类型。

语法规则

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

public interface 接口名称{
	//抽象方法
	public abstract void method1();	//public abstract是固定搭配,可以不写
	public void method2();
	abstract void method3();
	void method4();
}
//注意:在接口上述写法都是抽象方法,更推荐写法4,代码更简洁。

提示:

  1. 创建接口时,接口的命名一般以大写字母 I 开头。
  2. 接口的名字一般使用形容词性的单词
  3. 阿里编码规范中约定,接口中的方法和属性不要加任何修饰符号,保持代码的简洁性。

接口特性

  1. 接口是一种引用数据类型,但是不能直接new接口的对象。
public class Test {
    public static void main(String[] args) {
        IShape iShape = new IShape();   //编译报错,IShape是抽象的; 无法实例化
    }
}
  1. 接口当中的成员变量默认是public static final修饰的
public interface IShape {
    public static final int a = 1;
    int b = 2;
}
  1. 接口当中的成员方法默认是public static修饰的
public interface IShape {
    public abstract void draw();
    void draw1();
}
  1. 因为接口中的方法默认是public abstract修饰的,所以该方法的具体实现只能由实现接口的类来实现,在接口内不可以实现。
  2. 如果接口当中的方法被default修饰,该方法是可以具体实现的。
public interface IShape {
    default void test(){
        System.out.println("接口中的方法被default修饰是可以具体实现的");
    }
}
  1. 如果接口当中的方法被static修饰,该方法是可以具体实现的。
public interface IShape {
    static void test(){
        System.out.println("接口中的方法被static修饰是可以具体实现的");
    }
}
  1. 类和接口之间可以使用关键字 implements 来实现接口
public interface IShape {
    void draw();
}
public class Cycle implements IShape{
    @Override
    public void draw() {
        System.out.println("画一个⚪");
    }
}
  1. 重写接口当中的方法时,不能使用默认的访问权限
public interface IShape {
    void draw();    //默认是public static修饰的
}
public class Cycle implements IShape{

    @Override
    void draw() {
        System.out.println("画一个⚪");
    }
    //编译报错
    //java: Cycle中的draw()无法实现IShape中的draw()
  	//正在尝试分配更低的访问权限; 以前为public,default默认权限比public权限高,所以报错,
  	//要高于等于public权限,所以应该使用public修饰
}
  1. 一个接口对应一个字节码文件
  2. 如果一个类不想实现接口当中的方法,那么此时这个类可以被定义为抽象类,但是这个类如果被继承,那么就得实现所有没被实现的方法。

接口的使用

接口不能直接使用,必须要有一个实现类来实现该接口,实现接口中的所有抽象方法。
子类和父类之间是 extends 继承关系,类与接口之间是 implements 实现关系。

请实现笔记本电脑使用USB鼠标、USB键盘的例子:

  1. USB接口:包含打开设备、关闭设备功能
  2. 笔记本类:包含开机功能、关机功能、使用USB设备功能
  3. 鼠标类:实现USB接口,并具备点击功能
  4. 键盘类:实现USB接口,并具备输入功能
public interface USB {
    void openDevice();
    void closeDevice();
}
public class Mouse implements USB{
    @Override
    public void openDevice() {
        System.out.println("打开鼠标设备");
    }

    @Override
    public void closeDevice() {
        System.out.println("关闭鼠标设备");
    }

    public void click(){
        System.out.println("点击鼠标");
    }
}
public class KeyBoard implements USB{
    @Override
    public void openDevice() {
        System.out.println("打开键盘设备");
    }

    @Override
    public void closeDevice() {
        System.out.println("关闭键盘设备");
    }

    public void input(){
        System.out.println("键盘输入");
    }
}
public class Computer implements USB{
    @Override
    public void openDevice() {
        System.out.println("开机");
    }

    @Override
    public void closeDevice() {
        System.out.println("关机");
    }

    public void useUSBDevice(USB usb){
        usb.openDevice();
        if(usb instanceof Mouse){
            Mouse mouse = (Mouse)usb;
            mouse.click();
        }else if(usb instanceof KeyBoard){
            KeyBoard keyBoard = new KeyBoard();
            keyBoard.input();
        }
        usb.closeDevice();
    }
}
public class Test {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.openDevice();
        computer.useUSBDevice(new Mouse());
        computer.useUSBDevice(new KeyBoard());
        computer.closeDevice();
    }
}
//开机
//打开鼠标设备
//点击鼠标
//关闭鼠标设备
//打开键盘设备
//键盘输入
//关闭键盘设备
//关机

实现多个接口

在java中,类和类之间是单继承的,一个类只能有一个父类,即java中不支持多继承,但是一个类可以实现多个接口。
下面通过动物的例子来理解

public class Animal {
    protected String name;

    public Animal(String name) {
        this.name = name;
    }
}
public interface IRunning {
    void run();
}
public interface IFlying {
    void fly();
}
public interface ISwimming {
    void swim();
}
public class Cat extends Animal implements IRunning{

    public Cat(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(this.name + "正在跑");
    }
}
public class Fish extends Animal implements ISwimming{
    public Fish(String name) {
        super(name);
    }

    @Override
    public void swim() {
        System.out.println(this.name + "正在游泳");
    }
}
public class Frog extends Animal implements ISwimming,IRunning{
    public Frog(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(this.name + "正在跑步");
    }

    @Override
    public void swim() {
        System.out.println(this.name + "正在游泳");
    }
}
public class Duck extends Animal implements IRunning,ISwimming,IFlying{
    public Duck(String name) {
        super(name);
    }

    @Override
    public void fly() {
        System.out.println(this.name + "正在飞");
    }

    @Override
    public void run() {
        System.out.println(this.name + "正在跑步");
    }

    @Override
    public void swim() {
        System.out.println(this.name + "正在游泳");
    }
}

上面的代码展示了java面向对象编程中常用的用法:一个类继承一个父类,同时实现多种接口。
继承表达的含义是is-a的含义,而接口表达的含义是具有xxx属性

猫是一种动物,具有会跑的属性
鱼是一种动物,具有会游的属性
青蛙是一种动物,具有会跑、游的属性
鸭子是一种动物,具有会跑、游、飞的属性

这样设计的好处是让程序员忘记类型,有了接口之后,类的使用者不必关注具体类型,而只关注某个类是否具备某种能力
例如:我们实现一个散步的方法

public class Test {
    public static void walk(IRunning iRunning){
        iRunning.run();
    }

    public static void main(String[] args) {
        Cat cat = new Cat("猫");
        walk(cat);
        Frog frog = new Frog("青蛙");
        walk(frog);
    }
}
//结果为:
//猫正在跑
//青蛙正在跑步

而且参数可以不是动物,只要是具有跑的属性,实现了实现跑步接口的类都可以

public class Robot implements IRunning{
    protected String name;

    public Robot(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println(this.name + "正在跑");
    }
}
public class Test {
    public static void walk(IRunning iRunning){
        iRunning.run();
    }

    public static void main(String[] args) {
        Cat cat = new Cat("猫");
        walk(cat);
        Frog frog = new Frog("青蛙");
        walk(frog);
        Robot robot = new Robot("罗伯特");
        walk(robot);
    }
}
//结果为:
//猫正在跑
//青蛙正在跑步
//罗伯特正在跑

接口间的继承

在java中,类和类之间是单继承的,一个类可以实现多个接口,接口和接口之间可以多继承,即用接口可以实现多继承的目的。
接口可以继承一个接口,达到复用,使用extends关键字

public interface IRunning {
    void run();
}
public interface ISwimming {
    void swim();
}
//两栖动物既能跑,也能游
public interface IAmphibious extends IRunning,ISwimming{

}
public class Frog extends Animal implements IAmphibious{

    public Frog(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(this.name + "正在跑步");
    }

    @Override
    public void swim() {
        System.out.println(this.name + "正在游泳");
    }
}

通过接口继承创建一个新的接口IAmphibious表示两栖的,此时实现接口创建的Frog类,就要继承实现run方法和swim方法,接口间的继承相当于把多个接口合并在一起。

Object类

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

public class Person {
}
public class Student {
}
public class Test {
    public static void main(String[] args) {
        function(new Person());
        function(new Student());
    }
    public static void function(Object object){
        System.out.println(object);
    }
}
//结果为
//Person@3b07d329
//Student@404b9385

所以在开发中,Object类是参数的最高统一类型,而且Object类也存在一些定义好的方法
在这里插入图片描述
我们先熟悉红色方框中三种方法:toString()方法,hashCode()方法,equals()方法

获取对象信息

如果要打印对象中的信息,可以直接重写Object类中的toString方法,首先我们先看Object类中toString方法实现,当我们使用 System.out.println()方法就会调用Object类中toString方法,如果我们要打印自己需要的信息则需要重写该方法。

public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
public class Student {
    public String name;
    public int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("张三",18);
        System.out.println(student1);
    }
}
//结果为:
//Student@3b07d329
public class Student {
    public String name;
    public int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("张三",18);
        System.out.println(student1);
    }
}
//结果为:
//Student{name='张三', age=18}

对象比较equals方法

在java中,== 进行比较时:

  1. 如果 == 左右两侧是基本类型变量,比较的是变量中的值是否相同;
  2. 如果 == 左右两侧是引用类型变量,比较的是引用变量地址是否相同;
  3. 如果要比较对象中的内容,必须重写Object中的equals方法,因为equals方法 ,因为equals方法默认也是按照地址比较的:
//Object类中的equals方法
public boolean equals(Object obj) {
        return (this == obj);	//使用引用中的地址直接来进行比较
    }
public class Student {
    public String name;
    public int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("张三",18);
        Student student2 = new Student("张三",18);
        int a = 10;
        int b = 10;
        System.out.println(a == b);
        System.out.println(student1 == student2);
        System.out.println(student1.equals(student2));
    }
}
//结果为:
//true
//false
//false
public class Student {
    public String name;
    public int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if(o == null){
           return false;
        }
        if(o == this){
            return true;
        }
        //不是Student类对象
        if(!(o instanceof Student)){
            return false;
        }
        Student student = (Student)o;
        return this.name.equals(student.name) && this.age == student.age;
    }
}
public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("张三",18);
        Student student2 = new Student("张三",18);
        int a = 10;
        int b = 10;
        System.out.println(a == b);
        System.out.println(student1 == student2);
        System.out.println(student1.equals(student2));
    }
}
//结果为:
//true
//false
//true

hashCode()方法

如我们刚才看到的toString方法的源码中

public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

在源码中我们看到了hashCode()这个方法,帮我们找到了对象的具体位置,这里面涉及到数据结构,我们先简单的理解为给了我们内存地址,然后调用Integer.toHexString()方法,将这个地址以16进制输出。

hashCode方法源码

public native int hashCode();

该方法是一个native方法,底层是由C/C++代码写的。
我们认为两个名字相同,年龄相同的对象,将存储在同一个位置,如果不重写hashCode()方法,他们的位置是不同的。

public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("张三",18);
        Student student2 = new Student("张三",18);
        System.out.println(student1.hashCode());
        System.out.println(student2.hashCode());
    }
}
//结果为:
//990368553
//1096979270

两个对象的hash值不一样
我们对hashCode()方法进行重写

public class Student {
    public String name;
    public int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}
public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("张三",18);
        Student student2 = new Student("张三",18);
        System.out.println(student1.hashCode());
        System.out.println(student2.hashCode());
    }
}
//结果为:
//24022538
//24022538

哈希值一样。
结论:

  1. hashCode()方法用来确定对象在内存中存储的位置是否相同
  2. 事实上hashCode()方法在散列表中才有用,在其他情况下没用,在散列表中hashCode()的作用是获取对象的散列码,进而确定该对象在散列表中的位置。

匿名内部类

匿名内部类就是没有名字且在其他内部类中的一个类
语法格式:

new 类名或接口名{
	重写方法;
}

我们知道接口是没办法实例化的,如果使用匿名内部类就可以很好的解决这个问题

public interface Animal {
    void run();
}
public class Out {
    public void test(){
        System.out.println("out()方法");
    }
}
public class Test {
    public static void main(String[] args) {

        new Out(){

        }.test();

        new Out(){
            public void test(){
                System.out.println("haha");
            }
        }.test();

        new Animal(){
            public void run(){
                System.out.println("狗在跑");
            }
        }.run();
    }
}
//结果为:
//out()方法
//haha
//狗在跑

如果我们没有使用匿名内部类,我们要输出狗在跑,我们需要以下代码:

public interface Animal {
    void run();
}
public class Dog implements Animal {
    @Override
    public void run() {
        System.out.println("狗在跑");
    }
}
public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.run();
    }
}

接口的使用实例

给对象数组排序

public class Student {
    public String name;
    public int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class Test {
    public static void main(String[] args) {
        //我们想将三名学生按照年龄进行排序,是否可以如之前数组排序一样调用sort方法呢
        Student[] students = new Student[3];
        students[0] = new Student("张三",18);
        students[1] = new Student("李四",16);
        students[2] = new Student("王五",20);
        Arrays.sort(students);

    }
}
//运行出错,抛出异常
//Exception in thread "main" java.lang.ClassCastException:...

我们进一步思考,我们可以发现和以前的普通整数不一样,整数是可以直接比较的,大小关系明确,而两个学生对象的大小关系怎么确定,需要我们额外指定。
在sort方法中要求比较的对象要实现Comparable接口,并实现其中的compareTo方法

public class Student implements Comparable<Student>{
    public String name;
    public int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(Student o) {
        if(this.age > o.age){
            return 1;
        }else if(this.age < o.age){
            return -1;
        }else{
            return 0;
        }
    }
}
public class Test {
    public static void main(String[] args) {
        //我们想将三名学生按照年龄进行排序,是否可以如之前数组排序一样调用sort方法呢
        Student[] students = new Student[3];
        students[0] = new Student("张三",18);
        students[1] = new Student("李四",16);
        students[2] = new Student("王五",20);
        Arrays.sort(students);
        System.out.println(Arrays.toString(students));

    }
}
//结果为:
//[Student{name='李四', age=16}, Student{name='张三', age=18}, Student{name='王五', age=20}]

在sort方法中会自动调用compareTo方法,compareTo方法的参数是Object,其实传入的是Student类型的对象。然后比较当前对象和参数对象的大小关系(按照年龄大小)
如果当前对象应排在参数对象之前的话,则返回小于0的数字;
如果当前对象应排在参数对象之后的话,则返回大于0的数字;
如果当前对象和参数对象不分先后,则返回0.
为了进一步加深对接口的理解,我们可以尝试自己实现一个sort方法来完成和刚才的排序过程(使用冒泡排序)

public class Test {

    public static void mySort(Comparable[] comparable){
        for (int i = 0; i < comparable.length - 1; i++) {
            for (int j = 0; j < comparable.length - 1 - i; j++) {
                if (comparable[j].compareTo(comparable[j+1]) > 0){
                    Comparable tmp = comparable[j];
                    comparable[j] = comparable[j+1];
                    comparable[j+1] = tmp;
                }
            }
        }
    }

    public static void main(String[] args) {
        //我们想将三名学生按照年龄进行排序,是否可以如之前数组排序一样调用sort方法呢
        Student[] students = new Student[3];
        students[0] = new Student("张三",18);
        students[1] = new Student("李四",16);
        students[2] = new Student("王五",20);
        /*Arrays.sort(students);*/
        mySort(students);
        System.out.println(Arrays.toString(students));

    }
}
//结果为:
//[Student{name='李四', age=16}, Student{name='张三', age=18}, Student{name='王五', age=20}]

如果我们现在希望通过名字来排序的话我们就应该再次修改compareTo方法

public class Student implements Comparable<Student>{
    public String name;
    public int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(Student o) {
        return this.name.compareTo(o.name);
    }
}
public class Test {
    public static void main(String[] args) {
        //将三名学生按照名字进行排序
        Student[] students = new Student[3];
        students[0] = new Student("张三",18);
        students[1] = new Student("李四",16);
        students[2] = new Student("王五",20);
        Arrays.sort(students);
        System.out.println(Arrays.toString(students));

    }
}
//结果为:
//[Student{name='张三', age=18}, Student{name='李四', age=16}, Student{name='王五', age=20}]

这种比较的方式一般用于固定的比较,不适合非常灵活的比较,每次我们想换一种比较方式则需要修改compareTo方法,比较麻烦,解决方法就是使用另一种接口。
首先只要是自定义类型,涉及到了大小的比较,目前的结论是一定要实现Comparable接口
我们根据不同的属性进行比较,但是不想每次修改已经写好的方法,我们可以换一个接口Comparator这个接口来解决。

import java.util.Comparator;
public class NameComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.name.compareTo(o2.name);
    }
}

import java.util.Comparator;
public class AgeComparator implements Comparator<Student> {

    @Override
    public int compare(Student o1, Student o2) {
        return o1.age - o2.age;
    }
}
public class Test {
    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("张三",18);
        students[1] = new Student("李四",16);
        students[2] = new Student("王五",20);
        NameComparator nameComparator = new NameComparator();
        Arrays.sort(students,nameComparator);
        System.out.println(Arrays.toString(students));
    }
}
//结果为:
//[Student{name='张三', age=18}, Student{name='李四', age=16}, Student{name='王五', age=20}]
public class Test {
    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("张三",18);
        students[1] = new Student("李四",16);
        students[2] = new Student("王五",20);
        AgeComparator ageComparator = new AgeComparator();
        Arrays.sort(students,ageComparator);
        System.out.println(Arrays.toString(students));
    }
}
//结果为:
//[Student{name='李四', age=16}, Student{name='张三', age=18}, Student{name='王五', age=20}]

如何通过方法交换实参的值

public class Test {
    public static void swap(int a, int b){
        int tmp = a;
        a = b;
        b = tmp;
    }

    public static void main(String[] args) {
        int value1 = 10;
        int value2 = 20;
        System.out.println("交换前: value1 = " + value1 + " value2 = " + value2 );
        swap(value1 ,value2 );
        System.out.println("交换后: value1 = " + value1 + " value2 = " + value2 );
    }
}
//结果为:
//交换前: value1 = 10 value2 = 20
//交换后: value1 = 10 value2 = 20

在这里插入图片描述
只是交换了形参的值,而对实参的值并没有任何改变

public class Value {
    public Value(int value) {
        this.value = value;
    }

    public int value;
}
public class Test {
    public static void swap(Value v1, Value v2){
        int tmp = v1.value;
        v1.value = v2.value;
        v2.value = tmp;
    }
    public static void main(String[] args) {
        Value value1 = new Value(10);
        Value value2 = new Value(20);
        System.out.println("交换前: value1 = " + value1.value + " value2 = " + value2.value );
        swap(value1 ,value2 );
        System.out.println("交换后: value1 = " + value1.value + " value2 = " + value2.value );
    }
}
//结果为:
//交换前: value1 = 10 value2 = 20
//交换后: value1 = 20 value2 = 10

在这里插入图片描述

Clonable接口和深拷贝

java中内置了一些很有用的接口,Clonable就是其中之一。
Object类中存在一个clone方法,调用这个方法可以创建一个对象的“拷贝”,但是要想合法调用clone方法,必须要实现Clonable接口,否则就会抛出CloneNotSupportedException异常。
clone方法的源代码如下:

protected native Object clone() throws CloneNotSupportedException;

由protected修饰的,是Object类中的方法,限定再同一个包或不同包的子类中调用,我们想要调用需要我们在其子类进行重写该方法。
返回值是Object方法,所以要对进行强转后赋值给克隆对象。
该方法会抛出异常,我们有两种方式,依然在我们的方法后抛出异常,提醒调用者该方法会抛出异常,我们也可以处理该异常,具体细节我们将在后续学习异常进行理解。
我们再来看看Clonable接口代码

public interface Cloneable {
}

它是一个空接口,也叫做标记接口,表示当前类是可以被克隆的

public class Person implements Cloneable{
    public String name;
    public int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    protected Object clone() throws CloneNotSupportedException{
        return super.clone();
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException{
        Person person1 = new Person("张三",18);
        Person person2 = (Person) person1.clone();
        System.out.println(person1.toString());
        System.out.println(person2.toString());
    }
}
//结果为:
//Person{name='张三', age=18}
//Person{name='张三', age=18}

我们也可以对异常进行处理

public class Person implements Cloneable{
    public String name;
    public int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    protected Object clone(){
        Object o = null;
        try{
            o = super.clone();
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
        }
        return o;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class Test {
    public static void main(String[] args){
        Person person1 = new Person("张三",18);
        Person person2 = (Person) person1.clone();
        System.out.println(person1.toString());
        System.out.println(person2.toString());
    }
}
//结果为:
//Person{name='张三', age=18}
//Person{name='张三', age=18}

以上Cloneable拷贝出的对象是一份浅拷贝

浅拷贝和深拷贝

上述代码就是将person1所指对象克隆一份出来
在这里插入图片描述
那我们再来看一段代码

public class Money {
    public double m = 9.9;
}
public class Person implements Cloneable{
    public String name;
    public int age;
    Money money = new Money();

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    protected Object clone(){
        Object o = null;
        try{
            o = super.clone();
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
        }
        return o;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class Test {
    public static void main(String[] args){
        Person person1 = new Person("张三",18);
        Person person2 = (Person) person1.clone();
        System.out.println("修改前person1:" + person1.money.m);
        System.out.println("修改前person2:" + person2.money.m);
        person2.money.m = 99.9;
        System.out.println("修改后person1:" + person1.money.m);
        System.out.println("修改后person2:" + person2.money.m);
    }
}
//结果为:
//修改前person1:9.9
//修改前person2:9.9
//修改后person1:99.9
//修改后person2:99.9

上述代码克隆了Person对象,没有克隆Money对象。所以当我们对person2的money中的m值进行修改,person1中money的m值也发生了改变,这就是浅拷贝。
如果对Money对象也进行拷贝,那就是深拷贝
在这里插入图片描述
我们要进行深拷贝,对Money对象进行拷贝,就需要我们去修改clone方法,并对Money类实现Cloneable接口,并重写clone方法。

public class Money implements Cloneable{
    public double m = 9.9;
    protected Object clone() {
        Object o = null;
        try{
            o = super.clone();
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
        }
        return o;
    }
}
public class Person implements Cloneable{
    public String name;
    public int age;
    Money money = new Money();

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    protected Object clone(){
        Person tmp = null;
        try{
            tmp = (Person) super.clone();
            tmp.money = (Money) this.money.clone();
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
        }
        return tmp;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class Test {
    public static void main(String[] args){
        Person person1 = new Person("张三",18);
        Person person2 = (Person) person1.clone();
        System.out.println("修改前person1:" + person1.money.m);
        System.out.println("修改前person2:" + person2.money.m);
        person2.money.m = 99.9;
        System.out.println("修改后person1:" + person1.money.m);
        System.out.println("修改后person2:" + person2.money.m);
    }
}
//结果为:
//修改前person1:9.9
//修改前person2:9.9
//修改后person1:9.9
//修改后person2:99.9

在这里插入图片描述
此时完成的就是深拷贝。

抽象类和接口的区别

核心区别:抽象类中可以包含普通方法和普通字段,这样的普通方法和字段可以被子类使用(不必重写),而接口中不能表示普通方法,子类必须重写所有的抽象方法。
如我们之前写的Animal类,此处的Animal中包含一个name属性,这个属性在任何子类中都是存在的,因此此处的Animal只能作为一个抽象类,而不应该成为一个接口。

public class Animal {
    protected String name;

    public Animal(String name) {
        this.name = name;
    }
}

抽象类存在的意义就是为了让编译器更好的校验,像Animal这样的类我们并不会直接使用,而是使用它的子类,万一不小心创建了Animal的实例,编译器会及时提醒我们的。

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

我们对于抽象类和接口先学习到这,后续我们将继续一起学习和了解更多有趣的内容~~

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值