文章

Java易忽略

Java易忽略

for 和 forEach

遍历元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static void test_get() {
    String[] array = {"1", "2", "3"};
    for (String i : array) {
        System.out.println(i);
    }

    ArrayList<String> list = new ArrayList<>();
    list.add("111");
    list.add("222");
    list.add("333");
    for (String i : list) {
        System.out.println(i);
    }
}

遍历后结果如下:

1
2
3
4
5
6
1
2
3
111
222
333

再来看看编译后的源码 build/classed/java/main/包名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private static void test_get() {
    String[] array = new String[]{"1", "2", "3"};
    String[] var1 = array;
    int var2 = array.length;

    for(int var3 = 0; var3 < var2; ++var3) {
        String i = var1[var3];
        System.out.println(i);
    }

    ArrayList<String> list = new ArrayList();
    list.add("111");
    list.add("222");
    list.add("333");
    Iterator var6 = list.iterator();

    while(var6.hasNext()) {
        String i = (String)var6.next();
        System.out.println(i);
    }

}

可以看到数组的 forEach 是转换为普通的 for 实现的;List 的 forEach 是通过 Iterator 迭代器实现的

删除元素

普通 for

1
2
3
4
5
6
7
8
9
10
11
private static void test_del_for() {
    List<String> list = new ArrayList<>();
    list.add("111");
    list.add("222");
    list.add("333");
    System.out.println("del前list=" + list);
    for (int i = 0; i < list.size(); i++) {
        list.remove("222");
    }
    System.out.println("del后list=" + list);
}

输出:

1
2
del前list=[111, 222, 333]
del后list=[111, 333]

Iterator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private static void test_del_forIterator() {
    List<String> list = new ArrayList<>();
    list.add("111");
    list.add("222");
    list.add("333");
    System.out.println("del前list=" + list);

    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
        String next = iterator.next();
        if (next.equals("222")) {
            iterator.remove();
        }
    }

    System.out.println("del后list=" + list);
}

输出:

1
2
del前list=[111, 222, 333]
del后list=[111, 333]

forEach (ConcurrentModificationException)

1
2
3
4
5
6
7
8
9
10
11
12
private static void test_del_forEach() {
    List<String> list = new ArrayList<>();
    list.add("111");
    list.add("222");
    list.add("333");
    System.out.println("del前list=" + list);
    for (String i : list) {
        list.remove("222");
    }

    System.out.println("del后list=" + list);
}

输出:

1
2
del前list=[111, 222, 333]
Exception in thread "main" java.util.ConcurrentModificationException

反编译后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static void test_del_forEach() {
    List<String> list = new ArrayList();
    list.add("111");
    list.add("222");
    list.add("333");
    System.out.println("del前list=" + list);
    Iterator var1 = list.iterator();

    while(var1.hasNext()) {
        String i = (String)var1.next();
        list.remove("222");
    }

    System.out.println("del后list=" + list);
}

原因:

1
迭代器内部的每次遍历都会记录List内部的modcount当做预期值,然后在每次循环中用预期值与List的成员变量modCount作比较,但是普通list.remove调用的是List的remove,这时modcount++,但是iterator内记录的预期值=并没有变化,所以会报错。

如果想要删除元素的话需要使用迭代器内部的 remove 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ArrayList<String> list = new ArrayList<>();
list.add("111");
list.add("222");
list.add("333");
log.info(list.toString());
Iterator<String> it = list.iterator();
while (it.hasNext()){
    String next = it.next();
    //if外使用list的remove方法还是会报错的
    if(next.equals("222")){
        it.remove();//这里使用的是迭代器里面的remove()方法,
        // 当然如果使用list的remove方法在此删除质地感元素的话是成功的,比如:list.remove("222")
    }
}
log.info(list.toString());

修改元素

普通 for(可以修改)

1
2
3
4
5
6
7
8
9
10
11
private static void test_modify_for() {
    ArrayList<String> list = new ArrayList<>();
    list.add("111");
    list.add("222");
    list.add("333");
    System.out.println("[for]修改前list=" + list);
    for (int i = 0; i < list.size(); i++) {
        list.set(i, "444");
    }
    System.out.println("[for]修改后list=" + list);
}

输出:

1
2
[for]修改前list=[111, 222, 333]
[for]修改后list=[444, 444, 444]

forEach(不能修改对象,可以修改对象上的属性)

1
2
3
4
5
6
7
8
9
10
11
private static void test_modify_forEach() {
    ArrayList<String> list = new ArrayList<>();
    list.add("111");
    list.add("222");
    list.add("333");
    System.out.println("[forEach]修改前list=" + list);
    for (String i : list) {
        i = "444";
    }
    System.out.println("[forEach]修改后list=" + list);
}

输出:

1
2
[forEach]修改前list=[111, 222, 333]
[forEach]修改后list=[111, 222, 333]

我们看下反编译源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static void test_modify_forEach() {
    ArrayList<String> list = new ArrayList();
    list.add("111");
    list.add("222");
    list.add("333");
    System.out.println("[forEach]修改前list=" + list);

    String i;
    for(Iterator var1 = list.iterator(); var1.hasNext(); i = "444") {
        i = (String)var1.next();
    }

    System.out.println("[forEach]修改后list=" + list);
}

修改的 i 并没有操作的 list,所以 list 不会有任何改变

总结

  1. for 与 foreach 都可以遍历数组/集合,不过 for 则在较复杂的循环中效率更高
  2. foreach 不可以删除/修改集合元素,而 for 可以
  3. foreach 和 for 都可以修改元素里面的属性
  4. 相比较下来 for 循环更为灵活

Java 长整数坑

长整数造成数据溢出 (Java/Kotlin 都一样)

1
2
3
4
5
6
7
public class LongDivision { 
  public static void main(String args[]) { 
    final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000;  // 正常是,86400000000,实际是500654080
    final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;  // 86400000
    System.out.println(MICROS_PER_DAY/MILLIS_PER_DAY); // 5
  } 
}

整个过程,除数与被除数都是 long 型,很容易保存这两个数,结果一定是 1000,但是结果让你失望了,是 5。

Int 最大/小值是:

1
2
Int.MAX_VALUE=2147483647
Int.MIN_VALUE=-2147483648
  • 分析:

24 * 60 * 60 * 1000 * 1000 在计算时是 Int,计算后 Int 溢出了
ohotz

溢出后,这个值刚好是 500654080500654080/86400000=5

  • 案例
    在 Mashi 项目有个本地时间转换的操作:
1
2
const val DAY = 24 * 60 * 60
const val DAY_MILLIS = 24 * 60 * 60 * 1000

目前是没有什么提示,DAY_MILLIS*25 那就会出错了。

1
25*MILLIS_PER_DAY=-2134967296
  • 解决
    L
1
2
3
final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000L;  // 正常是,86400000000,实际是500654080
final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;  // 86400000
System.out.println(MICROS_PER_DAY/MILLIS_PER_DAY); // 1000

Java 自加运算符,j = j++,j 的值为什么没有变?

指令

iconst_2  将 2 放到操作数栈顶
istore_1 将栈顶的值放到局部变量 1 中
iload_1 将局部变量 1 的值放到栈顶
iinc 1, 1 将常量 1 加到局部变量 1 中去,但操作数栈顶值不变 (前面一个 1 表示第几个局部变量,第二个表示要加的值)

一个案例

1
2
3
4
public static void main(String[] args) {
    int j = 3;
    j = j++;
}

反编译

javap -c com.baidu.Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Compiled from "Test.java"
public class com.baidu.Test {
  public com.baidu.Test();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>
()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_3
       1: istore_1
       2: iload_1
       3: iinc          1, 1
       6: istore_1
       7: return
}

解释

1
2
3
4
5
6
7
8
 0: iconst_3   // 将3放到操作数栈顶
 1: istore_1   // 将栈顶的值放到局部变量1(即j)中
 2: iload_1    // 将局部变量1(即j)的值放到操作数栈顶
 3: iinc          1, 1   // 将常量1加到局部变量1中去,但此时栈顶的值并没有变化
 6: istore_1    // 将栈顶的值(此时栈顶还是3)放到局部变量1去
 7: return

为什么是局部变量1呢而不是0或者2呢因为前面有个Sting数组的args所以不是0又因为方法是static的所以没有this引用所以不是2如果把static去掉那么这句就要变成istore_2\. 如果方法中没有参数args那么就是istore_1这里局部变量1就是j

另外一个案例

1
2
3
4
5
6
7
public static void main(String[] args) {
    int m = 3;
    m = m++;

    int n = 5;
    n = ++n;
}

结果:

m=3,n=6

反编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
D:\workspace\browser_20150803\Test\bin>javap -c com.baidu.Test
Compiled from "Test.java"
public class com.baidu.Test {
  public com.baidu.Test();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":
()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_3
       1: istore_1
       2: iload_1
       3: iinc          1, 1
       6: istore_1
       7: iconst_5
       8: istore_2
       9: iinc          2, 1
      12: iload_2
      13: istore_2
      14: return
}

解释

1
2
3
4
5
6
7
8
9
10
11
12
 0: iconst_3  // 将操作数3放到操作数栈顶
 1: istore_1  // 将操作数栈顶的数放到局部变量1(即m)中
 2: iload_1   // 将局部变量1(即m)中的数放到操作数栈顶
 3: iinc          1, 1   // 将常量1累加到局部变量1(即m)中去,但并没有改变操作数栈顶的值(此时还是栈顶还是3)
 6: istore_1   // 将操作数栈顶的数(此时为3)放到局部变量1(即m)中去,此时m为3

 7: iconst_5   // 将操作数5放到操作数栈顶,此时栈顶为5
 8: istore_2   // 将操作数栈顶的数(即5)放到局部变量2(即n)中
 9: iinc          2, 1  // 将常量1累加到局部变量2(即n)中去,但并没有改变操作数栈顶的值(此时还是栈顶还是5)
 12: iload_2   // 将局部变量2(即n)的数放到操作数栈顶去(此时栈顶为6)
 13: istore_2  // 将操作数栈顶的数放到局部变量2(即n)去,此时n数为(6)
 14: return

参考

Java 自加运算符,j = j++,j 的值为什么没有变?
javap -c命令详解

Java 中 isAssignableFrom() 方法与 instanceof

isAssignableFrom() 方法与 instanceof 关键字的区别总结为以下两个点:

  1. isAssignableFrom() 方法是从类继承的角度去判断,instanceof 关键字是从实例继承的角度去判断。
  2. isAssignableFrom() 方法是判断是否为某个类的父类,instanceof 关键字是判断是否某个类的子类。
1
2
3
父类.class.isAssignableFrom(子类.class)

子类实例 instanceof 父类类型

isAssignableFrom() 方法的调用者和参数都是 Class 对象,调用者为父类,参数为本身或者其子类。

instanceof 关键字两个参数,前一个为类的实例,后一个为其本身或者父类的类型。

案例

我们有时候需要为对象字段设置默认值,即在别的处理中生成对象并对对象进行赋值后,有些值需要有默认值,但是又不方便通过构造方法设置的时候,我们可以通过反射配合注解来为其设置默认值而不用调用一堆 set 方法。

下面这个例子即反射配置注解为对象字段设置默认值(包括父类),仅支持 String 和本类型的包装部分包装类(Number 的子类)。

首先定义注解:

1
2
3
4
5
6
7
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ParamDefaultValue {
    String value();
}

定义注解的解析类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class ParamProcessor {
    public static void applyDefaultValue(Object o) {
        Class sourceClass = o.getClass();
        //获取对象所有字段 包括父类
        ArrayList<Field> fields = new ArrayList<>();
        while (sourceClass != null){
            fields.addAll(Arrays.asList(sourceClass.getDeclaredFields()));
            sourceClass = sourceClass.getSuperclass();
        }

        for (Field field : fields) {
            field.setAccessible(true);
            if (field.isAnnotationPresent(ParamDefaultValue.class)) {
                try {
                    Object val = field.get(o);
                    if (val != null) {
                        continue;
                    }
                    Class type = field.getType();
                    if (type.isPrimitive()) {
                        continue;
                    }
                    String defVal = field.getAnnotation(ParamDefaultValue.class).value();

                    if (String.class.isAssignableFrom(type)) {
                        field.set(o, defVal);
                    } else if (Number.class.isAssignableFrom(type)) {
                        if (Byte.class.isAssignableFrom(type)) {
                            field.set(o, Byte.valueOf(defVal));
                        } else if (Float.class.isAssignableFrom(type)) {
                            field.set(o, Float.valueOf(defVal));
                        } else if (Short.class.isAssignableFrom(type)) {
                            field.set(o, Short.valueOf(defVal));
                        } else if (Integer.class.isAssignableFrom(type)) {
                            field.set(o, Integer.valueOf(defVal));
                        } else if (Double.class.isAssignableFrom(type)) {
                            field.set(o, Double.valueOf(defVal));
                        } else if (Long.class.isAssignableFrom(type)) {
                            field.set(o, Long.valueOf(defVal));
                        }
                    }

                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Java 中 instanceof 和 isInstance 区别

instanceof

obj.instanceof(class)

这个对象 obj 是不是这种 class 类型,obj 是 obj 类本身或者其子类

1.一个对象 obj 是本身类的一个对象 class

2.一个对象 obj 是本身类 class 父类(父类的父类)和接口(接口的接口)的一个对象

3.所有对象 obj 都是 Object

4.凡是 null 有关的都是 false,null.instanceof(class)

isInstance

class.inInstance(obj)

这个对象 obj 能不能被转化为这个类 class,class 是 obj 类本身或 obj 类的父类

1.一个对象 obj 是本身类 class 的一个对象

2.一个对象 obj 能被转化为本身类所继承类 class(父类的父类等)和实现的接口(接口的父接口)强转

3.所有对象 obj 都能被 Object 的强转

4.凡是 null 有关的都是 false,class.inInstance(null)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
private static void testinstanceof() {
    Father father = new Son();
    Son son = new Son();
    Father grandSon = new GrandSon();

    boolean b = father instanceof Father;
    System.out.println("father instanceof Father?" + b); // true
    boolean b1 = son instanceof Father;
    System.out.println("son instanceof Father?" + b1); // true
    boolean b2 = son instanceof Son;
    System.out.println("son instanceof Son?" + b2); // true

    boolean b21 = grandSon instanceof Father;
    System.out.println("grandSon instanceof Father?" + b); // true
    boolean b22 = grandSon instanceof Son;
    System.out.println("grandSon instanceof Son?" + b1); // true
    boolean b23 = grandSon instanceof GrandSon;
    System.out.println("grandSon instanceof GrandSon?" + b2); // true

    Object obj = new Son();
    System.out.println("Object instanceof Son?" + (obj instanceof Son)); // true
}

public static void testisinstance() {
    Father father = new Son();
    Son son = new Son();
    Father grandSon = new GrandSon();

    System.out.println(Father.class.isInstance(father)); // true,father指向son对象,可以转换为Father,Father是Son的父类
    System.out.println(Father.class.isInstance(son)); // true,father指向son对象,可以转换为Father,Father是Son的父类
    System.out.println(Father.class.isInstance(grandSon)); // true,father指向grandSon对象,可以转换为Father,Father是Son父类,Son是GrandSon的父类

    System.out.println(Son.class.isInstance(father)); // true,father指向son对象,可以转换为Son,Son本身
    System.out.println(Son.class.isInstance(son)); // true,son指向son对象,可以转换为Son,Son本身
    System.out.println(Son.class.isInstance(grandSon)); // true,grandSon指向grandSon对象,可以转换为Son,,Son是GrandSon的父类

    System.out.println(GrandSon.class.isInstance(father)); // false,father指向son对象,son不能转为GrandSon
    System.out.println(GrandSon.class.isInstance(son)); // false,son指向son对象,son不能转为GrandSon
    System.out.println(GrandSon.class.isInstance(grandSon)); // true,grandSon指向GrandSon对象,grandSon本身

    System.out.println(Integer.class.isInstance(1)); // true
    System.out.println(GrandSon.class.isInstance(null)); // false,null都是false
}

interface Father {
}

static class Son implements Father {

}

static class GrandSon extends Son {

}
本文由作者按照 CC BY 4.0 进行授权