随遇而安——多态

为什么使用多态?
不用为每一增加的子类增加对应的麻烦,最重要又可以偷懒了~

什么是多态?
多态:同一个引用类型,使用不同的实例而执行不同操作

使用父类作为方法形参实现多态
使用父类作为形式参数,可以接受子类的对象作为实参。
进入到方法之后,该对象仅能当做父类使用,无法访问子类的成员。

public class Master {
    public void feed( Pet pet ) {
       //使用父类作为方法形参   
    }
}
// 测试方法 

Dog dog = new Dog();
Master master = new Master();
master.feed( dog );
//同一种操作方式,不同的操作对象 

父类作为返回值
使用父类作为返回值类型,可以返回任意子类的实例

public class Master {
    public Pet chooseRandomPet(Pet[] pets){
           … …         
    }
}

父类到子类的转换

public class Dog extends Pet {
	public void catchingFlyDisc() {
       	 … …
	}
}
public class Penguin  extends Pet {
	public void swimming () {
       	 … …
	}
}
public class Master {
	public void play(Pet pet){
		 //  pet.catchingFlyDisc();
		 //报错,父类引用不能调用子类特有方法

		Dog dog = (Dog)pet;
		dog.catchingFlyDisc();
		//可以使用强制类型转换,将父类转换成子类类型
	}
}
 //测试类 
Pet pet = new Dog();
Master master = new Master();
master.playWith(pet);
		

判断父类实例的类别
instanceof运算符

语法:对象 instanceof 类或接口

public class Master {
	public void play(Pet pet){
               if (pet instanceof Dog) {    //如果传入的是狗狗
	         Dog dog = (Dog) pet;
	         dog.catchFlyingDisc();
	}else if (pet instanceof Penguin) {   //如果传入的是企鹅
	         Penguin pgn = (Penguin) pet;
	         pgn.swim();
	}
	}
}		

塑型

  • 塑型(type-casting)又称为类型转换
  • 方式
    隐式(自动)的类型转换
    显式(强制)的类型转换
  • 塑型的对象包括
  • 基本数据类型
    将值从一种形式转换成另一种形式
    • 引用变量
      将对象暂时当成更一般的对象来对待,并不改变其类型
      只能被塑型为
      • 任何一个父类类型
      • 对象所属的类实现的一个接口
      • 被塑型为父类或接口后,再被塑型回其本身所在的类

隐式(自动)的类型转换

  • 基本数据类型
    相容类型之间存储容量低的自动向存储容量高的类型转换
  • 引用变量
    • 被塑型成更一般的类
Employee  emp; 
emp = new Manager(); //将Manager类型的对象直接赋给
                                     //Employee类的引用变量,系统会                                   
                                     //自动将Manage对象塑型为Employee类
  • 被塑型为对象所属类实现的接口类型
Car  jetta = new Car(); 
Insurable  item = jetta; 

显式(强制)的类型转换

  • 基本数据类型
(int)871.34354;     // 结果为 871 
(char)65;              // 结果为‘A’ 
(long)453;            // 结果为453L
  • 引用变量:还原为本来的类型
Employee  emp; 
Manager man;
emp = new Manager();
man = (Manager)emp; //将emp强制塑型为本来的类型

塑型应用的场合

  • 赋值转换
    赋值号右边的表达式类型或对象转换为左边的类型
  • 方法调用转换
    实参的类型转换为形参的类型
  • 算数表达式转换
    算数混合运算时,不同类型的项转换为相同的类型再进行运算
  • 字符串转换
    字符串连接运算时,如果一个操作数为字符串,一个操作数为数值型,则会自动将数值型转换为字符串

方法的查找
如果在塑型前和塑型后的类中都提供了相同的方法,如果将此方法发送给塑型后的对象,那么系统将会调用哪一个类中的方法?

  • 实例方法的查找
  • 类方法的查找

实例方法的查找
从对象创建时的类开始,沿类层次向上查找

Manager   man = new Manager(); 
Employee  emp1 = new Employee(); 
Employee  emp2 = (Employee)man; 
emp1.computePay();     // 调用Employee类中的computePay()方法 
man.computePay();       // 调用Manager类中的computePay()方法  
emp2.computePay();     // 调用Manager类中的computePay()方法 

类方法的查找
总是在引用变量声明时所属的类中进行查找

Manager  man = new Manager(); 
Employee emp1 = new Employee(); 
Employee emp2 = (Employee)man; 
man.expenseAllowance();          //in Manager 
emp1.expenseAllowance();         //in Employee 
emp2.expenseAllowance();         //in Employee!!! 

绑定

指将一个方法调用同一个方法主体连接到一起

  • 根据绑定时期的不同,可分为
    • 早期绑定
      程序运行之前执行绑定
    • 晚期绑定
      也叫作“动态绑定”或“运行期绑定
      基于对象的类别,在程序运行时执行绑定
//基类Shape建立了一个通用接口
class Shape { 
    void draw()    {} 
    void erase()   {} 
} 
//派生类覆盖了draw方法,为每种特殊的几何形状都提供独一无二的行为
class Circle extends Shape { 
	void draw() 
      { System.out.println("Circle.draw()"); } 
	void erase() 
      { System.out.println("Circle.erase()"); } 
}
class Square extends Shape { 
	void draw() 
      { System.out.println("Square.draw()"); }
	void erase() 
      { System.out.println("Square.erase()"); } 
} 
class Triangle extends Shape { 
	void draw() 
      { System.out.println("Triangle.draw()"); } 
   void erase() 
      { System.out.println("Triangle.erase()"); }
 } 
// 对动态绑定进行测试如下
public class BindingTester{ 
    public static void main(String[] args) { 
       Shape[] s = new Shape[9]; 
       int n;
       for(int i = 0; i < s.length; i++) { 
            n = (int)(Math.random() * 3);
            switch(n) {  
                case 0: s[i] =  new Circle(); break;
                case 1: s[i] =  new Square(); break;
                case 2: s[i] =  new Triangle();
             } 
        }      
        for(int i = 0; i < s.length; i++)   s[i].draw(); 
      }
}
运行结果
Square.draw()
Triangle.draw()
Circle.draw()
Triangle.draw()
Triangle.draw()
Circle.draw()
Square.draw()
Circle.draw()
Triangle.draw()
说明
编译时无法知道s数组元素的具体类型,运行时才能确定类型,所以是动态绑定
在主方法的循环体中,每次随机生成指向一个Circle、Square或者Triangle的引用
所有类都在music包中
Note类中定义了三个音符
Instrument类中声明并实现了一个play方法
Wind类继承了Instrument类,重载了play方法
Music类中包含了main方法
class Note {
  private int value;
  private Note(int val) { value = val; }
  public static final Note
    MIDDLE_C = new Note(0), 
    C_SHARP  = new Note(1),
    B_FLAT   = new Note(2);
} 
class Instrument {
  public void play(Note n) {
    System.out.println("Instrument.play()");
  }
}

class Wind extends Instrument {
  public void play(Note n) {
    System.out.println("Wind.play()");
  }
}
public class Music {
  public static void tune(Instrument i) {
      i.play(Note.MIDDLE_C);
  }
  public static void main(String[] args) {
    Wind flute = new Wind();
    tune(flute); 
  }
} 

运行结果
Wind.play()
说明
运行中,Instrument类的对象实际是Wind类的,所以调用了Wind类中的play方法

内部类

  • 在另一个类或方法的定义中定义的类
  • 可访问其外部类中的所有数据成员和方法成员
  • 可对逻辑上相互联系的类进行分组
  • 对于同一个包中的其他类来说,能够隐藏
  • 可非常方便地编写事件驱动程序
  • 声明方式
    • 命名的内部类:可在类的内部多次使用
    • 匿名内部类:可在new关键字后声明内部类,并立即创建一个对象
  • 假设外层类名为Myclass,则该类的内部类名为
    Myclass$c1.class (c1为命名的内部类名)
    Myclass$1.class (表示类中声明的第一个匿名内部类)
public class Parcel1 {
    class Contents { //内部类
        private int i = 11;
        public int value() { return i; }
     }
     class Destination  { //内部类
        private String label;
        Destination(String whereTo)  {   label = whereTo;    }
        String readLabel() { return label; }
     }
     public void ship(String dest) {
         Contents c = new Contents();
         Destination d = new Destination(dest);
         System.out.println(d.readLabel());
      }
      public static void main(String[] args)  {
       Parcel1 p = new Parcel1();
       p.ship("Tanzania");
    }
} 
说明
在Parcel1类中声明了两个内部类Contents、Destination
在ship方法中生成两个内部类对象,并调用了内部类中声明的一个方法

外部类的方法可以返回内部类的引用变量

public class Parcel2 {
   class Contents  {
       private int i = 11;
       public int value() { return i; }
    }
    class Destination {
        private String label;
        Destination(String whereTo) {  label = whereTo;  }
        String readLabel() { return label; }
     }
    public Destination to(String s)
        { return new Destination(s);  } 
    public Contents cont() { return new Contents();  }
    public void ship(String dest) {
        Contents c = cont();
        Destination d = to(dest);          
        System.out.println(d.readLabel());
    }  
    public static void main(String[] args) {
         Parcel2 p = new Parcel2();
         p.ship("Tanzania");
         Parcel2 q = new Parcel2();
          Parcel2.Contents c = q.cont();
          Parcel2.Destination d =q.to("Borneo");
     }
}
说明
to()方法返回内部类Destination的引用
cont()方法返回内部类Contents的引用

内部类实现接口(接口在下一节)
内部类实现接口

  • 可以完全不被看到,而且不能被调用
  • 可以方便实现“隐藏实现细则”。你所能得到的仅仅是指向基类(base class)或者接口的一个引用
abstract class Contents {
     abstract public int value();
}
interface Destination {
     String readLabel();
}
public class Parcel3 {
     private class PContents extends Contents {
          private int i = 11;
          public int value() { return i; }
     }
     protected class PDestination implements Destination {
          private String label;
          private PDestination(String whereTo) { label = whereTo;}
          public String readLabel() { return label; }
      }
     public Destination dest(String s) { return new PDestination(s); }
     public Contents cont() { return new PContents(); }
}
class Test {
    public static void main(String[] args) {
        Parcel3 p = new Parcel3();
        Contents c = p.cont();
        Destination d = p.dest("Tanzania");
   }
 } 
说明
内部类PContents实现了抽象了Contents
内部类PDenstination实现了接口Destination
外部类Test中不能声明对private的内部类的引用

方法中的内部类
在方法内定义一个内部类

  • 为实现某个接口,产生并返回一个引用
  • 为解决一个复杂问题,需要建立一个类,而又不想它为外界所用
public class Parcel4 {
    public Destination dest(String s) {
        class PDestination implements Destination {
             private String label;
             private PDestination(String whereTo) {
             label = whereTo;
         }
         public String readLabel() { return label; }
         return new PDestination(s);
     }
     public static void main(String[] args) {
         Parcel4 p = new Parcel4();
         Destination d = p.dest("Tanzania");
     }
}
public class Parcel5 {
    private void internalTracking(boolean b) {
        if(b) {
            class TrackingSlip {
                private String id;
                TrackingSlip(String s) { id = s;   }
                String getSlip() { return id; }
             }
            TrackingSlip ts = new TrackingSlip("slip");
            String s = ts.getSlip();
          }
     }
     public void track() { internalTracking(true); }
     public static void main(String[] args)  {   
         Parcel5 p = new Parcel5();    
         p.track();  
     }
}
public class Parcel6 {
    public Contents cont() {
        return new Contents() {
           private int i = 11;
           public int value() { return i; }
        }; 
     }
     public static void main(String[] args) {
         Parcel6 p = new Parcel6();
         Contents c = p.cont();
     }
 }

匿名的内部类
基类需要一个含参数的构造方法

public class Parcel7 {
    public Wrapping wrap(int x) {
        return new Wrapping(x) { 
           public int value() { return super.value() * 47; }
        }; 
     }
    public static void main(String[] args) {
        Parcel7 p = new Parcel7();
        Wrapping w = p.wrap(10);
    }
}

匿名内部类对象的初始化

public class Parcel8 {
    public Destination dest(final String dest) {
        return new Destination() {
            private String label = dest;
            public String readLabel() { return label; }
        };
     }
    public static void main(String[] args) {
        Parcel8 p = new Parcel8();
        Destination d = p.dest("Tanzania");
    }
 } 

通过实例初始化构造匿名内部类

public class Parcel9 {
    public Destination dest(final String dest, final float price) {
        return new Destination() {
             private int cost;
             { cost = Math.round(price);
                      If(cost > 100)
                             System.out.println("Over budget!");
                    }  //不能重载
             private String label = dest;
             public String readLabel() { return label; }
         };
     }
    public static void main(String[] args) {
         Parcel9 p = new Parcel9();
         Destination d = p.dest("Tanzania", 101.395F);
     }
}

Object类

Object类是Java中所有类的父类

Object类的方法

getClass()方法

  • getClass()方法是多态方法返回的是实际对象的类型
  • getClass()方法的返回值是Class类型,Class类型也继承自Object
  • getClass()方法是finial方法,不能在子类中重写
System.out.println(obj.getClass());
//可以得到对象所属的类的信息,支持输出
if(obj1.getClass() == obj2.getClass()) {
//可以用于判断两个不知类型的对象是不是同一个类型
}

toString()方法

  • 在System.out.println()中直接打印对象时,实际上是调用了对象的toString()方法
  • 如果不在子类中重写toString()方法,则直接调用Object对象的实现
public class Object {
	public String toString() {
		getClass().getname() + “@”+Integer.toHexString(hashCode());
	}
}

equals()方法
自定义类的时候,想想对象需不需要比较,如果是,则要重写equals()方法
如果在子类中不重写Object类的equals()方法,则equals()方法跟==完成的功能是一样的!!!
回忆String类:

  • ==用于判断两个对象是不是同一个对象
  • equals()方法用于判断两个对象是否具有相同的值
public class Object {
	public boolean equals(Object obj) {
		return this == obj;
	}
}

clone()方法

  • 可以创建本对象的复制品
    复制出的对象是一个新对象
    复制出的对象含有现有对象的值,而不是初始值

  • clone法为protected,在非子类方法中不能直接调用

  • 使用clone()方法比new一个对象,在复制要快

hashCode()方法

  • 它的实现是根据本地机器相关的
  • equals()相等的两个对象,hashCode()一定相等
  • equals()不相等的两个对象,却并不能证明他们的hashCode()不相等
  • Java中的很多类都重写了hashCode()方法,例如,String, Integer, Double等,他们都有自己内部实际存储的数据来计算hashCode

finalize()方法

  • 当一个对象在程序中不能再被使用,就成为一个无用对象
    没有任何引用指向该对象
  • Java虚拟机会周期性的释放无用对象,称为垃圾回收(Garbage Collection)
  • 在对象被垃圾回收的时候,垃圾回收器(Garbage Collector)会先调用对象的finalize()方法,然后再销毁对象
  • 无法知道垃圾回收什么时候执行,可以使用System.gc()方法来建议虚拟机进行垃圾回收
    1. finalize()方法不能等同于析构函数
    1. 无法知道对象的finalize()方法什么时候会被调用
    1. 即使是调用了System.gc(),垃圾回收也不是马上运行
    1. 如果要进行资源回收,建议用显式方法进行,不能依赖finalize()方法
    1. 无法主动销毁或调用finalize()方法,只能由虚拟机回收

垃圾回收
对象的引用指向其他对象,则原对象称为垃圾对象

Dog dog = new Dog();
dog = null;

匿名对象,在产生之后立马称为垃圾对象

new Dog();

在函数中的局部对象,退出函数之后变成垃圾对象

public void function() {
	Dog dog = new Dog();
	dog.print();
}

总结

  • 多态可以减少类中代码量,可以提高代码的可扩展性和可维护性
  • 向上转型——子类转换为父类,自动进行类型转换
  • 向下转型——父类转换为子类,结合instanceof运算符进行强制类型转换
  • 实现多态的两种方式 使用父类作为方法形参实现多态
  • 使用父类作为方法返回值实现多态

更多推荐

Java编程入门笔记(八)