JAVA和Nginx 教程大全

网站首页 > 精选教程 正文

03_Java_基础_表达式

wys521 2024-11-17 16:56:34 精选教程 28 ℃ 0 评论

表达式:Java提供了丰富的运算符环境,包括算数运算符,位运算符,关系运算符,逻辑运算符,赋值运算符,条件运算符。(运算符:operator)

一、算术表达式

运算符

名称

使用举例(a=7,b=5)

功能及结果

++

自加1运算符

a++或++a

使变量a的值加1

--

自减1运算符

a--或--a

使变量a的值减1

-

求相反数运算符

-a

变量a的相反数

+

加法运算符

a+b

求a与b相加的和,结果为 12

-

减法运算符

a -b

求a与b相减的差,结果为2

*

乘法运算符

a*b

求a与b相乘的积,结果为35

/

除法运算符

a/b

求a除以b的商,结果为1(取整)

%

求余运算符

a%b

求a除以b的余数,结果为2

使用注意事项:

1)算术运算符不能用在布尔型上,但可以用在char类型上,因为在Java中,char类型是int类型的一个子集

2)减运算也用作表示单个操作数的负号。但要注意如果作为对一个数取负是,此运算遵循自动类型提升规则。如byte a = -2;byte = -a; 前面一句代码将-2赋值给a,其中有隐含的强制类型转换,是正确的,但后面一句代码对a变量进行取负运算时,类型自动提升为int型,此时将一个int型变量赋值给byte类型会报错,可以改成byte b = (byte)(-a);

3)对整数进行“/”(除法)运算时,结果为整数,所有的余数都要舍弃,当除数为0时,将会报运行时错误,如int a = 10/0;//编译时报错。对于浮点数进行除法运算时的规则,两个浮点数或一个浮点数一个整数进行除法运算,结果得到一个浮点数,即便结果是整数,也被存储为浮点数类型。① 正浮点数(非零浮点数)除以零,将得到结果Infinity(正无穷大),如double a = 12/0;//结果为:Infinity;② 负浮点数(非零浮点数)除以零,将得到结果-Infinity(负无穷大)如:double a = -12/0;//得到的结果为:-Infinity;③ 浮点数零(0.0)除以零,将得到结果为NaN,例如,double a = 0.0/0;//结果为:NaN(表示不知道)。注意:常量Double.POSITIVE_INFINITY Double.NEGATIVE_INFINITY 以及Double.NaN分别表示上述三种特殊值。

4)在Java中求余运算符%不仅仅可以用于整数类型,而且可以用于浮点类型。(取模结果的符号永运与被除数相同如:-5%3=-2,5%(-3)=2),两个浮点数取模时得到的是被除数减去除数的整数倍结果,如42.24%10=2.24。当对整数进行求余运算时,如果右边的操作数为0,则报运行是错误;而如果对浮点数进行求余运算时,如果右边的操作数为0,则得到结果NaN。其实求余运算是按照如下步骤进行的:s 求出参与运算的两数的绝对值。s用左边操作数的绝对值减去右边操作数的绝对值,得到新的差。s 如果差比右边操作数绝对值大,就用差值减去右边操作数的绝对值,得到新的差。s 重复步骤(2),直至差比右边的操作数小将差作为结果返回。s 结果的符号,由左边的操作数决定。

5)使用自减自加运算时要注意:当自增或自减运算符放在运算数(变量)的前面(作为前缀),Java就会首先将该运算数加一或减一,然后在参加其他运算,而如果自增自减运算符放在运算数(变量)的后面(作为后缀)就会首先使用该变量参加其他运算,然后该变量在加一或减一。说明:自增自减运算符并不会进行类型的提升,即操作前是什么类型,操作后还是什么类型。

6) 在Java当中有多个变量参与运算时,结果的数据类型与这个多个变量中表示范围最大的那个变量类型保持一致(自动提升原则)。在运算符中在进行运算时满足自动类型提升的运算符有:“+”作为两个数相加时,“-”作为两个数相减时或者作为取负,“*”,“/”,“%”,都会遵循类型自动提升

7)在Java中“+”符号重载了三种功能:1.加法功能2.数值的正号3.字符串连接符。当“+”作为字符串连接符时,如果将数字与字符串组合连接到一起是“+”会将数字作为字符串处理,执行字符串连接功能并且“+”运算的顺序是从左到右,因此,System.out.println(“七加三”+7+3);与System.out.println(7+3+”是结果”);输出是不一样的。


二、关系表达式

运算符

名称

使用举例(a=7,b=6)

功能及结果

==

等于

a == b

false

!=

不等于

a != b

true

>

大于

a>b

true

>=

大于等于

a>=b

true

<

小于

a<b

false

<=

小于等于

a<=b

false

Instanceof




使用注意事项:

1)Java中的任意两个类型,包括整数,浮点数,字符以及布尔型都可以使用“==”来比较是否相等,用“!=”来测试是否不等。但只有数字类型(整数,浮点数,字符)或引用类型才能进行是否相等的比较,对于基本数据类型是比较两个值是否相等,对于引用类型是比较引用是否是同一个对象。并且关系运算总是生成boolean型结果。相等和不等运算符不但可以将类型相同的值进行比较,类型不同的值只要具有可比性(这里所说的可比性是8种基本数据类型boolean型只能与boolean型比较,其他7种则可以相互比较),也可以进行比较,例如:boolean a = (‘a’ == 97.0);当比较时运算符一边有字符型,则采用该字符的Unicode值作为数值进行比较。

2)无穷大与“NaN”的比较:在Java中浮点运算的结果可能会产生正负无穷大与“NaN”,同样可以使用等于(不等于)运算来判断运算的结果是否就是他们。System.out.println(0.0/0 == Double.NaN);//false;原因在于NaN表达的语义是“不知道”,“不知道”并不一定等于“不知道”,所以Java中规定两个“NaN”相比返回false即Double.NaN != Double.NaN结果是true。但正负无穷大却可以进行比较,例如:

System.out.println(12.0/0 == Double.POSITIVE_INFINITY);//返回true

3)比较大小运算符在8种基本数据类型中除了boolean型外都可以相互进行比较。同时比较大小运算符还可以用来对无穷大,“NaN”进行比较,将数值与无穷大进行比较是有意义的,但与“NaN”进行比较是没有意义的Java中规定任何数值与“NaN”进行大小的比较将永远返回“false”。

4)instanceof的使用:Instanceof的功能是检查引用指向的对象是否可以看作指定的类型,其基本的语法如下:<引用> instanceof <类或接口类型>左边表达式返回boolean值,当引用指向的对象可以看作是指定的类或接口类型是返回true,否则返回false,但要注意Instanceof不能用于测试对象到底是不是某个具体的类型若进行测试的引用时NULL值,其结果总是返回false

另外数组作为对象,同样其引用也可以进行instanceof测试,如Son[] sonArray = new Son[20];//Son是一个类,该类继承自Father类。sonArray instanceof Son[];//返回true;int[] intArray = new int[5];intArray instanceof int[];//返回true;但要注意对于引用数组而言,子类型的数组可以看作是父类型的数组,即sonArray instanceof Father[];//同样返回true;对于基本类型的数组,不可以看作其他任何类型的数组对象,如int类型的数组对象intArray不可以看作除int数组以外的其他任何数组对象。

并不是任何的类型的引用类型对任何类型的类或接口都可以进行测试,要想Instanceof测试能通过编译,参与测试的引用类型必须是能够强制类型转换的类或接口,若强制类型转换能通过编译,就可以进行instanceof测试。如Son s = new Son(); s instanceof String;//编译会报错的。因为Son类不可以通过强制类型转换为String类,instanceof可以用在执行强制类型转换前,若测试通过,在进行强制类型转换,避免运行时抛出“java.lang.classCastExceptioin”异常。


三、逻辑运算符

算符

名称

使用举例

功能及结果

&

逻辑与

false&true

false

|

逻辑或

false|true

true

^

异或

true^false

true

!

逻辑非

!true

false

&&

短路与

false&&true

false

||

短路或

false||true

true

使用注意:&&与||称为短路运算符,在逻辑或的运算中,如果第一个表达式A为真,则不管第二个表达式B(在指B表达式不再计算。)是真是假,其运算结果为真。同样,在逻辑与的运算中,如果第一个运算数为假,则不管第二个运算数B是真是假,其结果为假。而&与|则不同,他们首先要算A与B的值,然后在进行或或与运算。如int a=3,b=4,c=5;boolean g=(a>b)&&((c=a)<b);结果为false,c的值为5,而不是3。注意:进行逻辑运算的逻辑符两边必须是布尔类型的值而不能是数字类型,原因是在Java中布尔类型与数字类型不能相互转化。同时要注意|与&这两种非短路运算符,这两种运算符两边进行运算的数可以不是布尔型,如3&4 = 0;


四、位运算符

位运算:一般用于权限的管理。

运算符

名称

使用举例

功能及结果

~

按位取反

~x

对15的每个二进制位求反,结果为-16

&

按位与

x&y

对15,21对应的二进制位做与运算,结果为5

|

按位或

x|y

对15,21对应的二进制位做或运算,结果为21

^

按位异或

x^y

对15,21对应的二进制位做异或运算,结果为26

<<

按位左移

x<<z

将15各二进制位左移三位,结果为120

>>

按位右移

x>>z

将15各二进制位右移三位,结果为1

>>>

无符号右移

x>>>z

将15各二进制位无符号右移3位,结果为1

使用注意事项:

1)在Java中出,char类型和boolean类型之外所有的整数类型都是有符号的整数,这意味着它们既有正数又有负数。Java使用二进制补码来表示负数。负整数的补码表示方法是将该数绝对值的二进制代码按位取反(即1变0,0变1),然后对其结果加1

2)将整数每左移一位,就相当于将该值乘以2,当然要保证移位的过程中不会将数原有的任何一位移出。否则将得到错误结果。使用左移右移运算时只能是将数据乘以2的n次方或者除以2的n次方,乘以其他的数或者除以其他的数就只能使用标准的乘法和除法。

3)将值每右移一次,就相当与将该值除以2并且舍弃了余数,类似的,一定要保证移位的过程中不会将原数的任何一位移出。使用>>右移时,被移走的最高位(最左边的位)由原来的最高位的数字补充。如要移走的是负数,每一次右移都在左边补1,如果要移走的值是正数,每一次的右移都是在左边补0,这叫符号位扩展。

4)如果在移位过程中不希望进行符号位扩展(保留符号位),而是希望移位后总是在最高位(最左边)补0,可以使用Java中无符号右移运算符>>>,他总是在左边补0.(需要注意:无符号右移运算符>>>只对32位和64位的值才有意义,在表达式中过小的值总是自动扩大int型这意味着符号位扩展和移动总是发生在32位而不是8位或16位。例如byte b = 8;b = b>>2;编译会报错,会提示无法将int类型自动转换为byte类型)


注意:位运算适用于整数中的单个比特进行操作(这里所说的整数有int,byte,short,char),进行位运算的两边不仅仅可以是数字,可以是char类型的值例如’a’ &’b’ = 48,对于布尔类型同样可以进行位运算如:true&false;//结果为false,但要注意一点的是两个布尔类型的值进行位运算是结果为boolean类型(这是由于boolean类型是使用一位来存储true或false的进行位运算后结果同样是一位),而如果进行位运算的两个数是数字型或者是字符型,运算的结果会自动提升为int类型

如果进行位运算的两边的数一个是int型一个是char类型,同样可以进行位运算,进行位运算之前首先将char类型的数值提升到int类型,然后对两个int类型的数据进行位运算(同样可以是int型和short型)。但要注意的是由于boolean类型的值无法转换为int类型的值,所以无法完成对一个int类型一个boolean类型的值进行位运算位运算符同算术运算符一样具有自动提升功能,即经过位运算的结果至少是int型。如byte a = 12;byte b = 12 | 2;程序在编译时就会报一个异常。


5)移位运算中Java的机制:

Int a = 88>>32;
Long l = 67<<64;
System.out.println(a); //结果是88
System.out.println(b); //结果是67

原因:Java在进行移位前,系统首先把要移数的数据类型的位数与被移数的数据类型的位数求余数,然后移动余数个位数,在本例中由于int是32位,long 是64位,32对32求余,64对64求余结果是0,也就是没有移动。


五、赋值运算符

赋值运算符“=”

复合赋值运算符:

运算符

名称

一般表示法

JAVA语言表示法

+=

加法赋值运算符

x=x+y

x+=y

-=

减法赋值运算符

x=x-y

x-=y

*=

乘法赋值运算符

x=x*y

x*=y

/=

除法赋值运算符

x=x/y

x/=y

%=

模运算赋值运算符

x=x%y

x%=y

&=

按位与赋值运算符

x=x&y

x&=y

|=

按位或赋值运算符

x=x|y

x|=y

^=

按位异或赋值运算符

x=x^y

x^=y

>>=

按位右移赋值运算符

x=x>>y

x>>=y

>>>=

按位右移赋值运算符,左边空出的位以0填充

x=x>>>y

x>>>=y

<<=

位左移赋值运算符

x=x<<y

x<<=y

复合逻辑运算符有两个好处:1)他们比标准的等式要紧凑.。2)他们有助提高java的运算效率。


六、条件运算符

布尔表达式 ? expression1:experssion2;如果boolean-expression是真则条件表达式的结果为expression1,如果boolean-expression是假,则条件表达式的结果为expression2。

注意:表达式1和表达式2如果是基本数据类型要求他们的类型必须是兼容的,即表达式1和表达式2的类型是要能够想换转换的,如果不能转换,则编译报错,(a>b)?Ture:12,由于boolean类型与Int类型是不能够相互转换的,因此编译会报错。

但注意:如果表达式1,2为引用类型,则永远都是对的,因为三元表达式的返回类型将设置为两个表达式类型的最小公共父类类型,而Java中所有的类都继承自java.lange.Object类

对于三元运算符返回值类型与表达式1,2类型间的关系,有如下规则:

  • 对于基本数据类型,如果两个表达式类型不同,对int型以下(包括int)的字面常量将尽量进行窄化,如果不能进行窄化,最后返回级别高的类型。(a>b)?12:124L;//返回值类型为long
  • 窄化时要求字面常量的值在窄化的类型范围内,如12能窄化为byte型,而129则不行,如(a>b)?((byte)1244):123;//返回值类型是byte型,因为(byte)1244是byte类型,而“123”字面常量在byte范围内,进行窄化。
  • 变量不能进行窄化,按级别高的类型返回。(a>b)?c:123;//c为int类型; 返回值类型是int型,由于c是变量为int型,“123”也将看作int型。
  • 对于引用类型,表达式的返回值类型设置为两个表达式类型的最小公共父类类型。

上面这些规则都是为了使三元表达式能有唯一的返回值类型,且返回值类型尽量窄。


七、Java运算符的优先级和结合性

优先级

描述

运算符

结合性

1

最高优先级

. [] ()

左/右

2

单目运算符

++ -- - ~ !

3

算术运算符

* / %

4

算术运算符

+ -

5

移位运算符

<< >> >>>

6

大小关系运算符

> < <= >= instanceof

7

相等关系运算符

== !=

8

按位与运算符

&

9

按位异或运算符

^

10

按位或运算符

|

11

逻辑与运算符

&&

12

逻辑或运算符

||

13

条件运算符

?:

14

赋值及复合赋值运算符

= += -= *= /= >>>= %=

|= &= ^= >>= <<=

使用圆括号 使用圆括号(不管是不是多余)不会降低程序的运行速度,并且合理的使用圆括号可以是的逻辑更加清晰

扩展:交换两数的方法有哪些?

(1)设置一个中间变量:

int temp = a;

a = b;

b = temp;

(2)使用数据叠加后减回来 不过这种方法要注意在a+b的时候可能会存在越界。

a = a+b;

b = a-b;

a = a-b;

(3)异或运算:

a = a^b;

b = a^b;

a = a^b;

八、自增自减与贪心规则

8.1、前缀式 与 后缀式的真正区别

在Java中,运算是从左往右计算的,并且按照运算符的优先级,逐一计算出表达式的值,并用这个值参与下一个表达式的运算,如:1+2+3,其实是先计算出1+2表达式的值为3,再参与下一个表达式的运算(1+2)+3,即3+3。再如判断if(a+2==3)。如此类推。

??a++是一个表达式 ,那么a++就会有一个表达式的计算结果,这个计算结果就是a的旧值(加1前的值)。相对的,++a表达式的计算结果a加1后的值。所以,自增的前缀形式与后缀形式的本质区别是:表达式的值(运算结果) 是加1前的变量的值还是加1后的变量的值(自减也是如此)。并不是先加1 与 后加1的区别,或者说,前后缀形式都是先加1(减1)的,才得到表达式的值,再参与下一步运算。因为这是一个表达式,必须先计算完表达式的运算,最后才会得到表达式的值。

我们来看一个面试常见问题:

int a = 5;
a = a++;
//此时a的值是多少?

有猜到a的值吗?我们用上面所学到的分析一下:a=a++可以理解成以下几个步骤:

1> 计算a自加1,即 a=a+1

2> 计算表达式的值,因为这是后缀形式,所以a++表达式的值就是加1前a的值(值为5);

3> 将表达式的值赋值给a,即a=5。

所以最后a的值是5。

同理,如果改成a = ++a;,则a的值是6。这是因为++a表达式的值是加1后的a,即为6。

8.2、自增自减是包含两个两个操作,不是线程安全的

??自增、自减运算符本质上不是一个计算操作,而是两个计算操作。以a++为例,这个运算将会编译器解析成:a=a+1,即包含两个单目运算符(+、=),一个单目运算符的计算操作可以看作是一个原子性操作。a++的步骤可以描述成 :1> 先取a加1,将结果存储在临时空间;2>将结果赋给a。所以,自增自减运算符包含两个操作:一个加1(减1)的操作和一个赋值的操作

??这个原理好像对我的编程没有用吧?不是的,在单线程的环境下,你可以不管这个细节。但在多线程的情况下,你就得时刻牢记这个细节了。要知道,自增自减不是原子性操作,也就是说不是线程安全的运算。因此,在多线程下,如果你要对共享变量实现自增自减操作,就要加锁,或者使用JDK提供的原子操作类(如AtomincInteger,AtomicLong等)提供的原子性自增自减方。

来看个例子,验证一下。下面的例子提供三个静态变量(一个是原子操作类),创建了10个线程,每个线程都对这三个变量以不同的方式进行加1操作,并循环1000次。

public class MyTest {
      static int a = 0;
      static int b = 0;
      //原子性操作类
      static AtomicInteger atomicInt = new AtomicInteger(0);

      public static void main(String[] args) throws InterruptedException {

            for (int i = 0; i < 10; i++) {//创建10个线程
              	Thread t = new Thread() {
                      @Override
                      public void run() {
                        for (int j = 0; j < 1000; j++) {//计算1000次
                          a = a + 1;
                          b++;
                          atomicInt.incrementAndGet();//自增的原子性方法
                        }
                   }
         		 };
        		  t.start();
          }
          // 判断当前的活动线程是不是只有main线程,以确保10个计算线程执行完成。
          while (Thread.activeCount() > 1) {
           		 Thread.sleep(1000);
          }
          System.out.println("a=a+1在多线程下的结果是:" + a);
          System.out.println("b++在多线程下的结果是:" + b);
          System.out.println("原子操作类AtomicInteger在多线程下的结果是:" + atomicInt.get());
      }
}
运行结果:
a=a+1在多线程下的结果是:8883
b++在多线程下的结果是:8974
原子操作类AtomicInteger在多线程下的结果是:10000

??从运行的结果可以看出,a=a+1、b++不是线程安全的,没有计算出正确的结果10000。也就是说这两个表达式都不是原子性操作。事实上,它们都包含了两个计算操作。

8.3、由a+++b表达式引起的思考

看到这个表达式,真的很让人疑惑:编译器是怎么解析的,是解析成

a++ + b 还是 a+ ++b

真纠结,干脆直接在编译器上跑一趟,看看结果吧!

int a = 5;
int b = 5;
int c=a+++b;
System.out.println("a的值是: "+a);
System.out.println("b的值是: "+b);

运行结果:
a的值是: 6
b的值是: 5

从结果可以确认,a+++b 其实是解析成了 a++ +b,为什么要这样结合呢?其实有两点原因:

  • Java中的运算是从左往右进行的;
  • java编译器有一种规则——贪心规则。也就是说,编译器会尽可能多地结合有效的符号。

那么,a+++b这样的结合方式就可以解释了。

但是这种结合是:尽可能多的结合,而不管这样的结合是否合法。如:a--b会被编译器解析成a-- b

尽管这是不合法,但编译器还是这样处理了,这就导致编译不通过,产生编译错误。

编译器为什么要采用贪心规则呢?

??从上面的分析来看,贪心规则在编程中也不好利用。那么,贪心规则的主要目的是为了什么?

??贪心规则的主要目的就是为了分析String字符串,看看下面的例子就会明白:

String s = "\17";
System.out.println("\\17 转义字符的值是:"+s+" 长度是:"+s.length());
s = "\171";
System.out.println("\\171 转义字符的值是:"+s+" 长度是:"+s.length());
s = "\1717";
System.out.println("\\1717 转义字符的值是:"+s+" 长度是:"+s.length());
s = "\17178";
System.out.println("\\17178 转义字符的值是:"+s+" 长度是:"+s.length());

运行结果:

\17 转义字符的值是: ??长度是:1

\171 转义字符的值是:y ??长度是:1

\1717 转义字符的值是:y7 ??长度是:2

\17178 转义字符的值是:y78 ??长度是:3

??“\17” 经转义得到一个特殊字符 “” 。而“\171” 转义后也得到一个字符 “y”。但 “\1717”、“\17178” 得到的字符串大于1,不再是一个字符,分别是 “y7”、“y78”。

??也就是说,“\1717” 字符串只转义了“\171” 部分,再链接 “7” 部分。“\17178” 字符串只转义了 “\171” 部分,再连接 “78”。

??那为什么 “\171” 为什么不转义 ”\17“ 部分,再链接 ”1“ 呢,而是作为一个整体进行转义的字符串呢?

??这就是 ”贪心规则“ 所决定的。八进制的转义字符的取值范围是 \0~\377。所以解析 ”\171“ 字符串时,编译器尽可能多地结合字符成一个转移字符,”\171“ 还在取值范围内,所以就是一个字符。但 ”\1718” 字符串,最多结合前4个字符成一个有效的转义字符 “\171”,而“\1717” 已经超出取值范围,不是有效字符,所以最后解析成 “\171” + "7" 的结果。“17178” 也是如此。

总结:

  • 编译器在分析字符时,会尽可能多地结合成有效字符,但有可能会出现语法错误。
  • 贪心规则是有用的,特别编译器是对转义字符的处理。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表