1.接口的特性:
-
接口里的数据成员必须初始化,且数据成员均为常量;
-
接口里的方法必须全部声明为
abstract
,也就是说,接口不能像抽象类一样保有一般的方法,而必须全部是“抽象方法”。
2.接口定义的语法:
interface 接口名称 // 定义抽象类
{
final 数据类型 成员名称 = 常量; //数据成员必须赋初值
abstract 返回值的数据类型 方法名称(参数...);
//抽象方法,注意在抽象方法里,没有定义方法主体
}
接口与一般类一样,本身也具有数据成员与方法,但数据成员一定要赋初值,且此值将不能再更改,方法也必须是“抽象方法”。也正因为方法必须是抽象方法,而没有一般的方法,所以抽象方法声明的关键字 abstract
是可以省略的。
相同的情况也发生在数据成员身上,因数据成员必须赋初值,且此值不能再被更改,所以声明数据成员的关键字 final
也可省略。事实上只要记得:
-
接口里的“抽象方法”只要做声明即可,而不用定义其处理的方式;
-
数据成员必须赋初值。
既然接口里只有抽象方法,它只要声明而不用定义处理方式,于是自然可以联想到接口也没有办法像一般类一样,再用它来创建对象。利用接口打造新的类的过程,称之为接口的实现(implementation
)。
3.接口实现的语法:
class 类名称 implements 接口A,接口B //接口的实现
{
...
}
简单来说,接口和抽象类很相似,不能实例化对象,需要用类去实现接口,重写抽象方法,并且需要实现这个接口中的所有抽象方法
4.接口的扩展:
接口是 Java
实现多继承的一种机制,一个类只能继承一个父类,但如果需要一个类继承多个抽象方法的话,就明显无法实现,所以就出现了接口的概念。一个类只可以继承一个父类,但却可以实现多个接口。
接口与一般类一样,均可通过扩展的技术来派生出新的接口。原来的接口称为基本接口或父接口,派生出的接口称为派生接口或子接口。通过这种机制,派生接口不仅可以保留父接口的成员,同时也可加入新的成员以满足实际的需要。
同样的,接口的扩展(或继承)也是通过关键字 extends
来实现的。有趣的是,一个接口可以继承多个接口,这点与类的继承有所不同。
接口扩展的语法:
interface 子接口名称 extends 父接口1,父接口2...
{
...
}
编程要求
5.接口的继承:
public interface IPrinter1 {
default void print1(){
System.out.println("黑白打印!");
}
}
public interface IPrinter2 {
default void print2(){
System.out.println("彩色打印!");
}
}
public interface IPrinter extends IPrinter1,IPrinter2 {
default void print(){
print1();
print2();
}
}
6. 多态的条件:
-
继承的存在(继承是多态的基础,没有继承就没有多态);
-
子类重写父类的方法(多态下调用子类重写的方法);
-
父类引用变量指向子类对象(子类到父类的类型转换)。
子类转换成父类时的规则:
-
将一个父类的引用指向一个子类的对象,称为向上转型(
upcasting
),自动进行类型转换。此时通过父类引用调用的方法是子类覆盖或继承父类的方法,不是父类的方法。 此时通过父类引用变量无法调用子类特有的方法; -
如果父类要调用子类的特有方法就得将一个指向子类对象的父类引用赋给一个子类的引用,称为向下转型,此时必须进行强制类型转换。
例:
public class TestAnimalDemo {
public static void main(String[] args) {
show(new Cat()); // 以 Cat 对象调用 show 方法
show(new Dog()); // 以 Dog 对象调用 show 方法
Animal a = new Cat(); // 向上转型
a.eat(); // 调用的是 Cat 的 eat
Cat c = (Cat) a; // 向下转型
c.work(); // 调用的是 Cat 的 work
}
public static void show(Animal a) {
a.eat();
// 类型判断
if (a instanceof Cat) { // 猫做的事情
Cat c = (Cat) a;
c.work();
} else if (a instanceof Dog) { // 狗做的事情
Dog c = (Dog) a;
c.work();
}
}
}
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void work() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void work() {
System.out.println("看家");
}
}
可以用 instanceof
判断一个类是否实现了某个接口,也可以用它来判断一个实例对象是否属于一个类。instanceof
的语法格式为:
对象 instanceof 类(或接口)
7.多态的不同实现方式:
基于继承实现的多态
假设我们有一个父类 Animal
和两个子类 Dog
和 Cat
,它们都继承自 Animal
并重写了 eat()
方法。
// 父类
class Animal {
void eat() {
System.out.println("This animal eats food.");
}
}
// 子类 Dog
class Dog extends Animal {
@Override
void eat() {
System.out.println("Dog eats meat.");
}
}
// 子类 Cat
class Cat extends Animal {
@Override
void eat() {
System.out.println("Cat eats fish.");
}
}
// 测试类
public class TestPolymorphism {
public static void main(String[] args) {
Animal myDog = new Dog();
Animal myCat = new Cat();
myDog.eat(); // 输出: Dog eats meat.
myCat.eat(); // 输出: Cat eats fish.
// 即便引用类型是Animal,实际执行的是子类重写后的方法
}
}
在这个例子中,Animal
类型的引用 myDog
和 myCat
分别指向了 Dog
和 Cat
的实例。当调用 eat()
方法时,根据对象的实际类型(即运行时类型),Java虚拟机能够确定并执行对应子类中的方法。
基于接口实现的多态
现在,我们定义一个接口 Eatable
和两个实现了该接口的类 Apple
和 Banana
。
// 接口
interface Eatable {
void eat();
}
// 实现类 Apple
class Apple implements Eatable {
@Override
public void eat() {
System.out.println("Eating an apple.");
}
}
// 实现类 Banana
class Banana implements Eatable {
@Override
public void eat() {
System.out.println("Eating a banana.");
}
}
// 测试类
public class TestPolymorphismInterface {
public static void main(String[] args) {
Eatable fruit1 = new Apple();
Eatable fruit2 = new Banana();
fruit1.eat(); // 输出: Eating an apple.
fruit2.eat(); // 输出: Eating a banana.
// 即便引用类型是Eatable,实际执行的是实现类中的方法
}
}
在这个例子中,Eatable
类型的引用 fruit1
和 fruit2
分别指向了 Apple
和 Banana
的实例。同样地,当调用 eat()
方法时,Java虚拟机根据对象的实际类型来执行对应的方法。这种方式展示了接口如何提供一种灵活的方式来定义一组方法的集合,并由不同的类来实现这些方法,从而实现多态。
从表面上看,基于继承和基于接口实现的多态在Java中有很多相似之处,尤其是在它们如何允许不同的类以不同的方式实现相同的方法签名方面。然而,它们之间还是存在一些关键的区别和用途上的差异。
继承与多态
- 继承 是一种“is-a”关系,即子类是一个特殊的父类。当你通过继承来实现多态时,你通常是在处理一组具有共同特征和相关行为的类。
- 多态 通过继承实现时,主要是通过子类重写父类的方法来体现的。不同的子类可以以不同的方式实现相同的方法,从而在同一接口下展现出不同的行为。
- 继承允许子类继承父类的属性和方法,但同时也带来了耦合性较高的问题。如果父类发生变化,可能会影响到所有子类。
接口与多态
- 接口 是一种“can-do”关系,它定义了一组方法规范,但不提供具体的实现。实现接口的类必须遵循这些方法规范并提供具体实现。
- 多态 通过接口实现时,不同的类可以实现同一个接口,并各自提供接口中方法的实现。这使得在不知道具体实现类的情况下,可以编写通用的代码来操作实现了该接口的任何对象。
- 接口提供了一种更加灵活和松耦合的方式来定义和实现多态。一个类可以实现多个接口,从而具有多种行为。此外,接口还可以被用作类型,允许在编译时进行类型检查,同时保持运行时的灵活性。
关键点总结
- 继承 是为了代码复用和表达类之间的“is-a”关系。它允许子类继承父类的属性和方法,并通过重写来提供特定的实现。
- 接口 是为了定义一组方法的规范,实现接口的类必须遵循这些规范。接口提供了一种更加灵活和松耦合的方式来定义和实现多态。
- 多态 允许我们以统一的方式处理不同类型的对象,而无需关心它们的具体实现。无论是通过继承还是接口实现,多态都是面向对象编程中一个非常强大的特性。
8.向上转型和向下转型:
向上转型
把子类对象赋值给父类类型的变量(隐式转换,不用进行强制类型转换),被称为向上转型。
本质:父类的引用指向了子类的对象。
语法:父类类型 引用名 = new 子类类型();
class Animal{
public void info(){
System.out.println("我是动物");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("狗在吃东西");
}
public static void main(String[] args) {
Animal animal = new Dog(); // 向上转型
animal.info();
}
}
狗对象可以调用该方法,那么同样猫也可以调用该方法,这就做到了在父类中定义一个方法可以完成各个子类的功能。由于向上转型是一个从较具体类到较抽象类的转换,所以它总是安全的,因为我们可以说狗是动物,但是不能说动物是狗。
向上转型的特点如下:
-
向上转型对象不能操作子类新增的成员变量(失掉了这部分属性),不能使用子类新增的方法(失掉了一些功能);
-
向上转型对象可以操作子类继承或重写的成员变量,也可以使用子类继承的或重写的方法;
-
如果子类重写了父类的某个方法后,当对象的向上转型对象调用这个方法时一定是调用了这个重写的方法,因为程序在运行时知道,这个向上转型对象的实体是子类创建的,只不过损失了一些功能而已。
向下转型
向下转型是指子类引用父类对象,就是将父类对象能转换成子类对象,这时需要满足两个条件:一是必须执行强制类型转换;二是必须确保父类对象是子类的一个实例,否则抛出异常。
语法:子类类型 引用名 = (子类类型)父类引用;
class Animal{
public void info(){
System.out.println("我是动物");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("狗在吃东西");
}
public static void main(String[] args) {
Animal animal = new Dog(); // 向上转型
Dog animal1 = (Dog) animal; // 向下转型
animal1.eat();
}
}
向下转型的特点如下:
-
向下转型对象可以操作父类及子类成员变量和成员方法;
-
向下转型对象访问重写父类的方法时,操作的是子类的方法;
-
向下转型必须进行强制类型转换;
-
向下转型必须保证父类对象引用的是该子类的对象,如果引用的是父类的其他子类对象,会抛出类型不匹配异常。