多态:
★ 向上转型
★ 多态和动态绑定
★ 多态和构造器
枚举:
特殊的类
[访问权限] enum 枚举类型名
{枚举常量表列}
深入理解Java枚举类型(enum)-CSDN博客
enum Note // 枚举类型
{
CarmeloeAnthony, KobeBryant, KawhiLeonard
} //;可以没有
public class Main {
public static void main(String[] args) {
System.out.println("Hello world!");
Note n1 = Note.CarmeloeAnthony;//CarmeloeAnthony
System.out.println(n1); //
System.out.println(n1.ordinal()); //枚举常量序号0
}
}
// case 后面,常量标示名前不能有枚举类型名
// public void describe(Note degree) { // switch(degree) { // case CarmeloeAnthony: println("not spicy at all."); // break; // case KobeBryant : // case KawhiLeonard: println("a little hot."); // default: println("maybe too hot."); // } // }
★ 归纳与分析:
⑴ 使用多态的三个条件:继承、覆盖与向上转型
⑵ 当调用父类里面被覆盖方法的时候, 实际是哪个对象, 就调用这个对象(子类对象)的与之相关的覆盖方法
向上转型:
enum number // 枚举类型
{CarmeloeAnthony, KobeBryant, KawhiLeonard}
//Wind.java
class player {
public void play(
number n) {
System.out.println("MVP.play()" + n);
}
}
class Win extends player {
public void play(
Note n) {
System.out.println("Win.play()" + n);
}
}
public class exp1 {
public static void tune(player i) {
i.play(number.CarmeloeAnthony);
}
public static void main(String[] args) {
Win flute = new Win(); //
tune(flute); // 向上转型
flute.play(number.KobeBryant);
}
}
Win类“向上转换成”player类
★ 归纳:多态的优点(与前面缺点形成对比)
⑴ 减少工作量
⑵ 少做少错
◆ 总结:多态是将改变的事物与未改变的事物分离开来的重要技术(保证变化所造成的影响,局限在可以控制的范围)
绑定:
★ 将一个方法调用和一个方法主体关联起来的过程称为绑定
绑定(Banding)的分类:
⑴ 静态绑定(前期绑定,编译时绑定)
★ 在编译的时候,由编译器和连接器绑定
⑵ 动态绑定(后期绑定、运行时绑定)
★ 在运行的时候,根据对象类型进行绑定
除了static、final、private方法,其他方法都是动态绑定,可以实现多态调用。
class PrivateOverride {
private void f() {
System.out.println("super f( )");
}
public static void main(String[] args) {
PrivateOverride po = new Derived();
po.f(); // super f()私有方法:不存在与对象的动态绑定
}
}
class Derived extends PrivateOverride {
private void f() {
System.out.println("sub f( )");
}
public static void main(String[] args) {
PrivateOverride po = new Derived();;
//po.f(); //父类的私有函数不能调用
}
}
静态方法:不存在与对象的绑定
class StaticSuper {
public static String staticGet() {
return "Base staticGet()";
}
public String dynamicGet() {
return "Base dynamicGet()";
}
}
class StaticSub extends StaticSuper {
public static String staticGet() {
return "Derived staticGet()";
}
public String dynamicGet() {
return "Derived dynamicGet()";
}
}
//★ 静态方法:不存在与对象的绑定
public class StaticPolymorphism {
public static void main(String[] args) {
StaticSuper sup = new StaticSub();
System.out.println(sup.staticGet());
System.out.println(sup.dynamicGet());
}
}
class P
{ public void a(){ System.out.println("P.a()"); }
public void b() { System.out.println("P.b()"); }
}
class U extends P
{// 实现对父类方法的覆盖
public void b() { System.out.println("U.b()"); }
public void c() { System.out.println("U.c()"); }
}
class A
{ public static void main(String[] args)
{ // 向上转型
P pr = new U(); pr.a(); // 调用的是P.a()
pr.b(); //调用的是U.b() pr.c();// 编译错误
}
}
public class exp3 {
public static void main(String[] args){
A a = new A();
a.main(null);
}
}
★ 向上转型的归纳:父类 实例引用 = new 子类();
◆ 向上转型时,实例引用可以调用父类中所有的方法(包括未在子类中覆盖的方法)
◆ 实例可以调用子类中覆盖父类的方法(即多态)
◆ 但是不可以调用子类中特有的方法(即父类中没有的方法)
4、 注意:只有普通的方法调用可以是动态绑定的,域不存在动态绑定
覆盖函数的访问权限与父类有关,只能放松
构造器和多态 :
1、 构造器的调用顺序(复习)
构造器的调用原则:调用子类构造器之前,先触发基类构造器调用。
⑴ 首先:只有基类的构造器,才具有恰当的知识和权限来对自己的元素进行初始化
⑵ 过程:new操作将首先导致分配存储空间, 但在调用构造器初始化的时候,它发现自己没有能力对来自基类的成员进行初始化,于是只好上溯
反复递归(属于两上两下的第二次上下):
⑴ 不断上溯,一直到这个层次的根
◆ 不断触发基类构造器的调用
⑵ 调转方向不断下溯
◆ 首先创建(初始化)基类对象,然后以此为基础创建(初始化)下一层的子类对象,如此操作一直到最底层
class Bread // 面包
{
Bread() {
System.out.println("Bread()");
}
}
class Meal // 一餐
{
Meal() {
System.out.println("Meal()");
}
}
class Lunch extends Meal // 午餐
{
Lunch() {
System.out.println("Lunch()");
}
}
class Portablelunch extends Lunch // 便携式
{
Portablelunch() {
System.out.println("Portablelunch()");
}
}
class Sandwich extends Portablelunch //三明治
{
private Bread b = new Bread();
Sandwich() {
System.out.println("Portablelunch()");
}
public static void main(String[] args) {
new Sandwich();
}
}
public class exp4 {
public static void main(String[] args){
Sandwich fun = new Sandwich();
fun.main(null);
}
}
构造器内部的多态方法的行为 :
⑴ 首先,我们知道:动态绑定的调用是在运行时才决定的
★ 因为编译器无法知道:方法所要绑定的对象是基类对象还是派生类对象
⑵ 如果将这种思路推广到:在基类构造器内部调用一个动态绑定方法,那么会发生什么情况?
★ 结果难以预料,因为被覆盖的方法在子类对象被完全构造之前就会被调用。
class Glyph // 雕像,象形文字 P163
{
void draw() {
System.out.println("Glyph.draw()");
}
Glyph() {
System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() after draw()");
}
}
class RoundGlyph extends Glyph {
private int radius = 1;
RoundGlyph(int r) {
radius = r;
System.out.println("RoundGlyph.radius=" + radius);
}
void draw() // 覆盖虚函数
{
System.out.println("RoundGlyph.draw(), radius=" + radius);
}
}
public class PolyConstructors {
public static void main(String[] args) {
new RoundGlyph(5);
}
}
★ 分析其执行过程:
⑴ 当类的加载和静态初始化完毕(第一次上下完毕)
◆ 主类的这条语句将被首先执行:new RoundGlyph(5)
◆ 根据构造器的调用原则,编译器将不断上溯直至该类的根,然后开始调用其基类构造器
⑵ 在调用基类构造器Glyph()过程中,将会发生draw()方法的调用
◆ 根据动态绑定的理论,我们知道,这时候将会调用派生类的draw()方法
◆ 因为:new的是RoundGlyph而不是Glyph
⑶ 这时我们将看到一个悲剧:
◆ 派生类的draw()方法所涉及的数据成员radius还来不及初始化
◆ 掉进了一个两难境地的陷阱(派生类初始化依赖于基类,而基类却又依赖于派生类的初始化)
★ 归纳:完整的两上两下过程
◆ 在第二次上溯的过程中,当new操作分配了存储空间以后,接下来要完成的是自动初始化:
将分配给对象的存储空间初始化为二进制的零
◆ 在第二次下溯的过程中,才完成指定初始化和构造器初始化
◆ 注意:初始化的第一基本原则仍然满足
协变返回类型:
★ 只要子类方法与超类方法具有相同的方法签名,或者子类方法的返回类型是超类方法的子类型,就可以覆盖
class Grain // 谷类 P164
{
public String toString() {
return "Grain";
}
}
class Wheat extends Grain // 小麦
{
public String toString() {
return "Wheat";
}
}
class Mill // 磨坊
{
Grain process() {
return new Grain();
}
}
class WheatMill extends Mill // 小麦磨坊
{
Wheat process() // 覆盖
{
return new Wheat();
}
}
class CovariantReturn {
public static void main(String[] args) {
Mill m = new Mill();
Grain g = m.process();
System.out.println(g);
m = new WheatMill(); // 向上转型
g = m.process();// 向上转型
System.out.println(g); //多态
}
}
public class exp5 {
public static void main(String[] args){
CovariantReturn fun = new CovariantReturn();
fun.main(null);
}
}
因为满足了继承、覆盖和向上转型三个条件,多态的实现成为可能
用继承进行设计:
★ 当我们在使用现成的类来建立新类时,如果首先考虑使用继承技术,反而会
加重我们的设计负担
◆ 更好的方式是首先选择组合,尤其是不能十分确定应该使用哪一种方式时
★2、 向下转型与运行时类型识别
Java的类型检测更为严格,所有的转型都会受到严格检查
◆ 换句话说,Java在运行期间,仍然会对转型进行检查,以保证它的确是我们希望的那种类型。(运行时类型识别,RITT)(向下转型必须使用强制类型转换)
◆ 若不是,返回ClassCastException异常不能直接把父类引用赋给子类引用,也不能用子类引用指向父类对象。
class Useful { //P167
public void f( ) { }
}
class MoreUseful extends Useful {
public void f( ) { }
public void u( ) { }
}
public class RTTI {
public static void main(String[] args) {
Useful[ ] x = { new Useful( ), new MoreUseful( ) };
x[0].f( );
//! x[1].u( );
((MoreUseful)x[1]).u( ); // Downcast/RTTI
((MoreUseful)x[0]).u( ); // Exception thrown
}
}