[Java Tutorial学习分享]接口与继承
关于接口:
- 用途
- 为什么您可能想要编写接口
- 如何编写接口。
关于继承:
- 从一个类派生另一个类的方法。也就是说,子类如何从超类继承字段和方法。
- 所有类都是从Object类派生的
- 修改子类从超类继承的方法。
- interface-like 抽象类。
接口
概述
接口是阐明软件交互方式的契约。
Java 中的接口
- 在Java编程语言中,接口是一种引用类型,类似于类,它只能包含常量、方法签名、默认方法、静态方法和嵌套类型。
- 方法体只存在于默认方法和静态方法中。方法签名没有大括号,以分号结束。
- 接口不能实例化——它们只能由类实现或由其他interface extends。
- 如何使用接口?需要编写实现该接口的类。当一个实例化类实现一个接口时,它必须为接口中声明的每个方法提供一个方法体。
使用接口作为API
略。
定义一个接口
接口声明由修饰符、关键字interface、接口名、以逗号分隔的父接口列表(如果有的话)和接口主体组成。例如:
public interface GroupedInterface extends Interface1, Interface2, Interface3 {
// constant declarations
// base of natural logarithms
double E = 2.718282;
// method signatures
void doSomething (int i, double x);
int doSomethingElse(String s);
}
-
public指示接口可由任何包中的任何类使用。如果您没有指定接口是public的,那么您的接口只能被定义在与接口相同的包中的类访问。
-
接口可以扩展其他接口,就像一个类的子类或扩展另一个类一样。然而,类只能扩展另一个类,而接口可以扩展任意数量的接口。接口声明包括它扩展的所有接口的逗号分隔列表。
The Interface Body
-
接口主体可以包含抽象方法、默认方法和静态方法。
-
接口中的抽象方法后跟分号,但没有大括号(抽象方法不包含实现)。
-
默认方法用
default
修饰符定义,而静态方法用static
关键字定义。 -
接口中的所有抽象、默认和静态方法都是隐式公共的,因此可以省略public修饰符。
实现接口
- 要声明实现接口的类,可以在类声明中包括implements子句。您的类可以实现多个接口,因此implements关键字后面有一个逗号分隔的类实现的接口列表。
- 按照惯例,implements子句紧跟extends子句(如果有的话)。
使用接口作为类型
- 定义一个新的接口时,也同时定义一个新的引用数据类型。
- 接口名称可以和其他数据类型出现在相同的地方。
- 如果定义的引用变量的类型是接口,则赋给它的任何对象必须是实现该接口的类的实例。
进化的接口
- 如果要为旧接口添加新的抽象方法,那么旧的接口实现类将需要做出改动。
- 不修改旧的接口实现类,替代办法:
- 扩展旧接口,在新接口中定义抽象方法
- 添加默认方法、静态方法
默认方法
- 默认方法使您能够向库的接口添加新功能,并确保与为这些接口的旧版本编写的代码的二进制兼容性。
- 在方法签名的开头使用default关键字,将方法定义为默认方法。接口中的所有方法声明(包括默认方法)都是隐式公共的,因此可以省略public修饰符。
扩展包含默认方法的接口
当扩展包含默认方法的接口,有3件事情可以做:
- 根本不用提默认方法,它让您的扩展接口继承默认方法。
- 重新声明默认方法,这会使其抽象。
- 重新定义默认方法,该方法将覆盖它。
静态方法
- 除了默认方法之外,还可以在接口中定义静态方法。
- 静态方法是与定义它的类相关联的方法,而不是与任何对象相关联的方法。
- 类的每个实例共享它的静态方法。)这使您更容易组织库中的helper方法;可以将特定于接口的静态方法保存在相同的接口中,而不是保存在单独的类中。
- 与类中的静态方法一样,可以在方法签名的开头使用static关键字指定接口中的方法定义是静态方法。接口中的所有方法声明(包括静态方法)都是隐式公共的,因此可以省略public修饰符。
接口总结
- 接口声明可以包含方法签名、默认方法、静态方法和常量定义。唯一有实现的方法是默认方法和静态方法。
- 实现接口的类必须实现该接口中声明的所有方法。
- 接口名可以在任何可以使用类型的地方使用。
继承
概述
在Java语言中,类可以从其他类派生,从而从这些类继承字段和方法。
定义: 派生自另一个类的类称为子类(也是派生类、扩展类或子类)。派生子类的类称为超类(也是基类或父类)。
除了Object,它没有超类,每个类都有且只有一个直接超类(单继承)。在没有任何其他显式超类的情况下,每个类都是Object的隐式子类。
类可以从从类派生的类派生的类派生的类,以此类推,并最终从最顶层的类Object派生。这样的类被称为继承链中回溯到Object的所有类的后裔。
继承的作用:当您想创建一个新类,而已有一个类包含您想要的一些代码时,您可以从现有的类派生出您的新类。这样,您就可以重用现有类的字段和方法,而不必自己编写(和调试!)它们。
子类和超类的关系:子类从超类继承所有成员(字段、方法和嵌套类)。构造函数不是成员,所以它们不会被子类继承,但是超类的构造函数可以从子类调用。
Java平台类层次
对象类,在java.lang包中,定义并实现所有类通用的行为。在Java平台中,许多类直接从Object派生,其他类从其中的一些类派生,以此类推,形成类的层次结构。
在子类中可以做什么
- 无论该子类在哪个包中,子类继承其父类的所有public和protected的成员。
- 如果子类与父类在同一个包中,它还继承父类的package-private成员。
- 您可以按原样使用继承的成员,替换他们,隐藏他们,或用新成员补充他们:
- 继承的字段可以直接使用,就像任何其他字段一样。
- 可以在子类中声明一个与超类同名的字段,从而隐藏它(不推荐)
- 可以在子类中声明超类中没有的新字段。
- 继承的方法可以直接使用。
- 可以在子类中编写一个与超类中的签名相同的新实例方法,从而覆盖它。
- 可以在子类中编写一个新的静态方法,该方法具有与超类中的签名相同的签名,从而隐藏它。
- 可以在子类中声明超类中没有的新方法。
- 可以编写一个子类构造函数来调用超类的构造函数,可以是隐式的,也可以使用关键字super。
超类中的私有成员
- 子类不继承父类的私有成员。但是,如果超类有用于访问其私有字段的public或protected的方法,那么这些方法也可以被子类使用。
- 嵌套类可以访问其封闭类的所有私有成员——字段和方法。因此,子类继承的public或protected的嵌套类可以间接访问超类的所有私有成员。
- 可以通过public或protected的方法、嵌套类间接访问超类的私有成员。
强制转换对象
-
对象的数据类型与它所实例化的类的数据类型相同。
-
强制转换使得,可以用一种类型的对象代替另一种类型的对象。
-
子类变量赋值给超类的变量:隐式转换
-
父类变量赋值给子类变量
- 问题:抛出编译器错误
- 解决方法:强制转换
-
注意:您可以使用instanceof操作符对特定对象的类型进行逻辑测试。这可以避免由于不适当的强制转换而出现运行时错误。例如:
if (obj instanceof MountainBike) { MountainBike myBike = (MountainBike)obj; }
状态、实现和类型的多重继承
状态
- 类和接口之间的一个显著区别是类可以有字段,而接口不能。
- 您可以实例化一个类来创建一个对象,这是接口无法做到的。
- Java编程语言不允许扩展多个类的一个原因是为了避免状态的多重继承问题,即从多个类继承字段的能力。
实现
- 实现的多重继承是指从多个类继承方法定义的能力。这种类型的多重继承会出现问题,比如名称冲突和歧义。
- 当支持这种多继承类型的编程语言的编译器遇到包含同名方法的超类时,它们有时无法确定要访问或调用哪个成员或方法。此外,程序员可以通过向超类添加新方法而无意中引入名称冲突。
- 默认方法引入了实现的一种形式的多继承。一个类可以实现多个接口,这些接口可以包含具有相同名称的默认方法。Java编译器提供了一些规则来确定特定类使用的默认方法???。
类型
-
Java编程语言支持类型的多重继承,这是一个类实现多个接口的能力。
-
一个对象可以有多种类型:它自己类的类型和该类实现的所有接口的类型。
-
如果一个变量被声明为接口的类型,那么它的值可以引用从实现该接口的任何类实例化的任何对象。
-
与实现的多重继承一样,类可以继承在其扩展的接口中定义的方法的不同实现(默认的或静态的)。在这种情况下,编译器或用户必须决定使用哪一个。
重写和隐藏方法
实例方法
子类重写方法的能力允许类从行为“足够接近”的超类继承,然后根据需要修改行为。
- 覆盖的方法具有与它覆盖的方法相同的名称、参数数量和类型以及返回类型。
- 重写方法还可以返回被重写方法返回的类型的子类型。这个子类型称为协变返回类型。
重写方法时,您可能希望使用@Override注释,该注释指示编译器您打算重写超类中的方法。如果由于某种原因,编译器检测到该方法在某个超类中不存在,那么它将生成一个错误。有关@Override的更多信息,请参见注释。
静态方法
如果子类定义的静态方法具有与父类中的静态方法相同的签名,则子类中的方法隐藏父类中的方法。
隐藏静态方法和覆盖实例方法之间的区别有重要的含义:
- 被调用的覆盖实例方法的版本是子类中的版本。
- 被调用的隐藏静态方法的版本取决于它是从超类还是子类调用的。
接口方法
-
接口中的默认方法和抽象方法与实例方法一样继承。但是,当类或接口的超类型提供具有相同签名的多个默认方法时,Java编译器将遵循继承规则来解决名称冲突。这些规则是由以下两个原则驱动的:
-
实例方法优于接口默认方法。
-
已经被其他候选者重写的方法将被忽略。当多个超类型共享一个共同的祖先时,就会出现这种情况。
-
如果两个或多个独立定义的默认方法冲突,或者一个默认方法与一个抽象方法冲突,那么Java编译器将生成一个编译器错误。必须显式重写超类型方法。
// 有两个接口(OperateCar和FlyCar),它们为相同的方法(startEngine)提供默认实现: public interface OperateCar { // ... default public int startEngine(EncryptedKey key) { // Implementation } } public interface FlyCar { // ... default public int startEngine(EncryptedKey key) { // Implementation } } // 同时实现OperateCar和FlyCar的类必须重写startEngine方法。您可以使用super关键字调用任何默认实现。 public class FlyingCar implements OperateCar, FlyCar { // ... public int startEngine(EncryptedKey key) { FlyCar.super.startEngine(key); OperateCar.super.startEngine(key); } }
-
super之前的名称 = 定义或继承被调用方法的默认实现的 直接超接口。这种形式的方法调用并不仅限于区分包含具有相同签名的缺省方法的多个已实现接口。可以使用super关键字在类和接口中调用默认方法。
-
从类继承的实例方法可以覆盖抽象接口方法。
-
接口中的静态方法永远不会被继承。
-
-
控制符
覆盖方法的访问说明符可以比覆盖的方法允许更多而不是更少的访问。例如,超类中的protected实例方法可以在子类中改为public,而不是private。
如果您试图将超类中的实例方法更改为子类中的静态方法,则会得到编译时错误,反之亦然。
总结
下表总结了当定义具有与超类中的方法相同签名的方法时会发生什么。
超类实例方法 | 超类静态方法 | |
---|---|---|
子类实例方法 | 重写 | 编译器错误 |
子类静态方法 | 编译器错误 | 隐藏,额。。。 |
在子类中,可以重载从超类继承的方法。这种重载的方法既不隐藏也不重写超类实例方法——它们是新方法,是子类特有的。
多态
类的子类可以定义它们自己的独特行为,同时还可以共享父类的一些相同功能。
Java虚拟机(JVM)为每个变量引用的对象调用适当的方法。它不调用由变量的类型定义的方法。这种行为被称为虚拟方法调用,它演示了Java语言中重要的多态特性的一个方面。
隐藏字段
- 在类中,与超类中的字段同名的字段会隐藏超类的字段,即使它们的类型不同。
- 在子类中,超类中的字段不能通过其简单名称引用。相反,必须通过super访问该字段
- 一般来说,我们不建议隐藏字段,因为这会使代码难以阅读。
使用super关键字
访问父类的成员
- 如果您的方法覆盖了它的超类的一个方法,那么您可以通过使用关键字super来调用被覆盖的方法。
- 您还可以使用super来引用隐藏字段(尽管不鼓励隐藏字段)。
子类构造函数
- 超类构造函数的调用必须是子类构造函数的第一行。
- 如果构造函数没有显式地调用超类构造函数,Java编译器会自动插入对超类的无参数构造函数的调用。如果超类没有无参数构造函数,您将得到一个编译时错误。Object确实有这样的构造函数,所以如果Object是唯一的超类,就没有问题。
如果一个子类构造函数显式或隐式地调用其超类的构造函数,您可能会认为将调用一整条构造函数链,一直返回到Object的构造函数。事实上,情况就是这样。它被称为构造函数链接,当有很长的类下降线时,您需要注意它。
Object作为超类
略。
编写Final的类和方法
- 以在方法声明中使用final关键字来指示该方法不能被子类覆盖。
- 如果一个方法有一个不应该更改的实现,并且它对对象的一致状态至关重要,那么您可能希望将该方法设置为final。
- 从构造函数调用的方法通常应该声明为final。如果构造函数调用了一个非final方法,那么子类可能会重新定义该方法,从而产生令人吃惊的或不希望看到的结果。
- 可以声明整个类的final。声明为final的类不能被子类化。这是特别有用的,例如,当创建一个不可变类(如String类)时。
抽象方法和类
- 抽象类是声明为 abstract 的类——它可以包含也可以不包含抽象方法。
- 抽象类不能被实例化,但可以被子类化。
- 抽象方法是在没有实现的情况下声明的方法(没有大括号,后跟分号)
- 如果类包含抽象方法,则类本身必须声明为 abstract
- 当抽象类被子类化时,子类一般提供其父类中所有抽象方法的实现;否则,子类也必须声明为抽象。
抽象类与接口的比较
- 共同点:抽象类类似于接口,不能实例化它们,它们可能包含有或没有实现声明的方法的混合。
不同点:
- 内容的不同:
- 使用抽象类,可以声明非静态和final字段,并定义公共、受保护和私有具体方法。
- 使用接口,所有字段都自动是public、statice和final的,声明或定义的所有方法(作为默认方法)都是公共的。
- 扩展数量的不同:只能扩展一个类,无论它是否是抽象的,而您可以实现任意数量的接口。
你应该使用哪一个,抽象类还是接口?
- 考虑使用抽象类
- 您希望在几个密切相关的类之间共享代码。
- 您希望扩展抽象类的类有许多通用的方法或字段,或者需要除public之外的访问修饰符(例如protected和private)。
- 您希望声明非静态或非final字段。这使您能够定义能够访问和修改其所属对象状态的方法。
- 考虑使用接口
- 期望不相关的类来实现您的接口。例如,Comparable和Cloneable接口是由许多不相关的类实现的。
- 希望指定特定数据类型的行为,但不关心谁实现了它的行为。
- 希望利用类型的多重继承。
当抽象类实现接口时
- 如果接口实现类类被声明为抽象类,则可以定义一个不实现接口所有方法的类。
类成员
抽象类可以有静态字段和静态方法。可以通过类引用使用这些静态成员,就像对任何其他类一样。
继承的总结
- 除了Object类之外,类只有一个直接超类。类从其所有超类继承字段和方法,无论是直接的还是间接的。子类可以覆盖它继承的方法,也可以隐藏它继承的字段或方法。(注意,隐藏字段通常是糟糕的编程实践。)
- 重写和隐藏方法部分中的表(在前面的部分)显示了使用与超类中的方法相同的签名声明方法的效果。
- Object类是类层次结构的顶部。所有类都是这个类的后代,并从它继承方法。从Object继承的有用方法包括toString()、equals()、clone()和getClass()。
- 通过在类声明中使用final关键字,可以防止类被子类化。类似地,您可以通过将一个方法声明为final方法来防止它被子类覆盖。
- 抽象类只能被子类化;不能实例化它。抽象类可以包含抽象方法——声明但未实现的方法。然后子类提供抽象方法的实现。