0%

Java编程思想#操作符

一、操作符

Java中几乎所有的操作符只能操作基本类型,例外的操作符有===!=,这些操作符能够操作所有的对象,除此之外,++=操作符被String类支持。
具体操作符类别适用的类型范围无需特别关注,比如“逻辑操作符仅适用于boolean基本类型,移位操作符不适用于boolean基本类型”等,因为上述范围是显而易见的。
当一个表达式中存在多个操作符时,操作符的优先级就决定了各部分的计算顺序。最简单的规则是先乘除后加减,最好用括号明确规定计算顺序。
操作符描述见表1。

表1

操作符类别 操作符列表
赋值操作符 =
算术操作符 +-*/%
递增和递减操作符 ++--
关系操作符 <><=>===!=
逻辑操作符 &&||!
按位操作符 &|^~
移位操作符 <<>>>>>
包含赋值操作符的复合操作符 +=-=*=/=&=,…,<<=>>=
三元操作符 boolean-exp?value0:value1

备注:

  1. 递增和递减操作符。有“前缀式”和“后缀式”两种使用形式
  2. 逻辑操作符。使用逻辑操作符,存在短路现象。有些情况下对短路机制加以利用,可获得性能提升
  3. 移位操作符。<<,左移操作符,低位补0;>>,有符号右移操作符,高位补符号位;>>>,无符号右移操作符,高位补0
  4. 按位操作符。布尔类型可被作为单比特值对待,因而可对布尔类型使用除了~之外的按位操作符,即&|^。另外,对布尔类型使用按位操作符,不会产生短路现象。示例代码如下:
    1
    2
    3
    4
    5
    //会短路
    boolean flag1 = true && false && true;

    //不会短路
    boolean flag2 = true & false & true;

    二、基本类型之间的类型转换

    2.1、基本类型再细化

    根据Java编程思想#一切都是对象可知,基本类型有:boolean,byte,short,char,int,float,long,double。又可再细分为布尔型(boolean),整型(byte,short,char,int,long)和浮点型(float,double)这3类。

    2.2、类型转换分类

分类一:基本类型之间的类型转换根据是否需要明确指定类型转换过程而分为,显式类型转换和隐式类型转换
分类二:根据转换前后数值表达范围的变化可分为,窄化转换和扩展转换。窄化转换可能造成数据丢失,扩展转换不会造成数据丢失。窄化转换必须是显式转换,扩展转换既允许是隐式转换,也允许是显式转换
分类三:Java允许把任何基本类型转换成别的基本类型,但布尔型除外,布尔型根本不允许进行任何的类型转换。因此,最终允许的转换情形是:整型集合内部的类型转换,浮点型集合内部的类型转换以及整型和浮点型之间的类型转换

2.2.1、整型到浮点型

扩展转换,既允许是隐式转换,也允许是显式转换。

2.2.2、浮点型到整型

窄化转换,必须是显式转换,处理方式是截尾,可能丢失信息。示例如下:

1
2
3
4
5
6
7
//未丢失信息
float f = 1F;
int a = (int) f;

//丢失信息
float f = 1.1F;
int a = (int) f;

2.2.3、整型到整型

如果是扩展转换,既允许是隐式转换,也允许是显式转换,不丢失信息;如果是窄化转换,必须是显式转换,处理方式是截取低端位相应位数的二进制位,可能丢失信息。示例如下:

1
2
3
4
5
6
7
8
9
//丢失信息
int a = 0x1ff;
byte b = (byte) a;
//那么截取低端位8位二进制位,得到b=0xff

//未丢失信息
int a = 0x1f;
byte b = (byte) a;
//那么截取低端位8位二进制位,得到b=0x1f

2.2.4、浮点型到浮点型

如果是扩展转换,既允许是隐式转换,也允许是显式转换,不丢失信息;如果是窄化转换,必须是显式转换,处理方式按照IEEE 754标准规范[1],可能丢失信息。

2.3、隐式扩展类型转换触发

2.3.1、操作符触发

当使用的操作符为“算术操作符,按位操作符,移位操作符”且操作数为“整型基本类型”时,操作符要求操作数的类型至少是“int型”,即可以是int型或者long型,如果是byte型,short型,char型则自动转换成int型。
以下示例以byte型为例,short型和char型同理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
byte ba = 1;
byte bb = 2;

//算术操作符的例子,编译错误。因为:右边表达式中ba和bb会被隐式扩展成int型,而左边变量为byte型
byte bc = ba + bb;
byte bd = ba - bb;
byte be = ba * bb;
byte bf = ba / bb;
byte bg = ba % bb;

//按位操作符的例子,编译错误。因为:右边表达式中ba和bb会被隐式扩展成int型,而左边变量为byte型
byte bh = ba & bb;

//移位操作符的例子,编译错误。因为:右边表达式中ba和bb会被隐式扩展成int型,而左边变量为byte型
byte bi = ba >> bb;

2.3.2、表达式中最大的数据类型决定了最终结果的数据类型

比如操作符两边都为整型,其中一边是整型中的A类型,另一边是整型中的B类型,A类型大于B类型,那么B类型变量会被隐式扩展成A类型。
比如如下例子:

1
2
3
4
5
6
7
//byte型的a被隐式扩展成int型
byte a = 10;
int b = a;

//int型的b被隐式扩展成long型
long c = 10;
long d = b + c;

又比如操作符两边都为浮点型,其中一边是float型,另一边是double型,那么float型会被隐式扩展成double型;又比如操作符两边中一边是整型,另一边是浮点型,那么整型会被隐式扩展成浮点型。

2.3.3、方法调用过程中的隐式扩展类型

方法形参声明类型为A类型(整型或者浮点型),方法调用传入实参类型为B类型(整型或者浮点型),A类型大于B类型,那么B类型变量会被隐式扩展成A类型。
比如如下例子:

1
2
3
4
5
6
7
8
9
10
11
12
public class MethodArguExtending {

static void f(int a) {
System.out.println("int value is : " + a);
}

public static void main(String[] args) {
short b = 10;
f(b);
}

}

特别需要注意的是,如果方法有重载,方法调用时,会绑定到最契合传入实参的方法,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MethodArguExtending {

static void f(int a) {
System.out.println("int value is : " + a);
}

static void f(short a) {
System.out.println("short value is : " + a);
}

public static void main(String[] args) {
short b = 10;
f(b);
}

}

运行后得到结果如下:

1
short value is : 10

2.4、特殊的窄化转换

由以上描述可知,窄化转换必是显式转换,但是有一个特殊的窄化转换可以不必是显式转换,可以认为这个特殊情况由编译器负责处理
有形如(byte/short/char) 复合操作符(算术操作符的复合操作符,按位操作符的复合操作符,移位操作符的复合操作符) (另外一个整型或者浮点型)的表达式,根据“2.3.1、操作符触发”和“2.3.2、表达式中最大的数据类型决定了最终结果的数据类型”小节内容,右侧表达式值类型至少为int型,因此赋值时是窄化转换,但是不进行显式转换,编译仍可顺利通过;而使用不使用复合操作符的等价表达式形式,这个窄化转换必须进行显式转换,否则编译不能通过。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
byte a = 10;

byte b = 1;
int c = 1;
long l = 1L;
float f = 10.0F;
double d = 10.0D;

//以下不必进行显式转换
a += b;
a -= c;
a >>= l;
a |= l;
a += f;
a -= d;

//不使用复合操作符的等价表达式形式,必须进行显式转换
a = (byte) (a + b);
a = (byte) (a - c);
a = (byte) (a >> l);
a = (byte) (a | l);
a = (byte) (a + f);
a = (byte) (a - d);

三、数值直接常量

在Java中有3种数值直接常量,分别是:十六进制数值常量,八进制数值常量和十进制数值常量。
十六进制数值常量:以前缀0x(0X),后面跟随0-9或小写(或大写)的a-f来表示,比如0xffff。只可表示整型。
八进制数值常量:由前缀0以及后续的0-7来表示,比如0177。只可表示整型。
十进制数值常量:由0-9组成,如果是整型首位非0(整型情况下,首位为0表示八进制)。比如199,1000.321213。既可表示整型,也可表示浮点型。

数值直接常量隐含对应一个基本类型的变量。关于数值直接常量隐含对应一个基本类型的变量有以下3条规则。

3.1、规则1

在整型情形下(十六进制数值常量,八进制数值常量,十进制数值常量),数值直接常量对应的基本类型是int,通过在数值常量后加“l(或者L)”后缀,可以使得对应的基本类型是long。
在浮点型情形下(十进制数值常量),数值直接常量对应的基本类型是double,通过在数值常量后加“f(或者F)”后缀,可以使得对应的基本类型是float。通过在数值常量后加“d(或者D)”后缀,可以使得对应的基本类型是double。
示例代码如下:

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
public class Main {

public static void main(String[] args) {
Main main = new Main();
main.f1(5);
//main.f1(0x1fffffffff);
}

void f1(char x) {
System.out.println("f1(char)");
}

void f1(byte x) {
System.out.println("f1(byte)");
}

void f1(short x) {
System.out.println("f1(short)");
}

void f1(int x) {
System.out.println("f1(int)");
}

void f1(long x) {
System.out.println("f1(long)");
}

void f1(float x) {
System.out.println("f1(float)");
}

void f1(double x) {
System.out.println("f1(double)");
}
}

运行后得到的结果如下:

1
f1(int)

从中可知,数值直接常量5对应的基本类型是int。
将注释语句的注释去掉,可以发现,编译器报如下错误:

1
Integer number too large

这是因为数值直接常量“0x1fffffffff”对应的基本类型是int,而它超出了int的表示范围,因而报出如上错误,如果在“0x1fffffffff”后加上L,那么显式指定对应的基本类型是long,编译就能通过。

又比如有以下代码:

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
public class Main {

public static void main(String[] args) {
Main main = new Main();
main.f1(5.0);
main.f1(3.5E39);
//main.f1(3.5E39F);
}

void f1(char x) {
System.out.println("f1(char)");
}

void f1(byte x) {
System.out.println("f1(byte)");
}

void f1(short x) {
System.out.println("f1(short)");
}

void f1(int x) {
System.out.println("f1(int)");
}

void f1(long x) {
System.out.println("f1(long)");
}

void f1(float x) {
System.out.println("f1(float)");
}

void f1(double x) {
System.out.println("f1(double)");
}
}

运行后得到结果如下:

1
2
f1(double)
f1(double)

从中可知,数值直接常量5.0和3.5E39对应的基本类型是double。
将注释语句的注释去掉,可以发现,编译器报如下错误:

1
Floating point number too large

这是因为显式指定数值直接常量“3.5E39F“对应的基本类型是float,而它超出了float的表示范围,因而报出如上错误。

3.2、规则2

在Java程序中碰到数值直接常量,以其隐含对应的基本类型变量的身份去考虑,可以帮助我们理解,减少错误的发生。

3.3、规则3

关于“在Java程序中碰到数值直接常量,以其隐含对应的基本类型变量的身份去考虑”有一个例外:当数值直接常量为整型,且隐含对应的基本类型是int型,去给byte类型,short类型,char类型变量赋值,且数值直接常量的数值在相应变量的表示范围内,虽说此时仍旧可以以数值直接常量隐含对应的int类型变量身份去考虑问题,但是却不像真正的int类型变量那样需要显式窄化转换,可以认为这个特殊情况由编译器负责处理
除此情况外,当碰到数值直接常量时,就完全可以以其隐含对应的基本类型变量的身份去考虑问题:需要显式转换就显式转换,无需显式转换就无需显式转换。
可见如下代码所示例子:

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
public class Main {

public static void main(String[] args) {
byte b;
short s;
char c;
int i;
long l;
float f;
double d;

int itmp;
long ltmp;
float ftmp;
double dtmp;

// 分隔符------------------------
// 无需显式转换,验证的确无需显式转换
f = 10;
f = 10L;
d = 10;
d = 10L;
f = 10.0F;
d = 10.0;
d = 10.0D;
d = 10.0F;

// 验证
itmp = 10;
ltmp = 10L;
ftmp = 10.0F;
dtmp = 10.0D;

f = itmp;
f = ltmp;
d = itmp;
d = ltmp;
f = ftmp;
d = dtmp;
d = ftmp;

// 分隔符------------------------
// 需要显式转换,验证的确需要显式转换
f = (float) 10.0;

// 验证
dtmp = 10.0;
f = (float) dtmp;

// 分隔符------------------------
// 无需显式转换,验证的确无需显式转换
i = 10;
l = 10L;

// 验证
itmp = 10;
ltmp = 10L;

i = itmp;
l = ltmp;

// 分隔符------------------------
// 需要显式转换,验证的确需要显式转换
b = (byte) 10.0F;
b = (byte) 10.0D;

s = (short) 10.0F;
s = (short) 10.0D;

c = (char) 10.0F;
c = (char) 10.0D;

i = (int) 10.0F;
i = (int) 10.0D;

l = (long) 10.0F;
l = (long) 10.0D;

// 验证
ftmp = 10.0F;
dtmp = 10.0D;

b = (byte) ftmp;
b = (byte) dtmp;

s = (short) ftmp;
s = (short) dtmp;

c = (char) ftmp;
c = (char) dtmp;

i = (int) ftmp;
i = (int) dtmp;

l = (long) ftmp;
l = (long) dtmp;

// 分隔符------------------------
// 需要显式转换,验证的确需要显式转换
b = (byte) 2100000000;
b = (byte) 2100000000L;

s = (short) 2100000000;
s = (short) 2100000000L;

c = (char) 2100000000;
c = (char) 2100000000L;

// 验证
itmp = 2100000000;
ltmp = 2100000000L;

b = (byte) itmp;
b = (byte) ltmp;

s = (short) itmp;
s = (short) ltmp;

c = (char) itmp;
c = (char) ltmp;

// 分隔符------------------------
// 需要显式转换,验证的确需要显式转换
b = (byte) 10L;
s = (short) 10L;
c = (char) 10L;

ltmp = 10L;

b = (byte) ltmp;
s = (short) ltmp;
c = (char) ltmp;

// 分隔符------------------------
// 本应显式转换,却由编译器进行处理,验证的确是由编译器处理本应的显式转换
b = 10;
s = 10;
c = 10;

// 验证
itmp = 10;

b = (byte) itmp;
s = (short) itmp;
c = (char) itmp;
}
}

参考文献:
[1]http://stackoverflow.com/questions/10075058/converting-from-double-to-float-in-java
[2]http://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html

您的支持将鼓励我继续分享!