【Java】 抽象类和接口

目录

抽象类为什么存在?

 如何使用抽象类  关键字:abstract

抽象类与普通类有何不同

接口

接口定义

接口有何不同???

三个重要接口的使用

排序

Comparable

完整代码

Comparator

使用方法

 完整代码

Cloneable — 克隆

 深拷贝与浅拷贝

 例子

深拷贝

 浅拷贝

克隆的深拷贝


抽象类为什么存在?

class Circle extends DrawShape {
    @Override
    public void draw() {
        System.out.println("⚪");
    }
}

class Rectangle extends DrawShape {
    @Override
    public void draw() {
        System.out.println("▭");
    }
}
public class Test {
    public static void Draw(DrawShape drawShape) {
        drawShape.draw();
    }

    public static void main(String[] args) {
        DrawShape drawShape = new Circle();
        DrawShape drawShape1 = new Rectangle();
        Draw(drawShape);
        Draw(drawShape1);
    }
}

运行结果:

我们发现,我们定义的这个类中的方法其实并没有被执行,因为它会被子类重写,因此它起到的作用就是构建一个大多数情况下不被执行的方法,此外还有另外一个作用:被继承

class DrawShape {
    public void draw() {
        System.out.println("画图形");
    }
}

那么方法体就可以被忽略掉了  

 

但是我们发现如果这么做的话编译器会报错,此时,抽象类就出现了,解决了我们此时的难题。

 如何使用抽象类  关键字:abstract

当我们在不想实现方法体的成员方法(或者说被子类重写的方法)前面加上关键字abstract

此时,这个方法就变成了抽象方法,在class前面也需要加上abstract关键字,这个类就变成了抽象类。当另一个类继承该类时,子类需重写该类中的所有抽象方法。

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

抽象类与普通类有何不同

1.抽象类不可以被实例化但可以被继承 。

2.抽象类的引用可以引用子类的对象(向上转型)。

3.抽象类与普通类最大最突出的特点就是不能实例化,其余与普通类一样,可以有普通成变量,静态变量,静态代码块,实例代码块,构造方法,普通方法,静态方法。

4.子类必须重写所继承的抽象类中的所有抽象方法。

5.如果子类不想重写抽象方法,可以将子类设置为抽象类。(但你迟早要重写)

6.抽象方法不可以被static、final、private修饰。

接口

接口可以说是抽象类的进一步抽象。但是我认为接口使用起来更加的方便,我们将此接口想象成usb接口,当我们所有的设备使用的都是usb接口,那么我们只需准备同一种线就可以了,不需要再额外准备其他的线以备不时之需。此接口也类似,当我们用的时候,随便将它安到哪里它就可以使用,多方便,最重要的是,它可以多插口,此usb插口是传输文件,此usb插口是充电,此usb插口是给手机备份。按照Java来讲,它实现了类所不具备的多继承,不过我们需要将叫法改一改,一个类可以实现多个接口。所需关键字:interface implements 

接口定义

将class替换为interface

接口有何不同???

先抛个疑问:

定义在类中的成员变量不初始化会报错?? 为啥???

不同点: 

1.接口用 interface 来修饰。

2.类与接口之间用 implements 来关联。

3.可以定义成员变量,但成员变量默认被public static final修饰,需进行初始化

4.不可以定义 构造方法、静态代码块、实例代码块。

5.普通方法默认为抽象方法 被public abstract修饰

6.如果普通方法想要有具体实现,我们可以用default修饰

7.可以定义静态方法,且静态方法也可以有具体实现。

 

8.不管是static方法还是default方法,都默认被public修饰。

9.所实现接口的类中要重写接口中的抽象方法。

10.接口不可以被实例化,但接口可以引用所实现类的对象。

11.一个抽象类实现一个接口可以不重写接口中的抽象方法

12.接口继承接口:接口与接口之间可以用extends来连接,可理解为扩展

 

13.一个类可以实现多个接口,接口与接口之间用  ,  隔开

 

14.一个类先继承父类后实现接口

 

三个重要接口的使用

排序

class Animal {
    public String name;
    public int age;
    public int speed;

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

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

当我们定一个这个的一个类,并且创建了如下数组,且想要将这个数组进行排序,当我们运行的时候,我们发现,编译器报错了。

    public static void main(String[] args) {
        Animal[] animals = new Animal[3];
        animals[0] = new Animal("猎豹",13,210);
        animals[1] = new Animal("猎犬",15,130);
        animals[2] = new Animal("老虎",7,180);
        Arrays.sort(animals);
    }

让我们来思考一下编译器为什么会报错??? 

 我们是以什么为标准给这个数组来排序的呢???我们会神奇的发现我们没有标准就把数组扔给了Arrays.sort进行排序,编译器不发脾气才怪呢。

接下来我们来讲如何将它排序。

Comparable

首先我们先让Anima这个类实现Comparable这个接口

 报错是因为我们还没有重写接口中的抽象方法,<Animal>的意思为对Aniaml进行排序。

我们将鼠标放在Comparable上,按住Alt再按回车,就可以重写抽象方法了。

 然后后我们需要将其改写为:

 解释 this 和 o :

this表示对当前对象的引用,o表示对下面对象的引用。

如何确定当前对象呢???看谁调用它。

    public static void main(String[] args) {
        Animal[] animals = new Animal[3];
        animals[0] = new Animal("猎豹",13,210);
        animals[1] = new Animal("猎犬",15,130);
        animals[2] = new Animal("老虎",7,180);
        System.out.println(animals[0].compareTo(animals[1])); // 输出结果为:80
    }

此时这个animals[0]就是this,animals[1]就是o

好啦,当我们重写完成后,再运行。

//排序前
[Animal{name='猎豹', age=13, speed=210}, 
Animal{name='猎犬', age=15, speed=130}, 
Animal{name='老虎', age=7, speed=180}]

//排序后
[Animal{name='猎犬', age=15, speed=130}, 
Animal{name='老虎', age=7, speed=180}, 
Animal{name='猎豹', age=13, speed=210}]

 此时我们清楚地看到,它按照动物的速度进行排序了。

完整代码

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

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

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

    @Override
    public int compareTo(Animal o) {
        return this.speed - o.speed;
    }
}
public class Test3 {
    public static void main(String[] args) {
        Animal[] animals = new Animal[3];
        animals[0] = new Animal("猎豹",13,210);
        animals[1] = new Animal("猎犬",15,130);
        animals[2] = new Animal("老虎",7,180);

        System.out.println(Arrays.toString(animals));
        Arrays.sort(animals);
        System.out.println(Arrays.toString(animals));
    }
}

Comparator

我们发现,Comparable接口可以帮助我们对数组进行排序,但很快我们又发现了新的问题,

    @Override
    public int compareTo(Animal o) {
        return this.speed - o.speed;
    }

我们只能按照动物的奔跑速度的大小进行排序,假如我们想按照动物的年龄或者名字来进行排序,那么我们就要将此方法删掉再写另外的方法,很不方便,那么有没有一劳永逸的办法呢?比较方法实现后,可以随自己调用,想按照年龄排序就传一个年龄排序的方法,想按照名字排序就传一个按年龄排序的方法,一次实现多次使用。接下来就要向大家介绍Comparator ——构造器。

使用方法

我们首先要创建一个类,并实现Comparato,重写抽象方法。(要铭记快捷键,很好用 Alt + Enter)。

class AgeComparator implements Comparator<Animal> {

    @Override
    public int compare(Animal o1, Animal o2) {
        return o1.age - o2.age;
    }
}

class NameComparator implements Comparator<Animal> {

    @Override
    public int compare(Animal o1, Animal o2) {
        return o1.name.compareTo(o2.name);
    }
}

class SpeedComparator implements Comparator<Animal> {

    @Override
    public int compare(Animal o1, Animal o2) {
        return o1.speed - o2.speed;
    }
}

 这其中的o1和o2的意思大家应该很清晰吧,那么就不再多说了。

我们来讲一下比较字符串的这个,字符串是引用类型的与int类型的不一样,我们将鼠标放在String类型上,按住Ctrl并鼠标左键单击String,可以转到定义,我们发现它其中实现了Comparable接口,我们再用同样的方法转到Comparable的定义,发现其中有compareTo的方法,因此我们便可调用它。

o1.name.compareTo(o2.name);

 

当我们写好各种比较方法后,我们只需要在Arrays.sort中,连同待排序的数组一起传进去即可。

 

 完整代码

class Animal {
    public String name;
    public int age;
    public int speed;

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

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

class AgeComparator implements Comparator<Animal> {

    @Override
    public int compare(Animal o1, Animal o2) {
        return o1.age - o2.age;
    }
}

class NameComparator implements Comparator<Animal> {

    @Override
    public int compare(Animal o1, Animal o2) {
        return o1.name.compareTo(o2.name);
    }
}

class SpeedComparator implements Comparator<Animal> {

    @Override
    public int compare(Animal o1, Animal o2) {
        return o1.speed - o2.speed;
    }
}



public class TestDemo {
    public static void main(String[] args) {
        Animal[] animals = new Animal[3];
        animals[0] = new Animal("猎豹",13,210);
        animals[1] = new Animal("猎犬",15,130);
        animals[2] = new Animal("老虎",7,180);
        NameComparator nameComparator = new NameComparator();
        System.out.println(Arrays.toString(animals));
        Arrays.sort(animals,nameComparator);
        System.out.println(Arrays.toString(animals));
    }
}

Cloneable — 克隆

我定义了一个钱包类。

我们给一个类实现Clonable后,右击鼠标找到Generate,找到重写方法后,点击clone,最后确定。

此时我们的类中就有克隆方法了,我们来试试吧。

我们先实例化一个对象A,再实例化一个B当作容器,用对象A调用克隆方法,clone方法是父类Object的,因此我们要强制类型转换一下,可是我们发现他还是报错,我们可以这样解决。

(我听说这是抛异常,等我仔细学学再来告诉你们,咱们先把这个克隆用起来噢,不急)

来调用一个我们的副本:

此时,副本就产生了,从而又产生了另外一个问题,深拷贝与浅拷贝。

 深拷贝与浅拷贝

深与浅又是如何定义的呢?我来为大家解读一下,对于拷贝来说,什么地方或者说从什么方面来讲会涉及到深与浅的问题?答案是:内容是否会被更改  完整来说是:

当我们更改副本的内容时,源头是否会被改掉;如果源头未被改掉则是深拷贝,反之,则是浅拷贝。

 例子

深拷贝

    public static void main(String[] args) {
        int[] array1 = {1,7,9};
        int[] array2 = Arrays.copyOf(array1,array1.length);//此时数组1被拷贝到数组2中
        System.out.println(Arrays.toString(array2));  //我们打印来看一下
        array2[0] = 10;  //此时我们更改一个数据 看看源头是否被改掉
        System.out.println(Arrays.toString(array1));
    }

源头没有被改掉,因此是深拷贝

 浅拷贝

 
​
class Number {
    public int val = 9;
    public int num = 10;
}
public class TestDemo3 {
    public static void main(String[] args) {
        Number number1 = new Number();
        Number number2 = new Number();
        Number[] array1 = {number1,number2};
        Number[] array2 = Arrays.copyOf(array1,array1.length);
        System.out.println(Arrays.toString(array2));
        System.out.println("number1.num : "+number1.num);
        array2[0].num = 19;
        System.out.println("number1.num : "+number1.num);
    }
}

​

解释:

我们在数组中存的是两个引用,指向了根据Number这个类创建的两个对象。

 而我们拷贝array1后,

 我们发现数组二中的引用也完完全全被拷贝了过来,因此当我们通过引用来修改数据时,源   头也会被改掉,此时为浅拷贝。

克隆的深拷贝

我们将Cloneable中提到的代码修改一下,变成这样:

需变动的部分:

定义一个小偷类,偷钱包中的钱,看看我们还剩多少钱。

 测试结果:

 现在小偷偷了1000元,我们已经分文不剩了。

 原因想必大家很清楚啦,通过引用修改数据,源头也被改掉啦,接下来让我们着手将他便为浅拷贝。

我们的目的就是要保护源数据,而拷贝的时候我们只是机械的把引用拷贝过去了,而没有拷贝引用所指向的对象,我们只需把引用所指向的对象也拷贝一份就可以了。

这部分需要变动一下,要让这个方法也将引用所指向的对象拷贝一份出来。

 首先,我们要将Thief这个类实现Cloneable接口,让它也拥有克隆的能力,具体实现参照上方的讲解。

class Thief implements Cloneable{
    public int stealMoney = 800;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Wallet ret = (Wallet) super.clone();
        ret.thief = (Thief) this.thief.clone();
        return ret;
    }

当我们再次运行:

我们还剩200块啦!!! 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值