摘要:在中存在两种类型基本类型和引用类型。值得注意的是,基本类型的值的状态不会被共享。浮点类型和它们的值中的浮点类型遵循标准的定义。布尔类型和它们的值类型表示两个逻辑量,和。
众所周知,Java是一门静态类型的语言,这意味着所有的变量和表达式的类型会在编译时确定。同时,Java 还是一门强类型的语言,因此变量的值或表达式的结果的类型都会受到限制(比如一个声明为 String 的变量不的值不可能是一个数值 1),类型之间的运算也会被限制,这有助于在编译时发现绝大多数的错误。
在 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
值得注意的是,基本类型的值的状态不会被共享。
比如下面这个例子:
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);
上述代码将输出:
0 1整数类型和它们的值
整数类型 (IntegralType) 包含了以下五种类型:
类型 | 长度 / 位 (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
字符串拼接运算符: +
这里面加号出现了好几次,包括 一元运算符、加法运算符、自增运算符、字符串拼接运算符,后三者运算符就如它们的字面意思般,很好理解。
那么 一元运算符 是什么意思呢?
让我们来看看下面这份代码:
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 = 0,is(+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 - WikiwandIEEE 754_百度百科
在 IEEE 754 标准中,定义了 32位精度的 float、64位精度的 double,还有正负数、正负0、正负无穷和特殊的 NaN。
NaN 用于表示无效操作的结果,比如 0.0 / 0.0 (0 / 0 才适用整数运算中的 右操作数为0 的异常规则),你可以在 Float.NaN 和 Double.NaN 中找到。
NaN 是无序的,因此
如果一次运算中一个或两个操作数都是 NaN,则比较运算符 (<、<=、>、>=) 都会返回 false
如果操作数是 NaN,则相等运算符 (==) 返回 false
如果 x 或 y 是 NaN,则 (x < y) == !(x >= y) 将返回 false
如果任一操作数是 NaN, 则不等式运算符 != 将返回 true
当且仅当 x 为 NaN 时,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 + " "); for (int i = 0; i < 4; i++) System.out.print(" " + (d /= 100000) + " "); 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 类型表示两个逻辑量,true 和 false。
布尔运算符是:
关系运算符 ==和!= (
逻辑补足运算符 !
逻辑运算符& , ^ 和 |
条件运算符和条件运算符&& 和 ||
条件运算符? :
字符串连接运算符 + ,当给定一个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
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/68875.html
摘要:传值和传址有什么区别是传值还是传址开始在传参时,是传值还是传址传值和传址假设要将传到。传值和传址是传值是传值。分别是基本类型,对象和数组,还有。常量池时,好比是一张纸条,当要传值给时,事实是把纸条上的内容抄给了。 传值和传址有什么区别?Java是传值还是传址? 开始 Java在传参时,是传值还是传址? 传值和传址 假设要将A传到B。如果是传值,就意味着将A中存放的值复制一份给B,B存的...
摘要:前言今天的笔记来了解一下原子操作以及中如何实现原子操作。概念原子本意是不能被进一步分割的最小粒子,而原子操作意为不可被中断的一个或一系列操作。处理器实现原子操作处理器会保证基本内存操作的原子性。 showImg(https://segmentfault.com/img/bVVIRA?w=1242&h=536); 前言 今天的笔记来了解一下原子操作以及Java中如何实现原子操作。 概念 ...
摘要:虚拟机有个一加载机制,叫做双亲委派模型。扩展类加载器扩展类加载器的父类的加载器是启动类加载器。验证验证的目的就是需要符合虚拟机的规范。虚拟机会通过加锁的方式确保方法只执行一次。 引言 上一篇文章谈到Java运行的流程,其中有一环是类加载。今天就继续深入探讨JVM如何加载虚拟机。首先JVM加载类的一般流程分三步:·加载·链接·初始化那么是否全部Java类都是这样三步走的方式加载呢?我们可...
摘要:类最基本的作用,在于通过类获取到相应的对象,在向对象发送消息时以期望对象做某些特定的事情。先导概念引用中一切皆对象,因此采用一个指向对象的引用来操纵对象。对象可以存活于作用域之外。 欢迎各位读者关注我的微信公众号,共同探讨Java相关技术。生命不止,学习不休! showImg(https://segmentfault.com/img/bVboaBO?w=129&h=129); 也许你慢...
摘要:也就是说,一个实例变量,在的对象初始化过程中,最多可以被初始化次。当所有必要的类都已经装载结束,开始执行方法体,并用创建对象。对子类成员数据按照它们声明的顺序初始化,执行子类构造函数的其余部分。 类的拷贝和构造 C++是默认具有拷贝语义的,对于没有拷贝运算符和拷贝构造函数的类,可以直接进行二进制拷贝,但是Java并不天生支持深拷贝,它的拷贝只是拷贝在堆上的地址,不同的变量引用的是堆上的...
阅读 856·2021-10-25 09:45
阅读 3251·2021-09-22 14:58
阅读 3779·2021-08-31 09:43
阅读 893·2019-08-30 15:55
阅读 889·2019-08-29 13:51
阅读 1207·2019-08-29 13:02
阅读 3467·2019-08-29 12:52
阅读 1908·2019-08-26 13:27