抽象类
语法规则:
首先我们来看一段代码:
class Shape{
public void draw(){
}
}
class Cycle extends Shape{
@Override
public void draw(){
System.out.println("⚪");
}
}
class Rect extends Shape {
@Override
public void draw(){
System.out.println("口");
}
}
class Flower extends Shape {
@Override
public void draw(){
System.out.println("♣");
}
}
public class Test1 {
public static void main(String[] args) {
Shape shape1 = new Cycle();
Shape shape2 = new Rect();
Shape shape3 = new Flower();
drawMap(shape1);
drawMap(shape2);
drawMap(shape3);
}
public static void drawMap( Shape shape){
shape.draw();
}
}
在上面的打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由 Shape 的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstract method), 包含抽象方法的类我们称为 抽象类(abstract class).
- 在 draw 方法前加上 abstract 关键字, 表示这是一个抽象方法. 同时抽象方法没有方法体(没有 { }, 不能执行具体
代码). - 对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类.
注意事项:
- 抽象类不能直接实例化.
- 抽象方法不能是 private 的.
- 抽象类中可以包含其他的非抽象方法, 也可以包含字段. 这个非抽象方法和普通方法的规则都是一样的, 可以被重写, 也可以被子类直接调用
抽象类的作用
-
抽象类存在的最大意义就是为了被继承.
-
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法.
我们使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了, 使用普通类编译器是不会报错的. 但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题.
接口
接口是抽象类的更进一步. 抽象类中还可以包含非抽象方法, 和字段. 而接口中包含的方法都是抽象方法, 字段只能包含 静态常量.
语法规则:
在刚才的打印图形的示例中, 我们的父类 Shape 并没有包含别的非抽象方法, 也可以设计成一个接口
代码示例:
interface IShape{
void draw();
}
class Cyle implements IShape {
@Override
public void draw(){
System.out.println("⭕");
}
}
class Square implements IShape{
@Override
public void draw(){
System.out.println("▲");
}
}
public class ShapTest {
public static void main(String[] args) {
IShape shap = new Cyle();
shap.draw();
IShape shap1 = new Square();
shap1.draw();
}
}
-
使用 interface 定义一个接口
-
接口中的方法一定是抽象方法, 因此可以省略 abstract
-
接口中的方法一定是 public, 因此可以省略 public
-
Cycle 使用 implements 继承接口. 此时表达的含义不再是 “扩展”, 而是 “实现”
-
在调用的时候同样可以创建一个接口的引用, 对应到一个子类的实例.
-
接口不能单独被实例化
扩展(extends) vs 实现(implements)
扩展指的是当前已经有一定的功能了, 进一步扩充功能. 实现指的是当前啥都没有, 需要从头构造出来.
接口中只能包含抽象方法. 对于字段来说, 接口中只能包含静态常量(final static).
interface IShape {
void draw();
public static final int num = 10;
}
其中的 public, static, final 的关键字都可以省略,省略后的 num 仍然表示 public 的静态常量
提示:
- 我们创建接口的时候, 接口的命名一般以大写字母 I 开头.
- 接口的命名一般使用 “形容词” 词性的单词.
- 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性
实现多个接口
代码示例:
class Animal{ //定义一个动物的父类
protected String name;
public Animal(String name) {
this.name = name;
}
}
interface IFlying{ //一个会飞的接口
void fly();
}
interface IRunning{ //一个会跑的接口
void run();
}
interface ISwimming{ //一个会游泳的接口
void swim();
}
//猫, 是会跑的
class Cat extends Animal implements IRunning{
public Cat(String name){
super(name);
}
@Override
public void run(){
System.out.println(this.name + "正在用四条腿跑");
}
}
//鱼, 是会游的
class Fish extends Animal implements ISwimming{
public Fish(String name){
super(name);
}
@Override
public void swim(){
System.out.println(this.name + " 正在用尾巴游");
}
}
//青蛙, 既能跑, 又能游(两栖动物)
class Frog extends Animal implements ISwimming,IRunning{
public Frog(String name){
super(name);
}
@Override
public void swim(){
System.out.println(this.name + "正在蛙泳");
}
@Override
public void run(){
System.out.println(this.name + "正在往前跳");
}
}
//还有一种神奇的动物, 水陆空三栖, 叫做 "鸭子"
class Duck extends Animal implements IRunning,ISwimming,IFlying{
public Duck(String name){
super(name);
}
@Override
public void run(){
System.out.println(this.name + "正在用俩条腿跑");
}
@Override
public void swim(){
System.out.println(this.name + "正在用鸭掌游 ");
}
@Override
public void fly(){
System.out.println(this.name + "正在用翅膀飞");
}
}
public class AnimalTest {
public static void main(String[] args) {
Cat cat = new Cat("小猫咪");
cat.run();
Fish fish = new Fish("小鱼儿");
fish.swim();
Frog frog = new Frog("小青蛙");
frog.run();
frog.swim();
Duck duck = new Duck("小鸭子");
duck.fly();
duck.run();
duck.swim();
}
}
运行结果:
上面的代码展示了 Java 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口.
继承表达的含义是 is - a 语义, 而接口表达的含义是 具有 xxx 特性 .
- 这样设计有什么好处呢?
时刻牢记多态的好处, 让程序猿忘记类型. 有了接口之后, 类的使用者就不必关注具体类型, 而 只关注某个类是否具备某种能力.
接口间的继承
interface IRunning {
void run();
}
interface ISwimming {
void swim();
}
// 两栖的动物, 既能跑, 也能游
interface IAmphibious extends IRunning, ISwimming {
}
class Frog implements IAmphibious {
...
}
通过接口继承创建一个新的接口 IAmphibious 表示 “两栖的”. 此时实现接口创建的 Frog 类, 就继续要实现 run 方法, 也需要实现 swim 方法.
接口间的继承相当于把多个接口合并在一起
总结:
抽象类和接口都是 Java 中多态的常见使用方式. 都需要重点掌握. 同时又要认清两者的区别(重要!!! 常见面试题).
核心区别: 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不 能包含普通方法, 子类必须重写所有的抽象方法.