@学习thinking in java
二、控制程序流程
- 负数使用 Java 运算符:
运算符以一个或多个自变量为基础,可生成一个新值。自变量采用与原始方法调用不同的一种形式,但效果
是相同的。根据以前写程序的经验,运算符的常规概念应该不难理解。
加号(+)、减号和负号(-)、乘号(*)、除号(/)以及等号(=)的用法与其他所有编程语言都是类似
的。
所有运算符都能根据自己的运算对象生成一个值。除此以外,一个运算符可改变运算对象的值,这叫作“副
作用”(Side Effect)。运算符最常见的用途就是修改自己的运算对象,从而产生副作用。但要注意生成的
值亦可由没有副作用的运算符生成。
几乎所有运算符都只能操作“主类型”(Primitives)。唯一的例外是“=”、“==”和“!=”,它们能操作
所有对象(也是对象易令人混淆的一个地方)。除此以外,String 类支持“+”和“+=”。 - 优先级:个人觉得不必花费力气去记什么优先级,因为能常情况下我们直接用“()”小括号去定义我们的优先级就可以了
- 赋值:
赋值是用等号运算符(=)进行的。它的意思是“取得右边的值,把它复制到左边”。右边的值可以是任何常
数、变量或者表达式,只要能产生一个值就行。但左边的值必须是一个明确的、已命名的变量。也就是说,
它必须有一个物理性的空间来保存右边的值。举个例子来说,可将一个常数赋给一个变量(A=4;),但不可
将任何东西赋给一个常数(比如不能4=A)。- 基本类型:首先我们要知道java内存中对于基本类型的存储是位于栈空间中的,下面看例子:
@Test
public void test() {
int a=12;
int b;
b=a;
a=13;
System.out.println(b);//
/*
* 这里我们在栈中创建一个a=12,一个b
* 然后把b=a也就是说b的值也等于a,即12
* 这个时候把a变成了13,操作的是a
* b没有影响,故b还是12
* */
} - 引用类型也就是对象,这也是初学者最容易出错的地方,下面看例子:
@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);//
} class Demo1 {
int i;
} - 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 } -
补充:
代码中的字符串常量在编译的过程中收集并放在class文件的常量区中,如"123"、"123"+"456"等,含有变量的表达式不会收录,如"123"+a。
JVM在加载类的时候,根据常量区中的字符串生成常量池,每个字符序列如"123"会生成一个实例放在常量池里,这个实例是不在堆里的,也不会被GC
-
在执行到双引号包含字符串的语句时,如String a = "123",JVM会先到常量池里查找,如果有的话返回常量池里的这个实例的引用,否则的话创建一个新实例并置入常量池里。如果是 String a = "123" + b (假设b是"456"),前半部分"123"还是走常量池的路线,但是这个+操作符其实是转换成[SringBuffer].Appad()来实现的,所以最终a得到是一个新的实例引用,而且a的value存放的是一个新申请的字符数组内存空间的地址(存放着"123456"),而此时"123456"在常量池中是未必存在的。
要注意: 我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,创建了String类的对象str。担心陷阱!对象可能并没有被创建!而可能只是指向一个先前已经创建的对象。只有通过new()方法才能保证每次都创建一个新的对象
在执行String a = new String("123")的时候,首先走常量池的路线取到一个实例的引用,然后在堆上创建一个新的String实例,走以下构造函数给value属性赋值,然后把实例引用赋值给a
- String对象的实例调用intern方法后,可以让JVM检查常量池,如果没有实例的value属性对应的字符串序列比如"123"(注意是检查字符串序列而不是检查实例本身),就将本实例放入常量池,如果有当前实例的value属性对应的字符串序列"123"在常量池中存在,则返回常量池中"123"对应的实例的引用而不是当前实例的引用,即使当前实例的value也是"123"。
-
存在于.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 }
- Java内存模型
- 基本类型:首先我们要知道java内存中对于基本类型的存储是位于栈空间中的,下面看例子:
-
算术运算
注意优先级(用“()"就能解决)
-
关于进阶的问题
整数与整数运算不存在进阶,直接砍掉小数:例: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 */
- 逻辑运算符 AND(&&)、OR(||)以及 NOT(!)能生成一个布尔值(true 或 false)——以自变量的逻辑关
- 按位运算符(就是将任意一个数转化成二进制,然后在二进制的基础上按下面的原则进行计算,注意负数的二进制就是对应正数的反码+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);//
} - 右移:将二进制码向右移动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);
}
- 左移:将二进制码向左移动N格,空白位用0补足,例:
-
三元 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起头,那么后续所有运算对象都必
须是字串 - 转型(低位与高位加运算,自动向高位转)
二、执行控制
- 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后便不再继续。
- 1. return
- 循环
- 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);
}
}
} ///:~ - do-while
do-while 的格式如下:do
语句
while(布尔表达式)while 和do-while 唯一的区别就是do-while肯定会至少执行一次;也就是说,至少会将其中的语句“过一
遍”——即便表达式第一次便计算为false。而在 while 循环结构中,若条件第一次就为false,那么其中的
语句根本不会执行。在实际应用中,while 比 do-while 更常用一些。 - 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++;
}
}
- while,do-while和 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);//
if(i>=2)
break; }
}
- 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来控制循环,达到同样的效果
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必然会报空指针异常