Java基础:引用类型的强制类型转换      
View on GitHub

龙珠

修炼自己与发现世界

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. 编译器只检查类型之间有无继承关系,有则通过;运行时检查真正类型,是则通过。

参考资料: