Java—抽象类与接口

抽象类

在Java中可以创建一种类专门用来做父类,这种类称为“抽象类”。抽象类实际上也是一个类,只是与之前的普通类相比,内部新增了抽象方法。

抽象类的基本概念

抽象方法是只声明而未实现的方法,所有的抽象方法必须使用abstract关键字声明,包含抽象方法的类也必须使用abstract class声明。

抽象类定义规则如下:

⑴ 抽象类和抽象方法都必须用abstract关键字来修饰;

⑵ 抽象类不能直接实例化,也就是不能直接用new关键字去产生对象;

⑶ 抽象类定义时抽象方法只需声明,而不需实现;

⑷ 含有抽象方法的类必须被声明为抽象类,抽象类的子类必须覆写所有的抽象方法后才能被实例化,否则这个子类还是个抽象类。
在这里插入图片描述
例如:
在这里插入图片描述
由上例知:抽象类的定义就是比普通类多了一些抽象方法的定义而已。虽然定义了抽象类,但是抽象类却不能直接使用。
在这里插入图片描述
如果说一个类的对象可以被实例化,那么就表示这个对象可以调用类中的属性或者是方法,但是抽象类中存在抽象方法,而抽象方法没有方法体,没有方法体的方法无法使用。

所以,对于抽象类的使用原则如下。

抽象类必须有子类,子类使用extends继承抽象类,一个子类只能够继承一个抽象类;

子类(如果不是抽象类)则必须覆写抽象类之中的全部抽象方法;

如果要想实例化抽象类的对象,则可以使用子类进行对象的向上转型来完成。

抽象类的用法(代码AbstractClassDemo.java)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第1~7行声明了一个名为Person的抽象类,在Person中声明了3个属性和一个抽象方法——talk()。

第8~20行声明了一个Student类,此类继承自Person类,因为此类不为抽象类,所以需要覆写Person类中的抽象方法——talk()。

第21~33行声明了一个Worker类,此类继承自Person类,因为此类不为抽象类,所以需要覆写Person类中的抽象方法——talk()。

第21~33行声明了一个Worker类,此类继承自Person类,因为此类不为抽象类,所以需要覆写Person类中的抽象方法——talk()。

第38、39行分别实例化Student类与Worker类对象,并调用各自的构造方法初始化类属性。

第40、41行分别调用各自类中被覆写的talk()方法。

可以看到两个子类Student、Worker都分别按各自的要求覆写了talk()方法。可由下图表示。
在这里插入图片描述
抽象类的特征。

⑴ 抽象类中可以有构造方法。

与一般类相同,在抽象类中也可以拥有构造方法,但是这些构造方法必须在子类中被调用,并且子类实例化对象的时候依然满足类继承的关系,先默认调用父类的构造方法,而后再调用子类的构造方法,毕竟抽象类之中还是存在属性的,只不过这个抽象方法无法直接被外部实例化对象的时候所使用。

抽象类中构造方法的定义使用(代码AbstractConstructor.java)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第01~13行声明了一个名为Person的抽象类,在Person中声明了3个属性、一个构造函数和一个抽象方法——talk()。

第14~24行声明了一个Student类,此类继承自Person类,因为此类不为抽象类,所以需要覆写Person类中的抽象方法——talk()。

第18行明确调用抽象类中的构造方法。

第29行实例化Student类,建立对象s,并调用父类的构造方法初始化类属性。

第30行调用类中被覆写的talk()方法。

从程序中可以看到,抽象类也可以像普通类一样,有构造方法、一般方法和属性,更重要的是还可以有一些抽象方法,需要子类去实现,而且在抽象类中声明构造方法后,在子类中必须明确调用。

⑵ 抽象类不能够使用final定义。使用final定义的类不能有子类,而抽象类使用的时候必须有子类,这是一个矛盾的问题,所以抽象类上不能出现final定义。

⑶ 在外部抽象类上无法使用static声明,但是内部抽象类却可以使用static定义,使用static定义的内部抽象类就表示一个外部类。

验证static定义的内部抽象类(代码StaticInnerAbstractClass.java)。
在这里插入图片描述
在这里插入图片描述
第01~08行声明一个抽象类Book,抽象类中定义一个抽象方法和一个静态内部抽象类。

第09~15行继承抽象类。第13行进行抽象方法覆写。

第20行实例化对象cd。

第21行调用被覆写过的方法。

⑷ 抽象类之中可以没有抽象方法,但即便没有抽象方法的抽象类也不能够直接在外部通过关键字new实例化。

抽象类应用——模板设计模式

在使用抽象类时,可以将部分逻辑以具体方法和具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现,这就是模板方法模式。

现在有三类事物机器人、美女、帅哥,这三类事物可以完成的功能如下。

机器人:吃饭、工作;

美女:吃饭、跑步、睡觉;

帅哥:吃饭、工作、跑步、睡觉。

那么现在的问题就是如何将以上的程序变成操作类。

那么首先应该思考的是这三类事物的共同特征:四个固定的操作行为。

模板设计模式(代码TemplateMethod.java)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第01~44行定义操作行为类,其中在03~06行定义了四个全局常量,在07~10行声明了四个抽象方法,11~43行声明了一个具体方法。

第45~57行声明子类Robot,通过覆写抽象方法定义机器人的行为。

第58~73行声明子类Woman,通过覆写抽象方法定义美女的行为。

第74~92行声明子类Man,通过覆写抽象方法定义帅哥的行为。

此时,如果要想实现指定的操作,只需要将方法按照要求覆写即可,相当于父类定义出了一个操作模板。实际用的时候也可以在Servlet程序设计上使用。

接口

接口(interface)是Java所提供的另一种重要技术,是一种特殊的类,它的结构和抽象类非常相似,也具有数据成员与抽象方法,但它与抽象类又有不同,并且Java8中又添加了新特性。

接口的基本概念

接口里的数据成员必须初始化,且数据成员均为常量,常见的是全局变量。

接口里的方法为abstract,也就是说,接口不能像抽象类一样定义一般的方法,需定义“抽象方法”。

另,Java8中为避免在接口中添加新方法后要修改所有实现类,允许定义默认方法,即default方法,也可以称为Defender方法,或者虚拟扩展方法(Virtualextension methods)。

Default方法是指,在接口内部包含了一些默认的方法实现(也就是接口中可以包含方法体,这打破了Java之前版本对接口的语法限制),从而使得接口在进行扩展的时候,不会破坏与接口相关的实现类代码。

在Java中使用interface关键字来定义一个接口。

接口定义的语法如下。
在这里插入图片描述
定义接口例:
在这里插入图片描述
带默认方法的接口定义例:
在这里插入图片描述
虽然有了接口,可是定义的接口A和接口B因里面存在抽象方法,都不能被用户直接使用。

接口的使用原则

使用接口必须遵守如下原则。

接口必须有子类,子类依靠implements关键字可以同时实现多个接口;

接口的子类(如果不是抽象类)则必须覆写接口之中的全部抽象方法;

接口可以利用对象多态性,利用子类实现对象的实例化。

接口与一般类一样,本身也具有数据成员与方法,但数据成员一定要赋初值,且此值不能再更改,方法也必须是“抽象方法”或default方法。也正因为方法除default方法外必须是抽象方法,而没有一般的方法,所以接口定义格式中,抽象方法声明的关键字abstract是可以省略的。

同理,接口的数据成员身上,因数据成员必须赋初值,且此值不能再被更改,所以声明数据成员的关键字final也可省略。

简写的接口定义例:
在这里插入图片描述
在Java中接口是用于实现多继承的一种机制,也是Java设计中最重要的一个环节,每一个由接口实现的类必须在类内部覆写接口中的抽象方法,且可自由地使用接口中的常量。

既然接口里只有抽象方法,它只需声明而不用定义处理方式,于是自然可以联想到接口没有办法像一般类一样,再用它来创建对象。利用接口创建新类的过程,称之为接口的实现(implementation)。

以下为接口实现的语法。
在这里插入图片描述
带default方法接口的实现(代码Interfacedefault.java)。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第01~09行定义接口InterfaceA,其中定义全局变量INFO,抽象方法print(),默认方法otherprint()。

第10~17行定义子类InterfaceAB实现接口InterfaceA,定义抽象方法print()。

第22~27行实例化子类对象,并调用覆写过的抽象方法和默认方法,输出接口中的常量。

上例中定义了一个接口,接口中定义常量INFO,省略final,定义抽象方法print();,省略Abstract,定义带方法体的默认方法。

第15、25行分别引用接口中的常量。

Java8中允许在接口中只定义默认方法,无抽象方法。

仅有default方法接口的使用(代码Interfacedefaultonly.java)。
在这里插入图片描述
在这里插入图片描述
第01~07行定义接口InterfaceA,其中定义默认方法otherprint()。

第08~10行定义子类InterfaceAB实现接口InterfaceA,因接口中无抽象方法,不需覆写抽象方法。

第15~16行实例化子类对象,并调用默认方法。

上例中定义了仅有一个默认方法的接口,无抽象方法,继承接口的子类因不需覆写抽象方法,内容为空。

接口与抽象类相比,最大的区别就在于子类上,子类可以同时实现多个接口。

子类继承多个接口的应用(代码InterfaceDemo.java)。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第01~05行定义接口A,其中定义全局变量INFO,抽象方法print()

第06~09行定义接口B,定义抽象方法get()

第10~20行定义子类X实现接口A,B,分别对接口A,B中的抽象方法进行覆写。

由上例可以发现接口与抽象类相比,最大的区别就在于子类上,子类可以同时实现多个接口。

但在Java8中,如果一个类实现两个或多个接口,即多继承,但是若其中两个接口中都包含一个名字相同的default方法,如下例中的InterfaceA,InterfaceB,有同名的默认方法DefaultMethod(),但方法体不同。

在这里插入图片描述
然后定义一个类,同时实现这两个接口,如下例。
在这里插入图片描述
如果编译以上的代码,编译器会报错,因为编译器不知道应该在两个同名的default方法中选择哪一个,因此产生了二义性。
在这里插入图片描述
因此,一个类实现多个接口时,若接口中有默认方法,不能出现同名默认方法。多继承中,如果说在一个子类即要实现接口又要继承抽象类,则应该采用先继承后实现的顺序完成。

继承抽象类实现接口(代码ExtendsInterface.java)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第01~05行声明了一个A接口,并在里面声明了1个常量INFO并赋初值,定义一抽象方法print( )。

第06~09行声明了一个B接口,定义一个抽象方法get()。

第10~13行声明抽象类C,定义抽象方法fun( )。

第14~28行声明类X,X继承抽象类B,并且实现接口A,B。

第33行实例化了一个类X的对象x。

第34~35行实现父接口实例化。

第36行实现抽象类实例化。

接口使用过程中,一个抽象类可以继承多个接口,但是反过来讲,一个接口却不能够继承抽象类,但是一个接口却可以使用extends关键字继承多个接口。
在这里插入图片描述
一个接口可继承多个接口(代码AbstractInterfaces.java)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第01~05行声明了一个A接口,并在里面声明了1个常量INFO并赋初值,定义一抽象方法print( )。

第06~09行声明了一个B接口,定义一个抽象方法get()。

第10~13行声明抽象类C,抽象类C实现了接口A,B,定义抽象方法fun( )。

第14~17行定义接口D,接口D继承了接口A,B,定义抽象方法printD( )。

第18~36行定义类X,类X继承抽象类C,实现接口D,覆写四个抽象方法。

第41行实例化了一个类X的对象x

第42~43、45行实现父接口实例化。

第44行实现抽象类实例化。

抽象类C实现接口A,B,则抽象类中有三个抽象方法。抽象类C的实例c,可以引用c.get();, c.print()。

接口D继承接口A,B,则接口D中有三个抽象方法,print(),get(),printD()。

类X继承C,实现接口D,X中覆写了四个抽象方法。

由上例知,一个接口可以同时继承多个接口,也可以同时继承多个接口的抽象方法与常量。

接口的作用——制定标准

接口是标准,所谓的标准,指的是各方共同遵守的一个守则。只要操作标准统一了,所有的参与者才可以按照统一的规则操作。

如在电脑以及各个设备的连接上,USB就是一个操作标准,那么下面通过代码来验证以上的操作。

接口制定标准(代码Interfacestandards.java)。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第01~04行定义USB接口标准,其中有一个work( )抽象方法,表示拿到USB设备就要工作。

第05~11行定义Computer类,其中定义具体方法plugin(USB usb),表明在电脑上做出支持USB设备的操作,该类只要有USB设备就可以使用。

第12~18行定义Print类实现接口USB,定义具体的USB设备打印机。

第19~25行定义Flash类实现接口USB,定义具体的USB设备U盘。

第30行创建Computer类对象com。

第31~32行分别在电脑上使用打印机和U盘。

按照固定的USB接口标准,可以定义无数多个子类,并且这无数多个子类,都可以在电脑上插入使用。

按照以上的方式不管有多少个设备,电脑上支持度都是一样,所以现在的USB提供的就是一个操作标准。

接口的作用——工厂设计模式(Factory)

在面向对象编程中, 最通常的方法是一个new操作符产生一个对象实例,new操作符就是用来构造对象实例的。但是在一些情况下,new操作符直接生成对象会带来一些问题。举例来说,许多类型对象的创造需要一系列的步骤。你可能需要计算或取得对象的初始设置;选择生成哪个子对象实例;或在生成你需要的对象之前必须先生成一些辅助功能的对象。 在这些情况, 新对象的建立就是一个“过程”,不仅是一个操作,像一部大机器中的一个齿轮传动。

如何能轻松地建立这么“复杂”的对象即操作中不需要粘贴复制呢?建立一个工厂来创建新的对象。

接口的作用(代码SimpleFactory.java)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第01~04行定义一个水果标准Fruit,其中定义一个抽象方法eat( )。

第05~11行定义一个Apple类实现接口Fruit,覆写抽象方法eat( )。

第12~18行定义一个Orange类实现接口Fruit,覆写抽象方法eat( )。

第23行子类为接口实例化。

此例中发现在主类(客户端)之中直接让一个具体的子类和一个接口绑定在一起,那么如果要修改使用的子类,对于程序而言,就意味着要修改客户端。所以此时程序之中就出现了接口和子类的耦合问题,为解决这一问题,使用工厂模式。

工厂模式(代码Factory.java)。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第01~04行定义一个水果标准Fruit,其中定义一抽象方法。

第05~11行定义一个Apple类实现接口Fruit,覆写抽象方法eat( )。

第12~18行定义一个Orange类实现接口Fruit,覆写抽象方法eat( )。

第19~33行定义类Factory1。

第38行子类为接口实例化,根据参数内容实例化不同的子类。

根据参数args[0]的内容实例化不同的子类,参数内容为“apple”,实例化的是Apple类,参数内容“orange”,则实例化的是Orange类,即输出内容也不同。

也可以在主类中直接设置参数内容“orange”:Fruit f =Factory1.getInstance(“orange”) ;

此时的程序,客户端没有和具体的子类耦合在一起,这样一来,如果再有更多的Fruit接口子类出现,只需要修改Factory类即可,即:所有的接口对象都通过Factory类取得。在程序员自己开发的代码之中,只要是遇见了取得接口对象实例的操作,都应该采用工厂设计模式。

接口的作用——代理设计模式(Proxy)

代理模式:给某一对象提供代理对象,并由代理对象控制具体对象的引用。

代理,指的就是一个角色代表另一个角色采取行动,就像生活中,一个红酒厂商是不会直接把红酒零售给客户的,都是通过代理来完成他的销售业务的。而客户也不用为了喝红酒而到处找工厂,他只要找到厂商在当地的代理就行了,具体红酒工厂在那里,客户不用关心,代理会帮他处理。

这里产生了四个对象:客户、代理商、红酒厂商、代理商-红酒厂商(关系)。

代理模式作用:为其他对象(红酒厂商)提供一种代理(代理商)以控制对这个对象(红酒厂商)的访问。

代理对象可以在客户端(客户)和目标对象(红酒厂商)之间起到中介作用。

代理设计模式(代码Proxytest.java)。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第01~04行定义一个抽象类Subject,其中定义一抽象方法request( )。

第05~11行定义一个RealSubject类实现继承Subject,覆写抽象方法request( )。

第12~35行定义一个ProxySubject代理类,覆写抽象方法request ( ),定义preRequest( )。

代理设计模式的核心组成:一个接口有两个子类,一个子类负责真实的业务操作功能,另外一个子类负责完成与真实业务有关的操作。

抽象类和接口对比

abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。虽然两者在对于抽象类定义的支持方面具有很大的相似性,但也有各自不同的特点。
在这里插入图片描述
通过上面的系列分析,可以发现,抽象类和接口共同点。⑴ 都是抽象类型;

⑴ 都是抽象类型;

⑵ 都可以有实现方法(以前接口不行);

⑶ 都可以不需要实现类或者继承者去实现所有方法。(以前不行,现在接口中默认方法不需要实现者实现)

抽象类和接口不同点。

⑴ 抽象类不可以多重继承,接口可以(无论是多重类型继承还是多重行为继承);

⑵ 抽象类和接口所反映出的设计理念不同。其实抽象类表示的是"is-a"关系,接口表示的是"like-a"关系;

⑶ 接口中定义的变量默认是public static final 型,且必须给其初值,所以实现类中不能重新定义,也不能改变其值;抽象类中的变量默认是 friendly 型,其值可以在子类中重新定义,也可以重新赋值。

总体来说,抽象类和接口在很大程度上都是可以互相替换使用的,但就是由于抽象类本身具备单继承局限,所以当抽象类和接口全部都可以使用的时候优先考虑接口,因为接口没有单继承局限,并且在Java8中接口可以设定默认方法,在一定程度上避免代码重复,利于后期的维护。

1. 继承抽象类和继承普通类最大的区别

⑴ 在普通类之中所有的方法都是有方法体的,那么如果说有一些方法希望由子类来覆写的时候,子类即使不覆写也不会出现错误;

⑵ 如果使用抽象类的话,那么抽象类之中的抽象方法在语法上就必须要求子类进行覆写,这样就可以强制子类做一些固定操作。

2.接口、抽象类、类、对象的关系

⑴ 基本类:也就是一般的类(一般所说的类就是基本类),是对象的模板,是属性和方法的集合。可以继承其他基本类、抽象类、实现接口。

⑵ 抽象类:有抽象方法的类(抽象方法就是该方法必须由继承来实现,本身只定义,不实现)。抽象类可以有一个或多个抽象方法,它是基本类和接口类的过度。

⑶ 接口:接口中的所有方法除默认方法(带方法体)外都是抽象方法,抽象方法本身只定义不实现,用来制定标准。

四者间的关系如下图所示。
在这里插入图片描述
实际上所谓的接口就是指在类的基础上的进一步抽象。而很多的时候在开发之中,也会避免掉抽象类的出现,因为抽象类毕竟存在单继承局限。类与类之间的共性就成为了接口的定义标准。

类、抽象类、接口之间的联系,也可以举例如下,一个公司,有老板,老板聘的经理,还有员工,每一个员工就是一个对象,类就是员工,抽象类就是经理,接口就是老板。接口就是给个方法,但是他自己不做,比如老板说我要那个文件,给我定个机票,我要那个策划方案等,都是下面人做。老板只说不做。抽象类给的方法,有的他自己做,有的其他人做。比如经理说我要那个文档,员工就要发给他,但是他自己也要做点事,比如拿方案给老板看。经理又说又做。 一般类给的方法,就是什么都要做,都要实现。

3.接口和抽象类的应用

abstract class在Java语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在“is a”关系,即父类和派生类在概念本质上应该是相同的。对于interface 来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。

考虑这样一个例子,预研究建立一个关于Door的抽象概念,一般认为Door可执行两个动作open和close,若通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示。

使用abstract class方式定义Door。
在这里插入图片描述
其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。

如果现在要求Door还要具有报警的功能。如何设计类结果呢?

解决方案一

简单的在Door的定义中增加一个alarm方法,用抽象类定义如下。
在这里插入图片描述
则具有报警功能的AlarmDoor的定义方法如下。
在这里插入图片描述
或用interface:
在这里插入图片描述
则具有报警功能的AlarmDoor通过接口的定义方法如下。
在这里插入图片描述
直接增加alarm方法违反了面向对象设计中的一个核心原则ISP(InterfaceSegregation Principle),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变(比如:修改alarm方法的参数)而改变,反之依然。

解决方案二

显然,open、close和alarm属于两个不同的概念,根据ISP原则应该把它们分别定义在代表这两个概念的抽象类中。

定义的可能方式有三种。

⑴ 这两个概念都使用abstract class方式定义

由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。

⑵ 两个概念都使用interface方式定义

无法明确体现AlarmDoor在概念本质上到底是Door还是报警器,无法反映AlarmDoor在概念本质上和Door是一致的。

⑶ 一个概念使用abstract class方式定义,另一个概念使用interface方式定义
在这里插入图片描述
abstract class在Java语言中表示一种继承关系,而继承关系在本质上是"is a"关系,对于Door这个概念,我们应该使用abstract class方式来定义。interface表示的是"like a"关系,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为。

Java—众类鼻祖──Object类


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