Thinking in Java ---ch03笔记

Ch03 Controlling Program Flow

3.1 使用Java运算符
加号(+)、减号和负号(-)、乘号(*)、除号(/)以及等号(=

几乎所有运算符都只能操作主类型Primitives)。唯一的例外是“=”“==”“!=”,它们能操作所有对象(也是对象易令人混淆的一个地方)。除此以外,String类支持“+”“+=”

3.1.1 优先级
3.1.2 赋值
赋值是用等号运算符(=)进行的。


1.对主数据类型的赋值是非常直接的。由于主类型容纳了实际的值,而且并非指向一个对象的句柄,所以在为其赋值的时候,可将来自一个地方的内容复制到另一个地方。例如,假设为主类型使用“A=B”,那么B处的内容就复制到A。若接着又修改了A,那么B根本不会受这种修改的影响。

 


2.但在为对象赋值的时候,情况却发生了变化 。对一个对象进行操作时,我们真正操作的是它的句柄。所以倘若 从一个对象到另一个对象赋值,实际就是将句柄从一个地方复制到另一个地方。这意味着假若为对象使用“C=D”,那么CD最终都会指向最初只有D才指向的那个对象。

java c03.Assignment
运行位于一个包裹里的程序时,随时都要注意这方面的问题。
下面是例子:

//: Assignment.java

// Assignment with objects is a bit tricky

package c03;

 

class Number {

  int i;

}

 

public class Assignment {

  public static void main(String[] args) {

    Number n1 = new Number();

    Number n2 = new Number();

    n1.i = 9;

    n2.i = 47;

    System.out.println("1: n1.i: " + n1.i +

      ", n2.i: " + n2.i);

    n1 = n2;

    System.out.println("2: n1.i: " + n1.i +

      ", n2.i: " + n2.i);

    n1.i = 27;

    System.out.println("3: n1.i: " + n1.i +

      ", n2.i: " + n2.i);

  }

} ///:~


Number类非常简单,它的两个实例(n1n2)是在main()里创建的。每个Number中的i值都赋予了一个不同的值。随后,将n2赋给n1,而且n1发生改变。在许多程序设计语言中,我们都希望n1n2任何时候都相互独立。但由于我们已赋予了一个句柄,所以下面才是真实的输出:
1: n1.i: 9, n2.i: 47
2: n1.i: 47, n2.i: 47
3: n1.i: 27, n2.i: 27
看来改变n1的同时也改变了n2!这是由于无论n1还是n2都包含了相同的句柄,它指向相同的对象(最初的句柄位于n1内部,指向容纳了值9的一个对象。在赋值过程中,那个句柄实际已经丢失;它的对象会由垃圾收集器自动清除)。
这种特殊的现象通常也叫作别名,是Java操作对象的一种基本方式。但假若不愿意在这种情况下出现别名,又该怎么操作呢?可放弃赋值,并写入下述代码:
n1.i = n2.i;
这样便可保留两个独立的对象,而不是将n1n2绑定到相同的对象。但您很快就会意识到,这样做会使对象内部的字段处理发生混乱,并与标准的面向对象设计准则相悖。由于这并非一个简单的话题,所以留待第12章详细论述,那一章是专门讨论别名的。其时,大家也会注意到对象的赋值会产生一些令人震惊的效果。

3. 对象传递到方法内部时,也会产生别名现象。(如果传递的是简单类型的话,则不会出现别名现象)

//: PassObject.java

// Passing objects to methods can be a bit tricky

 

class Letter {

  char c;

}

public class PassObject {

  static void f(Letter y) {

    y.c = 'z';

  }

  public static void main(String[] args) {

    Letter x = new Letter();

    x.c = 'a';

    System.out.println("1: x.c: " + x.c);

    f(x);

    System.out.println("2: x.c: " + x.c);

  }

} ///:~


在许多程序设计语言中,f()方法表面上似乎要在方法的作用域内制作自己的自变量Letter y的一个副本。但同样地,实际传递的是一个句柄。所以下面这个程序行:
y.c = 'z';
实际改变的是f()之外的对象。输出结果如下:
1: x.c: a
2: x.c: z

 

--********

public class ChangeParam {

 

       /**

        * @param args

        */

       void a(String P1, String P2){

              System.out.println("P1 before:" + P1);

              P1="love snow";

              System.out.println("P1 after :" + P1);

       }

      

       public static void main(String[] args) {

              // TODO Auto-generated method stub

         ChangeParam dd=new ChangeParam();

        String a="aa";

        String b= "bb";

         dd.a(a,b);      

        System.out.println("a:"+ a);

              //dd.a();

       }

}

输出如下:

P1 before:aa

P1 after :love snow

a:aa

说明 a=”aa”经历dd.a(a,b)之后,自身并没有改变

3.1.3 算术运算符
Java的基本算术运算符与其他大多数程序设计语言是相同的。其中包括加号(+)、减号(-)、除号(/)、乘号(*)以及模数(%,从整数除法中获得余数)。整数除法会直接砍掉小数,而不是进位。
Java也用一种简写形式进行运算,并同时进行赋值操作。这是由等号前的一个运算符标记的,而且对于语言中的所有运算符都是固定的。例如,为了将4加到变量x,并将结果赋给x,可用:x+=4

为生成数字,程序首先会创建一个Random(随机)对象。由于自变量是在创建过程中传递的,所以Java将当前时间作为一个种子值,由随机数生成器利用。通过Random对象,程序可生成许多不同类型的随机数字。做法很简单,只需调用不同的方法即可:nextInt()nextLong()nextFloat()或者nextDouble()
若随同随机数生成器的结果使用,模数运算符(%)可将结果限制到运算对象减1的上限(本例是99)之下。

3.1.4 自动递增和递减
 
两种很不错的快捷运算方式是递增和递减运算符(常称作自动递增自动递减运算符)。其中,递减运算符是“--”,意为减少一个单位;递增运算符是“++”,意为增加一个单位。举个例子来说,假设A是一个int(整数)值,则表达式++A就等价于(A = A + 1)。递增和递减运算符结果生成的是变量的值。
对每种类型的运算符,都有两个版本可供选用;通常将其称为前缀版后缀版前递增表示++运算符位于变量或表达式的前面;而后递增表示++运算符位于变量或表达式的后面。类似地,前递减意味着--运算符位于变量或表达式的前面;而后递减意味着--运算符位于变量或表达式的后面。对于前递增和前递减(如++A--A),会先执行运算,再生成值(也就是前递增和前递减表达式运算后进行)。而对于后递增和后递减(如A++A--),会先生成值,再执行运算。下面是一个例子:

3.1.5 关系运算符
关系运算符生成的是一个布尔Boolean)结果。它们评价的是运算对象值之间的关系。若关系是真实的,关系表达式会生成true(真);若关系不真实,则生成false(假)。关系运算符包括小于(<)、大于(>)、小于或等于(<=)、大于或等于(>=)、等于(==)以及不等于(!=)。等于和不等于适用于所有内建的数据类型,但其他比较不适用于boolean类型。

1. 检查对象是否相等(关于equals在后面再认识)
关系运算符==!=也适用于所有对象,但它们的含义通常会使初涉Java领域的人找不到北。下面是一个例子:

//: Equivalence.java

public class Equivalence {

  public static void main(String[] args) {

    Integer n1 = new Integer(47);

    Integer n2 = new Integer(47);

    System.out.println(n1 == n2);

    System.out.println(n1 != n2);

  }

} ///:~


其中,表达式System.out.println(n1 == n2)可打印出内部的布尔比较结果。一般人都会认为输出结果肯定先是true,再是false,因为两个Integer对象都是相同的。
但尽管对象的内容相同,句柄却是不同的,而==!=比较的正好就是对象句柄。所以输出结果实际上先是false,再是true。这自然会使第一次接触的人感到惊奇。
若想对比两个对象的实际内容是否相同,又该如何操作呢?此时,必须使用所有对象都适用的特殊方法
equals()但这个方法不适用于主类型(主类型即基本类型),那些类型直接使用==!=即可。下面举例说明如何使用:

//: Equivalence.java

public class Equivalence {

  public static void main(String[] args) {

    Integer n1 = new Integer(47);

    Integer n2 = new Integer(47);

    System.out.println(n1 == n2);

    System.out.println(n1 != n2);

  }

} ///:~

 

note:注意这里的string比较时间

public class EqualsMethod2 {

       public static void main(String[] args) {


         

              String a = new String ("aa");

              String b = new String ("aa");

              System.out.println(a==b);

              System.out.println(a.equals(b));


 

              String c = "aa";

              String d = "aa";


              System.out.println(c==d);

              System.out.println(c.equals(d));

      

       }

} // /:~

输出:

false

true

true

true

显然,当String 类型是通过New String()得到时候只能用equals比较,否则就是简单类型,可以直接用“==“ 比较

3.1.6 逻辑运算符
逻辑运算符AND&&)、OR||)以及NOT!)能生成一个布尔值(truefalse——以自变量的逻辑关系为基础。下面这个例子向大家展示了如何使用关系和逻辑运算符。

1. 短路(可以忽略)

3.1.7 按位运算符
按位运算符允许我们操作一个整数主数据类型中的单个比特,即二进制位。按位运算符会对两个自变量中对应的位执行布尔代数,并最终生成一个结果。
按位运算来源于C语言的低级操作。我们经常都要直接操纵硬件,需要频繁设置硬件寄存器内的二进制位。Java的设计初衷是嵌入电视顶置盒内,所以这种低级操作仍被保留下来了。然而,由于操作系统的进步,现在也许不必过于频繁地进行按位运算。

AND运算符(&

OR运算符(|

XOR^,异或)

NOT~,也叫作运算符)属于一元运算符;

3.1.8 移位运算符
移位运算符面向的运算对象也是二进制的。可单独用它们处理整数类型(主类型的一种)。

这里再引一下负数二进制的表示方法,以前给学生讲过好多次,没有想到自己也会忘记,惭愧,呵呵

负数的二进制表示就是该负数的绝对值的二进制数按位取反再加一。

如果是-1的话, 也就是把1的二进制按位取反,然后加1

1二进制表示:0000 0000 0000 0000 0000 0000 0000 0001

按位取反    1111 1111 1111 1111 1111 1111 1111 1110

再加1      1111 1111 1111 1111 1111 1111 1111 1111(这就是-1的二进制表示)

无符号右移10位:0000 0000 0011 1111 1111 1111 1111

另外记住下面这个规则就ok,有时候面试会用到哦,

1)左移位运算符(<<)能将运算符左边的运算对象向左移动运算符右侧指定的位数(在低位补0)。

2有符号右移位运算符(>>)则将运算符左边的运算对象向右移动运算符右侧指定的位数。有符号右移位运算符使用了符号扩展:若值为正,则在高位插入0;若值为负,则在高位插入1

3Java也添加了一种无符号右移位运算符(>>>),它使用了零扩展:无论正负,都在高位插入0。这一运算符是CC++没有的。



4 )若对char byte 或者short 进行移位处理,那么在移位进行之前,它们会自动转换成一个int 。只有右侧的5 个低位才会用到。这样可防止我们在一个int 数里移动不切实际的位数。

5)若对一个long值进行处理,最后得到的结果也是long。此时只会用到右侧的6个低位,防止移动超过long值里现成的位数。

但在进行无符号右移位时,也可能遇到一个问题。若对byteshort值进行右移位运算,得到的可能不是正确的结果(Java 1.0Java 1.1特别突出)。它们会自动转换成int类型,并进行右移位。但零扩展不会发生,所以在那些情况下会得到-1的结果。可用下面这个例子检测自己的实现方案:

public class URShift {

       public static void main(String[] args) {

              System.out.println("==== Shift-Left Operations====");

              int i =- 1;

              i<<=2;

              System.out.println("-1<<=2:"+i);

           i =1;

              i<<=2;

              System.out.println(" 1<<=2:"+i);

              System.out.println("==== Shift Right - Signed====");

              i=1;

              i>>=2;

              System.out.println(" 1>>=2:"+i);        

              i =- 1;

              i>>=2;

              System.out.println("-1>>=2:"+i);

              System.out.println("==== Shift Right - unSigned====");

              i = -1;

              i >>>= 2;

              System.out.println("-1 >>>= 2"+i);

             

              long l = -1;

              l >>>= 10;

              System.out.println(l);

              short s = -1;

              s >>>= 10;

              System.out.println(s);

              byte b = -1;

              b >>>= 10;

              System.out.println(b);

       }

} // /:~

输出:

==== Shift-Left Operations====

-1<<=2:-4

 1<<=2:4

==== Shift Right - Signed====

 1>>=2:0

-1>>=2:-1

==== Shift Right - unSigned====

-1 >>>= 21073741823

18014398509481983

-1

-1

3.1.9 三元if-else运算符
这种运算符比较罕见,因为它有三个运算对象。但它确实属于运算符的一种,因为它最终也会生成一个值。这与本章后一节要讲述的普通if-else语句是不同的。表达式采取下述形式:

布尔表达式 ? 0:1

布尔表达式的结果为true,就计算0”,而且它的结果成为最终由运算符产生的值。但若布尔表达式的结果为false,计算的就是1”,而且它的结果成为最终由运算符产生的值。
当然,也可以换用普通的if-else语句(在后面介绍

可将条件运算符用于自己的副作用,或用于它生成的值。但通常都应将其用于值,因为那样做可将运算符与if-else明确区别开。

3.1.10 逗号运算符
Java里需要用到逗号的唯一场所就是for循环,本章稍后会对此详加解释。

3.1.11 字串运算符+

3.1.12 运算符常规操作规则
使用运算符的一个缺点是括号的运用经常容易搞错。即使对一个表达式如何计算有丝毫不确定的因素,都容易混淆括号的用法。这个问题在Java里仍然存在。
CC++中,一个特别常见的错误如下:

while(x = y) {
//...
}

 

程序的意图是测试是否相等==),而不是进行赋值操作。在CC++中,若y是一个非零值,那么这种赋值的结果肯定是true。这样使可能得到一个无限循环。在Java里,这个表达式的结果并不是布尔值,而编译器期望的是一个布尔值,而且不会从一个int数值中转换得来。所以在编译时,系统就会提示出现错误,有效地阻止我们进一步运行程序。

 

3.1.13 造型运算符
造型Cast)的作用是与一个模型匹配。在适当的时候,Java会将一种数据类型自动转换成另一种。例如,假设我们为浮点变量分配一个整数值,计算机会将int自动转换成float。通过造型,我们可明确设置这种类型的转换,或者在一般没有可能进行的时候强迫它进行。
为进行一次造型,要将括号中希望的数据类型(包括所有修改符)置于其他任何值的左侧。下面是一个例子:

void casts() {
int i = 200;
long l = (long)i;
long l2 = (long)200;
}

正如您看到的那样,既可对一个数值进行造型处理,亦可对一个变量进行造型处理。但在这儿展示的两种情况下,造型均是多余的,因为编译器在必要的时候会自动进行int值到long值的转换。

Java里,造型则是一种比较安全的操作。

1) “缩小转换Narrowing Conversion)的操作(也就是说,脚本是能容纳更多信息的数据类型,将其转换成容量较小的类型),此时就可能面临信息丢失的危险。此时,编译器会强迫我们进行造型,就好象说:这可能是一件危险的事情——如果您想让我不顾一切地做,那么对不起,请明确造型。

2)”放大转换Widening conversion),则不必进行明确造型,因为新类型肯定能容纳原来类型的信息,不会造成任何信息的丢失。
Java允许我们将任何主类型造型为其他任何一种主类型,但布尔值(bollean)要除外.

 
1. 字面值

//: Literals.java

 

class Literals {

  char c = 0xffff; // max char hex value

  byte b = 0x7f; // max byte hex value

  short s = 0x7fff; // max short hex value

  int i1 = 0x2f; // Hexadecimal (lowercase)

  int i2 = 0X2F; // Hexadecimal (uppercase)

  int i3 = 0177; // Octal (leading zero)

  // Hex and Oct also work with long.

  long n1 = 200L; // long suffix

  long n2 = 200l; // long suffix

  long n3 = 200;

  //! long l6(200); // not allowed

  float f1 = 1;

  float f2 = 1F; // float suffix

  float f3 = 1f; // float suffix

  float f4 = 1e-45f; // 10 to the power

  float f5 = 1e+9f; // float suffix

  double d1 = 1d; // double suffix

  double d2 = 1D; // double suffix

  double d3 = 47e47d; // 10 to the power

} ///:~

 

2. 转型
大家会发现假若对主数据类型执行任何算术或按位运算,只要它们int(即charbyte或者short),那么在正式执行运算之前,那些值会自动转换成int这样一来,最终生成的值就是int类型。所以只要把一个值赋回较小的类型,就必须使用造型。此外,由于是将值赋回给较小的类型,所以可能出现信息丢失的情况)。通常,表达式中最大的数据类型是决定了表达式最终结果大小的那个类型。若将一个float值与一个double值相乘,结果就是double;如将一个int和一个long值相加,则结果为long

 

3.1.15 复习计算顺序
在我举办的一次培训班中,有人抱怨运算符的优先顺序太难记了。一名学生推荐用一句话来帮助记忆:
“Ulcer Addicts Really Like C A lot”溃疡患者特别喜欢(维生素)C”

助记词

运算符类型

运算符

Ulcer

Unary

+ - ++ – [[ rest...]]

Addicts

Arithmetic (and shift)

* / % + - << >>

Really

Relational

> < >= <= == !=

Like

Logical (and bitwise)

&& || & | ^

C

Conditional (ternary)

A > B ? X : Y

A Lot

Assignment

= (and compound assignment like *=)


当然,对于移位和按位运算符,上表并不是完美的助记方法;但对于其他运算来说,它确实很管用。

 

3.2 执行控制

3.2.1 真和假

所有条件语句都利用条件表达式的真或假来决定执行流程。条件表达式的一个例子是A==B。它用条件运算符“==”来判断A值是否等于B值。该表达式返回truefalse。本章早些时候接触到的所有关系运算符都可拿来构造一个条件语句。注意Java不允许我们将一个数字作为布尔值使用,即使它在CC++里是允许的(真是非零,而假是零)。若想在一次布尔测试中使用一个非布尔值——比如在if(a)里,那么首先必须用一个条件表达式将其转换成一个布尔值,例如if(a!=0)。(以前自己也犯这种错)

3.2.2 if-else

1. return
return关键字有两方面的用途:指定一个方法返回什么值(假设它没有void返回值),并立即返回那个值。可据此改写上面的test()方法,使其利用这些特点:

static int test2(int testval) {

  if(testval > target)

    return -1;

  if(testval < target)

    return +1;

  return 0; // match

}

3.2.3 反复
whiledo-whilefor控制着循环,有时将其划分为反复语句。除非用于控制反复的布尔表达式得到的结果,否则语句会重复执行下去。while循环的格式如下:

while(布尔表达式)
语句

 

3.2.4 do-while
do-while的格式如下:

do
语句
while(布尔表达式)

 

3.2.5 for
for循环在第一次反复之前要进行初始化。随后,它会进行条件测试,而且在每一次反复的时候,进行某种形式的步进Stepping)。for循环的形式如下:

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

 

3.2.6 中断和继续
在任何循环语句的主体部分,亦可用breakcontinue控制循环的流程。其中,break用于强行退出循环,不执行循环中剩余的语句。而continue则停止执行当前的反复,然后退回循环起始和,开始新的反复。
下面这个程序向大家展示了breakcontinueforwhile循环中的例子:

 

3.2.7 开关
开关Switch)有时也被划分为一种选择语句。根据一个整数表达式的值,switch语句可从一系列代码选出一段执行。它的格式如下:

switch(整数选择因子) {
case 整数值1 : 语句; break;
case 整数值2 : 语句; break;
case 整数值3 : 语句; break;
case 整数值4 : 语句; break;
case 整数值5 : 语句; break;
//..
default:语句;
}

其中,整数选择因子是一个特殊的表达式,能产生整数值。switch能将整数选择因子的结果与每个整数值比较。若发现相符的,就执行对应的语句(简单或复合语句)。若没有发现相符的,就执行default语句。
在上面的定义中,大家会注意到每个case均以一个break结尾。这样可使执行流程跳转至switch主体的末尾。这是构建switch语句的一种传统方式,但break是可选的。若省略break,会继续执行后面的case语句的代码,直到遇到一个break为止。尽管通常不想出现这种情况,但对有经验的程序员来说,也许能够善加利用。注意最后的default语句没有break,因为执行流程已到了break的跳转目的地。当然,如果考虑到编程风格方面的原因,完全可以在default语句的末尾放置一个break,尽管它并没有任何实际的用处。
switch语句是实现多路选择的一种易行方式(比如从一系列执行路径中挑选一个)。但它要求使用一个选择因子,并且必须是intchar那样的整数值。(其实好像应该是int,short,byte,char,都可以例如,假若将一个字串或者浮点数作为选择因子使用,那么它们在switch语句里是不会工作的。对于非整数类型,则必须使用一系列if语句。
 

 

你可能感兴趣的:(Java,积累)