随遇而安——多态
为什么使用多态?
不用为每一增加的子类增加对应的麻烦,最重要又可以偷懒了~
什么是多态?
多态:同一个引用类型,使用不同的实例而执行不同操作
使用父类作为方法形参实现多态
使用父类作为形式参数,可以接受子类的对象作为实参。
进入到方法之后,该对象仅能当做父类使用,无法访问子类的成员。
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()方法来建议虚拟机进行垃圾回收
-
- finalize()方法不能等同于析构函数
-
- 无法知道对象的finalize()方法什么时候会被调用
-
- 即使是调用了System.gc(),垃圾回收也不是马上运行
-
- 如果要进行资源回收,建议用显式方法进行,不能依赖finalize()方法
-
- 无法主动销毁或调用finalize()方法,只能由虚拟机回收
垃圾回收
对象的引用指向其他对象,则原对象称为垃圾对象
Dog dog = new Dog();
dog = null;
匿名对象,在产生之后立马称为垃圾对象
new Dog();
在函数中的局部对象,退出函数之后变成垃圾对象
public void function() {
Dog dog = new Dog();
dog.print();
}
总结
- 多态可以减少类中代码量,可以提高代码的可扩展性和可维护性
- 向上转型——子类转换为父类,自动进行类型转换
- 向下转型——父类转换为子类,结合instanceof运算符进行强制类型转换
- 实现多态的两种方式 使用父类作为方法形参实现多态
- 使用父类作为方法返回值实现多态
更多推荐
Java编程入门笔记(八)
发布评论