java认证考试 难度_Java坑人面试题系列: 包装类(中级难度)

如果你看过往期的问题,就会发现每一个都不简单。

这些试题模拟了认证考试中的一些难题。 而 “中级(intermediate)” 和 “高级(advanced)” 指的是试题难度,而不是说这些知识本身很深。 一般来说,“高级”问题会稍微难一点。先思考一个简单的问题: 两个 Integer 包装类对象。 怎样比较它们的值是否相等,有哪些方法?

问题(中级难度)

在开发中我们经常会使用包装类(例如 Boolean, Double, 以及 Integer 等等)。

请看下面的代码片段:

String one = "1";

Boolean b1 = Boolean.valueOf(one); // line n1Integer i1 = new Integer(one);

Integer i2 = 1;

if (b1) {

System.out.print(i1 == i2);

}

执行结果是什么, 请选择:

A、 抛出运行时异常

B、 true

C、 false

D、 无任何输出

答案和解析

这个问题考察原生数据的包装类(primitive wrapper),主要是 Boolean 类比较生僻的 valueOf 工厂方法。

在认证考试和面试中,这个问题可能不太容易碰到,因为主要还是靠死记硬背, 大部分考试都会避免此类问题。

但是,这个问题从多个方面综合考察了面试者对Java语言的理解和认识水平, 有一点小坑,但关键在于解答的过程。

包装类主要提供了三种获取对象实例的方法:每个包装类都有名为 valueOf 的静态工厂方法。

如果语义很清晰, 在代码中将原生数据类型赋值给包装类的变量,则会发生自动装箱 (autoboxing)。 自动装箱只是语法上的简写,它允许编译器 (javac) 自动调用valueOf方法, 目的是为了编码更简洁。

第三种方法是使用构造器, 也就是通过 new 关键字来调用构造函数。 实际上,在 Java 9 中已经不推荐使用第三种方法, 而本文的一个目标是解释为什么不赞成使用它。

在Java中,只要使用 new 关键字调用构造函数,只会发生两种情况: 要么成功创建指定类型的新对象并返回,要么就抛异常。

这实际上是一个限制,如今一般是推荐使用工厂方法, 因为工厂方法除了达成构造函数的效果之外, 还会有一些优化。

工厂方法的有些功能是用构造函数实现不了的: 比如返回与请求参数相匹配的已缓存的实例对象。

因为 Integer 包装器是不可变的, 表示相同数值的两个Integer对象一般是可以互换的。

因此,创建多个表示相同值的对象实例会浪费内存。

很多情况下,工厂方法返回的两个对象允许使用 == 来比较, 而不必每次都写成 equals(Object o) 这种方式。

对于 Integer 类来说,一般只缓存了 -128 到 +127 范围内的值。这种行为类似于在编码中直接使用 "XXX" 这种字面量表示方式, 而不是 new String("XXX")。

工厂方法更加灵活:如果有多个工厂方法,则每个方法都可以使用不同的名称,因为名称不同,也就可以使用相同的入参声明。

对于构造函数而言,因为必须参数类型不同才能形成重载,也就不可能根据同样的参数构造不同的对象。

第三个优点是, Java中用 new 调用构造函数只能返回固定类型的对象。

而用工厂方法则可以返回兼容的各种类型对象实例(例如接口的实现类,而且这是一种隐藏实现细节的绝佳方法)。

回到这个问题,最关键的地方在于, 我们使用 Boolean.valueOf(...) 方法时, 只会得到两个常量对象: Boolean.TRUE 和 Boolean.FALSE。

这两个对象可以被重复利用,不会浪费多余的内存。 如果使用 new 调用显然是不可能的。

大部分包装类的工厂方法, 如果传入了 null 参数, 或者字符串参数不符合目标值的表现形式就会抛出异常,例如,Integer.valueOf("six") 就会抛异常。

但 java.lang.Boolean 类的工厂方法是个特例, 内部实现判断的是非空(null)并且等于 “true”(忽略大小写)。

内部实现如下所示:

public static boolean parseBoolean(String s) {

return ((s != null) && s.equalsIgnoreCase("true"));

}

这里的输出肯定是 false。

前面提到过,new 关键字的任何调用,要么产生一个新对象, 要么抛异常。

这意味着 v2 和 v1 引用了不同的对象,== 操作的结果为 false。

换一种方式,如果有以下代码:

Integer v1 = new Integer("1");

Integer v2 = 1;

System.out.print(v1 == v2);

这与面试题中的代码很像,一个使用构造函数, 一个使用自动装箱,可以肯定这也会输出 false。

构造函数创建的对象必定是唯一的新对象,因此,不可能 == 自动装箱为工厂方法返回的对象。

不可变对象的工厂方法一般都会有特殊处理,只要在一个范围内,并且参数相等,就返回同一个(缓存的)对象。

Integer 类的API文档中,对 valueOf(int) 方法有如下说明:“此方法将始终缓存 [-128 ~ 127] 范围内的值, 可能还会缓存这个范围之外的其他值。”

Integer v1 = Integer.valueOf(1);

Integer v2 = Integer.valueOf(1);

System.out.print(v1 == v2);

也就是说,上面这段代码肯定会输出 true。

虽然只在 valueOf(int) 和 valueOf(String) 方法的文档说明中提到了这个缓存保证。

但在实际的实现中, 其他包装类也表现出相同的缓存行为。

当然,这里讨论了两个 Integer 对象: 一个是使用构造函数创建,另一个是使用自动装箱创建(Integer.valueOf(int) 方法)。

假如我们稍微改变一下面试题中 if 语句,则输出内容将为 false。

总结: 本文开始提到的面试题, 选项D是正确答案。 这里只是附带的讨论。

————————————————

版权声明:本文为CSDN博主「铁锚」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值