文章目录
- 学习目标
- 1. 抽象类
- 1.1 抽象类注意事项
- 1.2 修饰符的使用
- 2. 接口
- 2.1 定义接口
- 2.2 接口里可以定义的成员
- 2.2 实现接口
- 2.2.1 实现接口语法格式
- 2.2.2 如何调用对应的方法
- 2.2.3 练习
- 2.3 接口的多实现
- 2.3.1 练习
- 2.4 冲突问题
- 2.5 接口的多继承(了解)
- 2.6 部分内置接口
学习目标
- 能够声明抽象类
- 能够说出抽象类的特点
- 能够继承抽象类
- 掌握声明接口的格式
- 掌握实现接口的格式
- 能够说出接口中的特点
- 系统内置接口
1. 抽象类
父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。既然被子类重写了,那么父类里的方法,就只有声明还有意义,而方法主体则没有存在的意义了。我们把没有方法主体的方法称为抽象方法。Java语法规定,包含抽象方法的类就是抽象类。
抽象类存在的意义:抽象类往往用来表示对问题领域进行分析、设计中得出的抽象概念。其存在的意义在于其设计性、复用性与扩展性,仅仅是对功能的声明,表示该类有这个功能,但是具体的功能需要由子类来实现。说白了,抽象类就是用来继承和重写的。
Java中使用关键字abstract
来声明抽象方法和抽象类。
abstract class 类名字 {
public abstract void 方法名();
}
1.1 抽象类注意事项
-
一个抽象类里可以有抽象方法,也可以没有抽象方法,不建议抽象类里不定义任何抽象方法的行为。
abstract class Test { public void test(){} } // 抽象类里可以全都是具体方法,没有抽象方法(不建议使用)
-
如果一个类里有了抽象方法,那么这个类必须要声明为抽象类。
abstract class Test{ public abstract void demo(); // 只要有了抽象方法,那么这个类必须要抽象 }
-
抽象类不能直接创建对象,如果一定要创建,需要实现(重写)抽象方法。
class Test { public static void main(String []args) { // Animal animal = new Animal(); 报错,抽象类不能直接创建对象 // 如果要创建对象,必须要实现抽象类里的所有抽象方法 Animal animal = new Animal(){ public void shout(){ System.out.println("动物正在叫"); } }; } } abstract class Animal{ public abstract void shout(); }
-
通常情况下,我们会创建一个继承抽象类的子类,在子类里实现所有的抽象方法,然后去创建子类对象。
public class Test { public static void main(String[] args) { Dog d = new Dog(); } } abstract class Animal { public abstract void shout(); public abstract void eat(); } class Dog extends Animal { @Override public void shout() { System.out.println("小狗正在汪汪汪"); } @Override public void eat() { System.out.println("小狗正在啃骨头"); } }
-
如果子类没有实现父类所有的抽象方法,那么这个子类也需要被定义成为抽象的。
abstract class Animal { public abstract void shout(); public abstract void eat(); } abstract class Dog extends Animal { @Override public void shout() { System.out.println("小狗正在汪汪汪"); } }
子类对父类抽象方法的重写操作也叫做实现方法。
1.2 修饰符的使用
外部类 | 成员变量 | 代码块 | 构造器 | 方法 | 局部变量 | 内部类(后面讲) | |
---|---|---|---|---|---|---|---|
public | √ | √ | × | √ | √ | × | √ |
protected | × | √ | × | √ | √ | × | √ |
缺省 | √ | √ | √ | √ | √ | √ | √ |
private | × | √ | × | √ | √ | × | √ |
static | × | √ | √ | × | √ | × | √ |
final | √ | √ | × | × | √ | √ | √ |
abstract | √ | × | × | × | √ | × | √ |
native | × | × | × | × | √ | × | × |
不能和abstract一起使用的修饰符?
(1)abstract和final不能一起修饰方法和类
(2)abstract和static不能一起修饰方法
(3)abstract和native不能一起修饰方法
(4)abstract和private不能一起修饰方法
static和final一起使用:
(1)修饰方法:可以,因为都不能被重写
(2)修饰成员变量:可以,表示静态常量
(3)修饰局部变量:不可以,static不能修饰局部变量
(4)修饰代码块:不可以,final不能修改代码块
(5)修饰内部类:可以一起修饰成员内部类,不能一起修饰局部内部类
2. 接口
- 一方面,有时必须从几个类中派生出一个子类,继承它们所有的方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
- 另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB连接。
- 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个"是不是"的关系,而接口实现则是 "能不能"的关系。has-a
- 例如:你想要会飞,那么必须拥有fly()方法
- 例如:你想要连接USB,那么必须拥有read()/in()和write()/out()等
- 例如:你的数据库产品想要被Java连接,那么你需要实现Connection, Statement等
- 接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
2.1 定义接口
接口,是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,那么接口的内部主要就是封装了方法,包含抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8),私有方法(JDK 9)。
接口的定义,它与定义类方式相似,但是使用 interface
关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。
引用数据类型:数组,类,接口。
接口的声明格式:
[修饰符】 interface 接口名{
//接口的成员列表:
// 静态常量
// 抽象方法
// 默认方法
// 静态方法
// 私有方法
}
示例代码:
interface Flyable{
//静态常量
long MAX_SPEED = 7900000;//这里单位是毫米/秒,7.9千米/秒,超过这个速度,就变成卫星
//抽象方法
void fly();
//默认方法
public default void start(){
System.out.println("开始");
}
public default void stop(){
System.out.println("结束");
}
//静态方法
public static void broken(){
System.out.println("飞行中遇到物体就坏了");
}
}
在JDK8之前,接口中只运行出现:
(1)公共的静态的常量:其中public static final可以省略。
(2)公共的抽象的方法:其中public abstract可以省略。
在JDK1.8时,接口中允许声明默认方法和静态方法:
(3)公共的默认的方法:其中public 可以省略,建议保留。
默认方法相当于允许给接口的抽象方法给出默认实现了,这样实现类(子类)既可以选择使用默认实现,还可以选择重写,更灵活了
(4)公共的静态的方法:其中public 可以省略,建议保留。
2.2 接口里可以定义的成员
接口里只能定义以下几种成员:
- 静态成员常量。接口里定义的变量默认被 public static final 修饰
- 抽象方法。接口里定义的方法默认被 public abstract修饰,不能有方法体
- 默认方法。JDK8以后,接口里可以使用 public default 定义有方法体的默认方法。
- 静态方法。JDK8以后,接口里还可以使用 public static 定义静态方法
- 私有方法。JDK9以后,接口里可以使用private定义私有方法。
注意:接口里不能定义构造方法!
2.2 实现接口
接口不能直接创建实例对象,需要定义一个子类实现(继承)这个接口,然后再创建子类对象。
类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements
关键字。
2.2.1 实现接口语法格式
[修饰符】 class 实现类 implements 接口{
// 重写接口中抽象方法[必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法[可选】
}
[修饰符】 class 实现类 extends 父类 implements 接口{
// 重写接口中抽象方法[必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法[可选】
}
非抽象子类实现接口:
-
必须重写接口中所有抽象方法。
-
继承了接口的默认方法,即可以直接调用,也可以重写。
重写时,default单词就不要再写了,它只用于在接口中表示默认方法,到类中就没有默认方法的概念了
-
不能重写静态方法
示例代码:
class Bird implements Flyable{
//重写/实现接口的抽象方法,[必选】
public void fly() {
System.out.println("展翅高飞");
}
//重写接口的默认方法,[可选】
//重写默认方法时,default单词去掉
public void start(){
System.out.println("先扇两下翅膀,一蹬腿,开始飞");
}
}
2.2.2 如何调用对应的方法
- 对于接口的抽象方法、默认方法,通过实现类对象就可以调用
- 但是对于静态方法,必须使用接口名才能调用。
public class TestInteface {
public static void main(String[] args) {
//创建实现类对象
Bird b = new Bird();
//通过实现类对象调用重写的抽象方法,以及接口的默认方法,如果实现类重写了就执行重写的默认方法,如果没有重写,就执行接口中的默认方法
b.start();
b.fly();
b.stop();
//通过接口名调用接口的静态方法
Flyable.broken();
}
}
2.2.3 练习
-
声明一个Creature接口
- 包含两个抽象方法:
- void eat();
- void breathe();
- 包含默认方法 default void sleep(),实现为打印“静止不动”
- 包含静态方法 static void drink(),实现为“喝水”
- 包含两个抽象方法:
-
声明动物Animal类,实现Creature接口。
- void eat();实现为“吃东西”,
- void breathe();实现为"吸入氧气呼出二氧化碳"
- void sleep()重写为”闭上眼睛睡觉"
-
声明植物Plant类,实现Creature接口。
- void eat();实现为“吸收营养”
- void breathe();实现为"吸入二氧化碳呼出氧气"
-
在测试类中,分别创建两个实现类的对象,调用对应的方法。通过接口名,调用静态方法。
定义接口:
public interface Creature {
// 定义抽象方法
public abstract void eat();
public abstract void breathe();
//定义默认方法
public default void sleep(){
System.out.println("静止不动");
}
//定义静态方法
public static void drink(){
System.out.println("喝水");
}
}
定义实现类:
public Animal implements Creature {
//重写/实现接口的抽象方法
@Override
public void eat() {
System.out.println("吃东西");
}
//重写/实现接口的抽象方法
@Override
public void breathe(){
System.out.println("吸入氧气呼出二氧化碳");
}
//重写接口的默认方法
@Override
public void sleep() {
System.out.println("闭上眼睛睡觉");
}
}
public class Plant implements Creature {
//重写/实现接口的抽象方法
@Override
public void eat() {
System.out.println("吸收营养");
}
//重写/实现接口的抽象方法
@Override
public void breathe(){
System.out.println("吸入二氧化碳呼出氧气");
}
}
定义测试类:
public class InterfaceDemo {
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();
//通过接口调用静态方法
Creature.drink();
}
}
输出结果:
吃东西
闭上眼睛睡觉
吸入氧气呼出二氧化碳
吸收营养
静止不动
吸入二氧化碳呼出氧气
喝水
2.3 接口的多实现
之前学过,在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现。并且,一个类能继承一个父类,同时实现多个接口。
实现格式:
[修饰符】 class 实现类 implements 接口1,接口2,接口3。。。{
// 重写接口中所有抽象方法[必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法[可选】
}
[修饰符】 class 实现类 extends 父类 implements 接口1,接口2,接口3。。。{
// 重写接口中所有抽象方法[必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法[可选】
}
接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次。
定义多个接口:
interface A {
public abstract void showA();
public abstract void show();
}
interface B {
public abstract void showB();
public abstract void show();
}
定义实现类:
public class C implements A,B{
@Override
public void showA() {
System.out.println("showA");
}
@Override
public void showB() {
System.out.println("showB");
}
@Override
public void show() {
System.out.println("show");
}
}
2.3.1 练习
-
声明第一个接口Runner,包含抽象方法:void run()
-
声明第二个接口Swimming,包含抽象方法:void swim()
-
声明兔子类,实现Runner接口
-
声明乌龟类,实现Runner接口和Swimming接口
interface Runner{
void run();
}
interface Swimming{
void swim();
}
class Rabbit implements Runner{
@Override
public void run() {
System.out.println("兔子跑得快");
}
}
class Tortoise implements Runner,Swimming{
@Override
public void swim() {
System.out.println("乌龟游得快");
}
@Override
public void run() {
System.out.println("乌龟跑的慢");
}
}
2.4 冲突问题
当一个类可以继承一个父类,同时又能实现若干个接口。如果当继承和多实现的情况都同时存在时,调用方法时就需要确定到底调用那个方法。
下面的几个原则大家需要了解并记忆:
-
自身优先:如果这个类自己实现或者重写了某个方法,优先调用自己的方法。
-
父类优先:父类中的成员方法与接口中的抽象方法重名,子类就近选择执行父类的成员方法。
class A { public void test() { System.out.println("AAAAAA"); } public void demo() { System.out.println("A类里的demo方法"); } } interface B { public default void test() { System.out.println("BBBBBB"); } public void demo() { System.out.println("B接口里的demo方法"); } } interface C { public default void test() { System.out.println("CCCCCC"); } public void demo() { System.out.println("C接口里的demo方法"); } } class D extends A implements B, C { @Override public void test() { System.out.println("DDDDDD"); } } public class Test { public static void main(String[] args) { D d = new D(); // 类自己重写了test方法,优先调用自己的方法。 d.test(); // DDDDDD // 类没有重写demo方法,会优先调用父类里的实现 d.demo(); // A类里的demo方法 } }
-
明确指定:在某些情况下,必须要自己手动实现或者明确的指定到底调用哪个接口的默认实现。
interface A { public default void test() { System.out.println("BBBBB"); } } interface B { public default void test() { System.out.println("CCCCCC"); } } // 编译报错!因为此时类C实现的两个接口里,都有 test 方法,编译器不知道到底要调用哪个 test // class C implements A, B { } // 可以选择自己完全重写 class D implements A,B { @Override public void test() { System.out.println("DDDDDD"); } } class E implements A,B { @Override public void test() { A.super.test(); // 在重写的时候,也可以指定到底调用哪个接口的方法 } }
2.5 接口的多继承(了解)
一个接口能继承另一个或者多个接口,接口的继承也使用 extends
关键字,子接口继承父接口的方法。
interface A {
void a();
public default void methodA(){
System.out.println("AAAAAAAAAAAAAAAAAAA");
}
}
interface B {
void b();
public default void methodB(){
System.out.println("BBBBBBBBBBBBBBBBBBB");
}
}
// 一个接口可以继承一个或者多个接口
interface C extends A,B{
@Override
public default void methodB() {
System.out.println("CCCCCCCCCCCCCCCCCCCC");
}
}
2.6 部分内置接口
- Cloneable接口
- Comparable接口
- Comparator接口