JavaSE - 抽象类和接口

目录

1. 抽象类 

2. 接口 

接口实现多态的例子:

2.1 实现多个接口

2.2 接口间的继承

2.3 接口使用实例

2.3.1 Comparable接口 和 Comparator接口(第二个也叫比较器)

1、实现Comparable接口,重写compareTo方法

2、实现Comparator接口,重写compare方法

2.3.2 Cloneable接口

2.4 Cloneable接口 与 浅拷贝和深拷贝

2.4.1 浅拷贝

图解:

代码:

结论:

2.4.2 深拷贝

图解:

代码:

结论:

3. 面试题:抽象类和接口的区别


1. 抽象类 

1. 被abstract修饰的类是抽象类,抽象类不能实例化对象,即不能new对象【abstract class

2. 被abstract修饰的方法是抽象方法,抽象方法没有具体的实现,抽象方法不能被private修饰,也不能被final和static修饰,要满足重写的规则。【因为抽象方法需要被子类重写,而private,final,static修饰的不能被子类重写】【public abstract

3. 一个类中有抽象方法,那这个类一定得是抽象类,抽象类中才能有抽象方法。

4. 抽象类和普通类中成员的区别就是:抽象类中可以有抽象方法不放抽象方法也不报错】而普通类不能有其余和普通类一模一样,也可以有属性和方法。

5. 抽象类中也可以有构造方法,为了方便子类能调用,来初始化抽象类中的成员。

6. 抽象类存在的最大意义 - 就是为了被继承

7. 抽象类最完美表现:抽象类中包含抽象方法,抽象类被子类继承,子类中重写抽象方法,实例化子类然后向上转型,实现多态。

比如下面这个例子:【这个例子就是抽象类存在的意义和表现】

abstract class Shape{//【!!!】
    public abstract void draw();//【!!!】
}

class Rectangle extends Shape{
    @Override
    public void draw() {
        System.out.println("画矩形");
    }
}
class Circle extends Shape{
    @Override
    public void draw() {
        System.out.println("画圆");
    }
}
class Triangle extends Shape{
    @Override
    public void draw() {
        System.out.println("画三角形");
    }
}
public class Test {

    public static void drawFuc(Shape shape){

        shape.draw();
    }

    public static void main(String[] args) {
        Shape shape = new Rectangle();
        drawFuc(shape);
        drawFuc(new Circle());
        drawFuc(new Triangle());

    }
}

 

8. 如果一个普通类继承了一个抽象类,如果抽象类中有抽象方法,此时必须重写这个抽象方法

9. 如果一个抽象类B继承了一个抽象类Shape,此时B当中不需要重写Shape中的抽象方法;但是如果B再被普通类C继承,那么C中就需要重写Shape中的抽象方法。

2. 接口 

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

接口是对行为的一种规范或标准

可以将接口看做抽象类的进一步,但接口不是类

定义接口interface+接口名(大驼峰) 

  • 接口名一般以大写字母I开头
  • 阿里编码规范中约定, 接口中的抽象方法和属性不要加任何修饰符号, 保持代码的简洁性

1. 将class关键字换成 interface 关键字,就定义了一个接口。

2. 接口当中的成员方法不能有具体的实现默认是抽象方法。如果此方法有具体实现,要么是default修饰的方法,要么是static修饰的方法接口中的所有方法默认都是public的,不写默认也有public,所以一般就直接不写,保持代码简洁性。

接口中的成员方法可以是:

  • 1. 抽象方法:默认是public abstract 的方法。我们一般直接省略不写。不写public abstract默认也会有。所以写成【protected abstract】这样是错的。下面写法都是对的:【public abstract】【abstract】【public】,缺的部分默认会给你添上。
  • 2. JDK1.8 开始,允许有具体实现的方法,但是这个方法只能是由default修饰的。我们一般写全【default public】默认是public方法。所以写成这样也可以:【default】,缺的默认会给你添上。
  • 3.  静态方法:也允许有具体的实现。我们一般写全【public static】默认是public方法。所以写成这样也可以:【static】,缺的默认会给你添上。 

3. 成员变量默认是public static final 修饰的我们一般直接省略不写(加或者不加默认都会有,写成这样就错了【protected static final】,要与默认的保持一致。)

4. 接口不能被实例化,即不能new对象

5. 接口中不能有静态代码块和构造方法

6. 类和接口之间采用implements 来实现多个接口,若接口中有抽象方法必须重写所有的抽象方法

7. 如果一个抽象类Bimplements实现一个接口,此时B当中不需要重写接口中的抽象方法;但是如果B再被普通类C继承,那么C中就需要重写接口中的抽象方法。

也就是说如果你这个类不想重写接口中的抽象方法,你就加个abstract,变成抽象类。但出来混迟早要还的,要是这个抽象类被普通类继承了,你还是得重写接口中的抽象方法。

8. 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class;所以,可以把接口看做是特殊的类,一般来说一个接口就是一个Java文件。

9. 接口也可以发生多态

10. 接口的存在就是为了重写其中的抽象方法

11. 接口的修饰符可以是abstract,不写其实默认也有。(不能是private,protected,final  

抽象方法和default修饰的方法,不能被super调用。 

接口中的静态方法,只能用接口名去调用,不能用实现接口的子类名去调用。

像抽象类或普通类中的静态方法,是可以用继承该类的子类名去调用的。接口很特殊。


接口实现多态的例子:

interface IShape{
    void draw();

}
class Rectangle implements IShape{
    @Override
    public void draw() {
        System.out.println("矩形");
    }
}
class Circle implements IShape{
    @Override
    public void draw() {
        System.out.println("圆");
    }
}
class Triangle implements  IShape{

    @Override
    public void draw() {
        System.out.println("三角形");
    }
}

public class Test {
    public static void drawFuc(IShape shape){
        shape.draw();
    }

    public static void main(String[] args) {
        drawFuc(new Rectangle());
        drawFuc(new Circle());
        drawFuc(new Triangle());

    }
}

 

/**
 * 请实现笔记本电脑使用USB鼠标、USB键盘的例子
 *
 * 1. USB接口:包含打开设备、关闭设备功能
 * 2. 笔记本类:包含开机功能、关机功能、使用USB设备功能
 * 3. 鼠标类:实现USB接口,并具备点击功能
 * 4. 键盘类:实现USB接口,并具备输入功能
 */
interface USB{
    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) usb).click();//向下转型
        }else if(usb instanceof KeyBoard){
            ((KeyBoard) usb).inPut();//向下转型
        }
        usb.closeDevice();
    }

}
public class Test {

    public static void main(String[] args) {
        /**
         * 调用类中的非静态方法,需要先实例化那个类,【引用.方法】才行。
         */
        //打开电脑
        Computer computer = new Computer();
        computer.powerOn();
        //使用鼠标设备
        computer.useDevice(new Mouse());//向上转型
        //使用键盘设备
        computer.useDevice(new KeyBoard());//向上转型
        //关闭电脑
        computer.powerOff();

    }
}

 

2.1 实现多个接口

一个类可以实现多个接口,使用implements,用逗号(,)隔开,可以解决多继承的问题。

Java当中只能继承一个类,不支持多继承,但可以实现多个接口。

继承表达的含义是 is - a 语义, 而接口表达的含义是 具有 xxx 特性

有了接口之后, 类的使用者就不必关注具体类型, 而只关注某个类是否具备某种能力。

实现多个接口的例子:

//接口
interface IRunning{
    void run();
}
interface ISwimming{
    void swim();
}
interface IFlying{
    void fly();
}
//类
class Animals{
    public String name;

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

    public void eat(){
        System.out.println("正在吃饭");
    }
}
//重写抽象方法快捷键:alt+enter
class Dog extends Animals implements IRunning,ISwimming{
    public Dog(String name) {
        super(name);
    }
    @Override
    public void run() {
        System.out.println(name+"正在跑");
    }

    @Override
    public void swim() {
        System.out.println(name+"正在游泳");
    }
    public void eat(){
        System.out.println(name+"正在吃狗粮");
    }
}
class Duck extends Animals implements IRunning,ISwimming,IFlying{

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

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

    @Override
    public void fly() {
        System.out.println(name+"正在飞");
    }
    public void eat(){
        System.out.println(name+"正在吃鸭粮");
    }
}
class Robert implements IRunning{

    @Override
    public void run() {
        System.out.println("机器人正在跑");
    }
}
public class Test {
    //跑
    public static void isRun(IRunning running){
        running.run();
    }
    //游泳
    public static void isSwim(ISwimming swimming){
        swimming.swim();
    }
    //吃
    public static void isEat(Animals animals){
        animals.eat();
    }
    public static void main(String[] args) {
        //跑:
        isRun(new Dog("小狗"));
        isRun(new Duck("小鸭子"));
        isRun(new Robert());
        System.out.println("-------------------");
        //游泳:
        isSwim(new Dog("小狗"));
        isSwim(new Duck("小鸭子"));
        System.out.println("-------------------");
        //吃
        isEat(new Dog("小狗"));
        isEat(new Duck("小鸭子"));

    }
}

2.2 接口间的继承

接口间的继承相当于把多个接口合并在一起一个接口可以继承多个接口。当类实现这个接口时,它继承的接口中的抽象类也需要重写。

接口间的继承用 extends关键字类实现接口用 implements

2.3 接口使用实例

接口是对行为的一种规范或标准,

实现这个接口,就具备了这个接口的行为或能力。

2.3.1 Comparable<T>接口 和 Comparator<T>接口(第二个也叫比较器)

自定义类型的对象之间比较,需要实现接口,使此类具备比较的能力,这样对象之间才能进行比较


1、实现Comparable<T>接口,重写compareTo方法

如下图,想要比较两个学生的大小,需要Student类实现Comparable<T>接口,使Student类具备比较的能力,下图是根据学生的年龄进行比较大小的。

谁调用compareTo这个方法,谁就是this,传入的参数就是o,所以这里,this就是student1,o就是student2

这样就实现了学生大小的比较。

那么如果我想通过学生的年龄比较大小,就得重新编写compareTo方法中的代码,可能会带来影响。Comparable接口对类的侵入性非常强,确定通过什么比较了,一般就不能更换了。

2、实现Comparator<T>接口,重写compare方法

Comparator接口中,不止一个抽象方法,为什么只需要实现一个compare方法就好了 ?

因为Object类中已经实现了equals方法,而所有类都会继承Object类,所以此类不需要再实现equals方法了。

Comparator接口对类的侵入性比较弱。例如,想要比较两个学生的大小,可以根据学生的年龄比较大小,也可以根据学生的名字比较大小。分别定义两个类继承Comparator接口就行。如下:

这是学生类: 

 根据年龄比较大小:

 

 根据名字比较大小:

2.3.2 Cloneable接口

自定义类型的对象想要实现克隆,需要如下几步:

1. 此类实现Cloneable接口,使其具备可克隆的能力

2. 重写Object类中的clone方法

3. 声明异常,强转类型


举个例子:

自定义类型Person的一个对象想要实现克隆,我们分别来看一下这几步:

第一步,实现Cloneable接口

我们进入到Cloneable接口中,发现里面什么都没有。 

这种接口叫做 空接口/标记接口,它的作用就是:实现了这个接口,当前类就拥有了可以被克隆的能力。

第二步,重写clone方法

因为所有的类都继承Object类,Object类中的clone方法是protected限定符修饰的。Object类在java.lang包里,和子类Person一定不在同一个包里。在不同包中时,只有在子类中可以访问。也就是说,只能在Person类中才能直接访问到Object类中的clone方法,在其它类中都不行。

如下图:

成功运行出来了。

但是,我们一般不直接在Person类里使用Person的实例化对象,会在另一个类(如Test类)中实例化Person对象,然后调用clone方法。这时,就是不同包的非子类了,于是Object类中的clone方法就无法被访问了。

那么,我们该怎么办呢?很简单,在子类Person中重写clone方法就可以啦。

子类重写clone方法,在方法中调用Object类的clone方法,那么我们通过调用子类的clone方法,就可以实现克隆啦,本质上调用的还是Object的clone方法。

第三步,声明异常,强转类型,如上。因为clone方法的返回值是Object类型的,所以需要强转成Person类型。

2.4 Cloneable接口 与 浅拷贝和深拷贝

2.4.1 浅拷贝

图解:

 代码:

结论:

浅拷贝只会拷贝Person类中的对象,不会拷贝这个对象中的Money的对象,Money的对象只有一份,一变全变。

2.4.2 深拷贝

图解:

代码:

结论:

深拷贝会拷贝Person类中的对象,也会拷贝这个对象中的Money的对象,所以不会一变全变。

3. 面试题:抽象类和接口的区别

1. 抽象类和接口都是 Java 中多态的常见使用方式,抽象类和接口都不能实例化对象。

2. 对于成员变量,抽象类中成员变量没有要求,接口中的成员变量默认是public static final修饰的。

3. 对于成员方法,

相同点是:抽象类和接口中都可以有抽象方法,抽象方法没有具体的实现。而且当抽象类被子类继承,或者子类实现了此接口,抽象类和接口中的所有抽象方法都必须被重写。

不同的是:抽象类中的抽象方法可以被除了private以外的其他三个访问修饰限定符修饰,而且抽象类中还可以有普通的成员方法。而接口的所有方法默认都是public修饰的,其他三个访问修饰限定符都不可以。接口中默认就是抽象方法。如果存在有具体实现的方法,要么是default修饰的,要么是静态方法。

4. 抽象类中可以有构造方法,接口中不能有构造方法

5. 子类使用extends关键字继承抽象类,使用implements关键字实现接口

6. 一个子类只能继承一个抽象类,但一个子类可以实现多个接口

7. 一个抽象类可以实现若干个接口,接口不能继承抽象类,但是接口可以使用extends关键字继承多个父接口

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值