在说明数据类型转换(下简称类型转换)之前,得先说明什么是数据,数据可不仅仅是一个值,而是包括描述数据的种类(数据类型)、值以及基于其类型基础上可进行的操作的集合。从类型A转换到类型B,这需要类型B拥有类型A的相关性质,他们之间的转换需要遵从一定规则,并不是可以任意转换的,最基本的转换思想:只有同类可转换,低级转高级可自动转换,高级转低级需要强制转换。
所谓的自动转换(也称为隐式转化),就是编译器会自动帮助将低级类型转换成高级类型,而不需要程序员添加相关代码;而强制转换(也称为显示转换)则必须由开发人员添加相关代码进行强制转换,强制转换一般用于基本数据类型和不明类型对象(Java程序中常会有对象丢失类型信息,最常见的就是集合中对象是无类型信息的)强制转化为已知类型(不过需要知道这个对象确实是这个类型或其子类类型,否则会抛异常)。
class A{}
class B extends A{}
A a = new B();//低级转高级,自动转换
B b = (B)new A();//高级转低级,需强制转换,但是,虽然编译不报错,运行却是会报
//错,因为高级转低级,引用数据类型中,运行时是要检查对象的真身的,真身不是引用对
//象的类型或子类型是会抛异常的,因为B的功能可能A不具有,
//也可以这样想:每个B肯定是一个A,但不是每一个A都是B,所以为了以防万一,运行时要做检查
//低级高级,在引用对象数据类型中,就是继承链(包括接口的实现)上位于顶端的(父类)
//是高级的,位于底层的(子类)是低级的;而在基本数据类型则自有规定
转换的场景包括从变量的定义到复制、数值变量的计算到方法的参数传递、基类与派生类间的造型等。而由于数据类型的不同,转换的方式也不尽相同,前一节讲过,Java数据类型分为两种:基本数据类型和引用数据类型(详见:Java 变量数据类型),下面就这两种分别讲述数据类型的转换。
Java变量数据类型一节讲过,Java基本数据类型实际上分为两类:数值类型和布尔类型。布尔类型只有boolean,所以boolean不能与其他7种基本类型互相转换,无论是自动转换还是强制转换。
前面已经说过,只有拥有相同性质的“同类”类型才可以相互转换,显然布尔类型与数值类型不是同一类,因为布尔类型是不能计算的,而像字符类型char,虽是字符类型,但其实现是无符号整型(字符的ASCII码),加减乘除获得的结果还是整型,仍可转换成字符型(不过这个时候需要强制转换)。
Java中规定,数值类型的从低级到高级:byte—>(short/char)—>int—>long—>float—>double。short/char同等级。其实这里的低级高级,也可以粗略看成范围的大小,范围小的低级,范围大的高级。
低等级到高等级可自动转换,即
byte b = 1;//默认1其实是int,但是直接赋值这种(右边是直接量,编译期就可以判
//断,若是变量则是需要运行期判断,所以为以防万一,需要强制转换),编译器只会检查
//是否越界,比如
byte b1 = 222;//这个时候编译器会报错:很显然右边值超出byte范围
long l = 100000000000l;//同样的,这里右边数值大于int范围,后面需要添加l/L
//这个时候不加l/L,前面用强制转换也不行,因为右边的值是被认为int,而这个值超出了
//int范围,小于int范围,就是一般的低级到高级自动转换
float f = b;
float f1 = 1.23f;//浮点型,默认double,不同于整型,这是高级到低级的转换,数值后面加f/F或进行强制转换
char c = b;//报错,虽然char比byte高级,但是char是无符号型,不能自动转换
另外,对于基本类型对应的包装类,也是能自动转换的,即:
Integer i = 12;//这里int自动转换成Integer
//但也只能对对应的基本类型进行自动转换
byte b = 12;
Integer i1 = b;//编译错误,这里就不能套用低级高级,因为是两个系统
Byte b1 = 12;
Integer i2 = b1;//同样不行,包装类也是引用类型,之间的转换遵守引用类型转换规则
高等级到低等级,需要强制转换
int i = 99;
char c =(int)i;
//很显然,这种转换是需要牺牲精度的,比如这个时候i =99999;大于char的取值范
//围,这个时候,c只能取i的低两个字节为值,牺牲了精度
当进行数学运算时,数据类型会自动发生提升到运算符左右之较大者,以此类推。当将最后的运算结果赋值给指定的数值类型时,可能需要进行强制类型转换。
这里不讨论,通过某种方法进行类型转换的情况,比如,类A有个方法:
public B toB(){
//通过某些操作达到类型A转换成类型B
return B;
}
//典型的有每一个类都有的toString();方法,尤其特殊的,对于String,任意类
//型对象 :a +"";即可转换成String
引用数据类型的分类,是通过继承链来分类的,何谓继承链?即:A继承B,B继承C,C继承D……这样就形成了一条链:A->B->C->D……其中,父类相对子类来说是高级的,反之则是低级的,不在一条链上就不是“同类”。另外,要说明一点的,不只是继承,实现接口也同样能形成继承链,其中父接口是其实现类的高级。
低级到高级自动转换,这一点同基本数据类型是一样的,但是,引用类型不同于基本数据类型,引用类型中是有两个东西的:引用与对象(这两个是不同的),而基本数据类型中的变量标识和值基本上可以看成一个:
class A{
public void walk(){}
}
class B extends A{
public void run(){}
}
A a = new B();//可自动转换,但是这个时候a不能调用run方法(没有对外接口)
a是引用,new B()是对象,这个时候,虽然转换了,但是,new B()还是B类型。Java中每次新建一个对象(不管是通过构造器还是反射、序列化等),这个对象的类型就已经确定了,在Java中我们可以通过继承、向上转型的关系使用父类类型来引用它,这个时候我们是使用功能较弱的类型(高级)引用功能较强的对象(低级),这是可行的。但是将功能较弱的类型强制转功能较强的对象时,就不一定可以行了。
就像上面的例子,new B()对象还是B类型(从这一方面来看,引用类型转换并没有“转换”),只不过,这个时候引用它的是A类型,在Java变量数据类型一节有提到,Java任何对象都是通过引用来使用的,这个时候new B()这个对象只能通过a来访问,而a被限定为A类型,只有A类型功能,其功能被消弱了。如果这个时候想将对象还原,可以这样:
B b = (B)a;//这个时候只是新增了一个B类型引用b指向new B()对象,引用a仍然是A类
//型,没受影响。但b却是可以使用B类型所有功能
高级转换成低级,需要强制转换
//最开始
B b = (B)new A();//编译正确,运行抛异常
Class C extends B{}
A a1 = new C();
B b1 = (B)a1;//编译正确,运行正确
父类强制转换成子类并不是总是成功,当引用类型的真实身份是父类本身的类型时,强制类型转换就会产生错误。编译器在编译时只会检查类型之间是否存在继承关系(是否是“同类”)以及上下级关系(是否需要强转),有则通过;而在运行时就会检查它的真实类型,是则通过,否则抛出ClassCastException异常。
所以在继承中,子类可以自动转型为父类,但是父类强制转换为子类时只有当引用类型真正的身份为子类或子类的子类时才会强制转换成功,否则失败。
想要更好的理解引用数据类型的转换,可以参考:Java的静态/动态类型。