4、 抽象类和接口
4.1、 抽象类
通过继承抽取公共的代码,可以简化编程。但是很多时候我们抽取的方法,不同子类重写时会有不同的实现方式,所以Java针对这种情况定义了一种抽象方法,而定义了抽象方法的类就叫抽象类,这种类不能直接实例化对象,它主要提供一个共同的、被其子类继承的结构。
用 abstract 关键字修饰的类就是抽象类,用 abstract 关键字修饰的方法就是抽象方法(不能用abstract修饰变量、代码块、构造器)
。
-
抽象方法:只有声明没有具体实现的方法,子类继承抽象类时,必须实现所有的抽象方法,除非子类也是抽象类。
//这是抽象方法 public abstract void test();
-
具体方法:是有实现的方法,即方法体内有具体的执行代码。
//这是一个具体的方法 public void test(){ //内部可以有执行的代码 System.out.println("可以有执行的代码"); }
有抽象方法的类必定是抽象类,但抽象类并不一定有抽象方法(抽象类可以有普通方法也可以有抽象方法)。
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、方法和构造方法等访问方式和普通类一样。所以抽象类需要子孙类去继承实现其全部抽象方法
,继承抽象类的子类必须实现父类全部抽象方法,否则自身也要设置为抽象类
。因为抽象方法本身没有任何意义,所以抽象方法必须要被重写去具体实现的(类的所有抽象方法都要实现)。
抽象类存在的意义在于其设计性、复用性与扩展性
。从开发的实际要求来讲,普通类尽量不要去继承另外一个普通类,而是去继承抽象类。
抽象类中各成员的具体作用:
构造方法:抽象类不能被直接实例化,构造方法在抽象类中依然有重要作用:
初始化父类的属性:抽象类可以包含一些共有的属性,抽象类的构造方法可以被子类的构造方法通过super调用来执行属性初始化。
执行必要的配置或设置:在实例化子类之前,可能需要执行一些基础设置,这些设置可以放在抽象类的构造方法中完成。
属性:抽象类中的属性定义了子类都应该具有的状态。
定义共有属性:可以定义所有子类共有的属性。
提供必要的数据结构:可以被抽象类自身或其子类中的方法所使用,形成一种内部状态管理。
具体方法:抽象类中的具体方法(非抽象方法)提供了可以立即使用的功能,不强制子类重写这些方法,但子类可以根据需要选择覆盖它们。这些具体方法的作用包括:
提供默认行为:抽象类中的具体方法可以为子类提供默认的行为。
允许代码复用:通过在抽象类中实现方法,可以减少子类中的代码重复。
辅助抽象方法的实现:抽象类中的具体方法可以辅助或调用类中的抽象方法,为抽象方法的实现提供公共的辅助功能。
抽象方法和某些关键字是有冲突的:
1、final修饰方法时表示方法不能被子类重写,抽象方法必定要被子类重写,所以不能在一起使用。
2、static修饰方法时,该方法为静态方法,属于类,而抽象方法需要被子类继承和实现,所以不能同用。
但是两者并非绝对互斥,在修饰内部类时两者可以共存。
3、native表示该方法要使用另一种依赖平台的编程语言实现,不存在子类继承的问题,所以不能使用。
4、synchronized是线程同步,而abstract抽象方法只声明没有实现,所以不存在同步,不能同用。(可以在实现该方法的子类内使用)
5、private的含义是私有,即不让自己外的其他类所知晓,只在自身内部调用,而抽象方法本身就是需要子类去实现的且是一个空方法,所以这两者是不能同用的
4.2、接口
在Java中一个类只能去继承一个父类,那么这在某些时候就有很大的局限性了。所以就出现了接口(interface)这个概念,它和类是并列的两种结构,在Java 中规定一个类可以去实现多个接口(单继承多实现)。
接口是对行为的抽象,内部不能有具体的方法(jdk7及以前版本接口由全局常量和公共的抽象方法所组成,在jdk8后可以有default默认方法和静态方法),接口也无法被实例化,类一旦实现接口就要实现接口内的所有方法。
接口和抽象类不同,抽象类是为了方便代码复用和扩展,而接口更多的是为了提供服务和规范(本质上接口也可以看成是特殊的抽象类)。
- 接口:就类似家中的插座,它有两孔的和三孔的,插座不关心电器种类,只要符合插口的相关规范,就可以使用相对应的插孔。
- 抽象类:就像手机的演变,最早的手机设定只有“远程通信”的概念,这就像是一个抽象类,定义了一个基础功能,到后来逐步发展成智能手机,它们继承了远程通信的功能,又再此基础上增加了上网、拍照等等一系列功能。
接口通常用来定义规范,很大程度上就是运用了向上转型的概念。
类使用 implements 关键字实现接口,如果类还继承了其他类,写在extend之后:public class Test extends ClassA implements InterfaceA,InterfaceB{ }。
//定义接口
public interface Test extends 父接口1,父接口2......{
}
//实现接口
public class TestImpl implement Test{
}
-
接口不能用于实例化对象,内部没有构造方法。一个实现接口的类,必须实现接口内所描述的所有抽象方法,否则就必须声明为抽象类。
-
接口中的方法可以是抽象的、默认的或静态
-
抽象方法:在Java 8之前,接口只能有抽象方法。这些方法默认为public abstract(不需要手动添加),必须由实现接口的类来提供具体实现。
public interface Animal { void eat(); // 隐式为 public abstract }
-
静态方法:Java 8引入了静态方法,静态方法实现类不能调用,只能通过接口名直接调用。
-
默认方法::Java 8引入了默认方法,允许在接口中提供方法的实现,这使得在不破坏现有代码的基础上向接口添加新功能成为可能(使用default关键字标识)。
default 方法可以由实现类的对象来调用,也可以被实现类重写(重写后的default就是一个普通的方法),重写后调用的是实现类重写后的方法。
-
一个接口中定义了一个默认方法,其实现类的父类中也定义了一个同名同参数的非抽象方法,不会出现冲突问题,因为接口中具有相同名称和参数的默认方法会被忽略。这就是:类优先原则。
-
如果类实现了多个接口,而这些接口中存在同名同参的默认方法,在实现类没有重写该方法的情况下,会出现接口冲突的错误,所以实现类必须重写该默认方法。
//定义一个接口 public interface InterfaceTest { //默认方法 default void defaultMenthod(){ System.out.println("JDK8增加的默认方法,非抽象"); } //静态方法 static void staticMenthod(){ System.out.println("接口中的静态方法,不能被实现类重写"); } }
class ImplementsInterfaceTest implements InterfaceTest{ @Test public void mainTest(){ //静态方法由接口调用 InterfaceTest.staticMenthod(); //创建实现类对象调用默认的方法 ImplementsInterfaceTest test = new ImplementsInterfaceTest(); test.defaultMenthod(); } //接口的默认方法, 子类可以实现也可以不实现 /*@Override public void defaultMenthod() { System.out.println("实现类重写默认方法"); InterfaceTest.super.defaultMenthod(); }*/ }
-
-
-
接口中可以有变量,变量会被隐式的被public static final来修饰,并且只能用public static final来修饰。
//接口的声明 [public] interface 接口名 [extends 其他接口]{ //变量 int ID = 1; //实际上就是:public static final int ID = 1; }
-
接口和接口之间也可以继承,接口之间是可以多继承.
public interface Movable { void move(); } public interface Stoppable { void stop(); } //接口之间进行多继承 public interface Vehicle extends Movable, Stoppable { void turn(); }
-
接口不是被类继承,而是要被类实现。一个类可以实现多个接口
public interface Flyable { void fly(); } public interface Readable { void read(); } //实现多个接口 public class Drone implements Flyable, Readable { public void fly() { System.out.println("Drone is flying"); } public void read() { System.out.println("Drone is scanning text"); } }
拿电脑来说,电脑上有很多的接口,其中有USB接口。而键盘、鼠标、耳机等,只要符合USB接口的规范就可以连接到电脑上。对比来说接口的功能就类似于USB,定义好后就只要符合了接口的规范,就可以使用
定义一个电脑
public class Computer{ //电脑定义一个USB的接口,只要符合USB接口就能在电脑上工作 private USB usb; public void setUsb(USB usb){ this.usb = usb; } public void work(){ if(usb != null){ System.out.println("USB设备正在工作"); usb.write(); } } }
定义USB接口
public interface USB { void read(); //定义了读写的方法 void write(); }
定义键盘鼠标,实现USB接口
//定义一个键盘一个鼠标,符合USB接口的规范 public class Keyboard implements USB { public void Knock(){ System.out.println("敲击键盘"); } @Override public void read() {} @Override public void write() { this.Knock(); } } public class Mouse implements USB { public void rClick(){ System.out.println("点击右键"); } public void lClick(){ System.out.println("点击左键"); } @Override public void read() { } @Override public void write() { this.rClick(); this.lClick(); } }
键盘鼠标通过电脑进行工作
因为接口是为了定义规范,实现分离设计而产生的,如果接口使用的好,可以降低程序不同模块之间的耦合,提高程序可扩展性和可维护性,所以在Java中,很多框架的设计都是基于接口的,所以有个说法叫面向接口编程。
4.3、区别
- 共同特征
- 不能实例化:都不能被实例化,只用由其他类实现或继承。
- 多态性:都支持多态,可以使用接口或抽象类类型来引用实现了该接口或继承了该抽象类的对象的实例。
- 抽象方法定义:都可以包含抽象方法,其实现类或子孙类必须实现这些抽象方法
- 不同点:
- 实现和继承:一个类可以同时实现多个接口,但只能继承一个抽象类
- 扩展:一个接口可以继承多个其他接口,并且不需要实现它们的方法。一个抽象类只能继承一个类(抽象或非抽象的),但可以实现多个接口。
- 方法定义:
- 接口:可以包含抽象方法、默认方法、静态方法。默认是公开的(public)。
- 抽象类:可以包含抽象方法、具体方法,可以有任何访问修饰符(private, protected, public)。
- 属性定义:
- 接口:接口中定义的属性都是公开的、静态的和最终的(public static final)。
- 抽象类:可以包含实例变量,这些变量可以有各种访问级别,抽象类可以维护状态。
- 构造函数:
- 接口:不能有构造函数,接口不能被实例化。
- 抽象类:可以有构造函数,但不能用来创建对象,仅仅是为了让子类调用完成相关的初始化
- 使用思想:
- 接口更多的是体现一种规范,接口规定了实现者向外提供哪些方法,规定了调用者可以调用哪些服务,所以接口更多的是多个程序之间的一种通信标准。
- 抽象类更多是一种基于模板式的设计,它是多个子类的抽象模板,是程序的中间产品,主要是为了程序的扩展,实现代码的复用
- 接口中不能含有初始化块,抽象类中可以含有初始化块
在设计思想上,接口主要是对类的行为进行约束,它只约束类行为的有无,对行为的具体实现不做规定。接口只定义行为,类的主体怎么实现,接口不关心。是like a 的关系。所以接口主要用于架构的不同模块之间的通信契约。
抽象类主要是为了实现代码的复用,当两个以上的类的某些方法的一部分具有相同实现的情况下,通常会把这些相同的行为抽象出来,做成抽象类,在继承了这些功能后还能自己扩展属于自己的功能。抽象类是对本质进行抽象,子类至少是一个父类,is a。