由来
当我们声明一个几何图形类:圆、矩形、三角形类等,发现这些类都有共同特征:求面积、求周长、获取图形详细信息。那么这些共同特征应该抽取到一个公共父类中。但是这些方法在父类中又无法给出具体的实现,而是应该交给子类各自具体实现。那么父类在声明这些方法时,就只有方法签名,没有方法体,我们把没有方法体的方法称为抽象方法。Java语法规定,包含抽象方法的类必须是抽象类。
抽象类的语法格式
特点:没有方法体且被abstract修饰的方法
抽象方法的语法格式
特点:抽象方法没有方法体 ,且被abstract关键字修饰
注意事项
关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。
-
抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
-
抽象类中,也有构造方法,是供子类创建对象时,初始化父类成员变量使用的。
理解:子类的构造方法中,有默认的super()或手动的super(实参列表),需要访问父类构造方法。
-
抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
-
抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。
理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
代码示例
定义抽象类
//抽象类
public abstract class Anima {
//类中成员变量
private String name;
//抽象类中,也有构造方法,是供子类创建对象时,初始化父类成员变量使用的。
public Anima(String name) {
this.name = name;
}
public Anima() {
}
//成员方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
public abstract void eat();
}
定义实现类
public class Dog extends Anima{
public Dog(String name) {
super(name);
}
public Dog() {
}
@Override
//抽象类的子类,必须重写抽象父类中所有的抽象方法,除非该子类也是抽象类。
public void eat() {
System.out.println("狗狗吃骨头");
}
}
定义测试类
public class Test {
public static void main(String[] args) {
// Anima anima = new Anima();抽象类不能创建对象
//只能创建其非抽象子类的对象。
Dog dog = new Dog("大黄");
dog.eat();//狗狗吃骨头
System.out.println(dog.getName());//大黄
}
}
接口
由来
电脑边上提供了USB插槽,这个插槽遵循了USB的规范,只要其他设备也是遵循USB规范的,那么就可以互联,并正常通信。至于这个电脑、以及其他设备是哪个厂家制造的,内部是如何实现的,我们都无需关心。
这种设计是将规范和实现分离,这也正是Java接口的好处。Java的软件系统会有很多模块组成,那么各个模块之间也应该采用这种面相接口的低耦合,为系统提供更好的可扩展性和可维护性。
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要...则必须能...”的思想。继承是一个"是不是"的is-a关系,而接口实现则是 "能不能"的has-a关系。理解:接口是从多个相似类中抽象出来的规范,不需要提供具体实现
- 例如:你能不能用USB进行连接,或是否具备USB通信功能,就看你是否遵循USB接口规范
- 例如:Java程序是否能够连接使用某种数据库产品,那么要看该数据库产品有没有实现Java设计的JDBC规范
为什么接口中只能声明公共的静态的常量?
- 因为接口是标准规范,在规范中需要声明一些底线边界值,当实现者在实现这些规范时,不能去随意修改和触碰这些底线,否则就有“危险”。
接口的定义,它与定义类方式相似,但是使用 interface关键字。它也会被编译成.class文件,但一定要明确接口并不是类,而是另外一种引用数据类型。
接口中可以声明什么?
在JDK8之前,接口中只允许出现:
- 公共的静态的常量:其中public static final可以省略。
- 公共的抽象的方法:其中public abstract可以省略
//定义接口
public interface Fly {
//公共的静态的常量:其中public static final可以省略
String name = "示例代码";
// 公共的抽象的方法:其中public abstract可以省略
void fly();
}
在JDK1.8时,接口中允许声明默认方法和静态方法:
- 公共的默认的方法:其中public 可以省略,建议保留,但是default不能省略。
- 我们要在已有的老版接口中提供新方法时,如果添加抽象方法,就会涉及到原来使用这些接口的类就会有问题,那么为了保持与旧版本代码的兼容性,只能允许在接口中定义默认方法实现。比如:Java8中对Collection、List、Comparator等接口提供了丰富的默认方法。
- 当我们接口的某个抽象方法,在很多实现类中的实现代码是一样的,此时将这个抽象方法设计为默认方法更为合适,那么实现类就可以选择重写,也可以选择不重写。
- 重写时,default单词就不要再写了,它只用于在接口中表示默认方法,到类中就没有默认方法的概念了
- 公共的静态的方法:其中public 可以省略,建议保留,但是static不能省略。之前的标准类库设计中,有很多Collection/Colletions或者Path/Paths这样成对的接口和类,后面的类中都是静态方法,而这些静态方法都是为前面的接口服务的,那么这样设计一对API,不如把静态方法直接定义到接口中使用和维护更方便
//定义接口
public interface Fly {
//默认方法
public default void start(){
System.out.println("默认方法");
}
//静态方法
public static void show(){
System.out.println("静态方法");
}
}
在JDK1.9时,接口又增加了:
- 私有方法:因为有了默认方法和静态方法这样具有具体实现的方法,那么就可能出现多个方法由共同的代码可以抽取,而这些共同的代码抽取出来的方法又只希望在接口内部使用,所以就增加了私有方法。
除此之外,接口中不能有其他成员,没有构造器,没有初始化块,因为接口中没有成员变量需要初始化。
接口的声明格式
实现接口
接口的使用,它不能创建对象,但是可以被实现(implements,类似于被继承)。类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也习惯称为接口的子类(严格来说,实现类不是子类)。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements关键字。
注意:实现类不能继承接口中的静态方法和私有方法
实现接口语法格式
如何调用对应的方法
-
对于接口的静态方法,直接使用“接口名.”进行调用即可。也只能使用“接口名."进行调用,不能通过实现类的对象进行调用
-
对于接口的抽象方法、默认方法,只能通过实现类对象才可以调用。接口不能直接创建对象,只能创建实现类的对象
代码示例
定义接口
//定义接口
public interface LiveAble {
// 定义抽象方法
public abstract void eat();
public abstract void breathe();
//定义默认方法
public default void sleep(){
System.out.println("静止不动");
}
//定义静态方法
public static void drink(){
System.out.println("喝水");
}
}
定义实现类Animal
public class Animal implements LiveAble{
//重写/实现接口的抽象方法
@Override
public void eat() {
System.out.println("吃东西");
}
//重写/实现接口的抽象方法
@Override
public void breathe(){
System.out.println("吸入氧气呼出二氧化碳");
}
//重写接口的默认方法
@Override
public void sleep() {
System.out.println("闭上眼睛睡觉");
}
}
定义实现类Plant
public class Plant implements LiveAble {
//重写/实现接口的抽象方法
@Override
public void eat() {
System.out.println("吸收营养");
}
//重写/实现接口的抽象方法
@Override
public void breathe(){
System.out.println("吸入二氧化碳呼出氧气");
}
}
定义测试类
public class Test {
public static void main(String[] args) {
// 创建实现类(子类)对象
Animal a = new Animal();
// 调用实现后的方法
a.eat(); //吃东西
a.sleep(); //闭上眼睛睡觉
a.breathe(); //吸入氧气呼出二氧化碳
//创建实现类(子类)对象
Plant p = new Plant();
p.eat(); //吸收营养
p.sleep(); //静止不动
p.breathe(); //吸入二氧化碳呼出氧气
//通过接口调用静态方法
LiveAble.drink();
// Animal.drink();错误,找不到符号
// Plant.drink();错误,找不到符号
}
}
接口的多实现
在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现。并且,一个类能继承一个父类,同时实现多个接口。
实现格式:
注意事项:
- 接口中,有多个抽象方法时,实现类如果不是抽象类就必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次。
- 当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的抽象方法重名,子类就近选择执行父类的成员方法
- 当一个类同时实现了多个接口,而多个接口中包含方法签名相同的默认方法时,要么选择保留其中一个,通过“接口名.super.方法名"的方法选择保留哪个接口的默认方法,要么选择自己完全重写
接口的多继承
一个接口能继承另一个或者多个接口,接口的继承也使用 extends关键字,子接口继承父接口的方法。最底层的实现类(非抽象类)要实现接口里所有的抽象方法,包括接口继承下来的。
定义接口
package sgg1.demo06;
// 父接口
interface A {
void a();
public default void methodA(){
System.out.println("A类默认方法");
}
}
// 父接口
interface B {
void b();
public default void methodB(){
System.out.println("B类默认方法");
}
}
//子接口
interface C extends A,B{
}
//定义实现类
class D implements C{
@Override
public void a() { //重写a接口的抽象方法
System.out.println("重写a接口的抽象方法");
}
@Override
public void b() {//重写b接口的抽象方法
System.out.println("重写b接口的抽象方法");
}
}
定义测试类
public class Test {
public static void main(String[] args) {
D d = new D();
d.a(); //重写a接口的抽象方法
d.b(); //重写b接口的抽象方法
d.methodA(); //A类默认方法
d.methodA(); //A类默认方法
}
}
接口与实现类对象的多态引用
实现类实现接口,类似于子类继承父类,因此,接口类型的变量与实现类的对象之间,也可以构成多态引用。通过接口类型的变量调用方法,最终执行的是你new的实现类对象实现的方法体。
public class TestInterface {
public static void main(String[] args) {
Flyable b = new Bird();
b.fly(); //展翅高飞
Flyable k = new Kite();
k.fly(); //别拽我,我要飞
}
}
interface Flyable{
//抽象方法
void fly();
}
class Bird implements Flyable{
@Override
public void fly() {
System.out.println("展翅高飞");
}
}
class Kite implements Flyable{
@Override
public void fly() {
System.out.println("别拽我,我要飞");
}
}