面试姊妹篇3:常用方法中的错误

目的

  • 专门收集一些看上去很有意思的题目
  • 这些题目,一看就会,一做就错
  • 持续更新,每十题作为一篇文章

目录

系列文章

【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);
        }
    }
}

猜你喜欢

转载自blog.csdn.net/ljfirst/article/details/106376574
今日推荐