抽象类和抽象方法
- 抽象方法
1.使用abstract
修饰的方法,没有方法体,只有声明;
2.定义的是一种“规范”,就是告诉子类必须给抽象方法提供具体的实现。 - 抽象类
包含抽象方法的类的抽象类;
通过抽象类,我们可以做到严格限制子类的设计,使子类之间更加通用。
package obstractClass;
// 如果类含有抽象方法,类就必须含有abstract
public abstract class Animal {
int age;
public abstract void rest();
public abstract void run();
public void shout(){
System.out.println("Animal.shout");
}
}
// 子类 Dog、Cat类中必须含有抽象父类里的抽象方法,不然报错。
class Dog extends Animal {
@Override
public void rest() {
System.out.println("Dog.rest");
}
@Override
public void run() {
System.out.println("Dog.run");
}
}
class Cat extends Animal {
@Override
public void rest() {
System.out.println("Cat.rest");
}
@Override
public void run() {
System.out.println("Cat.run");
}
}
抽象类的使用要点:
- 有抽象方法的类只能定义成抽象类。
- 抽象类不能实例化,即不能用 new 来实例化抽象类。
- 抽象类可以包含属性、方法、构造方法。但是构造方法不能用来
new
示例,只能用来被子类调用。 - 抽象类只能被继承。
- 抽象方法必须被子类实现。
接口(interface)
接口就是一种规范(就像我们人间的法律一样),所以实现类都要遵守。
接口的作用
- 为什么需要接口?接口和抽象类的区别?
接口就是比 “抽象类” 还 “抽象” 的 “抽象类”(笑死我了。可以简称 “抽象Plus”),可以更加规范的对子类进行约束。全面地专业的实现:规范和具体的实现分离。
接口是两个模块之间通信的标志,通信规范。如果能把你要设计的模块之间的接口定义好,就相当与完成了系统的设计大纲,剩下的就是添砖加瓦的具体实现了。工作时,做系统时往往就是使用 “面向接口” 的思想来设计系统。
接口和实现类不是父子关系,是实现规则的关系。比如:我定义一个接口 Runnable,Car 实现它就能在地上跑,Train 实现它也能在地上跑,飞机实现它也能在地上跑。就是说如果它是交通工具,就一定能跑,但是一定要实现 Runnable 接口。
如何定义和使用接口
格式生命
[访问修饰符] interface 接口名 [extends 父接口1, 父接口2...] {
常亮定义;
方法定义;
}
定义接口的详细说明:
- 访问修饰符: 只能是 public 或默认。
- 接口名: 和类名采用相同命名机制。
- extends: 接口可以多继承。
- 常量: 接口中的属性只能是常量,总是: public static final 修饰。不写也是。
- 方法: 接口中的方法只能是:public abstract。省略的话,也是 public abstrac。
package TestInterface;
public interface Volant {
/*public static final */int Fly_HEIGHT = 100;
/*public abstract */void fly();
}
interface Honest {
void helpOther();
}
class BirdMan implements Volant {
public void fly() {
System.out.println("I'm fly");
}
}
class Angle implements Volant, Honest {
@Override
public void helpOther() {
System.out.println("I'm angle");
}
@Override
public void fly() {
System.out.println("I'm fly");
}
}
class Plane implements Volant {
@Override
public void fly() {
System.out.println("Plane.fly");
}
}
// 测试 接口代码
package TestInterface;
import encapsulation.a.Person;
public class Test {
public static void main(String[] args) {
// 同时Plane实例拥有接口Volant的约束
Plane p = new Plane();
p.fly();
// 同时Angle实例拥有接口Volant, Honest的约束
Angle p2 = new Angle();
p2.fly();
p2.helpOther();
System.out.println(Volant.Fly_HEIGHT); // log: 100
}
}
// AI生成-代码解读:
// 第一段代码定义了几个接口和类:
// 1. Volant接口 - 定义了fly()方法和Fly_HEIGHT常量,表示可以飞行的能力
// 2. Honest接口 - 定义了helpOther()方法
// 3. BirdMan类 - 实现了Volant接口
// 4. Angle类 - 同时实现了Volant和Honest两个接口,既能飞又有爱心
// 5. Plane类 - 实现了Volant接口,可以飞行
// 第二段代码展示了接口的使用:
// 1. 创建Plane对象并调用fly()方法:
// - 通过接口类型Volant约束了Plane对象的行为
// - p.fly()输出"Plane.fly"
// 2. 创建Angle对象并调用两个接口方法:
// - 可以调用Volant接口的fly()方法
// - 也可以调用Honest接口的helpOther()方法
// - 展示了一个类可以实现多个接口
// 3. 访问接口常量:
// - Volant.Fly_HEIGHT直接通过接口名访问常量
// - 说明接口中的常量默认是public static final的
// - 输出常量值100
要点:
- 子类通过 implements 来实现接口中的规范。
- 接口不能创建实例,但是可用于声明引用变量类型。
- 一个类实现了接口,必须实现接口中所有的方法,并且这些方法只能是 public的。
- JDK1.8(不含8)之前,接口中只能包含静态常量、抽象方法,不能有普通属性、构造方法、普通方法。
- JDK1.8(含8)后,接口中包含普通的静态方法、默认方法。
接口中定义静态方法和默认方法
Java8之前,接口中只能定义抽象方法,不能定义静态方法和默认方法。
Java8后,接口中可以定义静态方法和默认方法。
默认方法
Java8 及以上版本,允许给接口添加一个非抽象的方法实现,只需要使用 default 修饰,这个特征又叫默认方法(也成为扩展方法)
默认方法和抽象方法的区别是抽象方法必须要被实现,默认方法不是。作为替代方式,接口可以提供默认方法的实现,所有这个接口的实现类都可以得到默认方法。
// 定义我们的默认方法
package TestInterface;
public interface TestDefault {
void printInfo();
// 利用default关键词定义默认方法
default void moren() {
System.out.println("TestDefault.moren");
System.out.println("测试默认方法");
}
}
class TestDefaultImp01 implements TestDefault {
@Override
public void printInfo() {
System.out.println("TestDefaultImp01");
}
}
package TestInterface;
import encapsulation.a.Person;
public class Test {
public static void main(String[] args) {
System.out.println("==============测试我们的默认方法==============");
TestDefaultImp01 td = new TestDefaultImp01();
td.printInfo(); // log: TestDefaultImp01
// log: TestDefault.moren
// log: 测试默认方法
td.moren();
}
}
JDK8 新特性-静态方法
JAVA8 以后,我们也可以在接口中直接定义静态方法的实现。这个静态方法直接从属于接口(接口也是类,一种特殊的类),可以通过接口名调用。
如果子类中定义了相同名字的静态方法,那就是完全不同的方法了,直接从属于了当可以通过子类名直接调用
// 在进口中定义静方法
package TestInterface;
// 测试接口中的新特性(默认方法、静态方法)
public interface TestDefault {
...
public static void testStatic01(){
System.out.println("TestDefault.testStatic01");
}
}
class TestDefaultImp01 implements TestDefault {
...
public static void testStatic01(){
System.out.println("TestDefaultImp01.test");
}
}
package TestInterface;
public class Test {
public static void main(String[] args) {
System.out.println("==============测试我们的静态方法==============");
TestDefault.testStatic01(); // TestDefault.testStatic01
// 如果在TestDefaultImp01类里没有写静态方法testStatic01时候,下面这段代码就会报错。
// td.testStatic01(); // error 子类无法使用接口中的静态方法类
// 除非在类里添加testStatic01, 但是它和接口里的testStatic01不是从属关系。两者个子属于自己的类/接口。切记
TestDefaultImp01.testStatic01(); // log: TestDefaultImp01.testt
}
}
总结
本文主要介绍了Java接口的以下重要知识点:
-
接口的基本概念和特性
- 接口是一种特殊的抽象类
- 接口中的方法默认都是public abstract的
- 接口中的属性默认都是public static final的
- 接口支持多继承
-
JDK8新特性 - 默认方法
- 可以使用default关键字在接口中定义默认方法实现
- 实现类可以直接使用默认方法,也可以重写默认方法
- 解决接口升级的问题
-
JDK8新特性 - 静态方法
- 接口中可以直接定义静态方法
- 静态方法属于接口本身
- 实现类不能直接使用接口的静态方法
- 实现类中的同名静态方法与接口中的静态方法无关
思考题
-
接口与抽象类有什么区别?
-
为什么Java 8要引入接口默认方法?这个特性主要解决什么问题?
-
以下说法正确的是:
A. 接口中的静态方法可以被实现类继承
B. 实现类可以直接调用接口的静态方法
C. 接口的静态方法只能通过接口名调用
D. 实现类中的同名静态方法会覆盖接口的静态方法 -
如果一个类实现了多个接口,这些接口中有相同的默认方法,编译器会如何处理?
-
接口是否可以有构造方法?为什么?
答案:
-
主要区别:
- 抽象类可以有构造方法,接口不能有
- 抽象类可以有普通成员变量,接口只能有常量
- 抽象类方法可以有不同访问修饰符,接口方法默认public
- 类只能单继承抽象类,但可以实现多个接口
-
引入默认方法主要是为了解决接口升级的问题。当需要在接口中新增方法时,若不提供默认实现,则所有实现类都必须实现新方法,这会导致现有代码不兼容。有了默认方法,就可以在接口中提供向下兼容的实现。
-
正确答案是C。接口的静态方法属于接口本身,只能通过接口名调用,不能被实现类继承或直接调用。实现类的同名静态方法是完全独立的方法。
-
这种情况下编译器会报错,要求实现类必须重写这个方法来解决冲突。这被称为"钻石问题"。
-
接口不能有构造方法。因为接口是一个完全抽象的类型,不能被实例化,所以不需要构造方法。接口中定义的都是抽象行为和常量。
寄语:千里跬步,始于足下。