V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
TommyLemon
V2EX  ›  Java

发现 JDK 的 3 个 bug

  •  
  •   TommyLemon ·
    TommyLemon · 2018-12-04 10:22:34 +08:00 · 6630 次点击
    这是一个创建于 2180 天前的主题,其中的信息可能已经有所发展或是发生改变。

    1.Annotation 引用非空 enum 数组返回空数组

    首次发现时的环境:JDK 1.8

    首次发现所在项目:APIJSON

    测试用例:

    public enum RequestRole {
    
    	/**未登录,不明身份的用户
    	 */
    	UNKNOWN,
    
    	/**已登录的用户
    	 */
    	LOGIN,
    
    	/**联系人,必须已登录
    	 */
    	CONTACT,
    
    	/**圈子成员(CONTACT + OWNER),必须已登录
    	 */
    	CIRCLE,
    
    	/**拥有者,必须已登录
    	 */
    	OWNER,
    
    	/**管理员,必须已登录
    	 */
    	ADMIN;
    
    	//似乎不管怎么做,外部引用后都是空值。并且如果在注解内的位置不是最前的,还会导致被注解的类在其它类中 import 报错。
    	//虽然直接打印显示正常,但被 @MethodAccess 内 RequestRole[] GET()等方法引用后获取的是空值
    	public static final RequestRole[] ALL = {RequestRole.UNKNOWN};//values();//所有
    	public static final RequestRole[] HIGHS;//高级
    	static {
    		HIGHS = new RequestRole[] {OWNER, ADMIN};
    	}
    
    	public static final String[] NAMES = {
    			UNKNOWN.name(), LOGIN.name(), CONTACT.name(), CIRCLE.name(), OWNER.name(), ADMIN.name()
    	};
    
    
    }
    
    
    @MethodAccess(
    		GETS = RequestRole.ALL,
    		HEADS = RequestRole.HIGHS
    		)
    public class Verify {
    
    }
    
    
    public class DemoVerifier {
    	// <TableName, <METHOD, allowRoles>>
    	// <User, <GET, [OWNER, ADMIN]>>
        public static final Map<String, Map<RequestMethod, RequestRole[]>> ACCESS_MAP;
    	static { //注册权限
            ACCESS_MAP = new HashMap<String, Map<RequestMethod, RequestRole[]>>();
    		ACCESS_MAP.put(Verify.class.getSimpleName(), getAccessMap(Verify.class.getAnnotation(MethodAccess.class)));
    	}
    
    	public static HashMap<RequestMethod, RequestRole[]> getAccessMap(MethodAccess access) {
    		if (access == null) {
    			return null;
    		}
    
    		HashMap<RequestMethod, RequestRole[]> map = new HashMap<>();
    		map.put(GET, access.GET());
    		map.put(HEAD, access.HEAD());
    		map.put(GETS, access.GETS());
    		map.put(HEADS, access.HEADS());
    		map.put(POST, access.POST());
    		map.put(PUT, access.PUT());
    		map.put(DELETE, access.DELETE());
    
    		return map;
    	}
    
    }
    
    

    解决方案:

    不抽象数组常量 ALL,HIGHTS 等,而是在每个用到的地方硬编码写死具体的值。

    2.ArrayList 可通过构造函数传入非指定泛型的 List 并在 get 时出错

    首次发现时的环境:JDK 1.7

    首次发现所在项目:APIJSON

    测试用例:

    JSONArray arr = new JSONArray(); //com.alibaba.fastjson.JSONArray
    arr.add("s");
    
    List<Long> list = new ArrayList<>(arr); 
    list.get(0); //throw new IllegalArgumentException
    

    解决方案:

    1.改用 Open JDK8

    2.升级 JDK

    注:后面多次测试,已无法复现。

    3.基本类型在三元表达式内可赋值为 null,编译通过但运行出错

    首次发现时的环境:JDK 1.7

    测试用例:

    int i = true ? null : 0; //Exception in thread "main" java.lang.NullPointerException
    

    首次发现所在项目:ZBLibrary

    解决方案:

    在给基础类型用 3 元表达式赋值时,null 先转为基础类型的默认值。

    最后再提 2 个不是 bug,但容易引发编程 bug 的问题:

    1.局部变量和同名的全局变量能在一个方法内,编译通过,运行也正常。

        public class Test {
            
            int val;
            @Override
            public String toString() {
                val = 1;
                String val = "";
                return super.toString();
            }
        }
    

    如果两个变量中间隔了比较长的其它代码,很可能会导致开发人员将两者混淆,导致逻辑认知错误,从而写出或改出有问题的代码。

    解决方案:

    命名局部变量前先搜素,确保没有已声明的同名全局变量。

    2. (非 JDK bug ) Gson 通过 TypeToken 转换 List<t> 能写入不属于 T 类型的数据,get 出来赋值给 T 类型的变量 /常量报错。</t>

    
            String json = "[1, '2', 'a']";
            Type type = new TypeToken<Integer>(){}.getType();
            Gson gson = new Gson();
            List<Integer> list = gson.fromJson(json, type);
            
            Integer i = list == null || list.isEmpty() ? null : list.get(0); //Exception cannot cast String to Integer
    
    

    解决方案:

    1.手动检查列表内数据都符合泛型 T

    2.改用 fastjson 等其它能静态检查类型的库。

    22 条回复    2018-12-05 19:05:23 +08:00
    Kaiv2
        1
    Kaiv2  
       2018-12-04 10:55:18 +08:00
    ```java
    JSONArray arr = new JSONArray(); //com.alibaba.fastjson.JSONArray
    arr.add("s");

    List<Long> list = new ArrayList<>(arr);
    list.get(0); //throw new IllegalArgumentException
    ```

    这个你确定能编译过?你看看 JSONArray 的源码

    ```java
    public class JSONArray extends JSON implements List<Object>, Cloneable, RandomAccess, Serializable {
    private static final long serialVersionUID = 1L;
    private final List<Object> list;
    protected transient Object relatedArray;
    protected transient Type componentType;

    public JSONArray() {
    this.list = new ArrayList();
    }
    ```
    里面明明定义的是 `Object` 的泛型
    TommyLemon
        2
    TommyLemon  
    OP
       2018-12-04 11:19:03 +08:00
    @Kaiv2
    我当时的环境下,就是能正常编译通过,并且只在运行到 get 时崩溃。
    后面写博客时,自己再试已经无法复现,所以就在文中加了备注:
    “注:后面多次测试,已无法复现。”
    但是可能部分其它开发者的环境下也会有这种问题,所以还是发出来更好。

    源码是 Object 类型,泛型本来就是用 Object 存,编译时静态检查,通过后擦除泛型,然后取出时强转,你看看原理以及 ArrayList 的代码就知道了。
    当时导致这个 bug 的原因是 ArrayList 构造函数是
    ```java
    public ArrayList(Collection<?> c) { //还是 Collection<? extends Object> 来着,记不清了,但肯定是两者中的一个
    ...
    }
    ```
    后面我再次测试,以及在 Android SDK 中看到的都是
    ```java
    public ArrayList(Collection<? extends E> c) {
    ...
    }
    ```
    然后就标记错误,无法编译通过了。
    sorra
        3
    sorra  
       2018-12-04 12:57:18 +08:00
    哪款编译器,啥版本?
    1. 编译器没报错?
    还有一种可能,这些类属于不同的 jar 分开编译,都放一起编译试试

    2. 编译器没警告?

    3. 确实是问题,编译器没警告,需要靠 IDE 警告。
    27
        4
    27  
       2018-12-04 14:03:09 +08:00
    第三个可以写成
    Integer i = true ? null : 0;
    TommyLemon
        5
    TommyLemon  
    OP
       2018-12-04 15:07:17 +08:00
    @sorra
    1.代码注释里有提到:
    “如果在注解内的位置不是最前的,会导致被注解的类在其它类中 import 报错。”
    其它情况就不报错,运行时在注解里拿到的值始终是空数组,但是非注解的地方拿到的值又是正常的(例如打印)。
    都是同一个 1.8 版本的 jar 编译的

    2.发博客前测试时会有报错的,具体看 #2 楼我的回复。

    3.都没任何提示,运行直接抛异常。

    以上 bug 都是在我个人的设备上发现的,具体的设备信息、环境信息等,只能等回去再看看。
    TommyLemon
        6
    TommyLemon  
    OP
       2018-12-04 15:17:07 +08:00
    @27 以上所有用例都是简化版本,实际业务代码里都不是这么直接的写出 null 值的,而是 变量 /方法返回值 等,
    例如
    ```java
    int quantity= response == null ? 0 : response.getInteger("quantity");
    ```
    getInteger 返回 null 就会导致问题 2,fastjson 可以用 getIntValue 来避免.

    另外改用 Integer 只能保证在这里是不会出错,但很多时候我们需要把变量传到其它方法里,例如
    ```java
    public static String formatNumber(int num) {
    //把 12345678 转换为 12,345,678
    }


    Integer quantity= response == null ? 0 : response.getInteger("quantity"); // getInteger return null
    String text = formatNumber(quantity); //抛异常 NullPointerException
    ```

    Java 的隐式类型转换是一定要谨慎使用的,调用方法也要注意看参数类型和返回值类型。
    TommyLemon
        7
    TommyLemon  
    OP
       2018-12-04 15:23:04 +08:00
    最后一个用例写错了
    ```java
    Integer i = list == null || list.isEmpty() ? null : list.get(0); //Exception cannot cast String to Integer
    ```
    应该改为
    ```java
    Integer i = list == null || list.isEmpty() ? null : list.get(1); //Exception cannot cast String to Integer
    ```
    zjp
        8
    zjp  
       2018-12-04 15:46:47 +08:00 via Android
    花式贴链接……
    三目运算符会隐式转型,int i = (Integer) null; 合法,int i = true ? null : 0; 也就合法
    nutting
        9
    nutting  
       2018-12-04 15:49:39 +08:00
    我想起一个,反射得到一个类的所有方法数组,jdk1.6 和 1.7 有差别,里面排序不一样,所以最好自己排一下
    Kaiv2
        10
    Kaiv2  
       2018-12-04 16:06:25 +08:00
    @TommyLemon

    这个 bug 无法确认是否真的存在过
    ```java
    public ArrayList(Collection<?> c) { //还是 Collection<? extends Object> 来着,记不清了,但肯定是两者中的一个
    ```
    如果之前这个方法真的存在,至少我觉得 get(0)会抛出 类型转换异常 `java.lang.ClassCastException`。

    //throw new IllegalArgumentException 是从哪里抛出的呢?
    TommyLemon
        11
    TommyLemon  
    OP
       2018-12-04 16:44:03 +08:00
    @Kaiv2 因为写博客时已经无法复现了,所以 Exception 也记错了,确实应该是 ClassCastException,不过也不排除 get 里面抛的是其它的,毕竟代码也找不到了,唉。
    TommyLemon
        12
    TommyLemon  
    OP
       2018-12-04 16:44:59 +08:00
    @zjp 是这样,所以才会导致 bug
    TommyLemon
        13
    TommyLemon  
    OP
       2018-12-04 16:51:11 +08:00
    @nutting 毕竟内部实现有改动很正常,还有 JDK 8 及以下是不能通过反射拿到成员变量的名称的,
    @ApiModelProperty(value="id" ,required=true)
    private Integer id;
    所以 Swagger 等注解里面还得手写名称等,其实本身就已经声明了变量名为 id。JDK 9 还是 10 已经加入了。
    JDK 7 比 6 在多了一个 getAnnotation(Class<T> c) , 之前只能 getAnnotations() 拿到所有注解再过滤。
    TommyLemon
        14
    TommyLemon  
    OP
       2018-12-04 17:40:01 +08:00
    @TommyLemon 不过这些不能算 bug 了,只能算功能缺失,而且都在后续更新中加入了
    chocotan
        15
    chocotan  
       2018-12-04 18:04:36 +08:00   ❤️ 1
    第一题 谷歌一下 https://stackoverflow.com/questions/13253624/how-to-supply-enum-value-to-an-annotation-from-a-constant-in-java
    第二题 java 泛型是类型擦除的,当然是什么类型都能进 list,哪里是 bug,至于要不要抛 ClassCastException,看有没有把 get 的返回值赋给具体的类型,get 方法里会强转;主贴里说的“ IllegalArgumentException ”,围观了一下 ArrayList 源码,都不会在 get 的时候抛
    mritd
        16
    mritd  
       2018-12-04 19:22:04 +08:00 via iPhone   ❤️ 2
    看了这个贴子外加上次推广以后,我承认我已经对这个项目彻底失去信心了,一开始进来我以为要上 jdk 官方 bug 列表,我还在想 v 站大佬真是多 ...现在么...
    cqy2016
        17
    cqy2016  
       2018-12-04 19:35:44 +08:00   ❤️ 2
    ...这应该叫你代码的 bug
    sagaxu
        19
    sagaxu  
       2018-12-05 01:27:11 +08:00 via Android   ❤️ 1
    发现 3 个广告
    james2013
        20
    james2013  
       2018-12-05 09:17:04 +08:00
    强行发广告...
    Keyes
        21
    Keyes  
       2018-12-05 10:05:40 +08:00
    变着花样推广项目,不过开源辛苦,就蒙上眼睛支持一下吧
    mritd
        22
    mritd  
       2018-12-05 19:05:23 +08:00 via iPhone
    容我再说句实话,我目测楼主以前可能是一位前端开发者,是否优秀不敢说,我也没资格评价,但是你这么跨界写 java,java 的操作我倒是不敢恭维; 我突然有点理解左耳朵耗子喷一堆后端开发者去前端跨界装逼的处境了……还有就是,那个三木运算吧,int 为啥要赋值 null? 你是不是想写 nil 你....
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5199 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 05:53 · PVG 13:53 · LAX 21:53 · JFK 00:53
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.