【Java】 抽象类和接口

  • Post author:
  • Post category: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块啦!!!



版权声明:本文为weixin_55719365原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。