Java 中的类型、值和变量 之 基本类型

众所周知,Java是一门静态类型的语言,这意味着全部的变量和表达式的类型会在编译时肯定。同时,Java 仍是一门强类型的语言,所以变量的值或表达式的结果的类型都会受到限制(好比一个声明为 String 的变量不的值不多是一个数值 1),类型之间的运算也会被限制,这有助于在编译时发现绝大多数的错误。html

Java中存在两种类型:基本类型和引用类型。java

PrimitiveType:
    {Annotation} NumericType
    {Annotation} boolean
    
NumericType:
    IntegralType
    FloatingPointType
    
IntegralType:
    (one of)
    byte short int long char

FloatingPointType:
    (one of)
    float double
Java Language Specification (Java SE 8 Edition) §4.2 Primitive Types and Values

值得注意的是,基本类型的值的状态不会被共享。oracle

好比下面这个例子:code

int i = 0;
int j = i;
i += 1;
System.out.println(j);

AtomicInteger i = new AtomicInteger(0);
AtomicInteger j = i;
i.addAndGet(1);
System.out.println(j);

上述代码将输出:orm

0
1

整数类型和它们的值

整数类型 (IntegralType) 包含了如下五种类型:htm

类型 长度 / 位 (bit) 取值范围
byte 有符号 8 -128 ~ 127
short 有符号 16 -32768 ~ 32767
int 有符号 32 -214783648 ~ 2147483647
long 有符号 64 -9223372036854775808 ~ 9223372036854775807
char 无符号 16 \u0000 ~ \uffff 等价于 0 ~ 65535

整数类型所支持的运算

  • 比较运算符: <<=>>===!= ,其结果为 boolean 类型;
  • 数值运算符:对象

    • 一元运算符: +-
    • 乘法运算符: */%
    • 加法运算符: +-
    • 自增运算符: ++, 分为前缀自增 (++i) 和后缀自增 (i++)
    • 自减运算符: --, 分为前缀自减 (--i) 和后缀自减 (i--)
    • 位移运算符:内存

      • 左移运算符: <<
      • 有符号右移: >>
      • 无符号右移: >>>
    • 按位互补运算符: ~
    • 整数按位运算符: &^|
  • 条件运算符: ? :
  • 类型转换运算符: cast
  • 字符串拼接运算符: +

这里面加号出现了好几回,包括 一元运算符加法运算符自增运算符字符串拼接运算符,后三者运算符就如它们的字面意思般,很好理解。ci

那么 一元运算符 是什么意思呢?字符串

让咱们来看看下面这份代码:

static void is (short i) {
    System.out.println("short");
}

static void is (int i) {
    System.out.println("int");
}

static void is (long i) {
    System.out.println("long");
}

static void main (String[] args) {
    short i = 5;
    int j = 10;
    is(i);
    is(+i);
    is(-i);
    is(j);
    is(+j);
    is(-j);
}

上述代码将输出:

short
int
int
int
int
int

很显然,第 17~19 行的调用执行的是参数类型为 int 的方法,然而第 20~21 行的调用执行的并非参数类型为 long 的方法。

我在 JSL § 15.15.3 Unary Plus Operator + 中并未看出一元运算符的具体影响,根据实验结果只能推测一元运算符会将低于 int 的数值类型提高到 int 类型 (你能够声明一个 byte h = 0is(+h) 仍然会调用参数类型为 int 的方法),并且对于 +- 都是提高类型的做用,并非直觉意义上的一个升一个降。

关于这个 一元运算符 的用法其实并非不少,有下列几种:

// 若是方法接受的是 int 类型的话,仅仅用来明确这是个正数仍是负数。
func(+1);
func(-1);

// 输出字符的代码值时的小技巧
// 由于字符类型 char 是低于 int 的整数类型
System.out.println(+'c'); // 99

整数运算的溢出和可能引起的异常

对于移位运算符之外的整数运算而言,若是两个操做数中至少有一个 long ,那么此次运算将会按 64位精度进行计算,而且其计算结果也是 long 类型,此时若是另外一个操做数不是 long,那么会将它提高到 long 类型再计算;若是两个操做数都不是 long,那么会按 32位精度进行计算,而且计算结果为 int,若是任何一个操做数不是 int, 那么都将提高到 int 类型后再计算。

整数运算符会在如下状况抛出异常:

  • 涉及到对引用类型拆箱时,若是是空引用,那么会抛出 NullPointerException
  • 若是右操做数为零,那么整数除法运算符和整数取余运算符都会抛出 ArithmeticException
  • 在自增和自减的时候,若是涉及到拆箱装箱且内存不足,会抛出 OutOfMemoryError

来看看规范中给出的示例代码:

class Test {
    public static void main (String[] args) {
        int i = 1000000;
        System.out.println(i * i);
        long l = i;
        System.out.println(l * l);
        System.out.println(20296 / (l - i));
    }
}

上述代码将输出:

-727379968
1000000000000
ArithmeticException

对于 int 来讲,1000000^2 太大了,而因为以前的运算规则,i * i 只能保存结果的低32位,十进制下也就是 -727379968

浮点类型和它们的值

Java 中的浮点类型遵循 IEEE 754 标准的定义。

IEEE 754-1985 - Wikiwand

IEEE 754_百度百科

IEEE 754 标准中,定义了 32位精度的 float、64位精度的 double,还有正负数、正负0、正负无穷和特殊的 NaN

NaN 用于表示无效操做的结果,好比 0.0 / 0.0 (0 / 0 才适用整数运算中的 右操做数为0 的异常规则),你能够在 Float.NaNDouble.NaN 中找到。

NaN 是无序的,所以

  • 若是一次运算中一个或两个操做数都是 NaN,则比较运算符 (<<=>>=) 都会返回 false
  • 若是操做数是 NaN,则相等运算符 (==) 返回 false

    若是 xyNaN,则 (x < y) == !(x >= y) 将返回 false

  • 若是任一操做数是 NaN, 则不等式运算符 != 将返回 true

    当且仅当 xNaN 时,x != x 将返回 true

浮点类型所支持的运算

  • 比较运算符: <<=>>===!= ,其结果为 boolean 类型;
  • 数值运算符:

    • 一元运算符: +-
    • 乘法运算符: */%
    • 加法运算符: +-
    • 自增运算符: ++, 分为前缀自增 (++i) 和后缀自增 (i++)
    • 自减运算符: --, 分为前缀自减 (--i) 和后缀自减 (i--)
  • 条件运算符: ? :
  • 类型转换运算符: cast
  • 字符串拼接运算符: +

若是一次计算中,至少有一个二元运算符的操做数是浮点类型的,那么该操做就是一个浮点运算,即便另外一个操做数是整数。

System.out.println(10 * 0.1); // 1.0

对于浮点运算而言,若是两个操做数中至少有一个 double,那么此次运算将会按 64位精度进行计算,而且其计算结果也是 double 类型,此时若是另外一个操做数不是 double,那么会将它提高到 double 类型再计算;若是两个操做数都不是 double,那么会按 32位精度进行计算,而且计算结果为 float,若是任何一个操做数不是 float, 那么都将提高到 float 类型后再计算。

浮点运算的溢出和可能引起的异常

浮点运算有溢出 (overflows) 和下溢 (underflows),其中溢出将产生有符号的无穷大,而下溢则产生一个非标准化 (denormalized) 的值或是一个有符号的0。

数学上没法肯定结果的浮点运算将产生 NaN

全部 NaN 参与的浮点运算都会产生 NaN

在下列状况中,浮点运算会抛出异常:

  • 计算时须要拆箱,而又是个空引用时,会抛出 NullPointerException
  • 自增自减的状况下,若是须要拆箱装箱且内存不够时,会抛出 OutOfMemoryError

接下来看看规范中给出的示例代码:

class Test {
    public static void main(String[] args) {
        // 溢出
        double d = 1e308;
        System.out.print("溢出产生了无穷大: ");
        System.out.println(d + "*10==" + d*10);
        // 渐变下溢 (gradual underflow)
        d = 1e-305 * Math.PI;
        System.out.print("渐变下溢: " + d + "\n   ");
        for (int i = 0; i < 4; i++)
            System.out.print(" " + (d /= 100000) + "\n");
        System.out.println();
        // 产生 NaN
        System.out.print("0.0/0.0 产生的不是数字: ");
        d = 0.0/0.0;
        System.out.println(d);
        // 产生不精确结果的四舍五入:
        System.out.print("单精度下的不精确结果:");
        for (int i = 0; i < 100; i++) {
            float z = 1.0f / i;
            if (z * i != 1.0f)
                System.out.print(" " + i);
        }
        System.out.println();
        // 另外一个产生不精确结果的四舍五入:
        System.out.print("双精度下的不精确结果:");
        for (int i = 0; i < 100; i++) {
            double z = 1.0 / i;
            if (z * i != 1.0)
                System.out.print(" " + i);
        }
        System.out.println();
        // 转换到整数时发生的结果阶段:
        System.out.print("强制转换到整数: ");
        d = 12345.6;
        System.out.println((int)d + " " + (int)(-d));
    }
}

上述代码将输出

溢出产生了无穷大: 1.0e + 308 * 10 == Infinity
渐变下溢: 3.141592653589793E-305
 3.1415926535898E-310
 3.141592653E-315
 3.142E-320 0.0
 0.0 / 0.0 产生的不是数字:NaN
单精度下的不精确结果:0 41 47 55 61 82 83 94 97
双精度下的不精确结果:0 49 98
强制转换到整数:12345 -12345

值得注意的是,在 渐变下溢 的例子中,咱们能够看到精度逐渐丧失。

布尔类型和它们的值

boolean 类型表示两个逻辑量,truefalse

布尔运算符是:

  • 关系运算符 ==!=
  • 逻辑补足运算符 !
  • 逻辑运算符&^|
  • 条件运算符和条件运算符&&||
  • 条件运算符? :
  • 字符串链接运算符 + ,当给定一个String操做数和一个 boolean 操做数时,它将把 boolean 操做符转换为一个String"true""false" ),而后产生一个新建立的String,其值为两个字符串的链接结果。

布尔表达式决定了几种语句中的控制流:

  • if 语句
  • while 语句
  • do 语句
  • for 语句

一个boolean表达式还决定在 ? : 运算符中使用哪一个子表达式的值做为结果 。

只有Boolean表达式和Boolean表达式能够在控制流程语句中使用。

经过表达式 x!=0 ,能够将整数或浮点表达式 x 转换为 boolean 值,这遵循了 C 语言约定,即任何非零值为true

经过表达式 obj!=null ,能够将对象引用 obj 转换为boolean值,这一样遵循了 C 语言约定(除null以外的任何引用为true

参考资料: Java Language Specification (Java SE 8 Edition) § 4.2
相关文章
相关标签/搜索