JavaSE基础(十)---抽象类和接口

🎈本章节掌握目标:

🎉(1)什么是抽象类?

🎉(2)为什么要使用抽象类?

🎉(3)抽象类使用的注意事项

🎉(4)接口的用法

目录

一、抽象类

1、抽象类的概念

2、抽象类的语法

3、抽象类的注意事项

4、抽象类的作用

二、接口 

1、接口的概念        

2、接口的语法

3、接口的使用

4、接口的特性

5、实现多个接口

6、接口之间的继承

三、接口的使用实例

1、Comparable接口

 2、Comparator接口(比较器)


一、抽象类

1、抽象类的概念

对于抽象类,从字面意思上看,可以理解成被抽象化后的类。🤔可这又表示什么意思呢?

在面向对象的过程中,通过类的描述来实例化对象,但是有时会出现一些类,这些类没有足够的信息具体地描述某一个对象。这样的类就被称为“抽象类”。


比如,在一个分析图形的过程中,图形类中存在着圆、三角形、正方形等图形,这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域并不是直接存在的,它就是一个抽象概念。

图形Shape和圆、三角形、正方形之间存在继承关系。

图形Shape类不是具体的图形,所以即使其内部存在draw()方法,也没办法实现该方法。

如果我们对这些图形都进行draw()绘画的动作,那么根据对象的不同,所绘制出来的也是不一样的,可图形类(Shape)作为父类,没法具体绘画出具体的图形,导致draw()方法无法具体地实现因此把图形类(Shape)称为“抽象类”。

        在打印图形例子中, 父类 Shape 中的 draw()方法并没有什么实际工作, 主要的绘制图形都是由 Shape 的各种子类(三角形、圆形、正方形)的 draw()方法来完成的.

        像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstract method), 包含抽象方法的类我们称为 抽象类(abstract class).

打印图形示例:

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

    @Override
    public void draw() {
        System.out.println("▲");
    }
}
class square extends Shape{

    @Override
    public void draw() {
        System.out.println("□");
    }
}
public class TestDemo1 {
    public static void main(String[] args) {
        //注意:不能实例化抽象类,此处实例化继承抽象类的子类
        Shape shape=new Circle();
        Shape shape1=new triangle();
        Shape shape2=new square();
        shape.draw();
        shape1.draw();
        shape2.draw();
    }
}

2、抽象类的语法

一般情况下,我们把被抽象化的类称为“抽象类”,而在抽象类中被抽象化的方法被称为“抽象方法”。抽象方法不需要有具体的实现语句.

而抽象类和抽象方法都是由abstract修饰的。

抽象类:

abstract class Shape{

          ........

}

抽象方法:

abstract public void func();

【注意】:

  • 当一个方法的具体实现不想写,加个abstract就可以让它抽象化,类也要加上abstract,让其变成抽象类。 
  • 类中多了一个抽象方法,这个类也得变成抽象类。
  • 抽象类和普通类的差不多,内部也可以具有普通成员变量、普通方法和构造方法。
  • 抽象类中可以没有抽象方法,但如果一个类中有抽象方法,那该类一定得是抽象类。

3、抽象类的注意事项

 在Java中,抽象类的使用有许多要注意的地方。稍不注意,就容易导致程序运行时报错,或出现意想不到的错误结果。

(1)抽象类存在的最大意义就是为了被继承。

(2)抽象类也可以发生向上转型,进一步发生多态 

(3)抽象类被实例化。(指实例化自己,还是可以实例化子类对象)

        因为抽象类是无法完整地描述一个对象才被抽象化的,怎么可能还能实例化一个对象。本质上没法描述类,为什么还要实例化呢?

(4)抽象类必须被继承,如果继承抽象类的子类是普通类,那一定要重写抽象类中的所有抽象方法;否则该子类也应该为抽象类(被abstract修饰),如果子类也是抽象类,就可以不用重写父类中的抽象方法。

如果让一个抽象类Flower再继承Shape类。

该类确实不用再重写父类(抽象类)的抽象方法,但有一点还需重视。

 即使该Flower类,没有重写,但如果后续有普通类继承Flower类,该普通类还是得重写前面抽象类中的所有抽象方法。(欠的总是要还的)

 我们可以通过鼠标点击红波浪线该行,点击快捷键Alt+Enter,可以直接进行重写方法。

(5)抽象类和抽象方法都不能被final修饰。

        被final修饰的抽象类是没法进行继承的,抽象类是一定要被继承的。抽象类存在的意义就是继承,怎么可能限制抽象类的继承呢?

 

        被final修饰的抽象方法没法进行重写,而继承抽象类,一定要重写抽象类中的抽象方法。         

(6)抽象方法不能被private修饰。

(7)抽象方法不可以被static修饰 

(8) 抽象类中不一定有抽象方法,但有抽象方法的类一定是抽象类。

(9)抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量。

与普通类唯一的区别是:里面多了抽象方法。


4、抽象类的作用

 一般情况下,我们都是用普通的父类和子类进行向上转型,之后再通过调用它们之间重写的方法,发生动态绑定,来具体实现某一个对象的功能。

但还是建议重写抽象类中的抽象方法。

而抽象类本身是不能被实例化的,要想使用,只能先创建一个继承该抽象类的子类,然后让该子类重写抽象类的抽象方法。

🤔如果普通类也可以发生向上转型,抽象类也可以发生向上转型,重写方法。那为什么建议使用抽象类和抽象方法呢?

        这是因为,使用抽象类相当于多了一层编译器的校验

        在前面我们可以知道,抽象类存在的最大意义就是为了被继承。

        通过子类引用父类(抽象类),重写父类中的所有抽象方法,进一步发生多态,看似与普通类发生多态的流程差不多。

        我们使用了普通类继承抽象类,那么我们在编写代码的时候,编译器会提示我们重写抽象类中的抽象方法,否则该程序是无法进行编译的。为我们平时编写代码提供了一层保险,帮助我们不犯错。

充分利用编译器的校验, 在实际开发中是非常有意义的


二、接口 

1、接口的概念        

对于接口,在日常生活中,我们首先可以联想到的:我们平常充电USB口,电源插头等等。

根据接口的不同,可以插不同的设备。

比如:电脑的USB口上,可以插:U盘、鼠标、键盘...所有符合USB协议的设备

           电源插座插孔上,可以插:电脑、电视机、电饭煲...所有符合规范的设备

从上面的例子可以看出,接口是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。

在Java中,接口是一种行为的规范和标准,给定一个统一的标准。接口是多个类的公共规范,是一种引用数据类型。

最需要知道的一点是,接口看似和类差不多。

但其实在Java程序设计语言中,接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。(取自Java核心技术卷Ⅰ基础知识)


2、接口的语法

接口其实和类差不多,但还是有所差别。接口的定义格式和类的定义格式基本相同。

接口是用interface关键字修饰的。

将平时定义类的class关键字,更换成interface关键词,就定义了一个接口。

接口的语法格式:

public interface 接口名称{
    //成员变量(接口中的成员变量一定得初始化)
    public static final int count=10;//public static final 是固定搭配,可以省略
    int count1=12;
    // 抽象方法
    public abstract void method1(); // public abstract 是固定搭配,可以省略
    void method2();
    abstract void method3();
    void method4();
    // 注意:在接口中上述写法都是抽象方法
}
    

接口的定义示例:

 接口当中的静态方法可以有具体的实现public stratic void fun(){},在main{}里,可以不用new对象就可以调用接口内的静态方法,直接接口.方法名()就可以调用!

【注意】:

接口的语法规则:

  • 接口是由interface关键字修饰的。
  • 接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一形式进行定义。
  • 接口中的方法都是抽象方法,默认为public abstract。
  • 接口中存在成员变量,但都默认为public static final静态常量,都得进行初始化。
  • 创建接口时,接口的命名一般都是以大写字母I开头。
  • 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性.

3、接口的使用

接口不能直接使用,需要有一个类来实现该接口,而该类还需重写接口中所有的抽象方法。

换句话说,一个类如果遵循了特定接口的规范,那么该类需要履行接口中的功能服务。

🤔那么要如何让一个类来实现接口呢?

  • 首先需要让该类声明为实现给定的接口;
  • 在该类中重写接口中的所有抽象方法;  

而将类声明为实现某一接口,需要用implements关键字。

语法格式:

class 类名词 implements 接口名称{
    ...
}

 【注意】:子类和父类之间是extends 继承关系,类与接口之间是 implements 实现关系


接口使用的代码示例:

以Animal接口,Dog类和Cat类实现该接口为例子


4、接口的特性

接口具有必须注意的特性。

(1)接口虽然是一种引用数据类型,但是不可以实例化new接口的对象,需要通过类实现该接口,之后实例化这个类对象。

(2)接口内的成员变量默认为静态常量,必须初始化,接口当中的成员变量即使不写public static final也会默认是public static final!

(3)接口中每一个方法都是public的抽象方法, 即接口中的方法会被隐式的默认为 public abstract,即使没有修饰符,编译器也会自动默认为public abstract(而且只能是 public abstract,其他修饰符都会报错) 

(4)接口中的方法是不能在接口中实现的,但可以定义静态方法来实现该方法。

 

(5) 在继承抽象类或实现接口时所有不想重写抽象方法的类,都可以使它变成抽象类 

(6) 重写接口中方法时,不能使用default访问权限修饰

 (7)接口当中的方法如果要实现,需要用default来修饰。(这个默认的方法可以被重写)

(8)接口中不能有静态代码块和构造方法

(9)接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class

以此处代码为例

 


5、实现多个接口

接口不像类的继承一样(在Java中,类和类之间是单继承的),子类在继承父类的时候,一次只能继承一个父类,没法继承多个。(Java中不支持多继承)

而类实现接口,可以同时实现多个。

实现的多个接口之间是通过逗号分隔开的。

语法格式:

interface Voice{
    void voice();
}
interface Move{
    void move();
}
interface Swim{
    void swim();
}
class Dog implements Voice,Move,Swim{

    @Override
    public void voice() {
        System.out.println("汪汪叫!");
    }

    @Override
    public void move() {
        System.out.println("狗在跑!");
    }

    @Override
    public void swim() {
        System.out.println("狗在狗刨游泳!");
    }
}

 【注意】:一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类。


对于我们前面学习到的继承关系,子类继承父类的这种关系,比如Animal和Dog类之间表达的是一种is-a 关系(狗是动物)。

而在实现接口这个地方,表达的含义是该Dog类具有Swim,Move等特性.

狗是一种动物,具有跑,游泳的特性。


6、接口之间的继承

在Java中,类和类之间是单继承的,一个类可以实现多个接口,

而接口与接口之间可以多继承。

即:用接口可以达到多继承的目的。 接口可以继承一个接口, 达到复用的效果. 和类继承一样使用 extends 关键字.

也就是说,一个接口继承其他接口,就具备了其他接口的功能。

比如:

 接口间的继承相当于把多个接口合并在一起.


三、接口的使用实例

1、Comparable接口

使用类来实现接口,可以帮助我们很方便地进行许多有意思地操作。

(具体内部源码暂不作分析)

🤔下面以学生类对象为例:

        如果我们实例化了三个学生对象,给他们各自初始化属性,之后以他们的某一属性为比较,比较三个学生之间的年龄高低或成绩高低,应该得怎么做呢?

 (1)首先我们先定义一个学生类

 (2)定义一个Student学生类数组,里面存放实例化的学生对象.

 (3)如果要对学生对象进行比较,我们得先确定要比较的属性是什么,类和普通的整型是不一样,整型是可以直接比较的, 大小关系明确,可不能说直接用学生的引用变量进行比较。

 (4)比如,以学生的年龄作为比较条件,我们可以让Student学生类,实现一个Comparable的接口,该接口是Java中自带的。

Comparable接口在java.lang这个包下面,而java.lang这个包由java解释器自动引入,而不用import语句引入

 

 需要注意的是,在Comparable接口后面有一个尖括号,里面应该存放要比较的类类型。

之后要在该Student类中重写ComparTo()方法

 如果没有重写该方法,会报编译错误。

通过查阅java帮助手册:

而且Comparable接口中只有一个方法---compareTo(),

 

通过重写compareTo方法可以对学生对象数组的属性进行比较排序

 (5)如果我们想比较的是学生的年龄,那么我们可以在compareTo()方法中,返回比较的两个学生对象年龄之差。(如果比较的是成绩,把age替换成score即可)

对于重写compareTo方法,我们要注意的是,调用该方法的类型和参数应该是“引用类型”,此处为引用Student类的对象,通过该对象访问其内部的属性来进行比较。

比较后进行的是年龄的升序排序。

this.代表当前引用的对象,o.代表对传入参数的引用

如果返回的是正数,说明前者的学生年龄大;this.age>o.age

如果返回的是负数,说明后者的学生年龄大;this.age<o.age;

如果返回的是0,说明两者的年龄一样大。this.age=o.age;

通过比较好后返回的数值,来确定这比较后两者的前后大小顺序,之后按对应的升序排序关系来放置当前的当前对象和参数对象。

(6)在前面我们定义了一个学生类数组,我们可以通过Arrays.sort()排序来对学生类进行排序。 

在Arrays.sort方法中传入的是一个学生类对象数组,在调用Arrays的sort()排序时,就已经通过调用了重写Comparable接口中的compareTo方法学生类对象进行了排序,

之后我们再通过Arrays.toString ()方法将该数组转换为字符串输出,即可输出学生对象之间的年龄大小关系.

 【注意】:使用Arrays时,记得要导包.

(7)如果比较的是学生对象的名字(字符串)该怎么比较呢?

只需再调用一个compareTo()方法比较, (具体内部源码暂不作分析) 

因为Student类的name也是一个引用类型.

需要用到compareTo再进行一次比较。

此时调用compareTo方法是,String类( name也是一个引用类型)里重写Comparable接口中的compareTo方法。

 


 2、Comparator接口(比较器)

        可如果我们比较完年龄后,又想比较学生对象的另外一个属性,该怎么办?

        总不能说在原有的基础上去更改吧,后续如果在做比较复杂的项目时,这么做容易导致自己,无法知道该比较是否被进行更改果。 

下面引出Comparator(比较器)

 

通过实现Comparator接口,我们后续可以很方便地对类对象进行各种比较。  


 如果我们想比较学生的成绩,我们可以定义一个实现Comparator接口的成绩比较类ScoreIgnore.

Comparator接口和Comparable接口一样,后面的尖括号内为要比较的类类型。

通过重写compare方法,来返回比较的结果

 

之后再通过实例化ScoreIgnore对象,通过Arrays中的sort方法,传入该对象和要比较的学生数组students,进行比较。Arrays类中的sort()方法根据参数列表的不同,调用相对应重载的方法。

该方法中传入了两个参数,分别是学生类对象数组T [ ]a和相对于的实现comparator接口的一个类Comparato<> c


 

如果要比较学生对象的名字大小,可以在重写的compare方法内,再调用compareTo方法进行比较.

  • 9
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星河栀染

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值