java静态分派+包装类+自动装箱拆箱

静态分派(Method Overload Resolution)

所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。典型应用方法是重载。

静态分派发生在编译阶段,因此确定静态分派动作是由编译器来完成的,在很多情况下,重载版本并不是唯一的,而是“当前最合适的”版本。

举一个静态分派的极端例子:

上面的代码输出hello char。’a‘是一个char类型,会自然寻找char的重载方法。

但如果注释掉char方法,则输出变为:

hello int。此时发生了一次自动类型转换,'a'除了可以代表字符串,也可以代表数字97(unicode数值)。

 

如果继续注释掉int方法,则输出会变为:

hello long。

这时发生了两次自动类型转换,'a'转型为整数97后,进一步转型为97L,重载了long类型方法。

事实上重载还能继续发生很多次,按照char>int>long>float>double进行,但不会匹配到byte和short,因为转型不安全。

 

如果继续注释掉long,则输出会变为:

hello Character。

这时发生了一次自动装箱。'a'被包装为它的封装类型java.lang.Character,匹配到Character的重载。

 

继续注释掉Character方法,输出会变为:

hello Serializable。

因为java.lang.Serializable是java.lang.Character实现的一个接口。

当自动装箱后还找不到装箱类,但是找到了装箱类实现了的接口类型,所以又发生一次自动转型。

char可以转型为int,但Character绝对不会转型为Integer。它只能安全地转型为它实现的接口或父类。

Character还实现了另外一个java.lang.Comparable<Character>如果同时出现,他们的优先级是一样的。编译器无法确定时会提示类型模糊,拒绝编译。

 

继续注释掉Serializable,则输出会变为:

hello Object

这时是char装箱后转型为父类了。

 

再次注释后,输出变为:

hello char...

变长参数重载优先级最低。

 

包装类型

Java语言是一个面向对象的语言,虽然每一个引用类型都和Object相容,但8种基本类型是不能的,这在实际使用时存在很多的不便。于是,Java为这8种基本类型提供了包装类。(Wrapper Class)。

每一个包装对象都是不可变的(状态绝不能改变,即不能被类型转换,见下文类型转换例子)

包装类均位于java.lang包,包装类和基本数据类型的对应关系如下表所示:

 

基本数据类型包装类
byteByte
doubleDouble
shortShort
charCharacter
intInteger
longLong
floatFloat
doubleDouble

 

 

为什么需要包装类?

比如,在集合类中,我们是无法将int 、double等类型放进去的。因为集合的容器要求元素是Object类型。

为了让基本类型也具有对象的特征,就出现了包装类型,它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。

 

自动拆箱与装箱

举例来说:

  • 如果一个int型量被传递到一个需要Integer对象的地方,那么编译器将在幕后插入一个对Integer构造方法的调用,这就叫做自动装箱。
  • 如果一个Integer对象被放到需要int型的地方,则编译器会插入一个intValue方法的调用,这叫自动拆箱。

当表格中基础类型与它们的包装类有如下几种情况时,编译器会自动帮我们进行装箱或拆箱.

  1. 进行 = 赋值操作(装箱或拆箱)
  2. 进行+,-,*,/混合运算 (拆箱)
  3. 进行>,<,==比较运算(拆箱)
  4. 调用equals进行比较(装箱)
  5. ArrayList,HashMap等集合类 添加基础类型数据时(装箱)

例如:

一个典型的自动拆装箱示例

public static  void main(String[]args){
    Integer integer=1; //装箱
    int i=integer; //拆箱
}

反编译后:

public static  void main(String[]args){
    Integer integer=Integer.valueOf(1); 
    int i=integer.intValue(); 
}

自动装箱是通过包装类的valueOf()方法来实现的.自动拆箱都是通过包装类对象的xxxValue()来实现的。

 

自动拆装箱可能产生的问题:

 

  • 用一个例子来解释自动装箱和包装类的类型转换限制(包装类不可变原则):

我们设置一个类,其变量可以被设置为Object的任意类型:

编写一个测试类:

在这个例子中,MemoryCell的实例m,先设置了一个String类型的变量,可以正常读取和打印。

然后,写入一个int类型123时,报了类型转换ClassCastException。

这里发生了两个操作:

1.由于m是Object泛型实例,write时,调用了自动装箱操作,将123转换为Integer类型,写入操作正常。

2.由于包装类型是不能互相转换的,在(String)强制转换过程中,抛出类型转换异常。

 

  • Integer cache的上下限问题:

1.基础类型a与包装类b进行==比较,这时b会拆箱,直接比较值,所以会打印true。

2.二个包装类型,都被赋值了100,所以根据我们之前的解析,这时会进行装箱,调用Integer的valueOf方法,生成2个Integer对象,引用类型==比较,直接比较对象指针,这里我们先给出结论,最后会分析原因,打印 true。

3.跟上面第2段代码类似,只不过赋值变成了200,打印 false。

默认Integer cache 的下限是-128,上限默认127,可以配置,所以到这里就清楚了,我们上面当赋值100给Integer时,刚好在这个range内,所以从cache中取对应的Integer并返回,所以二次返回的是同一个对象,所以==比较是相等的,当赋值200给Integer时,不在cache 的范围内,所以会new Integer并返回,当然==比较的结果是不相等的。

包装对象的数值比较,不能简单的使用==,虽然-128到127之间的数字可以,但是这个范围之外还是需要使用equals比较。

 

  • 小心空指针异常

有这么一段代码:

1

2

3

4

5

6

7

8

9

10

public static void main(String[] args) throws Exception

{

    Object obj = getObj(null);

    int i = (Integer)obj;

}

 

public static Object getObj(Object obj)

{

    return obj;

}

如果运行的话:

1

2

Exception in thread "main" java.lang.NullPointerException

    at main.Test7.main(Test7.java:8)

这种使用场景很常见,我们把一个int数值放在session或者request中,取出来的时候就是一个类似上面的场景了。所以,小心自动拆箱时候的空指针异常。

 

有些场景会进行自动拆装箱,同时也说过,由于自动拆箱,如果包装类对象为null,那么自动拆箱时就有可能抛出NPE。

另外的例子参见:Java中的三目运算符及拆装箱产生的异常

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值