java基础(二) 自增自减与贪心规则

引言  JDK中提供了自增运算符++,自减运算符–。这两个操作符各有两种使用方式:前缀式(++ a,–a),后缀式(a++,a–)。可能说到这里,说不得有读者就会吐槽说,前后缀式都挺简单的,前缀式不就是先进行+1(或-1),然后再使用该值参与运算嘛,后缀式则相反。有必要长篇大论吗?  前后缀式的区别确实是这样,最起码表面上理解起来是这样,但是更深入的理解就不是这么简单了,甚至严重影响到你的程序的正确性。不信,接下去看吧!### 1、java中前缀式和后缀式的真正区别

在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。### 2、自增和自减是两个操作,不是线程安全的

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

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

来看个例子,验证一下。下面的例子提供三个静态变量(一个是原子操作类),创建了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() {@Overridepublic 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。也就是说这两个表达式都不是原子性操作。事实上,它们都包含了两个计算操作。### 3、a+++b表达式引发的思考

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

a++ + b 

<img src=“https://juejin.cn/)![](https://juejin.cn/ “点击并拖拽以移动”” style=“margin: auto” />

还是

a+ ++b 

<img src=“https://juejin.cn/)![](https://juejin.cn/ “点击并拖拽以移动”” style=“margin: auto” />

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

 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” 也是如此。

4、总结

  • 编译器在分析字符时,会尽可能多地结合成有效字符,但有可能会出现语法错误。* 贪心规则是有用的,特别编译器是对转义字符的处理。> 出处:http://www.cnblogs.com/jinggod/p/8424808.html

文章有不当之处,欢迎指正,你也可以关注我的微信公众号:`好好学java`,获取优质学习资源。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值