zoukankan      html  css  js  c++  java
  • Java语言规范

    Java基础技术细节总结 - 语言规范

    开发莫忘基础,写业务写多了很多基础内容容易忘。这里将寻根溯源,总结Java语言规范和基础类中的一些细节问题。所有关于Java语言规范的细节问题,都可以参考 The Java® Language Specification, Java SE 8 Edition(JLS8) .

    本文将不断补充。。

    小数化为整数

    • Math.floor(x)返回小于等于x的最接近整数,返回类型为double;
    • Math.round(x)相当于四舍五入,返回值为long或int;
    • Math.ceil(x)返回大于等于x的最接近整数,返回类型为double。

    静态块与构造块

    静态块:用static申明,JVM加载类时执行,仅执行一次且优先于主函数。
    构造块:类中直接用{}定义,每一次创建对象时执行,相当于往构造器最前面加上构造块的内容(很像往每个构造器那里插了内联函数,构造块就相当于内联函数)。

    执行顺序优先级:静态块 > 构造块 > 构造方法

    有继承关系时,执行顺序通常是:父类静态块=>子类静态块=>父类构造块=>父类构造方法=>子类构造块=>子类构造方法
    测试:

    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
    public class test {
    public static void main(String[] args) {
    new Derived();
    }
    }
     
    class Base {
    static {
    System.out.println("fucking => Base::static");
    }
     
    {
    System.out.println("fucking => Base::before");
    }
     
    public Base() {
    System.out.println("Base::Base<init>");
    }
    }
     
    class Derived extends Base {
    static {
    System.out.println("fucking => Derived::static");
    }
     
    {
    System.out.println("fucking => Derived::before");
    }
     
    public Derived() {
    super();
    System.out.println("Derived::Derived<init>");
    }
    }

    输出:

    1
    2
    3
    4
    5
    6
    fucking => Base::static
    fucking => Derived::static
    fucking => Base::before
    Base::Base<init>
    fucking => Derived::before
    Derived::Derived<init>

    运算符规则 - 加法规则

    代码片段:

    1
    2
    3
    4
    5
    byte b1 = 1, b2 = 2, b3, b6;
    final byte b4 = 4, b5 = 6;
    b6 = b4 + b5;
    b3 = (b1 + b2);
    System.out.println(b3 + b6);

    结果:第四行编译错误。

    表达式的数据类型自动提升, 关于类型的自动提升,注意下面的规则。

    1. 所有的byte,short,char型的值将被提升为int
    2. 如果有一个操作数是long型,计算结果是long
    3. 如果有一个操作数是float型,计算结果是float
    4. 如果有一个操作数是double型,计算结果是double

    而声明为final的变量会被JVM优化,因此第三句在编译时就会优化为b6 = 10,不会出现问题。

    float x 与“零值”比较的if语句

    1
    if (fabs(x) < 0.00001f)

    float类型的还有double类型的,这些小数类型在趋近于0的时候不会直接等于零,一般都是无限趋近于0。因此不能用==来判断。应该用|x-0| < err来判断,这里|x-0|表示绝对值,err表示限定误差,用程序表示就是fabs(x) < 0.00001f

    关于try和finally

    1.首先执行到try里的return,但是有finally语句还要执行,于是先执行return后面的语句,例如(x++),把要返回的值保存到局部变量。
    2.执行finally语句的内容,其中有return语句,这时就会忽略try中的return,直接返回。

    返回值问题。可以认为try(或者catch)中的return语句的返回值放入线程栈的顶部:如果返回值是基本类型则顶部存放的就是值,如果返回值是引用类型,则顶部存放的是引用。finally中的return语句可以修改引用所对应的对象,无法修改基本类型。但不管是基本类型还是引用类型,都可以被finally返回的“具体值”具体值覆盖。

    三目运算符的类型转换问题

    三目运算符里的类型必须一致,比如下面的代码:

    1
    2
    3
    4
    int i = 40;
    String as_e1 = String.valueOf(i < 50 ? 233 : 666);
    String as_e2 = String.valueOf(i < 50 ? 233 : 666.0);
    assertEquals(true, as_e1.equals(as_e2));

    结果是测试不通过,这里就涉及到三元操作符的转换规则:

    1. 如果两个操作数无法转换,则不进行转换,返回Object对象
    2. 如果两个操作数是正常的类型,那么按照正常情况进行类型转换,比如int => long => float => double
    3. 如果两个操作数都是字面量数字,那么返回范围较大的类型

    Java中自增操作符的一些陷阱

    观察下面的一段代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class AutoIncTraps {
     
    public static void main(String[] args) {
    int count = 0;
    for(int i = 0; i < 10; i++) {
    count = count++;
    }
    System.out.println(count);
    }
    }

    这段代码的打印结果是0,也就是说自增在这里并没有什么卵用,这和C++是不一样的。反编译一下看一下字节码(main函数部分):

    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
    public static main([Ljava/lang/String;)V
    L0
    LINENUMBER 6 L0
    ICONST_0
    ISTORE 1
    L1
    LINENUMBER 7 L1
    ICONST_0
    ISTORE 2
    L2
    FRAME APPEND [I I]
    ILOAD 2
    BIPUSH 10
    IF_ICMPGE L3
    L4
    LINENUMBER 8 L4
    ILOAD 1
    IINC 1 1
    ISTORE 1
    L5
    LINENUMBER 7 L5
    IINC 2 1
    GOTO L2
    L3
    LINENUMBER 10 L3
    FRAME CHOP 1
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ILOAD 1
    INVOKEVIRTUAL java/io/PrintStream.println (I)V
    L6
    LINENUMBER 11 L6
    RETURN

    这里相当于创建了一个局部变量存放count++,但没有返回,因此count相当于没变。看了字节码后可能没感觉,写一下编译器处理后的代码吧:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class AutoIncTraps {
    public AutoIncTraps() {
    }
     
    public static void main(String[] args) {
    byte count = 0;
     
    for(int i = 0; i < 10; ++i) {
    int var3 = count + 1;
    count = count;
    }
     
    System.out.println(count);
    }
    }

    总结一下这里count的处理流程:

    1. JVM把count值(其值是0)拷贝到临时变量区。
    2. count值加1,这时候count的值是1。
    3. 返回临时变量区的值,注意这个值是0,没有修改过。
    4. 返回值赋值给count,此时count值被重置成0。

    单纯看这一个的字节码比较抽象,来看一下这三句的字节码,比较一下更容易理解:

    1
    2
    3
    count = ++count;
    count = count++;
    count++;

    字节码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    L4
    LINENUMBER 9 L4
    IINC 1 1
    ILOAD 1
    ISTORE 1
    L5
    LINENUMBER 10 L5
    ILOAD 1
    IINC 1 1
    ISTORE 1
    L6
    LINENUMBER 11 L6
    IINC 1 1

    另外,自增操作不是原子操作,在后边总结并发编程的时候会涉及到。

    instanceof操作符的注意事项

    instanceof操作符左右两边的操作数必须有继承或派生关系,否则不会编译成功。因此,instanceof操作符只能用于对象,不能用于基本类型(不会自动拆包)。

    下面是一些典型的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class FuckingIOF {
     
    @Test
    public void test() {
    List<Object> list = new ArrayList<>();
    list.add("String" instanceof Object);
    list.add(new String() instanceof Object);
    list.add(new Object() instanceof String);
    //list.add('a' instanceof Character); //此句会编译错误
    list.add(null instanceof String);
    list.add((String)null instanceof String);
    list.add(null instanceof Object);
    list.add(new Generic<String>().isDataInstance(""));
    list.forEach(System.out::println);
    }
    }
     
    class Generic<T> {
    public boolean isDataInstance(T t) {
    return t instanceof Date;
    }
    }

    运行结果和分析:

    1
    2
    3
    4
    5
    6
    7
    true => String是Object的子类
    true => 同上
    false => 同上
    false => Java语言规范规定null instanceof ? 都是false
    false => 同上,无论怎么转换还是null
    false => 同上
    false => 由于Java泛型在编译时会进行类型擦除,因此这里相当于Object instanceof Date了

    诡异的NaN类型

    根据 JLS8 4.2.3,对NaN有以下规定:

    • The numerical comparison operators < , <= , > , and >= return false if either or both operands are NaN (§15.20.1).
    • The equality operator == returns false if either operand is NaN.
    • In particular, (x<y) =="!(x">=y) will be false if x or y is NaN.
    • The inequality operator != returns true if either operand is NaN (§15.21.1).
    • In particular, x!=x is true if and only if x is NaN.

    注意到Double.NaN == Double.NaN返回false,这其实是遵循了IEEE 754 standard。NaN 代表一个非正常的数(比如除以0得到的数),其定义为:

    1
    2
    3
    4
    5
    6
    /**
    * A constant holding a Not-a-Number (NaN) value of type
    * {@code double}. It is equivalent to the value returned by
    * {@code Double.longBitsToDouble(0x7ff8000000000000L)}.
    */
    public static final double NaN = 0.0d / 0.0;

    Integer类的静态缓存 && valueOf和parseInt的对比

    这个问题是在StackOverflow上看到的。以下三个表达式:

    1
    2
    3
    System.out.println(Integer.valueOf("127") == Integer.valueOf("127"));
    System.out.println(Integer.valueOf("128") == Integer.valueOf("128"));
    System.out.println(Integer.parseInt("128") == Integer.valueOf("128"));

    结果分别是:

    1
    2
    3
    true
    false
    true

    为什么是这样的结果呢?我们看一下valueOf方法的源码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static Integer valueOf(String s) throws NumberFormatException {
    return Integer.valueOf(parseInt(s, 10));
    }
     
    public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
    return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
    }

    可以看到valueOf方法是在parseInt方法的基础上加了一个读取缓存的过程。我们再看一下IntegerCache类的源码:

    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
    /**
    * Cache to support the object identity semantics of autoboxing for values between
    * -128 and 127 (inclusive) as required by JLS.
    *
    * The cache is initialized on first usage. The size of the cache
    * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
    * During VM initialization, java.lang.Integer.IntegerCache.high property
    * may be set and saved in the private system properties in the
    * sun.misc.VM class.
    */
     
    private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];
     
    static {
    // high value may be configured by property
    int h = 127;
    String integerCacheHighPropValue =
    sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
    if (integerCacheHighPropValue != null) {
    try {
    int i = parseInt(integerCacheHighPropValue);
    i = Math.max(i, 127);
    // Maximum array size is Integer.MAX_VALUE
    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
    } catch( NumberFormatException nfe) {
    // If the property cannot be parsed into an int, ignore it.
    }
    }
    high = h;
     
    cache = new Integer[(high - low) + 1];
    int j = low;
    for(int k = 0; k < cache.length; k++)
    cache[k] = new Integer(j++);
     
    // range [-128, 127] must be interned (JLS7 5.1.7)
    assert IntegerCache.high >= 127;
    }
     
    private IntegerCache() {}
    }

    原来JVM会缓存一部分的Integer对象(默认范围为-128 - 127),在通过valueOf获取Integer对象时,如果是缓存范围内的就直接返回缓存的Integer对象,否则就会new一个Integer对象。返回的上限可通过JVM的参数-XX:AutoBoxCacheMax=<size>设置,而且不能小于127(参照JLS 5.1.7)。这样我们就可以解释Integer.valueOf("127") == Integer.valueOf("127")为什么是true了,因为它们获取的都是同一个缓存对象,而默认情况下Integer.valueOf("128") == Integer.valueOf("128")等效于new Integer(128) == new Integer(128),结果自然是false。

    我们再来看一下parseInt方法的原型,它返回一个原生int值:

    1
    public static int parseInt(String s) throws NumberFormatException

    由于一个原生值与一个包装值比较时,包装类型会自动拆包,因此Integer.parseInt("128") == Integer.valueOf("128")就等效于128 == 128,结果自然是true。

  • 相关阅读:
    SpringSecurity实现权限管理和页面导航栏动态实现
    “菜鸟”程序员和“大神”程序员差距在哪里
    错误提示:Dynamic Performance Tables not accessible, Automatic Statistics Disabled for this session You can disable statistics in the preference menu,or obtanin select priviliges on the v$session,v$sess
    oracle的知识点总结
    linuxVim命令合集
    Spring Security 整合freemaker 实现简单登录和角色控制
    oracle 创建的表为什么在table里没有,但是可以查出来
    头发对我来说已经不重要了!
    IntelliJ Idea 常用快捷键列表
    Firefox 笔记
  • 原文地址:https://www.cnblogs.com/lsohvaen001/p/7857890.html
Copyright © 2011-2022 走看看