Chapter 3:
1、检查对象是否相等:
package c03;
/**Thinking In Java example program.
* @author WangCan
* @2019-02-13
* */
public class Number {
public static void main(String[] args){
Integer n1=new Integer(47);
Integer n2=new Integer(47);
System.out.println(n1==n2); // n1不等于n2,因为对象的内容相同,句柄却是不同的
System.out.println(n1!=n2);
}
}
小结:尽管对象的内容相同,句柄却是不同的,而 ==和!=比较的正好就是对象句柄。所以输出结果实际上先是 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。但是假设您创建了自己的类,就象下面这样:
package c03;
/**The first Thinking In Java example program.
* @author WangCan
* @2019-02-13
* */
class Value{
int i;
}
public class Number {
public static void main(String[] args){
Value v1=new Value();
Value v2=new Value();
v1.i=v2.i=100;
System.out.println(v1.equals(v2));
System.out.println(v1==v2);
System.out.println(v1!=v2);
}
}
此时的结果又变回了false、false、true!这是由于 equals()的默认行为是比较句柄。所以除非在自己的新类中改变了 equals(),否则不可能表现出我们希望的行为。
2、输出二进制的方法:
1)、int类型转换二进制:
static void pBinInt(String s, int i) {
System.out.println(
s + ", int: " + i + ", binary: ");
System.out.print(" ");
for(int j = 31; j >=0; j--)
if(((1 << j) & i) != 0)
System.out.print("1"); //int类型的数据输出二进制
else
System.out.print("0");
System.out.println();
}
2)、long类型转换二进制:
static void pBinLong(String s, long l) {
System.out.println(
s + ", long: " + l + ", binary: ");
System.out.print(" ");
for(int i = 63; i >=0; i--)
if(((1L << i) & l) != 0)
System.out.print("1");
else
System.out.print("0");
System.out.println();
}
以上的方法可以直接在主函数中用pBinInt("string类型数值", int类型数值)和pBinLong("string类型数值",long类型数值)进行调用。
具体应用:
public class Number {
static void pBinInt(String s, int i) {
System.out.println(
s + ", int: " + i + ", binary: ");
System.out.print(" ");
for(int j = 31; j >=0; j--) //int转换输出二进制
if(((1 << j) & i) != 0)
System.out.print("1");
else
System.out.print("0");
System.out.println();
}
static void pBinLong(String s, long l) {
System.out.println(
s + ", long: " + l + ", binary: ");
System.out.print(" ");
for(int i = 63; i >=0; i--) //long转换输出二进制
if(((1L << i) & l) != 0)
System.out.print("1");
else
System.out.print("0");
System.out.println();
}
public static void main(String[] args){
Random rand =new Random();
int i=rand.nextInt();
int j=rand.nextInt();
pBinInt("-1",-1);
pBinInt("+1",+1);
int maxpos = 2147483647;
pBinInt("maxpos", maxpos);
int maxneg = -2147483648;
pBinInt("maxneg", maxneg);
pBinInt("i", i);
pBinInt("~i", ~i);
pBinInt("-i", -i);
pBinInt("j", j);
pBinInt("i & j", i & j);
pBinInt("i | j", i | j);
pBinInt("i ^ j", i ^ j);
pBinInt("i << 5", i << 5);
pBinInt("i >> 5", i >> 5);
pBinInt("(~i) >> 5", (~i) >> 5);
pBinInt("i >>> 5", i >>> 5);
pBinInt("(~i) >>> 5", (~i) >>> 5);
long l = rand.nextLong();
long m = rand.nextLong();
pBinLong("-1L", -1L);
pBinLong("+1L", +1L);
long ll = 9223372036854775807L;
pBinLong("maxpos", ll);
long lln = -9223372036854775808L;
pBinLong("maxneg", lln);
pBinLong("l", l);
pBinLong("~l", ~l);
pBinLong("-l", -l);
pBinLong("m", m);
pBinLong("l & m", l & m);
pBinLong("l | m", l | m);
pBinLong("l ^ m", l ^ m);
pBinLong("l << 5", l << 5);
pBinLong("l >> 5", l >> 5);
pBinLong("(~l) >> 5", (~l) >> 5);
pBinLong("l >>> 5", l >>> 5);
pBinLong("(~l) >>> 5", (~l) >>> 5);
}
}
3、循环的中断与连续:
可用break 和continue 控制循环的流程。其中,break 用于强行退出循环, 不执行循环中剩余的语句。而 continue 则停止执行当前的循环,然后退回循环起始位置,开始新的反复。
循环中插入标签label时,break和continue会中断当前循环,回到label标签存在的地方。label标签需要紧靠在循环之前, 中间插入任何语句都是不明智的:
label1: //label标签使用
外部循环{
内部循环{
break; //1:中断内部循环,并在外部循环结束
continue; //2:移回内部循环的起始处
continue label1; //3:却同时中断内部循环以及外部循环,并移至label1 处。随后,它实际是继续循环,但却从外部循环开始
break label1; //4:会中断所有循环,并回到 label1 处,但并不重新进入循环,而是完全终止了两个循环
}
}
public class Number {
public static void main(String[] args) {
int i = 0;
outer: //外部标签
for (; true; ) {
inner: //内部标签
for (; i < 10; i++) {
prt("i=" + i);
if (i == 2) {
prt("continue");
continue;
}
if (i == 3) {
prt("break");
i++; //否则i永远不会增加
break;
}
if (i == 7) {
prt("continue outer");
i++; //否则i永远不会增加
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;
}
}
}
}
}
static void prt(String s){
System.out.println(s);
}
}
如果没有break outer 语句,就没有办法在一个内部循环里找到出外部循环的路径。这是由于break本身只能中断最内层的循环(对于continue 同样如此)。 当然,若想在中断循环的同时退出方法,简单地用一个return 即可。
用Switch判断随机生成的字母是元音还是辅音字母:
public class Number {
public static void main(String[] args) {
for(int i = 0; i < 100; i++) {
char c = (char)(Math.random() * 26 + 'a'); //随机生成字母
System.out.print(c + ": ");
switch(c) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
System.out.println("vowel");
break;
case 'y':
case 'w':
System.out.println(
"Sometimes a vowel");
break;
default:
System.out.println("consonant");
}
}
}
}
尽管我们在这儿表面上要处理的是字符,但switch 语句实际使用的字符的整数值。在 case 语句中,用单引 号封闭起来的字符也会产生整数值,以便我们进行比较。
Math.random()会产生0到1之间的一个值,所以只需将其乘以想获得的最大随机数(对于英语字母,这个数字是 26),再加上一个偏移量,得到最小的随机数。多个条件输出相同的结果时可以向上面那样放在一起判断。
这里我们需要特别注意的是:
Math.random()会产生一个 double 值,所以 26 会转换成double 类型,以便执行乘法运算。这个运算也会产生一个 double 值。这意味着为了执行加法,必须先将'a'转换成一个 double类型的数,再利用一个类型的强制转换(char),double 结果会转换回 char。 我们的问题是,(char)会作什么样的处理呢?换言之,假设一个值是29.7,我们把它转换成一 个char,那么结果值到底是 30 还是29 呢?答案可从下面这个例子中得到:
public class CastingNumbers {
public static void main(String[] args) {
double above = 0.7,below = 0.4;
System.out.println("above: " + above);
System.out.println("below: " + below);
System.out.println("(int)above: " + (int)above); //结果为above=0
System.out.println("(int)below: " + (int)below); //结果为below=0
System.out.println("(char)('a' + above): " +(char)('a' + above));
//结果为(char)('a' + above):a
System.out.println("(char)('a' + below): " +(char)('a' + below));
//结果为(char)('a' + below):a
}
}
也就是说将一个 float 或 double 值造型成整数值后,总是将小数部分“砍掉”,不作任何进位处理。
4、用构建函数(构造器)自动初始化:
对于构建器的创建,可将其想象成为自己写的每个类都调用一次 initialize()。我们在命名构建器的时候应该使构建器的名字与类名相同。若某个类有一个构建函数(构造器),那么在创建对象时,Java 会自动调用那个构建器。
class Rock {
Rock(int i) { //构建器:方法名与类名完全相同
System.out.println("Creating Rock number " + i);
}
}
public class SimpleConstructor {
public static void main(String[] args) {
for(int i = 0; i < 10; i++)
new Rock(i);
}
}
构建函数属于一种较特殊的方法类型,因为它没有返回值。这与 void 返回值存在着明显的区别。对于void 返 回值,尽管方法本身不会自动返回什么,但仍然可以让它返回另一些东西。构造函数则不同,它不仅什么也不 会自动返回,而且根本不能有任何选择。若存在一个返回值,而且假设我们可以自行选择返回内容,那么编 译器多少要知道如何对那个返回值作什么样的处理。
由于构建函数的名字由类名决定,所以只能有一个 构建函数名称。但假若我们想用多种方式创建一个对象呢?例如,假设我们想创建一个类,令其用标准方式进行初始化,另外从文件里读取信息来初始化。此时,我们就需要两个构建函数,一个没有自变量(默认构建器),另一个将字串作为自变量——用于初始化对象的那个文件的名字。由于都是构建函数,所以它们必须有相同的名字即类名。所以为了让相同的方法名伴随不同的自变量类型使用,“方法重载”是非常关键的 一项措施。同时,尽管方法重载是构建函数必需的,但它亦可应用于其他任何方法,且用法非常方便。
区分多个重载方法的简单的规则:每个重载的方法都必 须采取独一无二的自变量类型列表。
public class Overloading{
static void print(String s,int i){
System.out.println("String:"+s+",int:"+i);
}
static void print(int i,String s){
System.out.println("int:"+i+",String:"+s);
}
public static void main(String[] args){
print("String first",11);
print(99,"int first");
}
}
若创建一个没有构 建器的类,则编译程序会帮我们自动创建一个默认构建器:
class Bird {
int i;
}
public class DefaultConstructor {
public static void main(String[] args) {
Bird nc = new Bird(); //new Bird()作用是新建一个对象,并调用默认构建器
}
}
5、this关键字:
假定我们在一个方法的内部,并希望获得当前对象的句柄。由于那个句柄是由编译器“秘密”传递的,所以没有标识符可用。然而,针对这一目的有个专用的关键字:this。this 关键字(注意只能在方法内部使用) 可为已调用了其方法的那个对象生成相应的句柄。可像对待其他任何对象句柄一样对待这个句柄。
public class Leaf{
private int i=0;
Leaf increment(){
i++;
return this; //在方法内部获得当前对象的句柄
}
void print(){
System.out.println("i="+i);
}
public static void main(String[] args){
Leaf x=new Leaf();
x.increment().increment().increment().print();
}
}
理解了 this 关键字后,我们可更完整地理解 static(静态)方法的含义。它意味着一个特定的方法没有 this。我们不可从一个 static 方法内部发出对非 static 方法的调用,反过来说是可以的。