代码改变世界

java 学习笔记《1》

2011-04-04 23:28  Rollen Holt  阅读(505)  评论(0编辑  收藏  举报

1.除了标准输出串流out之外,Java程式在执行之后,还会开启标准输入串流in与标准错误输出串流err。对于in来说,它对应至键盘或其它的输入来源,准备接受使用者或其它来源的输入。而对于err,它会将指定的字串输出至显示装置或其它指定的装置,与标准输出串流out不同的是,它会立即显示指定的(错误)讯息给使用者知道,例如即使您指定程式将结果重新导向至档案,err输出串流的讯息并不会被重新导向,而仍会显示在指定的显示装置上,下面这个例子给您一个简单的测试方式: HelloWorld.java文件

  1: public class HelloWorld { 
  2:     public static void main(String[] args) { 
  3:         System.out.println("Hello! World!"); 
  4:         System.err.println("Error Message Test"); 
  5:     } 
  6: }

 

但是在编译执行程序之后你会发现输出结果如下:

  1: java HelloWorld >> output.txt 
  2: Error Message Test

 

当我们开启output.txt之后,您会发现当中只有"Hello! World!"讯息,而Error Message Test讯息并没有被导向至档案中,而是直接显示在Console(或终端机)中。
要重新导向标准输出是用 '>' ,标准输入则是 '<' ,而'>>'除了重导标准输出之外,还有附加的功能,也就是会把输出附加到被导向的目标档案后头,如果目标档案本来不存在,那么效果就和'>'一样。

2.关于自动装箱和自动拆箱的一点问题。

自动装箱与拆箱是编译器在编译时期为您作好一切的事情,是 编译蜜糖(Compiler sugar) ,这很方便,但在运行阶段您还是了解Java的语义,例如下面的程式是可以通过编译的:

  1: Integer i = null;
  2: int j = i;

通过测试我们可以知道,这段代码在编译期间完全是可以通过的。但是在运行时期会有错误,因为null表示i 没有参考至任何的物件实体,它可以合法的指定给物件参考名称,但null值对于基本型态j 的指定是不合法的,上面的写法在运行时会出现NullPointerException的错误。再来看一个,先看看程式,您以为结果是如何?

  1: Integer i1 = 100;
  2: Integer i2 = 100;
  3: if (i1 == i2) 
  4:      System.out.println("i1 == i2");
  5: else 
  6:      System.out.println("i1 != i2");

先不说结果,我们在看看下面的这段代码:

  1: Integer i1 = 200;
  2: Integer i2 = 200;
  3: if (i1 == i2) 
  4:      System.out.println("i1 == i2");
  5: else 
  6:      System.out.println("i1 != i2");

你认为这个结果是什么呢?也许第一个正如你所猜想的。结果是i1==i2,但是我想第二个应该是出乎你的意料吧。因为这两段代码区别仅仅在于我改变了一个数值。经过运行我们可以知道,第二断代码的结果是:“i1!=i2”。为什么呢?

其实这与 '=='运算子 的比较有关,'=='可用来比较两个基本型态的变数值是否相等,事实上'=='也用于判断两个物件变数名称是否参考至同一个物件。
所以'=='可以比较两个基本型态的变数值是否相等,也可以判断两个物件变数的参考物件是否相同预设对于值从 -128到127 之间的值,它们被装箱为Integer物件后,会存在记忆体之中被重用,所以当值在100,使用'=='进行比较时,i1与i2实际上参考至同一个物件。预设 如果超过了从-128到127之间的值,被装箱后的Integer物件并不会被重用,即相当于每次都新建一个Integer物件,所以当值在200,使用'=='进行比较时,i1与i2参考的是不同的物件。
所以不要过份依赖自动装箱与拆箱,您还是必须知道基本型态与物件的差异,上面的程式最好还是依正规的方式来写,而不是依赖编译蜜糖(Compiler sugar),例如当值为200时,必须改写为以下才是正确的。

  1: Integer i1 = 200;
  2: Integer i2 = 200;
  3:  if (i1.equals(i2)) 
  4:      System.out.println("i1 == i2");
  5: else 
  6:      System.out.println("i1 != i2");

事实上在我们编写Integer i=100;的时候,编译器其实将我们的代码转化为 Integer i = Integer.valueOf(100); valueOf()方法会将-128到127的值放到快取之中,以重复使用,这可以查看Integer.java的原始码得知,如果是JDK5:

  1: public static Integer valueOf(int i) {
  2:     final int offset = 128;
  3:     if (i >= -128 && i <= 127) { // must cache 
  4:         return IntegerCache.cache[i + offset];
  5:     }
  6:         return new Integer(i);
  7: }
也就是在-128到127之间所产生的包裹物件,将会放到快取中重复使用 ,而在JDK6之后,则是这么写的:
  1: public static Integer valueOf(int i) {
  2:     if(i >= -128 && i <= IntegerCache.high)
  3:         return IntegerCache.cache[i + 128];
  4:     else
  5:         return new Integer(i);
  6: }
  7: 

IntegerCache.high预设是127,所以预设是 在-128到127之间所产生的包裹物件,将会放到快取中重复使用(可以透过设置属性

java.lang.Integer.IntegerCache.high来设定IntegerCache.high的值) 。

3.对于数组。和C/C++一样,java中不存在多维数组,只有一维数组,而我们平时所说的“多维数组”其实就是数组的数组。在java里面,我们可以有如下的代码:

  1: import java.util.Scanner;
  2:  
  3: public class CustomArrayLength {
  4:     public static void main(String[] args) {
  5:         Scanner scanner = new Scanner(System.in);
  6:  
  7:         System.out.print("请输入Array大小: "); 
  8:  
  9:         int length = scanner.nextInt();
 10:         int[] arr = new int[length]; // 动态配置长度 
 11:  
 12:         System.out.println("Array长度: " + arr.length); 
 13:         System.out.print("内容: "); 
 14:         for(int i = 0; i < arr.length; i++) 
 15:             System.out.print(arr[i] + ""); 
 16:         System.out.println();
 17:     }
 18: } 

请注意第9和第10行。在C++中我们不能写如下的代码片段:

  1: #include<iostream>
  2: using namespace std;
  3: int main(){
  4:     int i;
  5:     cin>>i;
  6:     int a[]=new int[i];
  7:     return 0;
  8: }

然而我们在java中确实可以的.这一点希望对于C++程序员有一点点提醒.

另外提醒一下,在java中,数组是一个对象。当我们使用"="将对象指定给数组名的时候,并不是对于数组进行复制,而是将对象名指定给数组名进行引用。

对于对维数组我们在输出的时候可以采用加强的for 循环:

  1: int [][] arr={ {1,2,3},
  2:                {1,2,3},
  3:                {1,2,3}
  4: 
  5: };
  6: for(int [] elment1 : arr)
  7:     for(int elment2 : elment1 )
  8:         System.out.println(elment2);

其次我们来谈谈对象数组吧.先来看下面的代码:

  1: int[] arr=new int[3];
你认为它产生了几个对象呢?答案是一个一维数组对象.
下面的呢?
  1: int[][]arr=new int [2][3];
产生了几个对象呢? 答案是3个对象.在进一步的说,
  1: Integer[] arr= new Integer[3];
产生了几个对象呢?答案还是一个对象数组.同理.对于 Integer[][] arr =new Integer[2][3];也是产生3个对象。
4.对于String.

一个字串物件一旦被配置,它的内容就是固定 不可变的(immutable) ,例如下面这个宣告:

String str = "caterpillar";

这个宣告会配置一个长度为11的字串物件,您无法改变它的内容;别以为下面这个宣告就是改变一个字串物件的内容:

String str = "just";
str = "justin";

串物件,您无法改变它的内容;别以为下面这个宣告就是改变一个字串物件的内容:

String str = "just";
str = "justin";

事实上在这个程序片段中,会有两个字串物件,一个是"just",长度为4,一个是"justin",长度为6,它们两个是不同的字串物件,您并不是

在"just"字串后加上"in"字串,而是让str名称参考至新的字串物件。

在Java中,使用=将一个字串物件指定给一个名称,其意义为改变名称的参考物件,原来的字串物件若没有其它名称来参考它,就会在适当的时机

被Java的 「垃圾回收」(Garbage collection) 机制回收,在Java中,程式设计人员通常不用关心无用物件的资源释放问题,Java会检查物件

是否不再被参考,如果没有任何名称参考的物件将会被回收。

所以如果您在程式中使用下面的方式来宣告,则实际上是指向同一个字串物件:

1: String str1 = "flyweight"; 2: String str2 = "flyweight";

3: System.out.println(str1 == str2);

程式的执行结果会显示true,在Java中,会维护一个 String Pool ,对于一些可以共享的字串物件,会先在String Pool中查找是否存在相同的String

内容(字元相同),如果有就直接传回,而不是直接创造一个新的String物件,以减少记忆体的耗用。

谈到String pool,那就不能不提String的 intern() 方法,来看看它的API说明的节录:

Returns a canonical representation for the string object.
A pool of strings, initially empty, is maintained privately by the class String.
When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the

equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a

reference to this String object is returned.

如果大家看过《设计模式》的话,应该可以想起Flyweight模式。看个例子再说吧:

1: public class StringIntern { 2: public static void main(String[] args) { 3: String str1 = "fly"; 4: String str2 = "weight"; 5: String str3 = "flyweight"; 6: String str4; 7: 8: str4 = str1 + str2; 9: System.out.println(str3 == str4); 10: 11: str4 = (str1 + str2).intern(); 12: System.out.println(str3 == str4); 13: } 14: }

在程式中第一次比较str3与str4物件是否为同一物件时,您知道结果会是false,而intern()方法会先检查String Pool中是否存在字元部份相同的字串

物件,如果有的话就传回,由于程式中之前已经有"flyweight"字串物件,intern()在String Pool中发现了它,所以直接传回,这时再进行比较,str3

与str4所指向的其实是同一物件,所以结果会是true。

注意到了吗? ==运算在Java中被用来比较两个名称是否参考至同一物件,所以 不可以用==来比较两个字串的内容是否相同 ,例如:

1: String str1 = new String("caterpillar"); 2: String str2 = new String("caterpillar"); 3: System.out.println(str1 == str2); 4: System.out.println(str1.equals(str2));

 

或许结果在你的意料之中。前者为false,后者为true、所以我们不能用“==”来比较两个字符串是否相等。另外说一句闲话,上面代码段的前3行产生了几个String的对象呢?很多人也许会认为是2个吧,我之前也是这样,但是答案却是3个,别忘记“caterpillar”就是一个。它存在于String Pool里面(⊙o⊙)哦,小盆友。