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。

本文标题:Java基础技术细节总结 - 语言规范

文章作者:sczyh30

发布时间:2015年09月18日

原始链接:http://www.sczyh30.com/posts/Java/java-basic-summary-01/

许可协议: "知识共享-保持署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

时间: 2024-10-04 15:17:06

Java语言规范的相关文章

JAVA语言规范和API网址

Java语言规范: http://docs.oracle.com/javase/specs/ Java API: http://docs.oracle.com/javase/8/docs/api/index.html (JDK1.8) http://docs.oracle.com/javase/7/docs/api/index.html (JDK1.7) http://docs.oracle.com/javase/6/docs/api/index.html (JDK1.6)  

java语言规范要求equals方法具有下面的特性

java语言规范要求equals方法具有下面的特性: (1)自反性:对于任何非空引用x,x.equals(x)应该返回true; (2)对称性:对于任何引用x,和y,当且仅当,y.equals(x)返回true,x.equals(y)也应该返回true; (3)传递性:对于任何引用x,y,z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也应该返回true; (4)一致性:如果x,y引用的对象没有发生变化,反复调用x.equals(y)应该

如何从oracle官网中下载The java language specification(java 语言规范)

第一步: 第二步: 第三步:下面这个图在这个页面的下方,所以你只要一直往下看,直到看到下图的文字为止: 第四步: 第五步: 这样你就可以成功下载该java 语言规范的pdf了. 它直接下载的网址为: http://docs.oracle.com/javase/specs/index.html

Java学习笔记(Java语言规范,API,JDK,IDE)

Java语言规范:java.sun.com/docs/books/jls 三个版本的API:J2SE J2EE J2ME 1. J2SE 客户端独立应用程序或者applet 2. J2EE 服务端应用程序 [Java Servlets&JavaServer Page] 3. J2ME 移动设备变成 JDK为Java开发提供一个开发环境(IDE) Java学习笔记(Java语言规范,API,JDK,IDE)

java 语言规范 java language specifications

在线地址: https://docs.oracle.com/javase/specs/ java语言规范下载: 链接:http://pan.baidu.com/s/1miEpJwk 密码:f89v java的各种规范(JSRs by Platform):https://jcp.org/en/jsr/platform?listBy=2&listByType=platform

java语言规范,main方法必须声明为public

注释: 根据java语言规范,main方法必须声明为public. 当main方法不是public时,有些版本的java解释器也可以执行java应用程序.有个程序员报告了这个bug. 如果感兴趣可以查一下这个bug号码4252539.这个bug被标明"关闭",不予修复.Sun公司的工程师解释说:java虚拟规范并没有要求main方法一定是public. 好在,这个问题在 java SE1.4及以后的版本中强制main方法是public 最终的到了解决.

JAVA命名规范

定义规范的目的是为了使项目的代码样式统一,使程序有良好的可读性. 包的命名  (全部小写,由域名定义) Java包的名字都是由小写单词组成.但是由于Java面向对象编程的特性,每一名Java程序员都 可以编写属于自己的Java包,为了保障每个Java包命名的唯一性,在最新的Java编程规范中,要求程序员在自己定义的包的名称之前加上唯一的前缀. 由于互联网上的域名称是不会重复的,所以程序员一般采用自己在互联网上的域名称作为自己程序包的唯一前缀. 例如:net.frontfree.javagroup

java虚拟机规范阅读(四)Java虚拟机指令集简介

Java 虚拟机的指令由一个字节长度的.代表着某种特定操作含义的操作码(Opcode)以及跟随其后的零至多个代表此操作所需参数的操作数(Operands)所构成.虚拟机中许多指令并不包含操作数,只有一个操作码. 如果忽略异常处理,那 Java 虚拟机的解释器使用下面这个伪代码的循环即可有效地工作: do {   自动计算 PC 寄存器以及从 PC 寄存器的位置取出操作码;   if (存在操作数) 取出操作数;   执行操作码所定义的操作 } while (处理下一次循环); do { 自动计算

谷歌Java编程规范

Google Java编程风格指南 January 20, 2014 作者:Hawstein 出处:http://hawstein.com/posts/google-java-style.html 声明:本文采用以下协议进行授权: 自由转载-非商用-非衍生-保持署名|Creative Commons BY-NC-ND 3.0 ,转载请注明作者及出处. 目录 前言 源文件基础 源文件结构 格式 命名约定 编程实践 Javadoc 后记 前言 这份文档是Google Java编程风格规范的完整定义.