Java类型强制转换

1.背景

      在Java中,之所以需要强制类型转换是为了防止程序员在不知情的情况下把A类型数据错当成B类型的数据。将一种类型的值赋给另一个类型的变量是很常见的,如果这两种是兼容的,那么Java将执行自动转换,例如int和long,将int类型赋值给long类型的变量,总是可行的。但不是所有的类型都兼容。例如,没有将double类型转换为byte型的定义。但是不兼容的类型之间的转换仍然是可能的。达到这个目的,就需要使用强制类型转换。完成两个不兼容类型的强制转换。

2.自动类型转换和强制类型转换

      在Java中由于继承和向上转型,子类可以非常自然地转换成父类,但是父类转换成子类则需要强制转换。因为子类拥有比父类更多的属性、更强的功能,所以父类转换为子类需要强制。

      当我们用一个类型的构造器构造出一个对象时,这个对象的类型就已经确定的,也就说它的本质是不会再发生变化了。在Java中我们可以通过继承、向上转型的关系使用父类类型来引用它,这个时候我们是使用功能较弱的类型引用功能较强的对象,这是可行的。但是将功能较弱的类型强制转功能较强的对象时,就不一定可以行了。

Father father = new Son();
Son son = (Son)father;
//编译出错,ClassCastException
Father father = new Father();
Son son = (Son) father;
      在第一个例子中,father被指向一个子类对象,子类也可以指向子类对象。而第二个例子中,father被传给一个父类对象,子类引用不能指向父类对象。即很重要的概念是:父类引用指向子类对象。将父类转换为子类之前,应该用instanceof检查。
static class One {
    public void foo() {
        System.out.println("One");
    }
}
static class Two extends One {
    public void foo() {
        System.out.println("Two");
    }
    public void dosth(){
        System.out.println("Two again");
    }
}
public static void main(String[] args) throws Exception{
    One t = new Two(); //向上转型,即父类引用指向子类对象,此时子类对象的类型为父类的类型
    t.foo();
    t.dosth();//编译错误
    t = (Two)t; 
    t.dosth();//编译错误 
    ((Two) t).dosth();//编译成功
}

向上转型是安全的,但是会有些子类特性会丢失,向下转型可以成功。

      总结:父类引用可以指向子类对象,子类引用不能指向父类对象。把子类对象直接赋给父类引用叫做向上转型,向上转型不用强制转型,如Father f1=new Son(),把指向子类对象的父类引用赋给子类引用叫做向下转型,要强制转型,如Son s1 = (Son)f1。向上转型会丢失子类特有的方法,但是子类overriding父类的方法,子类方法有效。 

      将一种类型的数据赋给另外一种类型变量时,满足下列两种要求将执行自动类型转换:1.两种类型是兼容的;2.目的类型的范围比来源类型大。   

      数据值类型按照范围从小到大为:byte,short,char,int,long,float,double

      例如int类型比所有byte合法类型大,因此不要求显式强制类型转换。对于数字类型,整型和浮点型都是彼此兼容的,但是数字类型和字符类型和布尔类型是不兼容的,字符类型和布尔类型也不是互相兼容的。

      自动类型转换不能满足所有的转换需求,比如int型变量赋值给byte型变量,这种转换不会自动进行,因为byte型比int型范围小。为了完成两种不兼容类型转换,需要用到强制类型转换。

      目标类型指定了要转换成为的类型。例如果将int型转为byte型,int型取值范围大于byte型,它的值将堆byte型范围进行取模。而把浮点型赋值给整数型,就会出现截断,截取掉小数部分。从其他数值类型转换为char类型时,必须进行强制转换。将char类型转换为其他数值类型时,除了byte、short必须强制转换之外,int、long、float、double都不用强制转换。

byte b;
int i = 257;
double d = 32.13;
b = (byte) i;
System.out.println("i and b = " + i + " " + b);
i = (int)d;
System.out.println("d and i = " + d + " " + i);
b = (byte)d;
System.out.println("d and b = " + d + " " + b);
byte e=(byte)130; 
System.out.println(e);
 
 
/*
* output
i and b = 257 1
d and i = 32.13 32
d and b = 32.13 32
-126
*/
       当257要转换为byte时,byte的范围是256,所以取模的结果是1。浮点值赋给整数型,截断了小数部分。
short s = 1;
s= s + 1;//编译错误
short s1 = 1;
s1 += 1;
      上述陷阱是s = s + 1,s + 1会隐式转换为int型,把int型号赋给short,会造成类型缺失。而s1 += 1相当于s1 = (short)(s1 + 1)会把s1 + 1强制转换为short类型,就不会报错。涉及到写代码的严谨性。

      对于自动装箱和自动拆箱,将一个基本数据类型转换为对应的引用类型时,不必强制转换。将一个基本数据类型的引用类型转换为基本数据类型时,也不必要强制转换。

int i1 = 10;
Integer iObj1 = i1;
Integer iObj2 = new Integer(10);
int i2 = iObj2;
     在三目运算符中,会按照算术类型中的提升原则将两端操作提升至统一类型,如果两端有一个操作数是引用类型,则整个表达式结果用Object型表示。

byte b = 10; 
short s = 20; 
int i = (b == 10) ? b : s; 
int i1 = 10; 
String str = "Hello"; 
Object obj = (i1 == 10) ? i1 : str;  // 在这里变量i进行了自动装箱操作
     int和String互相之间的转换,首先是int转String,有两种方法
int i = 12345;
String s = "";
s = i + "";
s = String.valueOf(i);
    第一种方法,s = i + "";会产生两个String对象, s = String.valueOf(i);直接使用String的静态方法。

String s = "12345";
int i ;
i=Integer.parseInt(s);
i=Integer.valueOf(s).intValue();
    第一种方法直接使用静态方法,不会产生多余的对象,但会抛出异常。第二种方法相当于new Integer(Integer.parseInt(s))也会抛出异常,但是会多产生一个对象。

      valueOf返回的是原对象,toString()返回的是字符串。

3.instanceof

      instanceof是Java二元操作符,测试左边对象是否是右边类的实例,返回boolean类型的数据,作用上可以避免强制类型转换失败。

4.JVM级别原理

      对于强制类型转换,且是引用类型,如果是父类向子类转换,这种只牵扯到引用名义类型的转换,具体的内存没有什么变化。

      而基本类型转换不同,数据发生了变化。但是基本数据类型不是对象,如果涉及到自动装箱和拆箱,实际上是调用类似Integer.parseInt()和Integer.valueOf()实现。

      父类向子类强制转换,如果成功不会有任何数据丢失,如果不是子类,那么会报错。基本类型之间的转换,如果是大范围向小范围的转换,有可能发生数据丢失或者精确丢失。








阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页