文章目录
一、循环结构基础语法
for循环
// JLS(Java Language Specification)
for ([FotInit]; [Expression]; [ForUpdate]) Statement
// 更新指的是执行一次循环后,更新循环控制变量
for (初始化; 布尔表达式; 更新) {
// 代码块
}
for-each循环
// JLS(Java Language Specification)
// Expression的类型必须是Iterable或者数组类型,否则会出现编译报错
for ({VariableModifier} : Expression)
for (声明语句 : 表达式) {
//代码句子
}
while循环
while(布尔表达式) {
// 循环内容
}
do-while循环
do {
// 代码块
} while(布尔表达式);
二、深入思考
for循环与while循环之间的转换
互相转换规律时,把for循环里面初始化写到外面,while括号里面只写判断语句,
条件改变方式写到while循环体中,这样就可以实现for循环与while循环互转了。
/**
* 描述 : for循环与while循环转换.
* <p>
* 说明:
* 互相转换规律时,把for循环里面初始化写到外面,while括号里面只写判断语句,
* 条件改变方式写到while循环体中,这样就可以实现for循环与while循环互转了
*
* @author : MoCha
* @version : v1
* @date : 2020-10-04 19:25
*/
public class Demo {
public static void main(String[] args) {
// 外层循环控制行数
for (int i = 1; i < 10; i++) {
// 内层循环控制每行个数
for (int j = 1; j <= i; j++) {
System.out.print(j + "\t");
}
System.out.println();
}
System.out.println("==========");
int k = 1;
while (k < 10) {
int n = 1;
while (n <= k) {
System.out.print(n + "\t");
n++;
}
System.out.println();
k++;
}
}
}
do-while循环为什么在开发中不常见
do-while特性是总会先执行一遍代码块的内容,再进行判断。
而我们开发过程中,在循环场景下往往是需要先根据条件来对循环操作进行限制。
比如当集合的元素不为0时,我们才去取集合中的元素。
do-while使用场景联想
抢票场景
对于抢票场景下,不管三七二十一,先抢就对了,然后才去做是否抢票成功的判断,如果成功那么退出循环,否则就继续抢票
do {
// 抢票动作代码
} while(没抢到票);
进制转换
JDK源码中,
Integer
的java.lang.Integer#formatUnsignedInt
就使用到了do-while结构。在十进制转二进制时,就会执行该方法,在进制转换的场景中,不论何种情况,肯定至少会执行一次进制转换。
// java.lang.Integer#formatUnsignedInt
static int formatUnsignedInt(int val, int shift, char[] buf, int offset, int len) {
int charPos = len;
int radix = 1 << shift;
int mask = radix - 1;
do {
buf[offset + --charPos] = Integer.digits[val & mask];
val >>>= shift;
} while (val != 0 && charPos > 0);
return charPos;
}
CAS自旋操作
java.util.concurrent.atomic.AtomicInteger
// setup to use Unsafe.compareAndSwapInt for updates
// Unsafe 对象,Atomic 中的原子操作都是借助 unsafe 对象所实现的
private static final Unsafe unsafe = Unsafe.getUnsafe();
// AtomicInteger 所包装变量在内存中的地址
// valueOffset表示的是变量值在内存中的偏移地址
// 因为Unsafe就是根据内存偏移地址获取数据的原值
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// AtomicInteger 包装的变量值
// 并且用 volatile 修饰,以确保变量的变化能被其它线程看到
private volatile int value;
// java.util.concurrent.atomic.AtomicInteger#getAndIncrement
public final int getAndIncrement() {
// 三个参数,1、当前的实例 2、value实例变量的偏移量 3、递增的值
return unsafe.getAndAddInt(this, valueOffset, 1);
}
内部使用自旋的方式进行CAS更新(while循环进行CAS更新,如果更新失败,则循环再次重试)
// Unsafe源码sun.misc.Unsafe#compareAndSwapInt boolean compareAndSwapInt(Object var1, long var2, int var4, int var5) // 将入参变量名进行调整,便于理解 boolean compareAndSwapInt(Object obj, long offset, int expected, int update)
compareAndSwapInt
入参说明:
- var1(obj),需要更新的对象
- var2(offset),对象中需要更新的变量的内存偏移量
- var4(expected),预期值
- var5(update),拟更新的值
compareAndSwapInt
当且仅当对象obj中内存偏移量为offset的field的值等于预期值expected时,将变量的值替换为update。替换成功,返回true,否则返回false。
// sun.misc.Unsafe#getAndAddInt
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
// 获取对象内存地址偏移量上的数值var5
var5 = this.getIntVolatile(var1, var2);
// 若现在还是var5,设置为 var5 + var4,否则返回false,继续循环再次重试
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
参考链接:
http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/sun/misc/Unsafe.java
http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/87ee5ee27509/src/share/vm/prims/unsafe.cpp
while(true)与for(;;)的区别
参考链接:
https://stackoverflow.com/questions/8880870/java-for-vs-whiletrue
https://stackoverflow.com/questions/2611246/is-for-faster-than-while-true-if-not-why-do-people-use-it
总结:从字节码文件和反编译的内容来看,两者本质上没有差别,取决于开发者的使用习惯。
/**
* 描述 : while(true)与for(;;)的区别.
*
* @author : MoCha
* @version : v1
* @date : 2020-10-04 14:16
*/
public class Demo {
public static void main(String[] args) {
}
public static void testWhile() {
int i = 0;
while (true) {
i++;
}
}
public static void testFor() {
int i = 0;
for (; ; ) {
i++;
}
}
}
// 对应的字节码文件内容
public class Demo {
public Demo() {
}
public static void main(String[] args) {
}
public static void testWhile() {
int var0 = 0;
while(true) {
++var0;
}
}
public static void testFor() {
int var0 = 0;
while(true) {
++var0;
}
}
}
// 对应的反编译内容
public class top.yangzefeng.test.Demo {
public top.yangzefeng.test.Demo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: return
public static void testWhile();
Code:
// 将int型0推送至栈顶
0: iconst_0
// 将栈顶int型数值存入第0个本地变量
1: istore_0
// 将指定int型变量增加指定值(i++,i--,i+=2),可以有两个变量,分别表示index, const,index指第index个int型本地变量,const增加的值
2: iinc 0, 1
5: goto 2
public static void testFor();
Code:
0: iconst_0
1: istore_0
2: iinc 0, 1
5: goto 2
}
三、最佳实践与注意事项
-
if/for/while/switch/do 等保留字与括号之间都必须加空格
-
循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展
下例中,反编译出的字节码文件显示每次循环都会 new 出一个 StringBuilder 对象,然后进行 append操作,最后通过 toString 方法返回 String 对象,造成内存资源浪费。
public static void main(String[] args) { String str = "start"; for (int i = 0; i < 100; i++) { str = str + "hello"; } }
public static void main(java.lang.String[]); Code: 0: ldc #2 // String start 2: astore_1 3: iconst_0 4: istore_2 5: iload_2 6: bipush 100 // 比较栈顶两int型数值大小,当结果大于等于0时跳转 8: if_icmpge 37 // 循环体内部开始 11: new #3 // class java/lang/StringBuilder 14: dup 15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 18: aload_1 19: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 22: ldc #6 // String hello 24: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 27: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 30: astore_1 31: iinc 2, 1 34: goto 5 37: return }