Think in java (笔记一)

1、Java 采用三个显式(明确)关键字以及一个隐式(暗示)关键字来设置类边界:public,private,protected 以及暗示性的friendly。若未明确指定其他关键字,则默认为后者。这些关键字的使用和含义都是相当直观的,它们决定了谁能使用后续的定义内容。“public”(公共)意味着后续的定义任何人均可使用。而在另一方面,“private”(私有)意味着除您自己、类型的创建者以及那个类型的内部函数成员,其他任何人都不能访问后续的定义信息。private 在您与客户程序员之间竖起了一堵墙。若有人试图访问私有成员,就会得到一个编译期错误。“friendly ”(友好的)涉及“包装”或“装”(Package)的概念——即Java 用来构建库的方法。若某样东西是“友好的”,意味着它只能在这个包装的范围内使用(所以这一访问级别有时也叫作“包装访问”)。“protected”(受保护的)与“private”相似,只是一个继承的类可访问受保护的成员,但不能访问私有成员。继承的问题不久就要谈到。

 

 

 

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

 

 

 

3、赋值是用等号运算符(=)进行的。它的意思是“取得右边的值,把它复制到左边”。右边的值可以是任何常
数、变量或者表达式,只要能产生一个值就行。但左边的值必须是一个明确的、已命名的变量。也就是说,
它必须有一个物理性的空间来保存右边的值。举个例子来说,可将一个常数赋给一个变量(A=4;),但不可
将任何东西赋给一个常数(比如不能4=A)。
对主数据类型的赋值是非常直接的。由于主类型容纳了实际的值,而且并非指向一个对象的句柄,所以在为
其赋值的时候,可将来自一个地方的内容复制到另一个地方。例如,假设为主类型使用“A=B”,那么B 处的
内容就复制到A。若接着又修改了A,那么B 根本不会受这种修改的影响。作为一名程序员,这应成为自己的
常识。
但在为对象“赋值”的时候,情况却发生了变化。对一个对象进行操作时,我们真正操作的是它的句柄。所
以倘若“从一个对象到另一个对象”赋值,实际就是将句柄从一个地方复制到另一个地方。这意味着假若为
对象使用“C=D”,那么C 和D 最终都会指向最初只有D 才指向的那个对象。下面这个例子将向大家阐示这一
点。

   举个例子

  

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 类非常简单,它的两个实例(n1 和n2)是在main()里创建的。每个Number 中的i 值都赋予了一个不
同的值。随后,将n2 赋给n1,而且n1 发生改变。在许多程序设计语言中,我们都希望n1 和n2 任何时候都
相互独立。但由于我们已赋予了一个句柄,所以下面才是真实的输出:
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 的一个对象。在赋值过程中,那个句柄实际已经丢失;它的对象会由
“垃圾收集器”自动清除)。

 

4、

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

 

 

5、i++ 和++i

 

public class AutoInc {
public static void main(String[] args) {
int i = 1;
prt("i : " + i);
prt("++i : " + ++i); // Pre-increment
prt("i++ : " + i++); // Post-increment
prt("i : " + i);
prt("--i : " + --i); // Pre-decrement
prt("i-- : " + i--); // Post-decrement
prt("i : " + i);
}
static void prt(String s) {
System.out.println(s);
}
} ///:~

 

该程序的输出如下:
i : 1
++i : 2
i++ : 2
i : 3
--i : 2
i-- : 2
i : 1

 

详解:从中可以看到,对于前缀形式,我们在执行完运算后才得到值。但对于后缀形式,则是在运算执行之前就得
到值。它们是唯一具有“副作用”的运算符(除那些涉及赋值的以外)。也就是说,它们会改变运算对象,
而不仅仅是使用自己的值。

 

6、关系运算符

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

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()。但这个方法不适用于“主类型”,那些类型直接使用==和!=即可。下面举例说明如何使用:

 

public class EqualsMethod {
public static void main(String[] args) {
Integer n1 = new Integer(47);
Integer n2 = new Integer(47);
System.out.println(n1.equals(n2));
}
} ///:~

 

正如我们预计的那样,此时得到的结果是true。但事情并未到此结束!假设您创建了自己的类,就象下面这

 

class Value {
int i;
}
public class EqualsMethod2 {
public static void main(String[] args) {
Value v1 = new Value();
Value v2 = new Value();
v1.i = v2.i = 100;
66
System.out.println(v1.equals(v2));
}
} ///:~

 

此时的结果又变回了false!这是由于equals()的默认行为是比较句柄。所以除非在自己的新类中改变了
equals(),否则不可能表现出我们希望的行为。不幸的是,要到第7 章才会学习如何改变行为。但要注意
equals()的这种行为方式同时或许能够避免一些“灾难”性的事件。
大多数Java 类库都实现了equals(),所以它实际比较的是对象的内容,而非它们的句柄。 

7、1. 短路
操作逻辑运算符时,我们会遇到一种名为“短路”的情况。这意味着只有明确得出整个表达式真或假的结
论,才会对表达式进行逻辑求值。因此,一个逻辑表达式的所有部分都有可能不进行求值:

public class ShortCircuit {
static boolean test1(int val) {
System.out.println("test1(" + val + ")");
System.out.println("result: " + (val < 1));
return val < 1;
}
static boolean test2(int val) {
System.out.println("test2(" + val + ")");
System.out.println("result: " + (val < 2));
return val < 2;
}
static boolean test3(int val) {
System.out.println("test3(" + val + ")");
System.out.println("result: " + (val < 3));
return val < 3;
}
public static void main(String[] args) {
if(test1(0) && test2(2) && test3(2))
System.out.println("expression is true");
else
System.out.println("expression is false");
}
} ///:~

 

 

每次测试都会比较自变量,并返回真或假。它不会显示与准备调用什么有关的资料。测试在下面这个表达式
中进行:
if(test1(0)) && test2(2) && test3(2))
68
很自然地,你也许认为所有这三个测试都会得以执行。但希望输出结果不至于使你大吃一惊:
test1(0)
result: true
test2(2)
result: false
expression is false
第一个测试生成一个true 结果,所以表达式求值会继续下去。然而,第二个测试产生了一个false 结果。由
于这意味着整个表达式肯定为false,所以为什么还要继续剩余的表达式呢?这样做只会徒劳无益

 

 

8、三元i f- e l s e 运算符

   布尔表达式 ? 值0:值1

   当然,也可以换用普通的if-else 语句(在后面介绍),但三元运算符更加简洁。还是要先多作一些思量——它很容易就会产生可读性极差的代码

 

 

9、字串运算符+

    这个运算符在Java 里有一项特殊用途:连接不同的字串。

    int x = 0, y = 1, z = 2;
String sString = "x, y, z ";
System.out.println(sString + x + y + z);
在这里,Java 编译程序会将x,y 和z 转换成它们的字串形式,而不是先把它们加到一起。然而,如果使用
下述语句:
System.out.println(x + sString);
那么早期版本的Java 就会提示出错

 

10、类型转换

  “缩小转换”(Narrowing Conversion)的操作(也就是说,脚本是能容纳更多信息的数据类型,将其转换
成容量较小的类型),此时就可能面临信息丢失的危险。此时,编译器会强迫我们进行造型,就好象说:
“这可能是一件危险的事情——如果您想让我不顾一切地做,那么对不起,请明确造型。”而对于“放大转
换”(Widening conversion),则不必进行明确造型,因为新类型肯定能容纳原来类型的信息,不会造成任
何信息的丢失。
Java 允许我们将任何主类型“造型”为其他任何一种主类型,但布尔值(bollean)要除外,后者根本不允
许进行任何造型处理。“类”不允许进行造型。为了将一种类转换成另一种,必须采用特殊的方法(字串是
一种特殊的情况,

 

 11、开关语句

   “开关”(Switch)有时也被划分为一种“选择语句”。根据一个整数表达式的值,switch 语句可从一系列
代码选出一段执行。它的格式如下:
switch(整数选择因子) {
case 整数值1 : 语句; break;
case 整数值2 : 语句; break;
case 整数值3 : 语句; break;
case 整数值4 : 语句; break;
case 整数值5 : 语句; break;
//..
default:语句;

 

。但它要求使用一个选择
因子,并且必须是int 或char 那样的整数值。例如,假若将一个字串或者浮点数作为选择因子使用,那么它
们在switch 语句里是不会工作的。对于非整数类型,则必须使用一系列if 语句。

 

char c = (char)(Math.random() * 26 + 'a');
Math.random()会产生一个double 值,所以26 会转换成double 类型,以便执行乘法运算。这个运算也会产
生一个double 值。这意味着为了执行加法,必须无将'a'转换成一个double。利用一个“造型”,double 结
果会转换回char

将一个float 或double 值造型成整数值后,总是将小数部分“砍掉”,不作任何进位处理。

Math.random();产生值的范围,用数字语言表达,输出值范围是[0,1)

 

12、构造器

 例如,假设我们想创建一个类,令其用标准方式进
行初始化,另外从文件里读取信息来初始化。此时,我们需要两个构建器,一个没有自变量(默认构建
器),另一个将字串作为自变量——用于初始化对象的那个文件的名字。由于都是构建器,所以它们必须有
相同的名字,亦即类名。所以为了让相同的方法名伴随不同的自变量类型使用,“方法过载”是非常关键的
一项措施。同时,尽管方法过载是构建器必需的,但它亦可应用于其他任何方法,且用法非常方便。
在下面这个例子里,我们向大家同时展示了过载构建器和过载的原始方法:

 

import java.util.*;
class Tree {
97
int height;
Tree() {
prt("Planting a seedling");
height = 0;
}
Tree(int i) {
prt("Creating new Tree that is "
+ i + " feet tall");
height = i;
}
void info() {
prt("Tree is " + height
+ " feet tall");
}
void info(String s) {
prt(s + ": Tree is "
+ height + " feet tall");
}
static void prt(String s) {
System.out.println(s);
}
}
public class Overloading {
public static void main(String[] args) {
for(int i = 0; i < 5; i++) {
Tree t = new Tree(i);
t.info();
t.info("overloaded method");
}
// Overloaded constructor:
new Tree();
}
} ///:~

 区分过载方法

若稍微思考几秒钟,就会想到这样一个问题:除根据自变量的类型,程序员如何区分两个同名方法的差异
呢?
即使自变量的顺序也足够我们区分两个方法(尽管我们通常不愿意采用这种方法,因为它会产生难以维护的

 

主类型的过载

若观察这个程序的输出,就会发现常数值5 被当作一个int 值处理。所以假若可以使用一个过载的方法,就
能获取它使用的int 值。在其他所有情况下,若我们的数据类型“小于”方法中使用的自变量,就会对那种
数据类型进行“转型”处理。char 获得的效果稍有些不同,这是由于假期它没有发现一个准确的char 匹
配,就会转型为int

大家可注意到这是一种“缩小转换”。也就是说,在造型或转型过程中可能丢失一些信息(显示转换);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值