5个Java面试题目,你一定不怎么了解。

前些日子,阿里妹(妹子出题也这么难)发表了一篇文章《悬赏征集!5 道题征集代码界前 3% 的超级王者》——看到这个标题,我内心非常非常激动,因为终于可以证明自己技术很牛逼了。

但遗憾的是,凭借 8 年的 Java 开发经验,我发现这五道题自己全解错了!惨痛的教训再次证明,我是那被秒杀的 97% 的工程师之一。

不过,好歹我这人脸皮特别厚,虽然全都做错了,但还是敢于坦然地面对自己。

01、原始类型的 float

第一题是这样的,代码如下

public class FloatPrimitiveTest {

public static void main(String[] args) {

float a = 1.0f - 0.9f;

float b = 0.9f - 0.8f;

if (a == b) {

System.out.println("true");

} else {

System.out.println("false");

}

}

}

乍一看,这道题也太简单了吧?

1.0f - 0.9f 的结果为 0.1f,0.9f - 0.8f 的结果为 0.1f,那自然 a == b 啊。

但实际的结果竟然不是这样的,太伤自尊了。

点击空白处查看答案(可以下拉)

float a = 1.0f - 0.9f;System.out.println(a); // 0.100000024float b = 0.9f - 0.8f;System.out.println(b); // 0.099999964

加上两条打印语句后,我明白了,原来发生了精度问题。

Java 语言支持两种基本的浮点类型:float 和 double ,以及与它们对应的包装类 Float 和 Double 。它们都依据 IEEE 754 标准,该标准用科学记数法以底数为 2 的小数来表示浮点数。

但浮点运算很少是精确的。虽然一些数字可以精确地表示为二进制小数,比如说 0.5,它等于 2-1;但有些数字则不能精确的表示,比如说 0.1。因此,浮点运算可能会导致舍入误差,产生的结果接近但并不等于我们希望的结果。

所以,我们看到了 0.1 的两个相近的浮点值,一个是比 0.1 略微大了一点点的 0.100000024,一个是比 0.1 略微小了一点点的 0.099999964。

Java 对于任意一个浮点字面量,最终都舍入到所能表示的最靠近的那个浮点值,遇到该值离左右两个能表示的浮点值距离相等时,默认采用偶数优先的原则——这就是为什么我们会看到两个都以 4 结尾的浮点值的原因。

02、包装器类型 Float

再来看第二题,代码如下:

public class FloatWrapperTest {

public static void main(String[] args) {

Float a = Float.valueOf(1.0f - 0.9f);

Float b = Float.valueOf(0.9f - 0.8f);

if (a.equals(b)) {

System.out.println("true");

} else {

System.out.println("false");

}

}

}

乍一看,这道题也不难,对吧?无非是把原始类型的 float 转成了包装器类型 Float,并且使用 equals 替代 == 进行判断。

这一次,我以为包装器会解决掉精度的问题,所以我猜想输出结果为 true。但结果再次打脸——虽然我脸皮厚,但仍然能感觉到脸有些微微的红了起来。

Float a = Float.valueOf(1.0f - 0.9f);

System.out.println(a); // 0.100000024

Float b = Float.valueOf(0.9f - 0.8f);

System.out.println(b); // 0.099999964

加上两条打印语句后,我明白了,原来包装器并不会解决精度的问题。

private final float value;

public Float(float value) {

this.value = value;

}

public static Float valueOf(float f) {

return new Float(f);

}

public boolean equals(Object obj) {

return (obj instanceof Float)

&& (floatToIntBits(((Float)obj).value) == floatToIntBits(value));

}

从源码可以看得出来,包装器 Float 的确没有对精度做任何处理,况且 equals 方法的内部仍然使用了 == 进行判断。

03、switch 判断 null 值的字符串

来看第三题,代码如下:

public class SwitchTest {

public static void main(String[] args) {

String param = null;

switch (param) {

case "null":

System.out.println("null");

break;

default:

System.out.println("default");

}

}

}

这道题就有点令我雾里看花了。

我们都知道,switch 是一种高效的判断语句,比起 if/else 真的是爽快多了。尤其是 JDK 1.7 之后,switch 的 case 条件可以是 char, byte, short, int, Character, Byte, Short, Integer, String, 或者 enum 类型。

本题中,param 类型为 String,那么我认为是可以作为 switch 的 case 条件的,但 param 的值为 null,null 和 “null” 肯定是不匹配的,我认为程序应该进入到 default 语句输出 default。

但结果再次打脸!程序抛出了异常:

Exception in thread "main" java.lang.NullPointerException

at com.cmower.java_demo.Test.main(Test.java:7)

也就是说,switch () 的括号中不允许传入 null。为什么呢?

我翻了翻 JDK 的官方文档,看到其中有这样一句描述,我直接搬过来大家看一眼就明白了。

When the switch statement is executed, first the Expression is evaluated. If the Expression evaluates to null, a NullPointerException is thrown and the entire switch statement completes abruptly for that reason. Otherwise, if the result is of a reference type, it is subject to unboxing conversion.

大致的意思就是说,switch 语句执行的时候,会先执行 switch () 表达式,如果表达式的值为 null,就会抛出 NullPointerException 异常。

那到底是为什么呢?

public static void main(String args[])

{

String param = null;

String s;

switch((s = param).hashCode())

{

case 3392903:

if(s.equals("null"))

{

System.out.println("null");

break;

}

// fall through

default:

System.out.println("default");

break;

}

}

借助 jad,我们来反编译一下 switch 的字节码,结果如上所示。原来 switch () 表达式内部执行的竟然是 (s = param).hashCode(),当 param 为 null 的时候,s 也为 null,调用 hashCode() 方法的时候自然会抛出 NullPointerException 了。

04、BigDecimal 的赋值方式

来看第四题,代码如下:

public class BigDecimalTest {

public static void main(String[] args) {

BigDecimal a = new BigDecimal(0.1);

System.out.println(a);

BigDecimal b = new BigDecimal("0.1");

System.out.println(b);

}

}

这道题真不难,a 和 b 的唯一区别就在于 a 在调用 BigDecimal 构造方法赋值的时候传入了浮点数,而 b 传入了字符串,a 和 b 的结果应该都为 0.1,所以我认为这两种赋值方式是一样的。

但实际上,输出结果完全出乎我的意料:

BigDecimal a = new BigDecimal(0.1);

System.out.println(a); // 0.1000000000000000055511151231257827021181583404541015625

BigDecimal b = new BigDecimal("0.1");

System.out.println(b); // 0.1

这究竟又是怎么回事呢?

这就必须看官方文档了,是时候搬出 BigDecimal(double val) 的 JavaDoc 镇楼了。

The results of this constructor can be somewhat unpredictable. One might assume that writing new BigDecimal(0.1) in Java creates a BigDecimal which is exactly equal to 0.1 (an unscaled value of 1, with a scale of 1), but it is actually equal to 0.10000000000000000555111512312578270211815834045410...

解释:使用 double 传参的时候会产生不可预期的结果,比如说 0.1 实际的值是 0.1000000000000000055511151231257827021181583404541015625,说白了,这还是精度的问题。(既然如此,为什么不废弃呢?)

The String constructor, on the other hand, is perfectly predictable: writing new BigDecimal(“0.1”) creates a BigDecimal which is exactly equal to 0.1, as one would expect. Therefore, it is generally recommended that the String constructor be used in preference to this one.

解释:使用字符串传参的时候会产生预期的结果,比如说 new BigDecimal("0.1") 的实际结果就是 0.1。

When a double must be used as a source for a BigDecimal, note that this constructor provides an exact conversion; it does not give the same result as converting the double to a String using the Double.toString(double) method and then using the BigDecimal(String) constructor.

解释:如果必须将一个 double 作为参数传递给 BigDecimal 的话,建议传递该 double 值匹配的字符串值。方式有两种:

double a = 0.1;

System.out.println(new BigDecimal(String.valueOf(a))); // 0.1

System.out.println(BigDecimal.valueOf(a)); // 0.1

第一种,使用 String.valueOf() 把 double 转为字符串。

第二种,使用 valueOf() 方法,该方法内部会调用 Double.toString() 将 double 转为字符串,源码如下:

public static BigDecimal valueOf(double val) {

// Reminder: a zero double returns ‘0.0‘, so we cannot fastpath

// to use the constant ZERO.  This might be important enough to

// justify a factory approach, a cache, or a few private

// constants, later.

return new BigDecimal(Double.toString(val));

}

05、ReentrantLock

最后一题,也就是第五题,代码如下:

public class LockTest {

private final static Lock lock = new ReentrantLock();

public static void main(String[] args) {

try {

lock.tryLock();

} catch (Exception e) {

e.printStackTrace();

} finally {

lock.unlock();

}

}

}

问题如下:

A: lock 是非公平锁 B: finally 代码块不会抛出异常 C: tryLock 获取锁失败则直接往下执行

很惭愧,我不知道 ReentrantLock 是不是公平锁;也不知道 finally 代码块会不会抛出异常;更不知道 tryLock 获取锁失败的时候会不会直接往下执行。没法作答了。

连续五道题解不出来,虽然我脸皮非常厚,但也觉得脸上火辣辣的,就像被人狠狠地抽了一个耳光。

容我研究研究吧。

1)lock 是非公平锁

ReentrantLock 是一个使用频率非常高的锁,支持重入性,能够对共享资源重复加锁,即当前线程获取该锁后再次获取时不会被阻塞。

ReentrantLock 既是公平锁又是非公平锁。调用无参构造方法时是非公平锁,源码如下:

public ReentrantLock() {

sync = new NonfairSync();

}

所以本题中的 lock 是非公平锁,A 选项是正确的。

ReentrantLock 还提供了另外一种构造方法,源码如下:

public ReentrantLock(boolean fair) {

sync = fair ? new FairSync() : new NonfairSync();

}

当传入 true 的时候为公平锁,false 的时候为非公平锁。

那公平锁和非公平锁到底有什么区别呢?

公平锁可以保证请求资源在时间上的绝对顺序,而非公平锁有可能导致其他线程永远无法获取到锁,造成“饥饿”的现象。

公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会减少一些上下文切换,性能开销相对较小,可以保证系统更大的吞吐量。

2)finally 代码块不会抛出异常

Lock 对象在调用 unlock 方法时,会调用 AbstractQueuedSynchronizer 的 tryRelease 方法,如果当前线程不持有锁的话,则抛出 IllegalMonitorStateException 异常。

所以建议本题的示例代码优化为以下形式(进入业务代码块之前,先判断当前线程是否持有锁):

boolean isLocked = lock.tryLock();

if (isLocked) {

try {

// doSomething();

} catch (Exception e) {

e.printStackTrace();

} finally {

lock.unlock();

}

}

3)tryLock 获取锁失败则直接往下执行

tryLock() 方法的 Javadoc 如下:

Acquires the lock if it is available and returns immediately with the value true. If the lock is not available then this method will return immediately with the value false.

中文意思是如果锁可以用,则获取该锁,并立即返回 true,如果锁不可用,则立即返回 false。针对本题的话, 在 tryLock 获取锁失败的时候,程序会执行 finally 块的代码。

原文地址:https://www.cnblogs.com/EarlyBridVic/p/12100014.html

时间: 2024-11-09 00:36:08

5个Java面试题目,你一定不怎么了解。的相关文章

【Java基础】Java面试题目整理与解说(二)

1.Collection 和 Collections 的差别. Collection 是集合类的上级接口,继承于他的接口主要有 Set 和 List. Collections 是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索.排序.线程安全化等操作. 2.HashMap 和 Hashtable 的差别. HashMap 是 Hashtable 的轻量级实现(非线程安全的实现),他们都完毕了 Map 接口,HashMap是非线程安全,效率上可能高于 Hashtable.在多个线程

【Java基础】Java面试题目整理与讲解(二)

1.Collection 和 Collections 的区别. Collection 是集合类的上级接口,继承于他的接口主要有 Set 和 List. Collections 是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索.排序.线程安全化等操作. 2.HashMap 和 Hashtable 的区别. HashMap 是 Hashtable 的轻量级实现(非线程安全的实现),他们都完成了 Map 接口,HashMap是非线程安全,效率上可能高于 Hashtable.在多个线程

(转)喜马拉雅2018 Java面试题目

背景:将网上的题目整理下. java基础 1:hashTable hashMap ConcurrentHashMap 的区别.数据结构.线程安全 2:equals和==区别, 重写equals一定要重写hashcode方法吗?为什么?hashcode方法有什么作用? 这个万年不变的面试题,这是何等的卧槽 ==说明: 对于基本类型来说 ,==比较两个基本类型的值是否相等, 对于引用类型来说,==比较的是内个引用类型的内存地址 equals说明: equals用来比较的是两个对象的内容是否相等,由于

Java面试题目整理

一.引言:本文主要整理遇到的面试题目,以及提供自己的见解,将会持续更新,如有问题,可评论交流,一起进步. 二.问题及我的见解: 1. n个结点可以组合成多少棵不同的二叉树? 答:2^n-n棵 2. 什么是bean? 答: bean本质上就是可复用的类,比如在spring中的bean就是表示组件的意思. 3. String.StringBuffer.StringBuilder的有什么区别? 答:String是内容和长度固定的类 StringBuffer是内容和长度可变的类,并且线程安全 Strin

一次Java面试题目记录

记忆得不太清楚了,不过一些基本问过的题目还是记得住的,就写一点吧. maven的使用情况问一点. snapshot和release版本的区别? maven的生命周期有了解过吗? 如何把自己写的架包推送到私服上?maven有一个deployed命令 maven有一个mirrors对吧,配置多个镜像的时候,多个镜像之间是怎么产生作用的?互相之间的作用是怎样的?调用顺序是. 如果有冲突会怎样?mirror本身是排除冲突的,我说的是你使用的哪一个插件的冲突,你配置的都是centre只会取一个. mirr

Java面试题目集合

有链接的都是引用别人的知识点. 1.equals方法用于比较对象的内容是否相等(覆盖以后) 2.hashcode方法只有在集合中用到 3.当覆盖了equals方法时,比较对象是否相等将通过覆盖后的equals方法进行比较(判断对象的内容是否相等). 4.将对象放入到集合中时,首先判断要放入对象的hashcode值与集合中的任意一个元素的hashcode值是否相等, 如果不相等直接将该对象放入集合中.如果hashcode值相等,然后再通过equals方法判断要放入对象与集合中的 任意一个对象是否相

【应聘】阿里巴巴Java面试题目

原文地址:http://blog.csdn.net/free0sky/article/details/7927275   一.String,StringBuffer, StringBuilder 的区别是什么?String为什么是不可变的? 答:   1.String是字符串常量,StringBuffer和StringBuilder都是字符串变量.后两者的字符内容可变,而前者创建后内容不可变. 2.String不可变是因为在JDK中String类被声明为一个final类. 3.StringBuf

java面试题目

1.项目中Spring AOP用在什么地方,为什么这么用,切点,织入,通知,用自己的话描述一下,AOP原理,动态代理2种实现. 主要是事务那方面,采用声明式的事务配置方式,是AOP给你封装好的. 通知: 定义:切面也需要完成工作.在 AOP 术语中,切面的工作被称为通知. 工作内容:通知定义了切面是什么以及何时使用.除了描述切面要完成的工作,通知还解决何时执行这个工作. Spring 切面可应用的 5 种通知类型: Before--在方法调用之前调用通知 After--在方法完成之后调用通知,无

java面试题目偏基础

一.JAVA基础篇-概念 1.简述你所知道的Linux: Linux起源于1991年,1995年流行起来的免费操作系统,目前, Linux是主流的服务器操作系统, 广泛应用于互联网.云计算.智能手机(Android)等领域.由于Java主要用于服务器端的开发,因此Java应用的部署环境有很多为Linux.Windows操作系统的目录结构,是以盘符为单位,C盘.D盘.E盘等等,数据存储在各个盘符之下,而Linux操作系统最顶层只有一个根目录root,所有文件都存储在这一个根目录之下.Linux不像