磨刀不误砍材工 – Java的基础语言要素(语句-深入理解)

最后更新于:2022-04-01 20:08:33

语句同样是Java重要的基础语言要素之一,那么在Java中语句是以什么形式体现的呢?通常分为: - 简单语句:就如同语文中以句号“。”结尾的一个句子就是一句语句一样,Java中以分号“;”结尾的一段代码就是最基本的一条Java语句。 - 块(复合)语句:指以一对花括号"{ }"包含起来的一系列程序语句的集合,所以又被称为复合语句。 提到块语句,我们就不得不提及与之紧密相关的一个名词:代码块。 代码块实际上也可以理解为作用域,之所以这样讲,是因为我们已经说过了代码块是以花括号“{ }”包含起来的一系列语句。 而块定义了变量的使用范围,各个块之间可以进行嵌套,而在块中声明的变量,只在当前块当中有效,在块以外将无法使用。 所以说,在使用代码块的时候,需要十分注意的两点就是: - 注意变量的作用范围,不要在无效范围中使用该变量,否则程序将编译失败。 - 不要在嵌套的两个块中,声明使用相同标示符的变量,否则也将导致程序编译失败。 那么,首先我们来看第一个注意点: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-17_57b4316083bae.jpg) 在这段代码中,值得我们注意的是: - **一个完整的Java程序(类)实际上正是由一个个嵌套的代码块组合起来的**。就像在类声明后用花括号包含起来的代码块被称为类代码块,方法声明后包含的代码块被称为方法代码块,嵌套在方法内的代码块被称为局部代码块等等一样。 - **嵌套在更内部层次的代码块可以使用嵌套外部的代码块中的内容,但位于嵌套更外部层次的代码块不能使用更内部层次的代码块中的内容。**所以在上面的例子中我们看到,方法代码块中可以使用类代码块中声明的变量;局部代码块中,在类代码块和方法代码快中声明的变量都能够被访问;但最后想要在方法代码块中访问局部代码块中声明的变量,程序就编译失败了。**这一切现象出现的原因,正是因为:在块中声明的变量只在当前块中有效。** - **合理的使用代码块,可以在一定程度上节约内存开销。**这是因为之所以说块中声明的变量只在当前块中有效,深入的讲,实际就是因为代码块限定了其生命周期,也就是说当虚拟机执行到该代码块,当中声明的变量才会被加载到内存之中,而随着该代码块的代码都执行完毕,当中的变量就会在内存中被释放,所以自然在块以外就无法再访问到了。**** 接着,我们来看第二个注意点: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-17_57b43160a6323.jpg) 注意以上代码截图中,用红色方框标记的两行代码。 我们写这段代码想要验证的是代码块的特性之一:不要在嵌套的两个代码块中声明相同命名的变量。 那么,第二个红框标注的代码恰恰印证了这一点,在方法代码块中声明了名为“method_block_var”的变量之后,如果再在其嵌套的局部代码块中声明,就会导致编译出错。 但让人在意的是,我们在类代码块中声明了一个“class_block_var”的变量,为何之后我们在其嵌套的方法代码块中,仍然可以声明相同命名的变量呢? 这实际上涉及到Java的内存机制,首先我们需要知道的就是:Java中声明在类代码块中的变量被称为该类的成员变量,而声明在方法或局部代码块中的变量被称为局部变量。 之所以造成这样的现象,究其根本是因为虚拟机内部的内存结构对于成员变量和局部变量的存储位置是不同的: 类的成员变量会随着类的对象一起,被存储在内存当中的堆内存当中;而局部变量则会随着方法的加载,而被存储到栈内存当中(方法的压栈)。 到了这里就不难理解了: 1.不同的两个班级:一班(堆内存)和二班(栈内存)中,都有一个名为“ 小明”的同学(变量)。这样的情况是没有任何问题的,因为你在调用时,可以通过“一班的小明”和“二班的小明”来正确的调用到目标学生。Java中也是这样的,在对成员变量进行调用时,实际上是隐式的调用了当前类对象关键字this。也就是说对成员变量的调用实际上是以:this.var的形式进行调用的,这就很好的与局部变量调用区分开了。 2.但同一个班级中(都位于栈内存)有两个相同名字的学生 ,那么再想要正确的调用目标学生就很难了,这会产生“调用的不确定性”。所以自然的,Java作为一门严谨的具有高度健壮性的语言,自然不会允许这样的“危险因素”存在。 ** ** **流程控制语句的使用** 话到这里,就来到了一个重要的部分:Java的程序流程控制语句的使用。 之所以使用流程控制语句,是因为一个程序的运行都是有序的,通常都是按照代码的书写顺序从上到下,从左到右进行执行。这一过程被称为顺序执行。 但实际开发中,我们通常都要根据实际需求,对程序的运行方式做一些目的性的改变,例如加一些条件判断等等。 于是,这个时候就必须用到流程控制语句,来进行程序流程的控制工作了。 Java中,主要的流程控制语句分为三种:选择/条件语句,循环语句,跳转语句。 ** ** **一、选择/条件语句** 顾名思义,就是指一段程序根据条件判断之后,根据判断结果再选择不同的处理方式的语句结构。 而必须铭记的是:条件语句的判断条件必须满足是boolean类型的数据。即: 条件只可以是一个boolean类型的值;是一个boolean类型的变量;或是一个返回boolean类型值的表达式。 **1.1、if条件语句.** 最简单的条件语句,作为条件分支语句,可以控制程序选择不同的处理方式执行。有三种表现形式: 第一种表现形式:if。 通常用于只有当满足某个条件,才具备程序继续执行的资格的情况。例如:判断一个人的年龄,只有判断结果为成年,才能继续执行相关代码: ~~~ private void if_Demo_1(int age) { if (age >= 18) { System.out.println("已成年,可以执行相关代码.."); //上网.. //饮酒.. } } ~~~ 第二种表现形式:if - else。 通常用于条件判断可能存在两种结果的情况.例如,判断一个人的性别,性别非男即女: ~~~ private void if_Demo_2(String gender) { if (gender.equals("male")) { System.out.println("男性"); } else { System.out.println("女性"); } } ~~~ 第三种表现形式:if - else if - else。 通常用于当条件判断可能存在两种以上结果的情况。例如,判断两个数a与b的大小,则可能存在a大于b,b大于a或二者相等的三种情况: ~~~ private void if_Demo_2(int a, int b) { if (a > b) { System.out.println(a + "大于" + b); } else if (a < b) { System.out.println(b + "大于" + a); } else { System.out.println(a + "等于" + b); } } ~~~ 最后顺带一提的就是,某些情况下会存在:仅仅通过一次条件判断,还无法选择正确的处理方式的情况。这时就可以通过对if语句嵌套,完成多次判断: ~~~ /* * 一家俱乐部,只针对年满18岁的女性营业 */ private void if_Demo_4(String gender, int age) { if (gender.equals("male")) { if (age >= 18) { System.out.println("欢迎观临."); } else { System.out.println("对不起,您未满18岁!"); } } else { System.out.println("对不起,我们只针对女性营业!"); } } ~~~ **1.2、switch条件/选择语句** 我们注意到在if语句的使用中,如果当条件判断存在多种结果的时候,则必须使用“if - else if - else”的方式来处理。 那自然我们可以想象如果当一个条件存在大量的可能结果时,我们可能就必须书写大量的else if语句,这样做可能会比较麻烦。 针对于这样的情况,Java提供了另一种相对简单一些的形式,就是switch条件语句。对于switch语句,值得注意的是: 其接受的条件只能是一个整型数据或枚举常量,只是在JDK1.7之后又新支持了String类型。 而同时我们知道Java中支持数据类型转换,而byte,short,char都可以被隐式的自动转换为int。 所以通常来说:switch语句所能接受的条件只能是byte,short,int,char或枚举类型的数据。 正是因为如此,所以switch语句相对于if语句而言,本身存在一定的局限性。 通常来说,一个switch语句的定义格式为: ~~~ switch (key) { case value: break; default: break; } ~~~ 举例而言,假设我们通过用户输入的一个整数,来查询对应的星期数: ~~~ private void switch_demo(int num) { switch (num) { case 1: System.out.println("星期一"); break; case 2: System.out.println("星期二"); break; case 3: System.out.println("星期三"); break; case 4: System.out.println("星期四"); break; case 5: System.out.println("星期五"); break; case 6: System.out.println("星期六"); break; case 7: System.out.println("星期日"); break; default: System.out.println("没有对应的星期数"); break; } } ~~~ swtich语句的执行过程是这样的,首先计算条件表达式的值,然后根据值分别对每个case进行匹配工作。 假如找到对应的匹配,则执行该case值下的程序语句;如果没有匹配的case值,则执行default下的程序语句。 在执行完case的语句块后,应当使用跳转语句break语句跳出该switch语句。 因为如果没有添加break语句,程序在执行完匹配的case值下的程序语句后, 并不会停止,而是将会连续执行下一个case值下的代码,直到碰到break语句为止。 **多重if和switch的区别** 我们已经知道在某些情况下,多重if和switch是可以完成相同的目的的。而它们最大的区别就在于: switch语句局限性更大,这是因为我们说过了:switch语句只能对类型为byte,short,int,long或枚举的数据的具体值进行判断。 但if既可以对具体的值进行判断;也可以进行区间判断;同时还可以对返回值类型为boolean类型的表达式进行判断,如: ~~~ public void ifTest(int a, char c,String s) { //对具体的值进行判断 if(a == 5); if(c == 'a'); if(s.equals("s")); //对区间进行判断 if(a>5&&a<=10); //对返回值为boolean类型的表达式进行判断 if(a>=c); } ~~~ 而网上很多人说,switch语句的效率相对高于if语句。这是因为:switch语句会将所有可能情况的代码语句一次性都加载进内存,所以在执行时效率相对较高。 但因为其本身的局限性,所以在实际开发中,还是因该根据实际需求选择最合适的做法。 **二、循环语句** 顾名思义,循环语句也就是指用于控制同一段程序反复多次运行的语句。Java中循环语句有三种,分别为:while、do-while以及for循环。 可以说,三种循环语句之间的区别实际不大,但同时也可以说都有本质的区别。下面我们分别来看一下三种循环的原理和使用。 **2.1、while循环语句** ~~~ /* * while循环语句的定义格式为: * while(条件){ * //循环内容.. * } */ private void while_demo(int a){ while(a < 20){ System.out.println(++a); } } ~~~ 与if条件语句相同,while语句也会接受一个boolean类型的值作为条件。 当该条件判断为true时,则会循环执行循环体的内容;当执行到条件判断结果为false时,就会结束循环。 **2.2、do-while循环语句** ~~~ /* * do-while循环语句的定义格式为: * do { //循环体 } while (condition); */ private void do_while_demo(int a){ do { System.out.println("执行一次循环:"+(++a)); } while (a<20); } ~~~ do-while循环与while循环最大的不同就是:无论循环条件的判断结果是否为true,都会至少执行一次循环体中的内容。 之所以Java会单独提供do-while这种循环方式,也正是因为:当使用while循环的时候,如果首次判断循环条件的结果就为假的话,那么该循环就会直接被跳过,根本不会执行。而事实上,我们很多时候会希望循环体至少执行一次。 **2.3、for循环语句** ~~~ /* * for循环的定义格式为: * for(初始化表达式;循环条件表达式;迭代运算) * { * 执行语句;(循环体) * } */ private void for_demo(){ for (int i = 0; i < 20; i++) { System.out.println(++i); } } ~~~ for循环的执行过程为: 1.执行循环的初始化,通过它设置循环的控制变量值 2.判断循环条件:如果为真,则执行循环体内容;如果为假,则退出循环; 3.执行迭代运算。迭代运算通常是一个表达式,用于改变循环控制变量的值。 4.再次执行循环条件判断,然后反复第2-3步的步骤,直到循环条件判断为假时,则退出循环。 需要注意的是:for循环的初始化表达式,只会在循环第一次开始时执行一次; 而迭代运算表达式则会在每一次循环体内容执行完毕后,紧接着执行一次。 可以通过一道网上流传的华为的Java面试题,来更形象的了解for循环的执行特点: ~~~ /* What is the result? A. ABDCBDCB B. ABCDABCD C. Compilation fails. D. An exception is thrown at runtime. */ public class Demo { static boolean foo(char c) { System.out.print(c); return true; } public static void main(String[] args) { int i = 0; //ABDCBDCB for (foo('A'); foo('B') && (i < 2); foo('C')) { i++; foo('D'); } } } ~~~ 最终执行的结果为:ABDCBDCB,我们来逐步分解该输出结果形成的原因: 1.for循环第一次开始执行,首先执行循环初始化表达式,于是foo('A')被执行,输出A。此时输出结果为:A 2.紧接着开始执行循环条件判断,于是foo('B')被执行,输出B;接着判断i(0)<2;循环条件判断结果为真。此时输出结果为:AB 3.由于该次判断结果为真,于是开始执行循环体。于是,i自增运算,值变为1;foo('D')被执行,输出D。此时输出结果为:ABD 4.一次循环体内容执行完毕,紧接着开始执行迭代运算表达式:foo('C'),于是输出C。此时输出结果为:ABDC 5.再次开始新一次的循环条件判断,于是foo('B')被执行,输出B;接着判断i(1)<2;循环条件判断结果为真。此时输出结果为:ABDCB 6.再次开始执行循环体。于是i继续自增运算,值变为2;foo('D')被执行,输出D。此时输出结果为:ABDCBD 7.循环体内容又一次执行完毕,同样紧接着开始执行迭代运算表达式:foo('C'),于是再次输出C。此时输出结果为:ABDCBDC 8.进行新一轮的循环条件判断。于是foo('B')被执行,又一次输出B;接着判断i(2)<2,此次循环条件判断结果为假,于是循环到此退出。 所以,最终该程序的运行结果为:ABDCBDCB **while循环与for循环的异同** while循环与for循环之间实际上是可以相互替换的。以常见的数的累加的问题来说,以while循环和for循环分别的实现形式为: ~~~ /* * 求1到100之间的数累加的和? */ public class Demo { private static int for_demo() { int sum = 0; for (int i = 1; i <= 100; i++) { sum += i; } return sum; } private static int while_demo() { int sum = 0; int i = 0; while (i <= 100) { sum = sum + (i++); } return sum; } public static void main(String[] args) { System.out.println("通过for循环完成累加的值为:" + for_demo()); System.out.println("通过while循环完成累加的值为:" + while_demo()); } } ~~~ 而while和for之间唯一的小差就在于:循环控制变量的作用域。这实际上正是涉及到在这篇博客中提到的代码块的相关知识。从上面的用例代码中,我们看到: 方法“for_demo”中,用于控制for循环的循环控制变量“i”,被声明在for循环语句内。所以这里的变量“i”实际上,是被声明在for循环语句块当中的局部变量,所以随着for循环语句块的运行结束,该变量就会从内存中释放,走到生命周期的尽头。 反观方法“while_demo”中,用于控制while循环的循环控制变量"i"则只能被声明在属于"while_demo"的方法代码块中,而不属于循环本身。也就是说,就算当while循环运行结束,该循环控制变量依然有效,仍然可以被访问,因为它实际是属于“while_demo”所声明的代码块中。 这也正是我在这个专栏系列里,第一篇文章[《第一个专栏《重走J2SE之路》,你是否和我有一样的困扰? 》](http://blog.csdn.net/ghost_programmer/article/details/42491083)中提到的: 为什么查看一些Java的源码时,发现老外很多时候选择使用for循环的原因,也正是因为for循环相对于while循环,可以在很小程度上减少内存的开销, **三、跳转语句** 跳转语句是指打破程序的正常运行,跳转到其他部分的语句。Java提供了三种跳转语句,分别为:break、continue以及return。 **3.1、break语句** break语句的使用方式主要三种:跳出switch条件语句、跳出循环以及通过代码块的标示符跳出代码块。我们通过一段代码分别看一下它们具体的应用: ~~~ void break_demo(int a) { // 跳出switch条件语句 switch (a) { case 1: System.out.println(); break; default: break; } // 跳出循环 while (true) { if (++a > 50) { break; } } // 跳出代码块 my_block_1: { my_block_2: { a++; if (a > 5) { break my_block_1; } } } } ~~~ 值得注意的是:在使用break跳出循环时,只会跳出其所在的循环,而其外部的循环并不会跳出,还会继续运行。 **3.2、continue语句** break语句可以用于跳出其所在循环。但是有时我们需要跳出一次循环剩余的循环部分,但同时还要继续下一次循环,这时就用到了continue语句。 ~~~ void continue_demo(){ for (int i = 1; i <= 30; i++) { System.out.print(i+"\t"); if(i%5!=0) continue; System.out.println("*"); } } ~~~ 这段代码运行的输出结果为: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-17_57b43160bd5b8.jpg) 这正是continue语句起到的作用,每执行一次循环体的内容。 首先会输出“i”当前的值及一个制表符,然后会判断当前“i”是否是5的倍数。 如果是不是5的倍数,则会通过continue语句结束该次剩余的部分,所以在其之后的输出语句便不会再被执行。 而如果是5的倍数的话,才会执行之后的输出语句,输出一个“*”号,并换行。 由此,最终才出现了上面我们看到的输出效果。 所以,总的来说break与continue的区别就在于:break语句用于退出整个循环;而continue语句则是用于结束该次循环的剩余循环部分,但继续新一次的循环。 ** ** **3.3、return语句** 简单的来说,如果说break用于跳出循环的话,而return语句则是用于结束整个方法并返回。 return语句可以说是实际开发中最常用的跳转语句了,因为任何方法都需要return语句来控制方法的结束并返回对应类型的值。 ~~~ void return_demo_1() { /* * 实际上void返回类型的值,也使用了return。 只不过在以void作为返回类型的方法中,return是隐式的存在的。 */ } int return_demo_2() { /* * 通过return */ for (int i = 0; i < 5; i++) { i++; for (int j = 0; j < 20; j++) { if (j == 3) { /* * 这里如果使用break,虽然内部循环会被退出,但外部循环仍然会继续执行 * 而使用return则意味结束整个方法,并返回值“5”, * 这样做代表这之后的代表将永远没有机会再运行。 */ return 5; } } // 该语句永远不会被执行到。 System.out.println("外部循环执行一次.."); } return 0; } ~~~ 到了这里,Java中程序语句的使用,我们已经有了一个不错的了解了。最后,就通过一个实际应用的小例子,输出九九乘法表作为结束吧: ~~~ package com.tsr.j2seoverstudy.base; public class Print99 { public static void main(String[] args) { System.out.println("*****************九九乘法表****************"); for (int i = 1; i <= 9; i++) { System.out.print("\t"+i); } System.out.println(); for (int i = 1; i <= 9; i++) { System.out.print(i+"\t"); for (int j = 1; j <= i; j++) { System.out.print(i*j+"\t"); } System.out.println(); } } } ~~~ 运行程序,查看其输出结果: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-17_57b4319cd04fb.jpg)
';