多态
Java引用变量有两个类型:一个是编译时类型,一个是运行时类型。编译时类型由声明该变量是使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就有可能出现所谓的多态(Ploymorphism)。
多态性
这里有个程序:
class BaseClass
{
public int book=6;
public void base()
{
System.out.println("父类的普通方法");
}
public void test()
{
System.out.println("父类的被覆盖的方法");
}
}
public class SubClass extends BaseClass
{
//重新定义一个book实例Field隐藏父类的book实例Field
public String book="轻量级Java EE企业应用实战";
public void test()
{
System.out.println("子类的覆盖父类的方法");
}
public void sub()
{
System.out.println("子类的普通方法");
}
public static void main(String[] args)
{
//下面编译时类型和运行时类型完全一样,因此不存在多态
BaseClass bc=new BaseClass();
// 输出 6
System.out.println(bc.book);
//下面两次调用将执行BaseClass的方法
bc.base();
bc.test();
//下面编译时类型和运行时类型完全一样,因此不存在多态
SubClass sc=new SubClass();
// 输出"轻量级J2EE企业应用实战"
System.out.println(sc.book);
//下面调用将执行从父类继承到的base方法
sc.base();
//下面调用将执行当前类的test方法
sc.test();
//下面编译时类型和运行时类型不一样,多态发生
BaseClass ploymophicBc=new SubClass();
// 输出 6 —— 表明访问的是父类Field
System.out.println(ploymophicBc.book);
//下面调用将执行从父类继承到的base方法
ploymophicBc.base();
//下面调用将执行当前类的test方法
ploymophicBc.test();
//因为ploymophicBc的编译时类型是BaseClass
//BaseClass类没有提供sub方法,所以下面代码编译时会出现错误
//ploymophicBc.sub();
}
}
第三个引用变量ploymophicBc则比较特殊,它的编译时类型是BaseClass,而运行时类型是SubClass,当调用该引用变量的test方法(BaseClass类中定义了该方法,子类SubClass覆盖了父类的该方法)时,实际执行的是SubClass类中覆盖后的test方法,这就可能出现多态了。
因为子类其实是一种特殊的父类,因此Java允许把一个子类对象直接赋给一个父类引用变量,无须任何类型转换,或者被称为向上转型(upcasting),向上转型由系统自动完成。
当把一个子类对象直接赋给父类引用变量时,例如上面的BaseClass ploymophicBc=new SubClass();,这个ploymophicBc引用变量的编译时类型是BaseClass,而运行时类型是SubClass,当运行时调用该引用变量的方法时,其方法行为总是表现出子类方法的行为特征,而不是父类方法的行为特征,这就可能出现:相同类型的变量、调用同一个方法时呈现出多种不同的行为特征,这就是多态。
上面的main的方法中注释了ploymophicBc.sub();,这行代码会在编译时引发错误。虽然ploymophicBc引用变量实际上确实包含sub()方法(例如,可以通过反射来执行该方法),但因为它的编译时类型为BaseClass,因此编译时无法调用sub()方法。
与方法不同的是,对象的Field则不具备多态性。比如上面的ploymophicBc引用变量,程序中输出它的book Field时,并不是输出SubClass类里定义的实例Field,而是输出BaseClass类的实例Field。
引用变量的强制类型转换
编写Java程序是,引用变量只能调用它编译时类型的方法,而不能调用它运行时类型的方法,即使它实际所引用的对象确实包含该方法,如果需要让这个引用变量调用它运行时类型的方法,则必须把它强制类型转换成运行时类型,强制类型转换需要借助于类型转换运算符。
类型转换运算符是小括号,类型转换运算符的用法是:(type)variable,这种用法可以将variable变量转换成一个type类型的变量。
public class ConversionTest
{
public static void main(String[] args)
{
double d=13.4;
long l=(long)d;
System.out.println(l);
int in=5;
//下面代码编译时出错:试图把一个数值类型变量转换为boolean类型
//编译时会提示: 不可转换的类型
boolean b=(boolean)in;
Object obj="Hello";
//obj变量的编译时类型为Object,是String类型的父类,可以强制类型转换
//而且obj变量的类型实际上是String类型,所以运行时也可通过
String objStr=(String)obj;
System.out.println(objStr);
//定义一个objPri变量,编译时类型为Object,实际类型为Integer
Object objPri=new Integer(5);
//objPri变量的编译时类型为Object,是String类型的父类
//可以强制类型转换,而objPri变量的类型实际上是Integer类型
//所以下面代码运行时引发ClassCastException异常
String str=(String)objPri;
}
}
考虑到进行强制类型转换时可能出现异常,因此进行类型转换之前应先通过instanceof运算符来判断是否可以成功转换。例如,上面的String str=(String)objPri;代码运行时会引发ClassCastException异常,这是因为objPri不可转换成String类型。为了让程序更加健壮,可以将代码改为如下:
if (objPri instanceof String)
{
String str=(String)objPri;
}
instanceof运算符
在使用instanceof运算符时需要注意:instanceof运算符前面操作数的编译时类型要么与后面的类相同,要么与后面的类具有父子继承关系,否则会引起编译错误:
public class InstanceofTest
{
public static void main(String[] args)
{
//声明hello时使用Object类,则hello的编译类型是Object
//Object是所有类的父类 , 但hello变量的实际类型是String
Object hello="Hello";
//String是Object类的子类,可以进行instanceof运算。返回true
System.out.println("字符串是否是Object类的实例:"+ (hello instanceof Object));
//返回true
System.out.println("字符串是否是String类的实例:"+ (hello instanceof String));
//Math是Object类的子类,可以进行instanceof运算。返回false
System.out.println("字符串是否是Math类的实例:"+ (hello instanceof Math));
//String实现了Comparable接口,所以返回true
System.out.println("字符串是否是Comparable接口的实例:"
+ (hello instanceof Comparable));
String a="Hello";
//String类既不是Math类,也不是Math类的父类
//所以下面代码编译无法通过
System.out.println("字符串是否是Math类的实例:"+ (a instanceof Math));
}
}
实例
简单的长度单位转换器
日常生活中,我们常常会看到英寸、英尺这样的国外常用的长度单位,但是我们对于这种长度单位并不熟悉,那么它到底是多少厘米或是多少米呢?我们可以在网上查找到1英寸=0.0254米,1米=39.370078740157英寸这样的信息。那么我们就可以利用这两个公式进行编程,开发一个简单的长度单位转换器了,新建项目length,并在其中创建一个length.java文件。在该类的主方法中定义5个成员字段来表示长度转换中要使用的对象,然后创建类对象来进行实例化,最后通过unitConversion()方法实现不同长度单位的转换:
package Length;
public class length {
double num; // 数值
String units; // 单位
String targetUnits; // 目标单位
double result = 0; // 最后换算的结果
String str = "";
// 英寸:inch 英尺:feet 米:meter 厘米:cm
public static void main(String[] args) {
System.out.println("长度单位之间的换数如下:");
// 创建类对象
length cd = new length(3, "米", "厘米");
length cd1 = new length(8, "米", "英尺");
length cd2 = new length(8945.56, "厘米", "米");
length cd3 = new length(12.5, "英尺", "米");
length cd4 = new length(12.2, "英寸", "厘米");
length cd5 = new length(45.96, "厘米", "英寸");
length cd6 = new length(14.5, "英尺", "英寸");
length cd7 = new length(30.2, "英寸", "英尺");
// 调用方法并打印出相应的结果
System.out.println(cd.unitConversion());
System.out.println(cd1.unitConversion());
System.out.println(cd2.unitConversion());
System.out.println(cd3.unitConversion());
System.out.println(cd4.unitConversion());
System.out.println(cd5.unitConversion());
System.out.println(cd6.unitConversion());
System.out.println(cd7.unitConversion());
}
// 利用构造方法为成员变量赋值
public length (double num, String units, String targetUnits) {
this.num = num;
this.targetUnits = targetUnits;
this.units = units;
}
// 进行各长度单位之间的换算
public String unitConversion() {
if (targetUnits.equals("英寸") && units.equals("厘米")) {// 厘米->英寸
result = num * (1 / 2.54);
str = num + "厘米 = " + result + "英寸";
} else if (targetUnits.equals("厘米") && units.equals("英寸")) {
// 英寸->厘米
result = num * 2.54;
str = num + "英寸 = " + result + "厘米";
} else if (targetUnits.equals("厘米") && units.equals("米")) {// 米-> 厘米
result = num * 100;
str = num + "米 = " + result + "厘米";
} else if (targetUnits.equals("米") && units.equals("厘米")) {// 厘米-> 米
result = num * (1 / 100.0);
str = num + "厘米 = " + result + "米";
} else if (targetUnits.equals("英尺") && units.equals("英寸")) {
// 英寸-> 英尺
result = num * (1 / 12.0);
str = num + "英寸 = " + result + "英尺";
} else if (targetUnits.equals("英寸") && units.equals("英尺")) {// 英尺-> 英寸
result = num * 12;
str = num + "英尺 = " + result + "英寸";
} else if (targetUnits.equals("英尺") && units.equals("米")) {// 米-> 英尺
result = num * 3.2809;
str = num + "米 = " + result + "英尺";
} else if (targetUnits.equals("米") && units.equals("英尺")) {// 英尺-> 米
result = num * (1 / 3.2809);
str = num + "英尺 = " + result + "米";
}
return str;
}
}
本实例主要体现了Java构造方法的应用。Java中的每个类都有构造方法,它是类的一种特殊的方法。构造方法用来初始化类的一个新的对象。它一般由系统在创建对象(即类的实例化)时自动调用。构造方法的定义方式与普通方法类似
在使用构造方法的时候需要注意以下几点:
- 构造方法的名字必须和类的名字完全相同。
- 构造方法没有返回值。
- 尽管没有返回值,其也不能用void修饰。
- 构造方法不能用static和final来修饰。