零基础带你入门 Java(第八章:“抽象类和接口“)
你好我是林森lsjs我的Github地址:sqyCoder (Qiyang) · GitHub恭喜你来到第八章前面我们已经学完了 Java 的基础语法、面向对象的封装、继承、多态三大特性这一章我们来学习面向对象中最核心也最容易混淆的两个概念抽象类和接口。我们从 为什么需要这个东西 讲起而不是只告诉你 这个东西怎么用。目录一、抽象类1. 什么是抽象类2. 抽象类的完整语法规则3. 抽象类的 6 个核心特性4. 抽象类的 3 个作用​编辑作用一强制子类必须实现某些方法作用二代码复用作用三实现多态二、接口1. 什么是接口2. 接口的完整语法规则3. 接口的基本使用流程4. 接口的 7 个核心特性5. 实现多个接口解决 Java 单继承的痛点三、抽象类 vs 接口四、总结一、抽象类1. 什么是抽象类在讲抽象类之前我们先来看一个实际的问题。假设我们现在要做一个动物园管理系统需要设计动物类。我们知道所有的动物都有名字、年龄都会吃东西、会叫。所以我们可以先写一个 Animal 类class Animal { protected String name; protected int age; public Animal(String name, int age) { this.name name; this.age age; } public void eat() { System.out.println(name 在吃东西); } public void shout() { // 这里该写什么 // 狗是汪汪汪猫是喵喵喵鸟是叽叽喳喳 // 不同的动物叫声不一样啊 } }问题来了shout()方法到底该怎么实现我们根本没办法在 Animal 类中写出一个通用的shout()方法因为 Animal 只是一个笼统的概念不是具体的某种动物。如果我们随便写一个实现比如public void shout() { System.out.println(动物在叫); }那所有继承 Animal 的子类如果忘记重写shout()方法就会输出 动物在叫这显然是错误的。我们希望强制所有子类都必须实现自己的shout()方法如果不实现编译器就报错。这就是抽象类诞生的原因抽象方法只有方法声明没有方法体的方法。它只定义了 做什么不定义 怎么做。抽象类包含抽象方法的类。它是一个 半成品 的类定义了子类应该具备的一些方法但是具体怎么实现交给子类自己去完成。2. 抽象类的完整语法规则抽象类和抽象方法都用abstract关键字来修饰。完整的语法格式如下// 抽象类的定义 [修饰符] abstract class 类名 { // 成员变量 [修饰符] 数据类型 变量名; // 构造方法 [修饰符] 构造方法名(参数列表) { // 构造方法体 } // 普通方法 [修饰符] 返回值类型 方法名(参数列表) { // 方法体 } // 抽象方法没有方法体以分号结束 [修饰符] abstract 返回值类型 方法名(参数列表); }现在我们把上面的 Animal 类改写成抽象类// 用abstract修饰类表示这是一个抽象类 abstract class Animal { // 普通成员变量 protected String name; protected int age; // 构造方法抽象类可以有构造方法给子类调用 public Animal(String name, int age) { this.name name; this.age age; } // 普通方法所有子类通用的方法 public void eat() { System.out.println(name 在吃东西); } // 抽象方法强制子类必须实现 public abstract void shout(); }语法注意事项抽象方法不能有方法体也就是不能有大括号{}必须以分号;结束抽象方法不能是 private的因为 private 方法不能被子类继承也就无法重写抽象方法不能是 static的因为 static 方法属于类不属于对象无法被重写抽象方法不能是 final的因为 final 方法不能被重写3. 抽象类的 6 个核心特性这部分是考试和面试的绝对重点我会逐条讲解并且给出错误的反例让你彻底记住。特性 1抽象类不能被实例化这是抽象类最重要的特性。你不能写new Animal();因为 Animal 是抽象的没有具体的实现就像你不能造一个 笼统的动物 一样只能造具体的狗、猫。错误写法public class Test { public static void main(String[] args) { Animal animal new Animal(动物, 1); // 编译错误抽象类不能实例化 } }特性 2抽象类中可以有普通成员变量、普通方法、构造方法、静态方法抽象类只是比普通类多了抽象方法而已其他的都和普通类完全一样。构造方法不能用来实例化抽象类本身但是可以被子类通过super()调用用来初始化抽象类中的成员变量静态方法属于抽象类本身可以直接通过类名调用不需要实例化特性 3抽象类中可以没有抽象方法但是有抽象方法的类一定是抽象类也就是说你可以写一个空的抽象类里面什么方法都没有// 合法的抽象类虽然里面什么都没有 abstract class EmptyAbstractClass { }但是只要你的类中有一个抽象方法那这个类就必须声明为抽象类否则编译器会报错特性 4一个普通类继承了抽象类必须重写抽象类中所有的抽象方法如果不重写那这个普通类也得变成抽象类。特殊情况如果子类也是抽象类那么可以不重写父类的抽象方法或者只重写一部分特性 5抽象类可以继承另一个抽象类抽象类之间的继承和普通类之间的继承一样用extends关键字。abstract class A { public abstract void methodA(); } abstract class B extends A { // 继承了methodA()可以选择重写或者不重写 public abstract void methodB(); } // 普通类C继承了B必须重写methodA()和methodB()两个抽象方法 class C extends B { Override public void methodA() { System.out.println(实现methodA); } Override public void methodB() { System.out.println(实现methodB); } }特性 6抽象类可以有 final 方法final 方法不能被重写所以抽象类中的 final 方法可以被子类继承但是不能被子类修改。abstract class Animal { // final方法子类不能重写 public final void breathe() { System.out.println(动物在呼吸); } public abstract void shout(); }4. 抽象类的 3 个作用抽象类不能实例化那它有什么用啊我直接写普通类不行吗问得好抽象类主要有以下 3 个核心作用作用一强制子类必须实现某些方法这是抽象类最核心的作用。我们把所有子类都必须具备的方法定义为抽象方法这样任何继承这个抽象类的子类都必须重写这些方法否则编译不通过。这就相当于我们给子类制定了一个 标准所有子类都必须遵守这个标准。比如我们规定所有的动物都必须会叫那任何继承 Animal 的子类都必须实现shout()方法。作用二代码复用抽象类中可以写一些所有子类都通用的方法和成员变量这样所有子类都可以直接继承使用不用重复写。比如上面的eat()方法所有动物吃东西的行为都是差不多的我们就可以把它写在抽象类 Animal 中Dog、Cat、Bird 这些子类直接继承就可以了不用每个子类都写一遍eat()方法。作用三实现多态抽象类是实现多态的重要方式之一。通过抽象类的引用指向不同的子类对象我们可以写出更加灵活、可扩展的代码。比如我们写一个动物园的方法让所有的动物都叫一声public class Zoo { // 这个方法接收一个Animal类型的参数 public static void makeAllShout(Animal[] animals) { for (Animal animal : animals) { animal.shout(); // 自动调用对应子类的shout()方法 } } public static void main(String[] args) { Animal[] animals { new Dog(旺财, 3), new Cat(咪咪, 2), new Bird(小鸟, 1) }; makeAllShout(animals); } }运行结果旺财说汪汪汪 咪咪说喵喵喵 小鸟说叽叽喳喳你看这个makeAllShout()方法非常灵活不管以后我们新增多少种动物只要它继承了 Animal 类就可以直接传入这个方法不需要修改方法本身。这就是多态的威力二、接口1. 什么是接口接口可以理解为一种纯粹的规范如果说抽象类是 半成品 的类那接口就是 纯规范它只定义了 能做什么完全不关心 怎么做。接口的例子USB 接口规定了尺寸、引脚定义、传输协议等规范。任何符合这个规范的设备U 盘、鼠标、键盘、手机都可以插在电脑上使用。电脑不需要知道这个设备具体是什么只要它符合 USB 规范就可以正常工作。在 Java 中接口定义了一组能力一个类实现了某个接口就表示它具备了这个接口定义的能力。比如一个类实现了Flyable接口就表示它会飞一个类实现了Swimmable接口就表示它会游泳2. 接口的完整语法规则接口用interface关键字来定义而不是class。完整的语法格式如下// 接口的定义 [修饰符] interface 接口名 { // 常量默认是public static final [public static final] 数据类型 常量名 值; // 抽象方法默认是public abstract [public abstract] 返回值类型 方法名(参数列表); }注意接口中的所有成员都有默认的修饰符这些修饰符可以省略不写但是编译器会自动加上。现在我们来定义一个 USB 接口// 用interface定义接口 interface USB { // 常量USB的最大传输速度 // 等价于 public static final int MAX_SPEED 100; int MAX_SPEED 100; // 抽象方法读取数据 // 等价于 public abstract void read(); void read(); // 抽象方法写入数据 // 等价于 public abstract void write(); void write(); }语法注意事项接口中的所有变量默认都是public static final的也就是常量。所以必须初始化而且不能被修改。接口中的所有方法默认都是public abstract的所以可以省略不写。接口不能有构造方法因为它不能被实例化。接口不能有普通方法接口不能有成员变量只能有常量。3. 接口的基本使用流程一个类使用implements关键字来实现接口并且必须重写接口中所有的抽象方法。完整的使用流程分为 3 步定义接口定义类实现接口重写所有抽象方法创建实现类的对象通过接口引用指向它多态例子// 第一步定义USB接口 interface USB { int MAX_SPEED 100; void read(); void write(); } // 第二步定义UDisk类实现USB接口 class UDisk implements USB { // 必须重写接口中所有的抽象方法 Override public void read() { System.out.println(U盘正在读取数据速度 MAX_SPEED MB/s); } Override public void write() { System.out.println(U盘正在写入数据速度 MAX_SPEED MB/s); } } // 第三步使用接口 public class Test { public static void main(String[] args) { USB usb new UDisk(); // 向上转型接口引用指向实现类对象 usb.read(); usb.write(); } }运行结果U盘正在读取数据速度100MB/s U盘正在写入数据速度100MB/s4. 接口的 7 个核心特性特性 1接口不能被实例化和抽象类一样接口不能被实例化。你不能写new USB();因为接口只是一个规范没有具体的实现。特性 2接口中的所有变量默认都是public static final的也就是说接口中的变量都是公共的、静态的、不可修改的常量。注意事项必须初始化不能被修改可以直接通过接口名访问特性 3接口中的所有方法默认都是public abstract的也就是说接口中的方法都是公共的、抽象的。注意事项不能是 private、protected不能是 static、final没有方法体以分号结束特性 4一个类实现了接口必须重写接口中所有的抽象方法如果不重写那这个类必须声明为抽象类。特殊情况如果实现类是抽象类那么可以不重写接口中的抽象方法特性 5一个类可以实现多个接口这是接口最重要的特性之一我们知道 Java 中类只能单继承也就是一个类只能有一个父类。但是一个类可以实现多个接口这就弥补了单继承的不足。这个特性我们会在下一节详细讲解。特性 6接口可以继承另一个接口而且可以多继承接口之间的继承用extends关键字和类之间的继承不同接口可以继承多个接口。特性 7接口的引用可以指向实现类的对象多态和抽象类一样接口的引用可以指向任何实现了这个接口的类的对象这就是多态的体现。5. 实现多个接口解决 Java 单继承的痛点我们来看一个经典的例子// 抽象类动物 abstract class Animal { protected String name; public Animal(String name) { this.name name; } public abstract void shout(); } // 接口会飞 interface Flyable { void fly(); } // 接口会游泳 interface Swimmable { void swim(); } // 接口会跑 interface Runnable { void run(); }现在我们要写几个动物类狗是动物会叫会跑鱼是动物会叫虽然鱼不会叫但是我们假设它会会游泳鸟是动物会叫会飞鸭子是动物会叫会飞会游泳会跑如果用继承的话鸭子只能继承一个类没办法同时继承会飞、会游泳、会跑这三个特性。但是用接口就可以完美解决这个问题// 狗继承Animal实现Runnable接口 class Dog extends Animal implements Runnable { public Dog(String name) { super(name); } Override public void shout() { System.out.println(name 说汪汪汪); } Override public void run() { System.out.println(name 在跑); } } // 鱼继承Animal实现Swimmable接口 class Fish extends Animal implements Swimmable { public Fish(String name) { super(name); } Override public void shout() { System.out.println(name 说泡泡泡); } Override public void swim() { System.out.println(name 在游泳); } } // 鸟继承Animal实现Flyable接口 class Bird extends Animal implements Flyable { public Bird(String name) { super(name); } Override public void shout() { System.out.println(name 说叽叽喳喳); } Override public void fly() { System.out.println(name 在飞); } } // 鸭子继承Animal实现Flyable、Swimmable、Runnable三个接口 class Duck extends Animal implements Flyable, Swimmable, Runnable { public Duck(String name) { super(name); } Override public void shout() { System.out.println(name 说嘎嘎嘎); } Override public void fly() { System.out.println(name 在飞); } Override public void swim() { System.out.println(name 在游泳); } Override public void run() { System.out.println(name 在跑); } }你看这样是不是非常灵活我们可以给一个类添加任意多的能力只要实现对应的接口就可以了。三、抽象类 vs 接口特性抽象类接口关键字abstract classinterface继承 / 实现类用extends继承只能单继承类用implements实现可以实现多个接口成员变量可以有普通成员变量和常量只能有常量默认public static final方法可以有普通方法、构造方法、静态方法、抽象方法Java 8 之前只能有抽象方法默认public abstract构造方法有构造方法没有构造方法设计目的对类的抽象是 is-a 的关系。比如鸭子是动物对行为的抽象是 can-do 的关系。比如鸭子会飞代码复用可以通过继承实现代码复用不能实现代码复用Java 8 之前多继承不支持多继承支持多继承接口可以继承多个接口核心区别抽象类是对类的抽象它描述的是 这个东西是什么接口是对行为的抽象它描述的是 这个东西能做什么四、总结抽象类是包含抽象方法的类用abstract修饰不能被实例化普通类继承抽象类必须重写所有抽象方法抽象类的作用是制定规范、代码复用和实现多态接口是纯粹的规范用interface修饰不能被实例化接口中的变量默认public static final方法默认public abstract类用implements实现接口必须重写所有抽象方法Java 中类只能单继承但可以实现多个接口解决了单继承的不足抽象类是 is-a 关系接口是 can-do 关系就到这了感谢你的浏览。诸位共勉无限学习PS到这里还远远没结束下篇我们继续深入探讨接口~

相关新闻