运算符
什么是运算符?
计算机的最基本用途之一就是执行数学运算,而运算符就是指明对操作数的运算方式。而运算符在java里就是用来操作对象和数据。
运算符的种类:
运算符按其功能来分,有算术运算符、赋值运算符、关系运算符、逻辑运算符、位运算符和其他运算符。
算术运算符(+、-、*、/、%、++、–)
+ : 相加运算符两侧的值
- : 左操作数-右操作数
* : 相乘运算符两侧的值
/ : 左操作数除以右操作数
% : 取模: 左操作数除以右操作数的余数
++ : 自增:操作的值增加1(++A是将A先增1之后再做其他运算 A++是如果
有其他赋值运算,先赋值,之后再将A增1)- - : 自减:操作的值减少1(–A和A–原理和自增相同)
关系运算符(==、!=、>、<、>=、<=)
扫描二维码关注公众号,回复: 11510267 查看本文章关系运算符生成的是一个“布尔”(Boolean)结果。它们评价的是运算对象值之间的关系。若关系是真实的,关系表达式会生成true(真);若关系不真实,则生成false(假)。
== : 如果两个操作数的值是否相等,如果相等则条件为真。
!= : 如果两个操作数的值是否相等,如果值不相等则条件为真。
> : 如果左操作数的值大于右操作数的值,则条件为真。
<: 如果左操作数的值小于右操作数的值,则条件为真。
>=: 如果左操作数的值大于等于右操作数的值,则条件为真。
<=: 如果左操作数的值小于等于右操作数的值,则条件为真。
例:检查对象是否相等发生的情况:
1、用”==”比较两个对象是否相等
public class Equivalence {
public static void main(String[] args) {
Integer n1 = new Integer(47);
Integer n2 = new Integer(47);
System.out.println(n1 == n2); //false
System.out.println(n1 != n2); //true
}
}
造成上述两个对象比较不相等的原因:
==和!=比较的是对象句柄(引用),n1 和 n2所指向的两个对象的内容虽然相同,但是句柄不相同,所以 n1 == n2 返回false, n2 != n2 返回true。
2、用“equals”比较两个对象是否相等
public class EqualsMethod {
public static void main(String[] args) {
Integer n1 = new Integer(47);
Integer n2 = new Integer(47);
System.out.println(n1.equals(n2));//true
}
}
注意: 这个方法不适用于“基本类型”,那些类型直接使用==和!=即可。
特殊情况:
class Value {
int i;
}
public class EqualsMethod2 {
public static void main(String[] args) {
Value v1 = new Value();
Value v2 = new Value();
v1.i = v2.i = 100;
System.out.println(v1.equals(v2)); //此时返回false
}
}
使用“equals”返回false的原因:
因为所有的类在默认情况下都继承Object,所以他们中的equals方法如果不进行重写,那么使用的将是父类“Object”中的“equals”方法,但是“Object”中的equals比较的是句柄(引用),而这里的句柄不同,指向的是不同的对象,所以返回false。
所以:如果有需要直接比较对象的内容,就必须实现相应的“equals”方法。
例:String类中的equals方法:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
比较的过程:
1、比较当前对象和传入的对象的句柄是否相同,如果相同,说明指向的是同一个对象,返回true。
2、判断传进来的anObject对象是不是String类型的实例,如果不是返回false。
3、如果anObject是String的实例,那么强转成String类型的变量,重新定义成变量anotherString。
4、循环变量字符串anotherString和原本的字符串,判断每个字符是否相等,如果其中有一个字符不相等,返回false。如果每个字符都相等,返回true。
位运算符(&、|、^、~、<<、>>、>>>)
& : 如果相对应位都是1,则结果是1,否则为0
| : 如果相对应位都是0,则结果为0,否则为1
^ : 如果相对应位值相同,则结果为0,否则为1
~: 按位补运算符 翻转操作数的每一位,即0变成1,1变成0
<<:按位左移运算符 左操作数按位左移右操作数指定的位数
>>:按位右移运算符 左操作数按位右移右操作数指定的位数
>>>:按位右移补零操作符。左操作数的值按右操作数指定的位数右移,移动得到的空位以零填充。
例:位的运算
int a = 60; /* 0011 1100 */
int b = 13;/* 0000 1101 */
int c = 0;
c = a & b; /* 12 = 0000 1100 */
c = a | b; /* 61 = 0011 1101 */
c = a ^ b; /* 49 = 0011 0001 */
c = ~a; /* -61 = 1100 0011 */
c = a<<2; /* 240 = 1111 0000 */
c = a>>2; /* 15 = 1111 */
c = a>>>2; /* 15 = 0000 1111 */
逻辑运算符(&&、||、!)
&& : 逻辑与运算符。当前仅当两个操作数都为真,条件才为真。
||:逻辑或运算符。任何两个操作数其中一个为真,条件为真。
!:逻辑非运算符。反转操作数的逻辑状态。如果条件为true,则逻辑非运算符将得到false。
短路逻辑运算:
只有明确得出整个表达式真或假的结论,才会对表达式进行逻辑求值。因此,一个逻辑表达式的所有部分都有可能不进行求值。
例:
int A = 60,B = 70,C = 80
boolean result = (A>B) && (A<C)
A>B 不成立 返回false 整个表达式返回false
所以此时不会去判断A是否小于C 这样对整个表达式来说,造成了短路的一种现象。
短路运算的优势和劣势:
短路运算的优势: 如果中途有一个表达式返回false,那么它不会进行下面的逻辑判断,直接返回false
短路运算的劣势: 如果有大量的逻辑判断 那么使用短路运算会造成代码逻辑的不清晰 ,代码的臃肿。
赋值运算符(=)
= : 赋值运算符 C = A+B
+= : 加和赋值操作符 C += A 等价于 C = C + A
-= : 减和赋值操作符 C-= A 等价于 C = C - A
= : 乘和赋值操作符 C = A 等价于 C = C * A
/= : 除和赋值操作符 C /= A 等价于 C = C / A
%= : 取模和赋值操作符 C %= A 等价于 C = C % A
<<= : 左位移赋值运算符 C <<= 2 等价于 C = C << 2
>>= : 右位移赋值运算符 C>>=2 等价于 C= C >> 2
&= : 按位与赋值运算符 C &= 2 等价于 C = C & 2
^= : 按位异或赋值运算符 C ^= 2 等价于 C = C ^ 2
|= : 按位或赋值操作符 C |= 2 等价于 C = C | 2
赋值运算符的注意点:
赋值是用“=”进行的,意思是取得右边的值,把它赋值给左边。
右边的值可以是任何常数、变量或者表达式,前提是能产生一个值。
左边的值必须是一个明确的、已命名的变量,也就是说,必须要有一个物理空间来保存右边所产生的值。
对基本类型进行赋值操作:
基本类型之间的赋值,是直接把值内容赋值给左边的变量
对“对象”进行赋值操作:
而对一个对象进行赋值操作的时候,真正操作的是它的句柄(引用)。
倘若“从一个对象到另一个对象”赋值,实际上就是将相应对象的句柄(引用),复制到另外一个对象上。
例:加入有C和D两个对象 现在使得C=D 那么C和D最终都会指向最初只有D才指向的那个对象。
对象赋值操作中的”别名”现象:
class Number {
int i;
}
public class Assignment {
public static void main(String[] args) {
// 创建Number类实例 n1
Number n1 = new Number();
// 创建Number类实例 n2
Number n2 = new Number();
// 给每个实例中的i变量附上不同的值
n1.i = 9;
n2.i = 47;
System.out.println("1: n1.i: " + n1.i + ", n2.i: " + n2.i);
// 将n2的句柄(引用)赋值给n1 此时n1的句柄(引用被改变)
n1 = n2;
System.out.println("2: n1.i: " + n1.i +
", n2.i: " + n2.i);
// 此时如果改变实例n1中属性i的值 实例n2中i的值也会随之改变(n2相当于有了一个"别名") 因为此时 他们的句柄(引用)相同,都指向同一个对象 而最初的n1的句柄(引用)则会被gc(垃圾收集器)自动清楚。
n1.i = 27;
System.out.println("3: n1.i: " + n1.i +
", n2.i: " + n2.i);
}
}
上面这种特殊的现象通常叫“别名”,是Java操作对象的一种基本方式。
方法调用中的“别名”现象:
将一个对象传递到方法内部时,也会产生“别名”现象。
class Letter {
char c;
}
public class PassObject {
//f()方法表面上似乎要在方法的作用域内制作自己的自变量Letter y的一个副本。实际传递的是一个句柄(引用)。
static void f(Letter y) {
y.c = 'z';
}
public static void main(String[] args) {
Letter x = new Letter();
x.c = 'a';
System.out.println("1: x.c: " + x.c);
f(x);
System.out.println("2: x.c: " + x.c);
}
}
方法的调用中也出现了“别名”现象,因为实际上传递的是一个句柄,所以x变量和y变量其实是具有相同的句柄(引用),指向的是同一个对象。
如果想把两个对象独立起来,而不是将n1和n2绑定到相同的对象,避免产生“别名”这种现象,那么可以这样操作:
n1.i = n2.i;
这样编写的缺陷:
会使对象内部的字段处理发生混乱,并与标准的面向对象设计准则相悖。
所以对“别名”的处理等到以后讲,这里只做对赋值运算符的说明。
- 其他运算符(instanceof、三元运算符、逗号运算符、字符串运算符 +、造型运算符)
instanceof运算符:
该运算符用于操作对象实例,检查该对象是否是一个特定类型(类的类型或接口类型)
使用格式:
(Object reference variable) instanceof (class/interface type)
如果运算符左侧变量所指的对象,是操作符右侧类或接口(class/interface)的一个(实例)对象,那么结果为真。
例子:
String name = “James”;
boolean result = name instanceof String;//由于 name是String类型,所以返回true
如果被比较的对象兼容于右侧类型,该运算符仍然返回true。
例子:
class Vehicle {}
public class Car extends Vehicle {
public static void main(String args[]) {
Car c1 = new Car();
Vehicle v2 = new Car();
Vehicle v3 = new Vehicle();
boolean result1 = c1 instanceof Vehicle; //true Vechicle向后兼容Car
boolean result2 = v2 instanceof Car; //true
boolean result3 = v2 instanceof Vehicle //true
boolean result4 = v3 instanceof Car; //false
}
}
三元运算符:
表达式: 布尔表达式? 值0:值1
若“布尔表达式”的结果为true,就计算“值0”,而且它的结果成为最终由运算符产生的值。但若“布尔表达式”的结果为false,计算的就是“值1”,而且它的结果成为最终由运算符产生的值。
而三元运算符也可以用if-else语句代替,从代码的可读性上来看,三元运算符不如if-else,但是从代码的整洁性来说,三元运算符要比if-else语句整洁,具体取舍,看你自己的需求。
逗号运算符:
在C和C++里,逗号不仅作为函数自变量列表的分隔符使用,也作为进行后续计算的一个运算符使用。在Java里需要用到逗号的唯一场所就是for循环。
字符串运算符 +
这个运算符在java里还有连接字符串的功能,不仅如此,如果他的两端,一边连接的是字符串,一边连接的是其他不同类型的变量,会自动转成字符串类型。
造型运算符
造型的作用: 与一个模型匹配
适当的时候,Java会将一种数据类型自动转换成另一种
从小类型转到大类型时,会自动转换,造型运算符可加可不加。
但是从大类型转到小类型时,由于会发生信息丢失的危险。但是,我们需要这种转型,所以必须用造型运算符,进行强转。
Java允许我们将任何主类型(基本类型)“造型”为其他任何一种主类型(基本类型),但布尔值(bollean)要除外,后者根本不允许进行任何造型处理。“类”不允许进行造型。为了将一种类转换成另一种,必须采用特殊的方法。
通常,表达式中最大的数据类型是决定了表达式最终结果大小的那个类型。若将一个float值与一个double值相乘,结果就是double;如将一个int和一个long值相加,则结果为long。
运算符中的字面值:
最开始的时候,若在一个程序里插入“字面值”(Literal),编译器通常能准确知道要生成什么样的类型。但在有些时候,对于类型却是暧昧不清的。若发生这种情况,必须对编译器加以适当的“引导”。方法是用与字面值关联的字符形式加入一些额外的信息。
class Literals {
char c = 0xffff; // max char hex value
byte b = 0x7f; // max byte hex value
short s = 0x7fff; // max short hex value
int i1 = 0x2f; // Hexadecimal (lowercase)
int i2 = 0X2F; // Hexadecimal (uppercase)
int i3 = 0177; // Octal (leading zero)
// Hex and Oct also work with long.
long n1 = 200L; // long suffix
long n2 = 200l; // long suffix
long n3 = 200;
//! long l6(200); // not allowed
float f1 = 1;
float f2 = 1F; // float suffix
float f3 = 1f; // float suffix
float f4 = 1e-45f; // 10 to the power
float f5 = 1e+9f; // float suffix
double d1 = 1d; // double suffix
double d2 = 1D; // double suffix
double d3 = 47e47d; // 10 to the power
}
十六进制(Base 16)——它适用于所有整数数据类型——用一个前置的0x或0X指示。并在后面跟随采用大写或小写形式的0-9以及a-f。若试图将一个变量初始化成超出自身能力的一个值(无论这个值的数值形式如何),编译器就会向我们报告一条出错消息。注意在上述代码中,最大的十六进制值只会在char,byte以及short身上出现。若超出这一限制,编译器会将值自动变成一个int,并告诉我们需要对这一次赋值进行“缩小造型”。这样一来,我们就可清楚获知自己已超载了边界。
八进制(Base 8)是用数字中的一个前置0以及0-7的数位指示的。在C,C++或者Java中,对二进制数字没有相应的“字面”表示方法。
字面值后的尾随字符标志着它的类型。若为大写或小写的L,代表long;大写或小写的F,代表float;大写或小写的D,则代表double。
指数总是采用一种我们认为很不直观的记号方法:1.39e-47f。
在科学与工程学领域,“e”代表自然对数的基数,约等于2.718(Java一种更精确的double值采用Math.E的形式)。
它在象“1.39×e的-47次方”这样的指数表达式中使用,意味着“1.39×2.718的-47次方”。然而,自FORTRAN语言发明后,人们自然而然地觉得e代表“10多少次幂”。