【JAVASE】-9- 接口语法基础
一、上节回顾
1. 向上转型(子 → 父 类型转换)
向上转型是 “子类对象赋值给父类引用” 的过程,是多态的基础,图中总结了 3 种常见时机:
1. 直接赋值
Animal animal = new Dog("圆圆", 19);
- 解释:
Dog
是Animal
的子类,直接将Dog
实例赋值给Animal
类型的引用animal
,自动完成向上转型。
2. 方法传参时转型
public static void func1(Animal animal) { /* ... */ }public static void main(String[] args) {Dog dog = new Dog("圆圆", 19);func1(dog); // 传参时,dog(Dog类型)自动向上转型为Animal类型
}
- 解释:调用
func1
时,实际参数是Dog
类型,但方法形参要求Animal
类型,因此自动发生向上转型。
3. 返回值转型
public static Animal func2() {Dog dog = new Dog("圆圆", 19);return dog; // 返回时,dog(Dog类型)自动向上转型为Animal类型
}
- 解释:方法声明返回
Animal
类型,但实际返回Dog
类型,因此自动发生向上转型。
2. 方法重写(Override)
子类对父类的非静态、非 private、非 final方法进行 “同名、同参数列表、兼容返回值” 的实现,是多态的核心表现。
- 方法名相同;
- 参数列表(参数个数、类型、顺序)相同;
- 返回值相同(或子类返回值是父类返回值的 “协变类型”,如父类返回
Animal
,子类可返回Dog
); - 访问修饰符:子类方法的访问范围 ≥ 父类方法(如父类是
protected
,子类可改为public
); - 限制:
private
、static
、final
修饰的方法不能被重写,构造方法也不能被重写。
@Override
public void eat() {System.out.println(this.name + " 正在吃狗粮!");
}
- 作用:编译期校验 “是否真的重写了父类方法”。如果方法签名与父类不匹配(如方法名写错、参数不对),编译器会报错,避免逻辑错误。
3. 动态绑定(运行时多态)
Java 中,对象的方法调用是 “运行时绑定”—— 即调用哪个类的方法,由运行时对象的实际类型决定,而非编译时的引用类型。
Animal animal = new Dog("圆圆", 19);
animal.eat();
- 编译时:编译器看到
animal
是Animal
类型,因此会检查Animal
类中是否有eat()
方法(有则编译通过)。 - 运行时:
animal
实际指向Dog
实例,因此 JVM 会调用Dog
类的eat()
方法(即使编译时检查的是Animal
的方法)。
字节码层面(第三张图黑色部分):
invokevirtual #5 // Method Animal.eat:()V
:编译时生成的是调用Animal.eat()
的字节码,但运行时 JVM 会根据对象实际类型(Dog
),动态找到并调用Dog.eat()
。
4. 协变返回类型(拓展知识)
子类重写方法时,返回值可以是父类方法返回值的子类类型,称为 “协变返回”。
class Animal {public Animal eat() { return this; }
}class Dog extends Animal {@Overridepublic Dog eat() { return this; } // 合法:Dog是Animal的子类
}
- 作用:让方法重写更灵活,子类可返回更具体的类型。
二、接口引入
USB
:接口,定义 USB 设备的通用方法(openDevice()
、closeDevice()
)。Mouse
:实现USB
接口的类,代表 “鼠标”,额外有click()
方法。KeyBoard
:实现USB
接口的类,代表 “键盘”,额外有input()
方法。Computer
:电脑类,通过useService(USB usb)
方法使用 USB 设备。
1. Computer
类的方法
// 关闭电脑的方法
public void powerOff() {System.out.println("关闭电脑!");
}// 电脑使用 USB 设备的核心方法
public void useService(USB usb) {usb.openDevice(); // 调用 USB 设备的“打开”方法(多态:实际调用子类重写的方法)// 向下转型:判断 usb 实际类型,转为子类并调用特有方法if (usb instanceof Mouse) {Mouse mouse = (Mouse) usb; // 向下转型为 Mousemouse.click(); // 调用 Mouse 特有的“点击”方法} else if (usb instanceof KeyBoard) {KeyBoard keyBoard = (KeyBoard) usb; // 向下转型为 KeyBoardkeyBoard.input(); // 调用 KeyBoard 特有的“输入”方法}usb.closeDevice(); // 调用 USB 设备的“关闭”方法(多态)
}
2. Test
类的 main
方法
public class Test {public static void main(String[] args) {// 1. 创建电脑对象Computer computer = new Computer();computer.powerOn(); // 假设存在 powerOn 方法(代码中未展示,逻辑上是“开机”)// 2. 创建鼠标、键盘对象Mouse mouse = new Mouse();KeyBoard keyBoard = new KeyBoard();// 3. 电脑使用鼠标(向上转型)computer.useService(mouse); // mouse(Mouse 类型)自动向上转型为 USB 类型System.out.println("==========");// 4. 电脑使用键盘(向上转型)computer.useService(keyBoard); // keyBoard(KeyBoard 类型)自动向上转型为 USB 类型// 5. 关闭电脑computer.powerOff();}
}
1. 向上转型(Upcasting)
- 发生场景:调用
computer.useService(mouse)
和computer.useService(keyBoard)
时。也就是Usb usb=new Mouse(),后者是mouse,mouse向上转型为Usb类型 - 解释:
Mouse
和KeyBoard
都实现了USB
接口,因此它们的实例可以自动转换为USB
类型(父接口引用指向子实现对象)。 - 作用:让
Computer
类无需关心 “具体是鼠标还是键盘”,只需通过USB
接口调用通用方法(openDevice()
、closeDevice()
),体现了多态的 “抽象依赖”。
2. 向下转型(Downcasting)
- 发生场景:
useService
方法中(Mouse) usb
和(KeyBoard) usb
。 - 解释:将
USB
类型的引用强制转换为具体子类类型(Mouse
或KeyBoard
),从而调用子类的特有方法(如mouse.click()
、keyBoard.input()
)。 - 前提:必须先用
instanceof
判断usb
的实际类型,否则运行时可能抛出ClassCastException
(类型转换异常)。
3. 多态(Polymorphism)
- 体现:
usb.openDevice()
和usb.closeDevice()
方法。这是接口的抽象方法,子类通过继承来实现特定的该方法,多态。- 编译时,
usb
是USB
类型,因此编译器检查USB
接口是否有openDevice()
/closeDevice()
方法。 - 运行时,
usb
实际是Mouse
或KeyBoard
实例,因此会调用子类重写的openDevice()
/closeDevice()
方法。这叫动态绑定。
- 编译时,
三、接口
1. 接口的定义形式
接口是一种引用数据类型,使用 interface
关键字定义,例如:
interface USB {// 接口内容
}
2. 接口中的方法规则
接口中默认只有抽象方法(没有方法体的方法),但有两个例外:
static
修饰的方法:属于接口的 “静态方法”,有方法体,通过接口名直接调用(如USB.show()
)。default
修饰的方法:属于接口的 “默认方法”,有方法体,实现类可以直接继承或重写。
static不能重写,抽象方法必须重写,default可重写也可以不重写
此外,接口中普通的抽象方法默认会被 public abstract
修饰(即使不写,编译器也会自动添加)。
3. 接口中的成员变量规则
接口中的成员变量,默认会被 public static final
修饰(即 “公共的、静态的、最终的” 常量):
public
:全局可见。static
:属于接口本身,可通过接口名直接访问(如USB.VERSION
)。final
:值不可修改(常量)。
interface USB {String VERSION = "3.0"; // 等价于 public static final String VERSION = "3.0";
}
4. 接口不能实例化
接口是 “行为规范”,不是具体的类,因此不能直接创建接口的对象(如 new USB()
会报错)。
要使用接口,必须通过 ** 实现类(implements
接口的类)** 创建对象,再通过接口引用指向实现类对象(多态)。
5. 类与接口的关系
类和接口之间是 **“实现(implements)” 关系 **,一个类可以实现多个接口(Java 支持 “多接口实现”,弥补了单继承的不足)。
class Mouse implements USB, Wireless { // Mouse 类实现了 USB 和 Wireless 两个接口// 实现接口的抽象方法@Overridepublic void openDevice() {System.out.println("鼠标打开");}// ...
}
6. 接口的字节码文件
接口编译后,会生成独立的 .class
字节码文件(与类的字节码类似),JVM 会像加载类一样加载接口的字节码。
四、接口和类的关系
1. “接口也属于一种类型”
在 Java 中,类型分为基本数据类型(如 int
、char
等)和引用数据类型(如类、接口、数组等)。接口属于引用数据类型,它和类一样,能用于声明变量、作为方法参数或返回值类型等。例如,我们可以声明一个接口类型的变量:
// USB 是一个接口
USB usb;
2. “接口可以去引用 实现了该接口的具体的类型”
如果有一个类 Mouse
实现了 USB
接口(使用 implements
关键字),那么我们可以用 USB
类型的变量来引用 Mouse
类的对象,这就是多态的一种表现形式。示例如下:
interface USB {void open();void close();
}class Mouse implements USB {@Overridepublic void open() {System.out.println("鼠标开启");}@Overridepublic void close() {System.out.println("鼠标关闭");}// 鼠标特有的方法public void click() {System.out.println("鼠标点击");}
}public class Test {public static void main(String[] args) {// 接口类型的变量 usb 引用了实现类 Mouse 的对象USB usb = new Mouse();// 调用接口中定义的方法,实际执行的是 Mouse 类中重写的方法usb.open();usb.close();// 注意:此时 usb 不能直接调用 Mouse 类特有的 click 方法,// 如果要调用,需要进行向下转型if (usb instanceof Mouse) {Mouse mouse = (Mouse) usb;mouse.click();}}
}
在这个例子中,USB
是接口,Mouse
是实现了 USB
接口的具体类。我们创建了 Mouse
类的对象,并把它赋值给 USB
类型的变量 usb
,这就是 “接口引用实现了该接口的具体类的对象”。通过这种方式,我们可以在不关心具体实现类的情况下,通过接口来调用方法,体现了面向接口编程的思想,也让代码更加灵活、可扩展。
五、接口如何解决 Java 的 “多继承限制”
子类是父类(继承),具有什么功能(接口)
1. Java 的 “单继承限制”
Java 中,一个类只能直接继承一个父类(即 “单继承”)。如果我们想让一个类同时拥有 “飞行”“奔跑”“游泳” 等多种行为,仅靠类的继承无法实现 —— 因为不能同时继承多个包含这些行为的父类。
2. 直接在父类中写通用行为的问题
假设我们有抽象父类 Animal
,如果把 fly()
、run()
、swim()
直接写在 Animal
中:
abstract class Animal {// ... 其他成员public void fly() {} // 飞行行为public void run() {} // 奔跑行为public void swim() {} // 游泳行为
}
这会产生问题:不是所有动物都具备这些行为(比如蛇不会飞,鱼不会跑)。如果子类继承 Animal
,会被迫继承所有不需要的行为,违背 “设计的合理性”。
3. 接口的 “多实现” 解决思路
为了让类能灵活组合不同的行为,我们可以把 “飞行”“奔跑”“游泳” 这些行为抽象为独立的接口:
interface IFly {void fly();
}interface IRun {void run();
}interface ISwim {void swim();
}
然后,类可以同时实现多个接口,从而 “选择性地拥有所需行为”:
// 例如:鸟类(会飞、会跑)
class Bird extends Animal implements IFly, IRun {@Overridepublic void fly() { System.out.println("鸟飞行"); }@Overridepublic void run() { System.out.println("鸟奔跑"); }
}// 例如:鱼类(会游泳)
class Fish extends Animal implements ISwim {@Overridepublic void swim() { System.out.println("鱼游泳"); }
}
4. 接口解决 “多继承” 的本质
- Java 类单继承:保证类的继承关系简单,避免 “钻石继承” 等复杂问题。
- Java 类多实现接口:突破了单继承的限制,让类可以同时拥有多个接口的 “行为能力”,实现了 “多行为的组合”。
这种设计既保留了单继承的简洁性,又通过接口的多实现,满足了 “一个类需要多种不同行为” 的需求,因此说接口解决了 Java 的多继承问题(更准确地说,是 “补充了单继承的不足,实现了类似多继承的‘多行为组合’效果”)。
⭐ 总结
接口通过 “多实现” 的特性,让 Java 类能在不违反 “单继承” 规则的前提下,灵活组合多种行为,从而弥补了单继承无法实现 “多行为复用” 的缺陷,间接解决了 “多继承” 场景下的需求。
六、接口继承接口,类实现接口
1. 接口的继承关系
- 接口
A
声明了抽象方法testA()
。 - 接口
B
通过extends A
继承了接口A
,因此B
不仅有自己的testB()
方法,还 “继承” 了A
的testA()
方法。 - 此时,接口
B
包含两个抽象方法:testA()
(来自A
)和testB()
(自己声明)。
2. 实现类的要求
类 TestDemo1
实现了接口 B
(使用 implements B
),因此必须重写 B
中所有的抽象方法,包括:
testA()
:继承自接口A
的方法。testB()
:接口B
自身声明的方法。
3. 代码逻辑演示
interface A {void testA();
}interface B extends A {void testB();
}class TestDemo1 implements B {@Overridepublic void testA() {System.out.println("实现 testA 方法");}@Overridepublic void testB() {System.out.println("实现 testB 方法");}
}public class Main {public static void main(String[] args) {TestDemo1 demo = new TestDemo1();demo.testA(); // 输出:实现 testA 方法demo.testB(); // 输出:实现 testB 方法}
}
七、接口 + 抽象类的优势
通过抽象类封装 “共有属性 / 基础行为”,接口封装 “特有行为”,可以完美解决上述问题:
// 抽象父类:封装动物的共有属性(如名字、年龄)和基础行为
abstract class Animal {protected String name;protected int age;public Animal(String name, int age) {this.name = name;this.age = age;}// 所有动物都需要“吃”,定义为抽象方法,由子类实现public abstract void eat();
}// 接口:封装“飞行”行为
interface IFly {void fly();
}// 接口:封装“奔跑”行为
interface IRun {void run();
}// 接口:封装“游泳”行为
interface ISwim {void swim();
}// 子类:鸟(会飞、会跑)
class Bird extends Animal implements IFly, IRun {public Bird(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println(name + " 吃虫子");}@Overridepublic void fly() {System.out.println(name + " 拍打翅膀飞行");}@Overridepublic void run() {System.out.println(name + " 用爪子奔跑");}
}// 子类:鱼(会游泳)
class Fish extends Animal implements ISwim {public Fish(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println(name + " 吃鱼食");}@Overridepublic void swim() {System.out.println(name + " 摆尾游泳");}
}
优势 1:行为更精准
Bird
只实现IFly
和IRun
,因此只有 “飞行” 和 “奔跑” 行为,符合实际特性。Fish
只实现ISwim
,因此只有 “游泳” 行为,也符合实际特性。
优势 2:扩展性强
- 如果新增 “兔子(只会跑)”,只需让它实现
IRun
即可,不会继承多余行为。 - 如果需要修改 “飞行” 行为(比如新增 “昆虫飞行”),只需定义新的
IFly
实现类(如Insect
),不会影响其他类。
优势 3:遵循设计原则
- 单一职责:每个接口只负责一种行为,每个类只负责自己的核心特性。
- 开闭原则:新增行为时,只需新增接口 / 实现类,无需修改已有代码。