• 重新开始学习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必然会报空指针异常

      



     

  • 相关阅读:
    BZOJ2738 矩阵乘法
    BZOJ3585 mex
    BZOJ1930 [Shoi2003]pacman 吃豆豆
    BZOJ3858 Number Transformation
    vue2.0学习小列子
    vue2.0 tab切换几种方式
    github学习
    只有自己看的懂的vue 二叉树的3级联动
    vuex学习
    vue2.0 MintUI安装和基本使用
  • 原文地址:https://www.cnblogs.com/wangyang108/p/5773904.html
Copyright © 2020-2023  润新知