Java基础进阶系列-04之循环结构基础讲解

一、循环结构基础语法

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源码中,Integerjava.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
    }
    
©️2020 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页