Java基础:引用类型的强制类型转换
By arthur503 -- 07 Oct 2013
对于引用类型的强制类型转换(基本数据类型本文不探讨),之前一直有的疑问是: 1. 若两个类型不同,是否可以强制类型转换?何时才可以? 2. 若两个类型不同,在强制类型转换后,该对象的内容是否依照被转换的类型发生改变?若转换回来还是否拥有之前的内容?
看《Java编程思想》CH11的时候,正好看到,就编下代码测试一下。
Apple.java和Orange.java的代码如下:
class Apple{
private static int counter;
private int id = counter++;
private String name = "Apple";
public int id(){
return id;
}
public String name(){
return name;
}
}
class Orange extends Apple{
private static int counter;
private int id = counter++;
private String name = "Orange";
public String familyName = "Orange Family Name";
public int id(){
return id + 10;
}
public String name(){
return name;
}
public int counter(){
return counter + 100;
}
}
Orange继承自Apple,且id()和counter()方法不同(分别+10和+100),并且多了familyName参数。下面测试一下,测试代码如下:
Apple apple = new Apple();
System.out.println("Name:"+apple.name());
System.out.println("id:"+apple.id());
System.out.println();
Orange orange = new Orange();
System.out.println("Name:"+orange.name());
System.out.println("id:"+orange.id());
System.out.println("counter:"+orange.counter());
System.out.println("familyName:"+orange.familyName);
System.out.println();
System.out.println("Change Apple to Orange...");
orange = (Orange)apple;
System.out.println("Name:"+orange.name());
System.out.println("id:"+orange.id());
System.out.println("counter:"+orange.counter());
System.out.println("familyName:"+orange.familyName);
System.out.println();
结果执行结果报错:
Name:Apple
id:0
Name:Orange
id:10
counter:101
familyName:Orange Family Name
Change Apple to Orange...
Exception in thread "main" java.lang.ClassCastException: ch11.Apple cannot be cast to ch11.Orange
at ch11.TEST.main(TEST.java:19)
也就是说,父类Apple无法转换成子类Orange。那这是不是因为子类Orange和父类不同,所以无法转换?如果完全相同是否可以转换呢?于是,重新写了Banana类,直接继承Apple,无修改。
class Banana extends Apple{
}
此次测试代码如下:
Apple apple = new Apple();
System.out.println("Name:"+apple.name());
System.out.println("id:"+apple.id());
System.out.println();
Banana banana = new Banana();
System.out.println("Name:"+banana.name());
System.out.println("id:"+banana.id());
System.out.println();
banana = (Banana)apple;
System.out.println("Name:"+banana.name());
System.out.println("id:"+banana.id());
System.out.println();
这次运行结果:
Name:Apple
id:0
Name:Apple
id:1
Exception in thread "main" java.lang.ClassCastException: ch11.Apple cannot be cast to ch11.Banana
at ch11.TEST.main(TEST.java:16)
可以看到,即便Banana完全继承自父类Apple,父类也是无法转换为子类的。即:父类转换为子类,编译器不报错,但运行时报错。
继续测试子类转换为父类,代码如下:
Apple apple = new Apple();
System.out.println("Name:"+apple.name());
System.out.println("id:"+apple.id());
System.out.println();
Orange orange = new Orange();
System.out.println("Name:"+orange.name());
System.out.println("id:"+orange.id());
System.out.println("counter:"+orange.counter());
System.out.println("familyName:"+orange.familyName);
System.out.println();
apple = (Apple)orange;
System.out.println("Change Orange to Apple...");
System.out.println("Name:"+apple.name());
System.out.println("id:"+apple.id());
System.out.println();
orange = (Orange)apple;
System.out.println("Change Orange back to Orange...");
System.out.println("Name:"+orange.name());
System.out.println("id:"+orange.id());
System.out.println("counter:"+orange.counter());
System.out.println("familyName:"+orange.familyName);
System.out.println();
运行结果:
Name:Apple
id:0
Name:Orange
id:10
counter:101
familyName:Orange Family Name
Change Orange to Apple...
Name:Orange
id:10
Change Orange back to Orange...
Name:Orange
id:10
counter:101
familyName:Orange Family Name
可以看到,子类转换为父类后,相同变量名的值仍然保持不变。在再次转换回来后,子类特有的变量仍然是保持的。也就是说,强制转换并不改变类型的内容,只是有选择性的显示被转换后类型所拥有的变量。此时还可以再转换回来,并且之前的所有变量都保持不变。
另外,又测试了下对于String类的强制转换,最好先使用instanceof关键字测试下是否可以转换,对于不可转换的Eclipse会报错:Incompatible conditional operand types Orange and String(这是对下面测试代码中被注释的一行的报错)。测试代码如下:
System.out.println(""+(orange instanceof Orange));
System.out.println(""+(orange instanceof Apple));
System.out.println(""+(orange instanceof Object));
//System.out.println(""+(orange instanceof String));
System.out.println(""+(String.valueOf(orange)));
System.out.println(orange);
System.out.println(orange.toString());
System.out.println();
System.out.println((String)(Object)orange); //ERROR: Orange cannot be cast to java.lang.String
运行结果是:
true
true
true
ch11.Orange@1542a75
ch11.Orange@1542a75
ch11.Orange@1542a75
Exception in thread "main" java.lang.ClassCastException: ch11.Orange cannot be cast to java.lang.String
at ch11.TEST.main(TEST.java:61)
可以看到,使用String.valueOf()方法、直接println(Object)方法、和toString()(不考虑改写)方法,在结果上是相同的,最后都输出toString()的结果。我们改写一下Orange的toString方法如下:
@Override
public String toString() {
// TODO Auto-generated method stub
return this.familyName+":"+this.name +" "+ this.id +" "+this.counter();
}
再次运行结果如下:
true
true
true
Orange Family Name:Orange 0 101
Orange Family Name:Orange 0 101
Orange Family Name:Orange 0 101
Exception in thread "main" java.lang.ClassCastException: ch11.Orange cannot be cast to java.lang.String
at ch11.TEST.main(TEST.java:61)
验证正确。最后的报错是对应测试代码中的这一行:
System.out.println((String)(Object)orange); //ERROR: Orange cannot be cast to java.lang.String
orange本身是无法直接强制转换为String的,编译器不通过。但是可以通过先转换为Object对象,再转换过来。不过我们可以看到,最后结果还是不可以强制转换,出错。
下面开始测试,如果创建的时候就进行强制类型转换,结果如何。测试代码如下:
System.out.println("Test apple2...");
Apple ap2 = new Orange();
System.out.println("Name:"+ap2.name());
System.out.println("id:"+ap2.id());
// System.out.println("counter:"+ap2.counter());
// System.out.println("familyName:"+ap2.familyName);
System.out.println();
System.out.println("Change apple2 to Orange...");
Orange or2 = (Orange)ap2;
System.out.println("Name:"+or2.name());
System.out.println("id:"+or2.id());
System.out.println("counter:"+or2.counter());
System.out.println("familyName:"+or2.familyName);
System.out.println();
运行结果如下:
Test apple2...
Name:Orange
id:11
Change apple2 to Orange...
Name:Orange
id:11
counter:102
familyName:Orange Family Name
也就是说,引用类型变量的创建是根据其构造方法决定的,即便在创建的时候直接将它提升至父类,也是仍然包含子类中所有的信息。
最后回答最开始的提问:
1. 若两个类型不同,是否可以强制类型转换?何时才可以? 2. 若两个类型不同,在强制类型转换后,该对象的内容是否依照被转换的类型发生改变?若转换回来还是否拥有之前的内容?
1. 继承的子类可以向父类强制转换(还可以再转换回来);但父类不可以向子类强制转换(编译不通过); 2. 内容不会发生改变,存储空间也不会变化,转换回来后还是拥有之前的内容。
另外,还有两点:
1. 对象的真实类型,也就是它是使用什么类的构造方法构造出来的。真实类型和前面的强制类型转换无关。 2. 编译器只检查类型之间有无继承关系,有则通过;运行时检查真正类型,是则通过。
参考资料:
- 《Java编程思想》
- JAVA强制类型转换
- java的强制类型转换