目录
抽象类
语法规则
注意事项(重要,全部掌握)
抽象类的作用
接口
语法规则
注意事项
提示
类实现多个接口
接口使用实例(Comparable 接口与Comparator接口)
Comparable接口
Comparator接口(比较器)
Comparable接口 与Comparator接口的区别
代码示例
接口间的继承(扩展)
总结
抽象类
语法规则
在多态关于形状的代码例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由Shape 的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstract method), 包含抽象方法的类我们称为 抽象类(abstract class).
abstract class Shape { abstract public void draw(); }
- 在 draw 方法前加上 abstract 关键字, 表示这是一个抽象方法. 同时抽象方法没有方法体(没有 { }, 不能执行具体代码).
- 对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类.
注意事项(重要,全部掌握)
抽象方法:一个方法如果被abstract修饰,那么这个方法就是抽象方法。抽象方法可以没有具体的实现。
同时抽象方法不能被private修饰,因为一旦被private修饰后,非抽象子类是不能重写父类私有的抽象方法的。
包含抽象方法的类称作抽象类,其必须被abstract所修饰,一个抽象类中可以没有抽象方法,但是如果一个类中有抽象方法,那么这个类一定是抽象类,其必须被abstract所修饰
抽象类不可以被实例化。不能使用例如Shape shape = new Shape();这样的语句
但是不影响抽象类发生向上转型,所以说抽象类不可以被实例化,但是可以发生向上转型.
类内的数据成员,和普通类没有区别,可以包含其他的非抽象方法, 也可以包含字段. 这个非抽象方法和普通方法的规则都是一样的, 可以被重写,也可以被子类直接调用
抽象类主要就是用来被继承的.
如果一个非抽象类继承了这个抽象类,那么这个类必须重写抽象类当中的所有的抽象方法。(重要)
当抽象类A 继承 抽象类B 那么A可以不重写B中的抽象方法,但是一旦A要是再被一个非抽象类c继承,那么c类中一定还要重写A中和B中的抽象方法.
代码示例:
abstract class A { abstract public void eat(); public void drink() { } } abstract class C extends A { abstract public void fly(); } class b extends C { @Override public void eat() { System.out.println("eat"); } @Override public void fly() { System.out.println("fly"); } }
抽象类和抽象方法一定是不能被final修饰的,因为一旦类被final修饰,便不能继承,方法被final修饰,不能被重写
抽象类不能实例化的目的也就是为了继承和重写,所以两者不能同时使用
抽象类实现接口时,可以不需要对接口方法进行重写,即可以重写一部分,不重写一部分
抽象类有构造方法,但是不能使用,即不能创建具体的对象
抽象类的作用
抽象类存在的最大意义就是为了被继承.
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法
.
有些同学可能会说了, 普通的类也可以被继承呀, 普通的方法也可以被重写呀, 为啥非得用抽象类和抽象方法呢?
答:确实如此. 但是使用抽象类相当于多了一重编译器的校验.
使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成.
那么此时如果不小心误用成父类了, 使用普通类编译器是不会报错的. 但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题.
很多语法存在的意义都是为了 “预防出错”, 例如我们曾经用过的 final 也是类似. 创建的变量用户不去修改, 不就相当于常量嘛? 但是加上 final 能够在不小心误修改的时候, 让编译器及时提醒我们.
充分利用编译器的校验, 在实际开发中是非常有意义的.
在实际开发中,抽象类的作用也是非常重要的:
抽象类可以降低接口实现类对接口实现过程难度,因为在实际开发中一个接口中可能会有很多接口是使用不到的,当一个非抽象类去继承这个接口的时候,就需要重写这个接口中的所有抽象方法,造成代码冗余,为了避免这种情况的发生,此时就需要抽象类将接口中不需要使用的抽象方法进行重写,将需要使用的抽象方法继承下来.
这样其他类只需要去继承不同的抽象类,依照自己业务的要求去寻找自己所需要的抽象类,然后对抽象类中的抽象方法进行重写就行了,从而降低了接口实现过程中的难度。
接口
接口是抽象类的更进一步. 抽象类中还可以包含非抽象方法, 和字段. 而接口中包含的方法都是抽象方法, 字段只能包含静态常量.
语法规则
我们直接通过一段代码来进行总结:
1.interface Shape1 { - //接口中定义的成员变量都会被默认为常量,由public static final默认进行修饰,所以就算不写public static final也无所谓, - int a = 10; - public static final String name = "sss"; - - //接口中的方法几乎都为抽象方法,默认为public abstract进行修饰,所以就算不写public abstract也无所谓 - void draw(); - - //当然接口中也可以定义非抽象方法,用default关键字即可,default是在java8中引入的关键字,具体可看csdn博客 - default void drink() { - System.out.println("喝水"); - } 13.} - 15.class Cycle1 implements Shape1 { - @Override - public void draw() { - System.out.println("画一个⚪"); - } - 21.} - 23.class React1 implements Shape1 { - @Override//注解 - public void draw() { - System.out.println("画一个□"); - } - 29.} - 31.public class TestMain { - public static void fun(Shape1 shape) { - shape.draw(); - } - - public static void main(String[] args) { - //接口也是可以发生向上转型的,前提是一个类必须实现了这个接口 - //例如下面的代码,因为Cycle1类实现了Shape1这个接口,所以此时接口类型的shape引用可以指向Cycle1类的实例了 - Shape1 shape = new Cycle1(); - Shape1 shape1=new React1(); - shape.draw(); - shape1.draw(); - } 44.}
使用 interface 定义一个接口
接口中的方法一定是抽象方法, 因此可以省略 abstract
接口中的方法一定是 public,因此可以省略 public
Cycle 使用 implements 继承接口. 此时implements表达的含义不再是 “扩展”, 而是 “实现”
在调用的时候同样可以创建一个接口的引用, 对应到一个子类的实例.
接口不能单独被实例化.
注意事项
接口当中的方法都是抽象方法。其默认前缀为public abstract,在书写时是可以省略的,因为编译器默认这个方法就是 public abstract
抽象类其实可以有具体实现的方法。这个方法是被default修饰的(JDK1.8加入的)
接口当中只能包含静态常量,所有常量的前缀全部默认为public static
final,在书写时是可以省略的,因为编译器默认这个成员变量就是public static final
接口当中的成员变量默认是:public static final 成员方法是:public abstract
接口是不可以被实例化的。 Shape shape = new Shape();(不允许)
接口和类之间的关系 : implements(实现),当一个非抽象类实现了这个接口且接口中有抽象方法时,则这个类必须重写接口中的所有抽象方法,如果这个类是抽象类,则可以不实现接口中的所有方法,因为抽象类中允许有抽象方法的存在
接口的出现是为了实现多继承.一个类可以实现多个接口。但是只能继承一个父类
只要这个类 实现了该接口,那么就可以进行向上转型。
当然一个接口也可以去继承(扩展)多个接口
扩展(extends)与实现(implements)的区别
扩展指的是当前已经有一定的功能了, 进一步扩充功能.
实现指的是当前啥都没有, 需要从头构造出来.
提示
1.我们创建接口的时候, 接口的命名一般以大写字母 I 开头.
2.接口的命名一般使用 “形容词” 词性的单词.
3.阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性.
不加任何修饰符号的意思就是常量省略public static final ,抽象方法省略前缀public abstract
一个错误的代码:
interface IShape { // 即便不写public,也是默认为public权限 abstract void draw(); } class Rect implements IShape { void draw() { //权限更加严格了,所以无法覆写。意思就是Rect类中重写draw方法时必须加上public才可以 System.out.println("□"); } }
类实现多个接口
有的时候我们需要让一个类同时继承自多个父类. 这件事情在有些编程语言通过 多继承 的方式来实现的.
然而 Java 中只支持单继承, 一个类只能 extends 一个父类. 但是可以同时实现多个接口, 也能达到多继承类似的效果. 现在我们通过类来表示一组动物
.
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 IRunning, ISwimming { public Frog(String name) { super(name); } @Override public void run() { System.out.println(this.name + "正在往前跳"); } @Override public void swim() { System.out.println(this.name + "正在蹬腿游泳"); } }
有一种神奇的动物, 水陆空三栖, 叫做 “鸭子”
class Duck extends Animal implements IRunning, ISwimming, IFlying { public Duck(String name) { super(name); } @Override public void fly() { System.out.println(this.name + "正在用翅膀飞"); } @Override public void run() { System.out.println(this.name + "正在用两条腿跑"); } @Override public void swim() { System.out.println(this.name + "正在漂在水上"); } }
上面的代码展示了 Java 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口.
继承表达的含义是 is - a 语义, 而接口表达的含义是 具有 xxx 特性
.猫是一种动物, 具有会跑的特性.
青蛙也是一种动物, 既能跑, 也能游泳
鸭子也是一种动物, 既能跑, 也能游, 还能飞
这样设计有什么好处呢? 时刻牢记多态的好处, 让程序猿忘记类型. 有了接口之后, 类的使用者就不必关注具体类型, 而只关注某个类是否具备某种能力.
例如, 现在实现一个方法, 叫 “散步”
public static void walk(IRunning running) { running.run(); }
在这个 walk 方法内部, 我们并不关注到底是哪种动物, 只要参数是会跑的,就行,此时需要注意的是这个会跑的前提是这个类必须实现了IRunning接口才可以
//因为此时Cat类实现的是IRunning接口,所以此时可以使用向上转型如下所示,若没有实现IRunning接口,则会报错 IRunning iRunning = new Cat("猫猫"); walk(iRunning); //同样的因为此时Frog类实现的是IRunning接口,所以此时可以使用向上转型如下所示,若没有实现IRunning接口,同样会报错 IRunning iRunning1 = new Frog("青蛙"); walk(iRunning1);