目的
- 专门收集一些看上去很有意思的题目
- 这些题目,一看就会,一做就错
- 持续更新,每十题作为一篇文章
目录
系列文章
【1】关于三目运算符
- 下面的运行结果应该输出什么?
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = null;
Integer d = a > b ? a + b : c;
System.out.println(d);
}
- 你期待的:
null
- 实际上:
Exception in thread "main" java.lang.NullPointerException
at DesignPattern.single.dfghj.main(dfghj.java:16)
Process finished with exit code 1
-
原因
- 报错的第16行是这一句:Integer d = a > b ? a + b : c;
- 首先我们明确:条件运算符是右结合的,也就是说,从右向左分组计算。例如,a?b:c?d:e将按 a?b:(c?d:e) 执行。
- 基本类型跟包装类型在做转换的时候,有个自动包装和解包装的过程,会调用.xxxxValue(),比如.booleanValue(),.intValue(),.StringValue()。
- 三目运算符的“:”左右两边会自动保持一致,那么如果类型不一致,会发生解包装的过程。
- 如果两边类型不一致,比如题目中的:左边计算出来是int3,而不是Integer3,右边的也会解包装,那么null本身再次吊用.xxxxValue(),会发生NPE。
-
解决办法:保持“:”两边一致,
- 要么修改 a+b:Integer d = a > b ? Integer.valueOf(a + b) :c;
- 要么修改 c :Integer d = a > b ? a + b :null;
-
参考博客:三目运算符的空指针问题
【2】关于effective final
- 下面的运行结果应该输出什么?
public class ljtest{
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread name:" + Thread.currentThread().getName() + ",i:" + i);
}
});
}
}
}
- 你期待的:
thread name:Thread-0,i:0
thread name:Thread-1,i:1
thread name:Thread-2,i:2
thread name:Thread-4,i:4
thread name:Thread-6,i:6
thread name:Thread-8,i:8
thread name:Thread-9,i:9
thread name:Thread-3,i:3
thread name:Thread-5,i:5
thread name:Thread-7,i:7
- 实际上:
编译期都过不了,会在输出语句哪一行的 i 哪里显示红线。
- 原因分析
- 因为内部类在访问外部类成员变量的时候,这个变量不能改变,他必须是稳定的,要么是final,要么是effective final。
- final的意思是不可更改的,effective final的意思是差不多相当于不可更改的,如果一个变量在使用前,没有出现过多次赋值(两次),那么就默认是形式上的不可变量。
- 解决办法
- 将变量 i 变成final,或者effective final,例如在for循环下面加上一句:int temp = i;(注意输出语句中的 i 也要改成temp哦)
- 现在将 int temp = i;加上了,结果会是期望那样么?
- 不会,因为线程根本没有启动。
- 解决办法:
- 办法一:在for循环内,每个线程后面加上 t.start();
- 办法二:交给线程池来管理,就不需要加上 t.start();
【3】关于饥饿死锁
- 下面的运行结果应该输出什么?
public class ljtest{
private static ExecutorService executorService = Executors.newSingleThreadExecutor();
static class t1 implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("i am t1");
return "t1===";
}
}
static class t2 implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("i am t2");
Future<String> submit = executorService.submit(new t1());
return "t2===" + submit.get();
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
t2 t = new t2();
Future<String> submit = executorService.submit(t);
System.out.println(submit.get());
System.out.println("la~la~la~");
executorService.shutdown();
}
}
- 你期待的:
i am t2
i am t1
t2===t1===
la~la~la~
- 实际上:只有这个
i am t2
- 原因分析
- 我们用到的线程池是Executors.newSingleThreadExecutor();他是只有一个线程的线程池。
- 当我们把t2放进去执行的时候,t2又把t1放进去了,这个时候t1在等t2结束,t2在等t1的返回值,出现饥饿等待。
【4】交换a和b的值
- 不使用其他变量的情况下,如何交换a和b的值
常规写法:
public static void main(String[] args) {
int a = 5;
int b = 6;
a = a + b;
b = a - b;
a = a - b;
System.out.println("a:" + a + "b:" + b);
}
异或写法
public static void main(String[] args) {
int a = 8;
int b = 8;
a = a ^ b;
b = a ^ b;
a = a ^ b;
System.out.println("new--->" + "a:" + a + ",b:" + b);
}
- 针对有博客指出常规写法有内存溢出问题,理论上是有的,但是可能jdk1.8以后做了优化,亲测没有溢出的。
- 针对有博客指出异或写法对两个相同的数异或有bug,但是亲测也没有。
- 异或写法可能相对常规写法,会减少计算的累加次数。
【5】计时攻击
- 写一段判断字符串相等的代码
常规写法
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
- 上面这一段选自JDK的equals方法,大致先判断
- 是否引用地址一样
- 是否属于String类型
- 长度是否一致
- 挨个对比每个字符是否一样
- 写成这样就可以了,但是不完美,这会引发安全问题,暴露一种叫做计时攻击的漏洞(请自行百度计时攻击)。
- 按照下面这种写法,会牺牲效率,达到防计时攻击。
防计时攻击写法
public boolean equals(String s1, String s2) {
if (s1 == null || s2 == null) {
return false;
}
if (s1 == s2) {
return true;
}
if (s1.length() != s2.length()) {
return false;
}
int res = 0;
for (int i = 0; i < s1.length(); i++) {
res |= s1.charAt(i) ^ s2.charAt(i);
}
return res == 0;
}
【6】哪一个才是我的属性
- 下面的运行结果应该输出什么?
public class ljtest {
public final int value = 4;//5.注释掉本行
public void dop(){
int value = 6;//4.注释掉本行
Runnable r = new Runnable() {
public final int value = 9;//3.注释掉本行
@Override
public void run() {
int value = 10;//2.注释掉本行
System.out.println(this.value);//1.去掉this
}
};
r.run();
}
public static void main(String[] args) {
ljtest f = new ljtest();
f.dop();
}
}
- 输出
9
-
看代码的注释部分,考虑下列情形,输出分别是什么?
- //1.去掉this
- //2.注释掉本行
- //3.注释掉本行
- //4.注释掉本行
- //5.注释掉本行
-
输出结果
- 1、输出:10
- 2、输出:9
- 3、输出:6
- 4、输出:4
- 5、输出:编译报错
-
结论:
- 分清内部类的属性和值
【7】计算1+2+…+n的值
- 题目描述:求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
- 解题思路及代码:
public class Solution {
private static int[] res = {
0};
public int Sum_Solution(int n) {
/* 1、循环的写法
int sum = 0;
for(int i = 1;i <= n; i++){
sum += i;
}
return sum; */
/* 2、数学规律
return (1 + n) * n / 2; */
/* 3、递归
return n == 0 ? 0 : n + Sum_Solution(n-1); */
// 4、递归-满足题意
try{
return res[n];
}catch(Exception e) {
return n + Sum_Solution(n-1);
}
}
}