java包装类面试_深入浅出JAVA包装类及面试题陷阱

什么是包装类

虽然 Java 语言是典型的面向对象编程语言,但其中的八种基本数据类型并不支持面向对象编程,基本类型的数据不具备“对象”的特性——不携带属性、没有方法可调用。 沿用它们只是为了迎合人类根深蒂固的习惯,并的确能简单、有效地进行常规数据处理。这种借助于非面向对象技术的做法有时也会带来不便。比如:编码过程中只接收对象的情况,比如List中只能存入对象,不能存入基本数据类型;比如一个方法的参数是Object时,不能传入基本数据类型,但可以传入对应的包装类; 比如泛型等等。

基本数据类型没有toString()方法等

Java中对每种基本类型都有一个对应的包装类:byte -> Byte

short -> Short

int -> Integer

long -> Long

float -> Float

double -> Double

boolean -> Boolean

char -> Character

其中需要注意int对应的是Integer,char对应的Character,其他6个都是基本类型首字母大写即可。

每个包装类的对象可以封装一个相应的基本类型的数据,并提供了其它一些有用的方法。包装类对象一经创建,其内容(所封装的基本类型数据值)不可改变(类似于String类,可看源码)。

基本数据类型和包装类的区别定义不同。包装类属于对象,基本数据类型不是

声明和使用方式不同。包装类使用new初始化,有些集合类的定义不能使用基本数据类型,例如 ArrayList

初始值不同。包装类默认值为null,基本数据类型则不同的类型不一样(具体见上表)

存储方式和位置不同,从而性能不同。基本数据类型存储在栈(stack)中,包装类则分成引用和实例,引用在栈(stack)中,具体实例在堆(heap)中。可以通过程序来验证速度的不同。

拆箱/装箱

基本类型和对应的包装类可以相互装换:由基本类型向对应的包装类转换称为装箱,例如把 int 包装成 Integer 类的对象:

Integer i = Integer.valueOf(1); //手动装箱Integer j = 1; //自动装箱包装类向对应的基本类型转换称为拆箱,例如把 Integer 类的对象重新简化为 int。

Integer i0 = new Integer(1);

int i1 = i0; //自动拆箱int i2 = i0.intValue(); //手动拆箱

jdk5.0开始增加自动装箱/拆箱

面试陷阱

介绍完包装类的基本情况,下面重点讲几个面试/笔试中的常见陷阱。关于equals

double i0 = 0.1;

Double i1 = new Double(0.1);

Double i2 = new Double(0.1);

System.out.println(i1.equals(i2)); //true 2个包装类比较,比较的是包装的基本数据类型的值System.out.println(i1.equals(i0)); //true 基本数据类型和包装类型比较时,会先把基本数据类型包装后再比较

注意equals方法比较的是真正的值。

基本数据类型和包装类比较时,会先把基本数据类型包装成对应的包装类型,再进行比较。这一点可以通过查看.class字节码来证明:

为什么需要这么做? 我们来查看JDK中Double包装类的equals方法:

可以看到equals方法中的参数是一个Object对象,而基本数据类型并不是一个对象类型,所以需要先将基本数据类型包装成对应的包装类后才能作为参数传入equals方法中。由此也侧面验证了包装类的必要性。

2. 关于==(双等号)比较

==(双等号)比较的陷阱比较多。对此有必要先讲一个前置知识点:

对于基本数据类型,==(双等号)比较的是值,而对于包装类型,==(双等号)比较的则是2个对象的内存地址。

OK,下面开始讲题:

double i0 = 0.1;

Double i1 = new Double(0.1);

Double i2 = new Double(0.1);

System.out.println(i1 == i2); //false new出来的都是新的对象System.out.println(i1 == i0); //true 基本数据类型和包装类比较,会先把包装类拆箱

注意一点规律,new出来的都是新的对象。2个新的对象内存地址不同,那么==号比较的结果肯定是false。

基本数据类型和包装类型比较时,会先把包装类拆箱再进行值比较(和equals是反的)。这个也可以通过查看字节码来证明:

为什么要这么做?因为如果反过来把基本数据类型进行包装后再比较,那么2个包装类对象的内存地址是不一样的,==号比较肯定一直为false!

继续来看另一个陷阱:

Double i1 = Double.valueOf(0.1);

Double i2 = Double.valueOf(0.1);

System.out.println(i1 == i2); //false valueOf方法内部实际上也是new

注意这里i1和i2是用包装类的静态方法valueOf来构造的。我们看一下Double.valueOf的源码:

可以看到,valueOf内部也是用的new方法来构造对象的。2个new出来的对象,内存地址肯定是不一样的。

那么问题来了,下面的代码运行结果却让人大跌眼镜:

System.out.println(Integer.valueOf(1) ==Integer.valueOf(1)); //trueSystem.out.println(Integer.valueOf(999) ==Integer.valueOf(999)); //false

是不是感觉不会再爱了?

  没关系,我们还是从源码来分析问题。查看Integer.valueOf方法的源码:

可以看到,当i的值在low和high范围内是,返回的是一个cache里的Integer对象。

我们继续追踪IntegerCache.cache的源码:

可以看到从-128到到127之间的值都被缓存到cache里了。这个叫做包装类的缓存。

Java对部分经常使用的数据采用缓存技术,在类第一次被加载时换创建缓存和数据。当使用等值对象时直接从缓存中获取,从而提高了程序执行性能。(通常只对常用数据进行缓存)

Integer默认缓存是-128到127之间的对象,上限127可以通过修改JVM参数来更改。

回到我们的示例代码中,可以看到Integer.valueOf(1)是会直接从缓存中去取被缓存的同一个对象,所以比较的值是true。而Integer.valueOf(999)是缓存范围外的值,那么就会new一个新的对象出来,所以比较的结果是false。

实际上,包装类中整数型的类型基本上都有缓存数据。可以通过阅读源码了解。不过只有Integer类可以通过修改JVM参数来更改缓存上限。

其他类型的缓存范围为:Integer类型有缓存-128-127的对象。缓存上限可以通过配置jvm更改

Byte,Short,Long类型有缓存(-128-127)

Character缓存0-127

Boolean缓存TRUE、FALSE

尤其需要特别注意的是,只有valueOf方法构造对象时会用到缓存,new方法等不会使用缓存!

了解了缓存机制,那么再看下面这道题目:

Integer i4 = Integer.valueOf(1);

Integer i5 =1;

System.out.println(i4 == i5); //true

Integer i7 = Integer.valueOf(999);

Integer i8 = 999;

System.out.println(i7 == i8); //false

是不是觉得脑仁疼?

没关系,我们看一下上面自动包装时的class字节码:

发现什么了? 自动包装时实际上还是调用的valueOf方法。而上面我们讲过的,valueOf方法用到了缓存池。

通过今天的学习,相信大家已经体验到了如何从“根源”上去分析和解决问题。养成阅读字节码和源代码的好习惯,对我们的成长是非常有帮助的。

记住,我们要学习的是“方法”而不是“答案”。

喜欢的话可以关注我或者关注我的专栏。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很抱歉,我无法提供完整的2024年的Java面试题及答案,因为我无法预测未来的情况。但是,我可以给你提供一些常见的Java面试题和答案,帮助你准备面试。 1. Java中的基本数据类型有哪些? 答案:Java中的基本数据类型包括byte、short、int、long、float、double、boolean和char。 2. Java中的包装类是什么? 答案:包装类是一种将基本数据类型封装成对象的类。例如,Integer是封装int类型的包装类。 3. Java中的String类是可变的吗? 答案:String类是不可变的,一旦创建就不能被修改。如果需要修改字符串,可以使用StringBuilder或StringBuffer类。 4. Java中的继承和接口有什么区别? 答案:继承是指一个类从另一个类获取属性和方法的过程,通过extends关键字实现。接口是一种规范,定义了一组方法的集合,通过implements关键字实现。 5. Java中的异常处理机制是什么? 答案:Java中的异常处理机制通过try-catch-finally语句块来实现。try块中包含可能抛出异常的代码,catch块用于捕获并处理异常,finally块用于执行无论是否发生异常都需要执行的代码。 6. Java中的多线程是如何实现的? 答案:Java中的多线程可以通过继承Thread类或实现Runnable接口来实现。另外,还可以使用线程池来管理和调度线程。 7. Java中的反射是什么? 答案:反射是指在运行时动态获取和操作类的信息。通过反射,可以获取类的属性、方法和构造函数等信息,并且可以在运行时调用这些方法。 8. Java中的泛型是什么? 答案:泛型是一种参数化类型的机制,可以在编译时检查类型的安全性。通过使用泛型,可以使代码更加灵活和可重用。 9. Java中的集合框架有哪些? 答案:Java中的集合框架包括List、Set、Map等接口和它们的实现类。这些集合类提供了一组用于存储和操作对象的方法。 10. Java中的内存管理是如何工作的? 答案:Java中的内存管理由Java虚拟机(JVM)负责。JVM使用垃圾回收机制来自动管理内存,当对象不再被引用时,垃圾回收器会自动回收该对象所占用的内存空间。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值