Java中的类型转换

来源:互联网 发布:c语言强制转换 编辑:程序博客网 时间:2024/05/22 03:22

为什么要进行类型转换?

Java是强类型语言,在强类型语言中变量必须被声明,而且变量在赋值和运算时也必须类型相同。
在实际应用的时候我们常常需要对不同类型的变量进行操作,因此就必须进行类型转换。
类型转换分为:

  • 自动类型转换:又叫隐式类型转换,“隐”就是不需要手动转换,系统会自动进行类型转换。
  • 强制类型转换:又叫显式类型转换,“显”就是需要手动加入语法进行转换,来提示“这可能是一件危险的事情,信息可能会发生丢失。”强制类型转换又分为基本数据类型和引用数据类型两种

在我看来,类型转换其实只分为基本数据类型转换引用数据类型转换两种,下面对这两种进行分析:


基本数据类型转换

在学习基本数据类型转换之前我们需要明确:整数型字面量(例如9)会被JVM默认为int类型数据,浮点型字面量(例如9.0)会被JVM默认为double类型数据。

基本数据类型的转换无非就是两种:
1.将存储范围小的类型转换成存储范围大的类型
2.将存储范围大的类型转换成存储范围小的类型

先来看第一种:.将存储范围小的类型转换成存储范围大的类型
存储范围由小到大为:byte→short→int→long→float→double
这种类型转换属于自动类型转换,不需要我们进行任何操作,系统会帮助我们完成。范围小的数据类型可以自动转换成任一比它范围大的数据类型。 【这是一种扩展转换,新类型肯定能容纳原来类型的信息,不会造成任何信息丢失,是安全的转换】
示例代码:

    byte  b = 20;    short s = b;    double d = s;    System.out.println(d);

最后输出结果是20.0
byte类型在自动转换成short类型时,JVM首先会把b转换成short类型,然后再将b赋值给s。

第二种:将存储范围大的类型转换成存储范围小的类型
存储范围由大到小为:double→float→long→int→short→byte
将范围大的数据类型转换成范围小的数据类型需要我们手动添加代码进行强制类型转换,这是一种窄化转换,因为将能容纳更多信息的数据类型转换成无法容纳那么多信息的类型,就有可能面临信息丢失的危险,编译器会强制要求我们进行类型转换,这实际上是说:“这可能是一件危险的事情,如果无论如何要这么做,必须显式的进行类型转换。”
示例代码:

 double d = 126.7; byte b = (byte) d; System.out.println(b);

输出结果是126,小数位信息发生丢失,这就是强制类型转换的原因之一:提示我们可能发生信息丢失。

细节补充:

细节一:对float类型的赋值问题:当我们为float赋值一个小数时,我们会发现编译器要求我们必须在末尾加上f,例如float f =8.2f;然而当我们为其赋值一个整数时,我们会发现其末尾可以加f,也可以不加f,例如float f =8;这是为什么呢?

解释:前面已经提到,整数型字面量(例如9)会被JVM默认为int类型数据,浮点型字面量(例如9.0)会被JVM默认为double类型数据。也就是说当我们为float赋值小数时,小数的字面值被JVM默认为double类型,把double类型赋值给float类型需要进行强制类型转换,在末尾加上f可以理解成是强制类型转换的一种方式,所以将小数赋值给float时必须在末尾加上f。当我们为float赋值整数时,整数的字面值被JVM默认为int类型,将int类型转换成float类型会进行自动类型转换,因此可以不在末尾加f,加上也不会报错。为了避免错误,最好float型都在末尾加上f。

细节二:对long类型的赋值问题:当我们为一个long类型赋值时,比如long = 100,此时编译不会有错,但是当我们long = 10000000000时发现会报错,但是long = 10000000000L在末尾加上L后错误消失,这是为什么?加上L是强制类型转换吗?

解释:当执行long = 100时,100默认为int类型,int向long进行转换是自动转换,因此long = 100不会有任何问题。但是当我们赋值long = 10000000000时,10000000000这个数已经超出了int的表示范围,但是JVM还是会默认它为int类型,这已经是一个错误了,更谈不上赋值给long了,因此需要在其末尾添加L,来告诉编译器10000000000是一个long类型,将10000000000变回自己本身的类型。当数值超过int型范围时,必须得加L。
在这里加L并不是强制类型转换,而是一种语法约定。为了避免错误,最好long型都在末尾加上L,最好是大写的L,避免跟数字1混淆。


引用数据类型转换

(部分参考自:Java引用类型强制转换)

引用数据类型的转换分为两种:

  • 向上转型:父类引用子类的实例,子类可以自动转型为父类。
  • 向下转型:子类引用父类的实例,父类必须进行强制转换才能被子类引用。

下面分别进行分析:

>>引用数据类型转换之向上转型:

首先给出一个示例:
创建一个父类Animal类,Animal类中包含了一个sleep()方法:

public class Animal {    public void sleep(){        System.out.println("This is sleep method!");    }}

再创建Animal的子类,Bird类,里面有bird自己的一个fly()方法:

public class Bird extends Animal {    private int height;    public void fly() {        System.out.println("小鸟飞行高度是:" + height);    }}

向上转型是父类引用子类的实例:Animal animal = new Bird(),子类可以非常自然地转换成父类。
数据进行类型转换时总是避免不了一个问题,那就是“安全”,在转型过程中我们需要清楚这次转型是否安全,向上转型只是把引用子类的能力临时削弱,向上转型后不可以使用引用子类中新的方法,只能使用父类中所拥有的方法。功能弱的类型引用功能较强的类型是可行并且安全的。

>>引用数据类型转换之向下转型

在上面Animal跟Bird类的基础上,我们来演示向下转型。
向下转型是子类引用父类的实例:

 Animal animal = new Bird();//向上转型 Bird bird = (Bird) animal;//向下转型

向下转型是引用数据类型的强制转换,向下转型是一个将向上转型时子类被削弱的功能还原的一个过程。

但是需要注意的是: 当引用类型的真实身份是父类本身的类型时,强制类型转换就会产生错误!如下面代码:

Animal animal = new Animal();Bird bird1 = (Bird) animal;bird1.fly();

以上代码编译时不会出错,但是运行时会出现Java.lang.ClassCastException错误。
因为这种转型是不安全的,当我们用一个类型的构造器构造出一个对象时,这个对象的类型就已经确定的,也就说它的本质是不会再发生变化了。Animal类中没有Bird类中的fly()方法,即使将Animal强制转换成Bird类,在实际运行中还是找不到animal中的fly()方法。可以理解成“可以说鸟是动物,但是你无法说动物是鸟。”
总起来就是:子类可以自动转型为父类,但是父类强制转换为子类时只有当引用类型真正的身份为子类时才会强制转换成功,否则失败。


总结与补充:

1.总结:不管是基本数据类型转换还是引用数据类型转换,总结起来就是:『由下往上转换是自动类型转换,由上往下转换是强制类型转换』[基本数据类型由下往上是由范围小→范围大;引用类型由下往上是子类→父类]。但是引用数据强制类型转换时需要注意限制条件:父类强制转换为子类时只有当引用类型真正的身份为子类时才会强制转换成功。
2.补充:向上转型可以实现多态,即子类对象(实际是地址引用)指向父类这样可以使代码复用。例如在写一个框架时,写一个类,通过多态,不管以后这个类被继承出了多少个儿子、孙子、重孙子,这段代码都不用改变。举一个例子来进行说明:

1.首先创建一个父类:Animal

public class Animal {    public void sleep(){        System.out.println("这是父类中的sleep方法");    }}

2.创建子类Bird:重写父类的sleep方法

public class Bird extends Animal {    @Override    public void sleep(){        System.out.println("Bird已经sleep!");    }    }

3.创建子类Dog:也重写父类的sleep方法

public class Dog extends Animal {    @Override    public void sleep(){        System.out.println("Dog已经sleep!");    }}

4.假设我这个框架中封装了一个类来实现某种动物的睡眠:

public class GoToSleep {    private Animal animal;    public GoToSleep(Animal animal){        this.animal = animal;    }    public void ToSleep(){        animal.sleep();    }}

5.当我们要使用这个框架内部类来实现动物的睡眠,比如让Bird进入睡眠可以这么写:

public class Main {    public static void main(String[] args) {        Animal animal = new Bird();        GoToSleep goToSleep = new GoToSleep(animal);        goToSleep.ToSleep();    }}

输出结果是:
这里写图片描述

当然如果想让Dog进入睡眠跟Bird是一样的:

public class Main {    public static void main(String[] args) {        Animal animal = new Dog();        GoToSleep goToSleep = new GoToSleep(animal);        goToSleep.ToSleep();    }}

输出结果为:
这里写图片描述
通过以上示例,我们会发现利用向上转型,我们可以复用封装类GoToSleep,这就是向上转型的作用之一。

原创粉丝点击