重新开始学习javase_控制程序流程

@学习thinking in java

二、控制程序流程

  1. 负数使用 Java 运算符:
    运算符以一个或多个自变量为基础,可生成一个新值。自变量采用与原始方法调用不同的一种形式,但效果
    是相同的。根据以前写程序的经验,运算符的常规概念应该不难理解。
    加号(+)、减号和负号(-)、乘号(*)、除号(/)以及等号(=)的用法与其他所有编程语言都是类似
    的。
    所有运算符都能根据自己的运算对象生成一个值。除此以外,一个运算符可改变运算对象的值,这叫作“副
    作用”(Side Effect)。运算符最常见的用途就是修改自己的运算对象,从而产生副作用。但要注意生成的
    值亦可由没有副作用的运算符生成。
    几乎所有运算符都只能操作“主类型”(Primitives)。唯一的例外是“=”、“==”和“!=”,它们能操作
    所有对象(也是对象易令人混淆的一个地方)。除此以外,String 类支持“+”和“+=”。
  2. 优先级:个人觉得不必花费力气去记什么优先级,因为能常情况下我们直接用“()”小括号去定义我们的优先级就可以了

  3. 赋值:

    赋值是用等号运算符(=)进行的。它的意思是“取得右边的值,把它复制到左边”。右边的值可以是任何常
    数、变量或者表达式,只要能产生一个值就行。但左边的值必须是一个明确的、已命名的变量。也就是说,
    它必须有一个物理性的空间来保存右边的值。举个例子来说,可将一个常数赋给一个变量(A=4;),但不可
    将任何东西赋给一个常数(比如不能4=A)。


    1.   基本类型:首先我们要知道java内存中对于基本类型的存储是位于栈空间中的,下面看例子:
      @Test
          public void test() {
              int a=12;
              int b;
              b=a;
              a=13;
              System.out.println(b);//12
              /*
               * 这里我们在栈中创建一个a=12,一个b
               * 然后把b=a也就是说b的值也等于a,即12
               * 这个时候把a变成了13,操作的是a
               * b没有影响,故b还是12
               * */
          }
    2.   引用类型也就是对象,这也是初学者最容易出错的地方,下面看例子:
      @Test
          public void test01() {
              // 本来想用Integer来做例子的,发现Integer除了new新对象改值,没有什么方法去修改,所以就用Demo1类吧
              // 1.创建Demo对象d1并赋值为12,这里我们要注意的是程序首先在栈中创建一个d1的句柄,然后在堆中new 一个对象,然后这个d1的句柄
              // 指向了这个对象
              Demo1 d1 = new Demo1();
              d1.i = 12;
              // 2.创建Demo对象d2,并让其也具体d1的功能,d1指向对象A,所以d2也指向对象A,
              Demo1 d2 = d1;
              // d1指向的对象A的值改变了
              d1.i = 13;
              // d2指向的对象,即同样的A,所以当然也改变了
              System.out.println(d2.i);// 13
          }
      
          class Demo1 {
              int i;
          }
    3. String对象的赋值:@学习http://www.cnblogs.com/ITtangtang/p/3976820.html
      •   Java内存模型
        对于String常量,它的值是在常量池中的。而JVM中的常量池在内存当中是以表的形式存在的, 对于String类型,有一张固定长度的CONSTANT_String_info表用来存储文字字符串值,注意:该表只存储文字字符串值,不存储符号引用。说到这里,对常量池中的字符串值的存储位置应该有一个比较明了的理解了。在程序执行的时候,常量池会储存在Method Area,而不是堆中。常量池中保存着很多String对象; 并且可以被共享使用,因此它提高了效率
      • 例:这个例子是我在网上看到的比较全的,讲的比较透彻的例子

        @Test
            public void test04() {
                /**
                 * 情景一:字符串池 JAVA虚拟机(JVM)中存在着一个字符串池,其中保存着很多String对象; 并且可以被共享使用,因此它提高了效率。
                 * 由于String类是final的,它的值一经创建就不可改变。
                 * 字符串池由String类维护,我们可以调用intern()方法来访问字符串池。
                 */
                String s1 = "abc";
                // ↑ 在字符串池创建了一个对象
                String s2 = "abc";
                // ↑ 字符串pool已经存在对象“abc”(共享),所以创建0个对象,累计创建一个对象
                System.out.println("s1 == s2 : " + (s1 == s2));
                // ↑ true 指向同一个对象,
                System.out.println("s1.equals(s2) : " + (s1.equals(s2)));
                // ↑ true 值相等
                // ↑------------------------------------------------------over
                /**
                 * 情景二:关于new String("")
                 * 
                 */
                String s3 = new String("abc");
                // ↑ 创建了两个对象,一个存放在字符串池中,一个存在与堆区中;
                // ↑ 还有一个对象引用s3存放在栈中
                String s4 = new String("abc");
                // ↑ 字符串池中已经存在“abc”对象,所以只在堆中创建了一个对象
                System.out.println("s3 == s4 : " + (s3 == s4));
                // ↑false s3和s4栈区的地址不同,指向堆区的不同地址;
                System.out.println("s3.equals(s4) : " + (s3.equals(s4)));
                // ↑true s3和s4的值相同
                System.out.println("s1 == s3 : " + (s1 == s3));
                // ↑false 存放的地区多不同,一个栈区,一个堆区
                System.out.println("s1.equals(s3) : " + (s1.equals(s3)));
                // ↑true 值相同
                // ↑------------------------------------------------------over
                /**
                 * 情景三: 由于常量的值在编译的时候就被确定(优化)了。 在这里,"ab"和"cd"都是常量,
                 * 这行代码编译后的效果等同于: String str3 = "abcd";
                 */
                String str1 = "ab" + "cd"; // 1个对象
                String str11 = "abcd";
                System.out.println("str1 = str11 : " + (str1 == str11));
                // ↑------------------------------------------------------over
                /**
                 * 情景四: 局部变量str2,str3存储的是存储两个拘留字符串对象(intern字符串对象)的地址。
                 * 
                 * 第三行代码原理(str2+str3): 运行期JVM首先会在堆中创建一个StringBuilder类,
                 * 同时用str2指向的拘留字符串对象完成初始化, 然后调用append方法完成对str3所指向的拘留字符串的合并,
                 * 接着调用StringBuilder的toString()方法在堆中创建一个String对象,
                 * 最后将刚生成的String对象的堆地址存放在局部变量str3中。
                 * 
                 * 而str5存储的是字符串池中"abcd"所对应的拘留字符串对象的地址。 str4与str5地址当然不一样了。
                 * 
                 * 内存中实际上有五个字符串对象: 三个拘留字符串对象、一个String对象和一个StringBuilder对象。
                 */
                String str2 = "ab"; // 1个对象
                String str3 = "cd"; // 1个对象
                String str4 = str2 + str3;
                String str5 = "abcd";
                System.out.println("str4 = str5 : " + (str4 == str5)); // false
                // ↑------------------------------------------------------over
                /**
                 * 情景五: JAVA编译器对string + 基本类型/常量 是当成常量表达式直接求值来优化的。
                 * 运行期的两个string相加,会产生新的对象的,存储在堆(heap)中
                 */
                String str6 = "b";
                String str7 = "a" + str6;
                String str67 = "ab";
                System.out.println("str7 = str67 : " + (str7 == str67));
                // ↑str6为变量,在运行期才会被解析。
                final String str8 = "b";
                String str9 = "a" + str8;
                String str89 = "ab";
                System.out.println("str9 = str89 : " + (str9 == str89));
                // ↑str8为常量变量,编译期会被优化
                // ↑------------------------------------------------------over
        
            }
      • 补充:

        1. 代码中的字符串常量在编译的过程中收集并放在class文件的常量区中,如"123"、"123"+"456"等,含有变量的表达式不会收录,如"123"+a。

        2. JVM在加载类的时候,根据常量区中的字符串生成常量池,每个字符序列如"123"会生成一个实例放在常量池里,这个实例是不在堆里的,也不会被GC

        3. 在执行到双引号包含字符串的语句时,如String a = "123",JVM会先到常量池里查找,如果有的话返回常量池里的这个实例的引用,否则的话创建一个新实例并置入常量池里。如果是 String a = "123" + b (假设b是"456"),前半部分"123"还是走常量池的路线,但是这个+操作符其实是转换成[SringBuffer].Appad()来实现的,所以最终a得到是一个新的实例引用,而且a的value存放的是一个新申请的字符数组内存空间的地址(存放着"123456"),而此时"123456"在常量池中是未必存在的。

          要注意: 我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,创建了String类的对象str。担心陷阱!对象可能并没有被创建!而可能只是指向一个先前已经创建的对象。只有通过new()方法才能保证每次都创建一个新的对象

        4. 在执行String a = new String("123")的时候,首先走常量池的路线取到一个实例的引用,然后在堆上创建一个新的String实例,走以下构造函数给value属性赋值,然后把实例引用赋值给a

        5. String对象的实例调用intern方法后,可以让JVM检查常量池,如果没有实例的value属性对应的字符串序列比如"123"(注意是检查字符串序列而不是检查实例本身),就将本实例放入常量池,如果有当前实例的value属性对应的字符串序列"123"在常量池中存在,则返回常量池中"123"对应的实例的引用而不是当前实例的引用,即使当前实例的value也是"123"。

        6. 存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充。String的 intern()方法就是扩充常量池的 一个方法;当一个String实例str调用intern()方法时,Java 查找常量池中 是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常 量池中增加一个Unicode等于str的字符串并返回它的引用;看示例就清楚了:

          @Test
              public void test06() {
                  String s0 = "kvill"; 
                  String s1 = new String("kvill"); 
                  String s2 = new String("kvill"); 
                  System.out.println( s0 == s1 ); //false
                  System.out.println( "**********" ); 
                  s1.intern(); //虽然执行了s1.intern(),但它的返回值没有赋给s1
                  s2 = s2.intern(); //把常量池中"kvill"的引用赋给s2 
                  System.out.println( s0 == s1); //flase
                  System.out.println( s0 == s1.intern() ); //true//说明s1.intern()返回的是常量池中"kvill"的引用
                  System.out.println( s0 == s2 ); //true
          
              }
  4.  算术运算

    •   注意优先级(用“()"就能解决)

    •      关于进阶的问题

      •   整数与整数运算不存在进阶,直接砍掉小数:例:System.out.println(3/2)//1

    •   自动递增和递减(注意++i和i++,前者先运算后用值,后者先用值,用运算
      @Test
          public void test08() {
              int i=0;
              int j=0;
              System.out.println(i++);//先用i所以输出为0
              System.out.println(++j);//先运算后等于1,再打印为1
          }
    • 关系运算符(注意:检查对象是否相等)

    • 逻辑运算符
      • 逻辑运算符 AND(&&)、OR(||)以及 NOT(!)能生成一个布尔值(true 或 false)——以自变量的逻辑关
        系为基础。要注意其优先级的问题,不过本人还是一般用”()“来搞定这个问题
      •    短路的问题(&)、(|)其实很简单比如说:A||B||C和A|B|C,如果A正确显示不管B和C是否正确,整个表达式肯定是正确的,前者将不再验证BC的条件,后者却依然进行验证,这称为短路,举个例子:
        @Test
            public void test09() {
                Demo2_1 d = new Demo2_1();
                System.out.println(d.A() || d.B() || d.C());
                System.out.println("**************");
                System.out.println(d.A() | d.B() | d.C());
            }
        
            class Demo2_1 {
                public boolean A() {
                    System.out.println("A");
                    return true;
                }
        
                public boolean B() {
                    System.out.println("B");
                    return true;
                }
        
                public boolean C() {
                    System.out.println("C");
                    return true;
                }
        
            }
        /*
        运行结果:
        A
        true
        **************
        A
        B
        C
        true
        
        */
    •   按位运算符(就是将任意一个数转化成二进制,然后在二进制的基础上按下面的原则进行计算,注意负数的二进制就是对应正数的反码+1)

      若两个输入位都是1,则按位AND运算符(&)在输出位里生成一个1;否则生成0。若两个输入位里至少有一个是1,则按位OR运算符(|)在输出位里生成一个1;只有在两个输入位都是0的情况下,它才会生成一个0。若两个输入位的某一个是1,但不全都是1,那么按位XOR(^,异或)在输出位里生成一个1。按位NOT(~,也叫作“非”运算符)属于一元运算符;它只对一个自变量进行操作(其他所有运算符都是二元运算符)。按位NOT生成与输入位的相反的值——若输入0,则输出1;输入1,则输出0,例:
    • 移位运算符

      •   左移:将二进制码向左移动N格,空白位用0补足,例:
        @Test
            public void test11() {
                int i=3;
                System.out.println(i<<2);//12
            }
      •       右移:将二进制码向右移动N格,空白位用符号位数字补足,即正数用0补,负数用1补,例:
        @Test
            public void test12() {
                int i=3;
                int j=-3;
                System.out.println(i>>2);
                System.out.println(j>>2);
            }

      • 无符号右移(>>>),针对右移(>>)java中的无符号右移就是改变了右移(>>)中的用符号位填充空白处用0去填充空白处,例:
        @Test
            public void test13() {
                int i=3;
                int j=-3;
                System.out.println(i>>>2);
                System.out.println(j>>>2);
            }




    •  三元 if-else 运算符(个人感觉就是if—else的减化版) 
      布尔表达式 ? 值 0:值 1     例:

      @Test
          public void test14() {
              int a = 12;
              int b = 10;
              if (a > b) {
                  System.out.println(a);
              } else {
                  System.out.println(b);
              }
              System.out.println("*************");
              System.out.println(a > b ? a : b);
              /*
               * 12
               *************
               * 12
               */
          }
    • 字符串运算符
      运用“String +”时一些有趣的现象。若表达式以一个String起头,那么后续所有运算对象都必
      须是字串


    • 转型(低位与高位加运算,自动向高位转)

二、执行控制

  1. if-else
    if-else 语句或许是控制程序流程最基本的形式。其中的 else 是可选的,所以可按下述两种形式来使用if:
    if(布尔表达式)
    语句
    或者
    if(布尔表达式)
    语句
    else
    语句

    条件必须产生一个布尔结果。“语句”要么是用分号结尾的一个简单语句,要么是一个复合语句——封闭在
    括号内的一组简单语句。在本书任何地方,只要提及“语句”这个词,就有可能包括简单或复合语句。
    作为if-else 的一个例子,下面这个 test()方法可告诉我们猜测的一个数字位于目标数字之上、之下还是相
    等:
    static int test(int testval) {
      int result = 0;
      if(testval > target)
        result = -1;
      else if(testval < target)
        result = +1;
      else
        result = 0; // match
        return result;
    }
    最好将流程控制语句缩进排列,使读者能方便地看出起点与终点。
    • 1. return
      return关键字有两方面的用途:指定一个方法返回什么值(假设它没有 void 返回值),并立即返回那个
      值。可据此改写上面的 test()方法,使其利用这些特点:
      85
      static int test2(int testval) {
        if(testval > target)
          return -1;
        if(testval < target)
           return +1;
           return 0; // match
      }
      不必加上else,因为方法在遇到 return后便不再继续。


  2.   循环
      1.   while,do-while和 for控制着循环,有时将其划分为“反复语句”。除非用于控制反复的布尔表达式得到
        “假”的结果,否则语句会重复执行下去。while 循环的格式如下:
        while(布尔表达式)
        语句
        在循环刚开始时,会计算一次“布尔表达式”的值。而对于后来每一次额外的循环,都会在开始前重新计算
        一次。
        下面这个简单的例子可产生随机数,直到符合特定的条件为止:
        public class WhileTest {
        public static void main(String[] args) {
            double r = 0;
            while(r < 0.99d) {
                r = Math.random();
                System.out.println(r);
            }
          } 
        } ///:~        
      2. do-while
        do-while 的格式如下:

        do
        语句
        while(布尔表达式)

        while 和do-while 唯一的区别就是do-while肯定会至少执行一次;也就是说,至少会将其中的语句“过一
        遍”——即便表达式第一次便计算为false。而在 while 循环结构中,若条件第一次就为false,那么其中的
        语句根本不会执行。在实际应用中,while 比 do-while 更常用一些。


      3. for

    for(初始表达式; 布尔表达式; 步进)语句

      •  要学习for中的执行顺序:
        @Test
            public void test() {
                for (int i = getI(); i < getJ(); i = IJaJa(i)) {
                    System.out.println("****************");
                }
                /*
                 * 分析:首先执行语句之前初始表达式(这个过程只执行一次)故先打印一个"getI()"
                 * 然后,判断条件,满足打印一个"getJ()",满足就执行语句块打印************** 再然后,步进,打印一个“i++"
                 * 再然后,判断条件,满足打印一个"getJ()",满足就执行语句块打印************** 再然后,步进,打印一个“i++"
                 * 再然后,判断条件,不满足,则结束程序
                 */
        
            }
        
            public int getI() {
                System.out.println("getI()");
                return 0;
            }
        
            public int getJ() {
                System.out.println("getJ()");
                return 2;
            }
        
            public int IJaJa(int i) {
                System.out.println("i++");
                return ++i;
            }

         其实等效下列的语句:

        @Test
            public void test01() {
                int i=0;
                while(i<2){
                    System.out.println("************");
                    ++i;
                }
            }

        这是for例子的结果:

        getI()
        getJ()
        ****************
        i++
        getJ()
        ****************
        i++
        getJ()
      •  无论初始表达式,布尔表达式,还是步进,都可以置空。
        其实知道了for的执行顺序之后,相信应该明白,当for循环全部置空就是无限循环了
      •    for循环中的无论初始表达式,布尔表达式,还是步进可以为多个条件,例:
        @Test
            public void test02() {
                for(int i=0,j=0;i<2&&j<1;i++,j++){
                    int count=1;
                    System.out.println("******count: "+count);//******count: 1
                    count++;
                }
            }




  3.   循环的中断(对while和for通用,这里仅用for举例)
    •   用break 和continue 控制循环的流程
      •   break:中断当前循环,例:
        @Test
            public void test03() {
                for(int i=0;;){//这是一个无限循环
                    System.out.println(i++);
                    if(i>1)//当i>1的时候终止当前循环,注意if可不是循环
                        break;//所以输出0,1
                }
            }
      •     continue:忽略循环体后面的内容,继续进行循环体:
        @Test
            public void test04() {
                for(int i=0;;){//这是一个无限循环
                    i++;
                    if(i<2)//当i<2的时候,注意if可不是循环
                        continue;//不再后面的的内容,再次循环,所以执行下面的语句
                    System.out.println(i);//2
                    if(i>=2)
                        break;
                    
                }
            }
    •   使用“标签”配合continue和break:

      “标签”是后面跟一个冒号的标识符,就象下面这样:
      label1:
      对Java 来说,唯一用到标签的地方是在循环语句之前。进一步说,它实际需要紧靠在循环语句的前方——在
      标签和循环之间置入任何语句都是不明智的。而在循环之前设置标签的唯一理由是:我们希望在其中嵌套另
      一个循环或者一个开关。这是由于 break和 continue 关键字通常只中断当前循环,但若随同标签使用,它们
      就会中断到存在标签的地方。如下所示:

      label1:
      外部循环{
      内部循环{
      //...
      break; //1
      //...
      continue; //2
      //...
      continue label1; //3
      //...
      break label1; //4
      }
      }


      在条件1 中,break 中断内部循环,继续外部循环。在条件2 中,continue 移回内部循环的起始处。但
      在条件3 中,continue label1 却同时中断内部循环以及外部循环,并移至label1 处。随后,它实际是继续
      循环,但却从外部循环开始。在条件4 中,break label1也会中断所有循环,并回到label1 处,但并不重
      新进入循环。也就是说,它实际是完全中止了两个循环。
      下面是for 循环的一个例子:

          @Test
          public void test06() {
              int i = 0;
              outer: // 标签要紧邻着循环
              for (; true;) { // 这是个无限循环
                  inner: // 标签要紧邻着循环
                  for (; i < 10; i++) {
                      prt("i = " + i);// static void prt(String s)
                                      // {System.out.println(s);}
                      if (i == 2) {
                          prt("continue");
                          continue;
                      }
                      if (i == 3) {
                          prt("break");
                          i++; // i增加的语句不能放break后面,不然永远不会得到增加
                          break;
                      }
                      if (i == 7) {
                          prt("continue outer");
                          i++; // i增加的语句不能放break后面,不然永远不会得到增加
                          continue outer;
                      }
                      if (i == 8) {
                          prt("break outer");
                          break outer;
                      }
                      for (int k = 0; k < 5; k++) {
                          if (k == 3) {
                              prt("continue inner");
                              continue inner;
                          }
                      }
                  }
              }
              /*
               * i = 0 continue inner i = 1 continue inner i = 2 continue i = 3 break
               * i = 4 continue inner i = 5 continue inner i = 6 continue inner i = 7
               * continue outer i = 8 break outer
               */
          }
      
          static void prt(String s) {
              System.out.println(s);
          }

      如果没有break outer 语句,就没有办法在一个内部循环里找到出外部循环的路径。这是由于break 本身只
      能中断最内层的循环(对于continue 同样如此)。
      当然,若想在中断循环的同时退出方法,简单地用一个return 即可。
      备注:其实吧,这种也不推荐使用,我们完成可以做个flag来控制循环,达到同样的效果




  4.   switch

    switch(整数选择因子) {
    case 整数值1 : 语句; break;
    case 整数值2 : 语句; break;
    case 整数值3 : 语句; break;
    case 整数值4 : 语句; break;
    case 整数值5 : 语句; break;
    //..
    default:语句;
    }
    要注意的是这里的break不能少,少了break不会报错,会一直往下执行


    •   在Java7之前,switch只能支持 byte、short、char、int或者其对应的封装类以及Enum类型。在Java7中,呼吁很久的String支持也终于被加上了。例:
      @Test
          public void test07() {
              String a="a";
              switch(a){
              case "b" :
              case "a" : System.out.println("a"); break;//a
              default  :System.out.println("**");
              }
          }

       注意这里的String a不能为空,代码的原理其实是根据String的hashCode来匹配的,String 为null ,String.hashCode必然会报空指针异常

  



 

原文地址:https://www.cnblogs.com/wangyang108/p/5773904.html