JavaSE學習筆記——1、Basic knowledge

Notice——注意,Pitfall——陷阱,Caue——原因,Vice Versa——反之亦然

備註:一、二……等標號代表電子版筆記章節,(1)(2、3)……等標號對應書面筆記章節。

一、Introduction(1)(20130115)

Java三個版本:Standard、Micro、Enterprise。

Java Development Kit,JDK包含編譯和運行所需的一切。Java Runtime Environment,JRE包含運行所需的一切。

目錄:bin-binary二進制可執行文件的目錄,其中javac.exe是java compile。

配置:Window環境下運行文件時,會從系統變量中Path中所有的目錄中找,因此,爲了使javac、java能夠在cmd下被識別,應該將所有目錄附加在系統變量的Path中(環境變量也可)。

環境變量:JAVA_HOME = c:\program files\java\jdk1.x.x

path += ;.;%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;

classpath = %JAVA_HOME%\lib;%JAVA_HOME%\jre\lib;

編寫第一個java file:用NotePad即可,保存時以.java後綴。

編譯:以類名(而非文件名)生成.class文件  -javac  xxx.java。

執行:-java xxx

總結:Java執行的是字節碼而非binary,通過JVM即時解釋運行,Java之所以跨平臺因爲執行字節碼的JVM(C實現)與System關聯。(每個System都有對應的JVM)

二、Primitive Data Type/Reference Type(2、3)

int a;//定義一個變量
a = 2;//爲變量賦值,右邊賦值給左邊
int a = 2;//等價上2行代碼

常量——值不會變化的量。

Java中變量命名不能以數字開頭,同時不建議大寫or“$”開頭。

8種Primitive Data Type:

int(4byte)、byte(I/OStream中常用)、short(2byte)、long(8byte)、float(4byte)、double(8byte)、char(2byte)、boolean

Pitfall:Java中浮點型默認使用double,因此,在未進行類型強轉的情況下即使所賦值處於float範圍內也會報錯。

Notice:variable使用前應先賦值,給定type,低type可以賦值給高type,not vice versa。

關於註釋:

//單行註釋
/*
多行註釋,源自C++
*/
/**
*文檔註釋,用於產生help documents
*/

三、Operator(4)

+、-、*、/當若干variable參與運算,結果取決於表示範圍最大的type。

//混合類型運算判斷技巧:
int a = 3;
float b = 3.2f;
float c = a*b;//以等號爲界,賦值前結果類型取決於參與運算的表示範圍最大的variable type,賦值時遵循表示範圍小可賦值給表示範圍大的原則。

Notice:取模運算的結果的符號只與被除數有關。(被除數)5/3(除數)

四、Relation Operator(5)

>、<、==、!=、>=、<=、&&、||、!

它們的result都是boolean。

雙目運算符:有兩個操作數的運算符。

variable的字增減:++、--、+=、-=、*=、/=。

五、Flow Control Statement(6、7)

//三元表達式:
d==a?b:c//(d==a)爲true返回b,爲false返回c 
//判斷
if(boolean expression)
{fragment code}
else
{fragment code}
//循環一
for(int i=0;i<10,i++)//當想以計數循環時(可預見的計數範圍)
{fragment code}
//循環二
do//do程序塊的代碼至少會被執行一次
{fragment code}
while(boolean expression)//當僅想以某種狀態運行至得到一種結果時
{fragment code
continue;//跳出本次loop,但不中止loop,繼續下次loop,即continue可以實現當滿足某種情況就跳過continue以下的代碼
}
//選擇
switch(variable){
case 1:……break;//break結束本次控制語句,不加則繼續執行後續case&default
case 2:……break;
……
default:……break;
}

六、Inside Object Oriented Programming:OOP(8)

什麼是OOP?

兩個重要概念:

class——一種抽象概念,包含了Data&Behavior。

object——一種具體概念,包含property、attribute、member variable(三者屬於一個概念,都是data)&method(對數據的操作,就是behavior)。

七、OOP Feature(9)

1、Inheritance。2、Encapsulation。3、Polymorphism 。

八、Java Memory(10、11)

九、OO Summary(12、13、14)

Reference type是用於object的,一個object可被多個reference所指,一個reference只能指向唯一object。

一個reference對指向object的修改會反映到其他指向該object的reference。

十、方法參數傳遞(15)

一個java源文件中可以定義多個類,但只能有一個public class,並且當有public class存在,main方法只能寫在public class中。

如果一個源文件有兩個以上類,那麼它也會被編譯成相應數量的.class文件。

當method的實參接收的是primitive type,那麼它與形參之間是值傳遞。

當method的實參接收的是reference type,那麼它與形參之間是引用傳遞。

這與C&C++是相似的,只不過Java把它稱爲reference,C&C++把他們稱爲Pointer。

 十一、Overload(early binding)(16)

條件:1)param個數不同、2)param類型不同。Overload與返回值無關。

重載構造方法同上,可以通過this(param)調用其它構造方法。

十二、Inheritance(Inheritence)(17)

對Java特性的認識,應多思考其在memory中的加載過程。

當SubClass extends SuperClass,new SubClass();時會先調用SuperClass的不帶參的construction,然後再是SubClass的不帶參的construction。

Notice:當SubClass的construction通過super(param);顯示制定調用SuperClass中的某一construction時,那麼執行時就不會再調用不帶參的construction。

Notice:super、this用於調用construction時,前面都不能有其它可執行語句,即super();this();必須位於首行。

十三、Override(late binding)(18)

條件:1)方法名一致、2)參數一致、3)返回類型一致。BasicClass可通過Super();調用SuperClass中被重寫的方法。

Notice:Overload屬於平行關係,僅發生在類內部。Override屬於層次關係,發生於SuperClass&BasicClass。

Notice:Polymorphism的表現形式之一就是SuperClass的引用可指向SubClass的實例,not vice versa。如果不是late binding,就一定不是Polymorphism。

十四、Polymorphism(19、20、21)

Notice:當一個Class不繼承於任何Class時,它就隱士地繼承了Object。Object是所有Java類的根源。

請區別upcast(SuperClass superClass = subClass;)、downcast(SubClass subClass = (SubClass)superClass;)。

abstract class無法實例化,其內部方法有聲明無實現,均以”;“結尾(abstract void method();)

abstract method只能存在於abstract class,abstract class不一定要有abstract method,也可以有implemented method。

sub class必須實現abstract class的所有abstract method。

Notice:abstract method的存在是爲了制定約束&規範,而實現或者說如何具體滿足這種規範交由sub class處理(比如由二次開者完成)。

雖然創建一個general super class,讓繼承它的sub class override它的方法也可實現這一機制,但這就顯得無意義了。

十五、Interface(22)

Interface的地位等價於class,Interface中所有method都是abstract method(故key word abstract被忽略)。

通過key word imlements實現接口,Interface可看作一個特殊的abstract class。

一個sub class只可extends一個super class,但可以implements多個Interface(以“,”隔開)。

Interface中允許定義member variable,通常它們被默認public static final(不然則無意義)。

十六、static&final(23、24)

 1、static

在瞭解static前,請先區別下列數據的作用範圍:

1)局部變量。(位於method內)

2)實例變量、成員變量、Instance Methods。(位於class內)

3)全局變量、類變量、Class Methods。(位於class內,且用static修飾)

Notice:類變量較爲特殊,無論聲明瞭多少個引用指向它,都共享一個實例。

Notice:Class Methods(類方法/靜態方法)只能繼承,不能override。

2、final

final可以直觀地從字面意思理解,final class不能被extends,final method不能被override,final attribute不能被改變。

Notice:final修飾primitives,則該primitives的值不能被修改。final修飾reference,則該reference的指向不能被修改,被指向的對象可以修改。

Notice:final attribute必須賦初值||在construction內爲其賦值,但只能進行一次賦值!

類加載過程簡述:從繼承結構的最頂層的static block開始,依次向下,然後是construction,其中static block僅在類加載時執行一次。

十七、Singleton Patterns(25)

public abstract final class Test{……}//思考這樣寫對嗎?

Design Patterns:

每一種設計模式都包含了一種思想

//Singleton Patterns(創建型模式分支)
//一個類只會生成唯一一個對象
public class Singleton{
  private static Singleton instance = new Singleton();
  private Singleton()
  {
  }
  public static Singleton getInstance()
  {
    return instance;
  }
}

十七、包與導入語句(26)

package用於對不同功能的java文件進行分類。

命名規則:將公司域名反轉作爲包名,每個字都要小寫。eg:Realmname——www.microsoft.com,Package——com.microsoft.xxx。

import com.microsoft.xxx;//import爲導入包關鍵字

十八、Access Modifier(27)

Modifier Class Package Sub Class All
private yes      
default yes yes    
protected yes yes yes  
public yes yes yes yes

Notice:Reference instanceof ClassName可用於判斷某個對象是否是某個類的實例。(返回boolean)

十九、Object(28)

每個Java類都默認繼承Object類,因此無需顯示import java.lang.Object。

學會自查API,Application Programming Interface瞭解陌生對象的使用方法。

System.out.println(Obj);
//等價於
System.out.println(Obj.toString());
//未被Override的toString()
public String toString()
{
  return getClass().getName()+"@"+Integer.toHexString(hashCode());
}

Cause:當試圖打印任何一個對象時,如果這個對象不是String類型,都將自動調用它的toString();(所有對象都繼承自Object根類,因此都具有toString())。

如果Override了toString(),那麼System.out.println()的輸出也會發生相應的變化。

Supplement:hash code存放於內存中對象的頭部,對象頭部(overhead)中有三類信息:hash code、reference info、garbage collection info。

二十、String(29、30)

首先思考:

String a = "hello";
String b = "hello";
String c = "hel";
String d = "lo";
String e = new String("hello");
String f = new String("hello);
a==b;//true
a==(c+d)//false
a==e//false
e==f//false

Notice:在一般對公項目中,60%以上都是對字符串的處理,因爲無論是用戶輸入的信息,還是輸出給用戶的信息都是字符串。因此,掌握並精通String類型的處理時必要的,這也是很多企業的面試重點。

Object對象中的十一種method必須掌握,它們分別是:clone、equals、finalize、getClass、hashCode、notify、notifyAll、toString、wait*3。

//String重寫的equals方法
public boolean equals(Object anObject)
{
  if(this == anObject)
  {
    return true;
  }
  if(anObject instanceof String)
  {
    String anotherString = (String)anObject;
    int n = count;
    if(n == anotherString.count)//判斷兩個字符串長度是否相等
    {
      char v1[] = value;
      char v2[] = anotherString.value;
      int i = offset;
      int j = anotherString.offset;
      while(n--!=0)
      {
        if(v1[i++] != v2[i++])//逐個字符判斷是否相等
          return false;
      }
      return true;//全等返回true
    }
  }
  return false;
}

通過重寫equals(),可完成一些自定義類型的對比,以適應不同的業務需求。

Notice:要將所學靈活運用,必須有耐心、恆心、且細心。

String是constant,其對象一旦創建完畢無法改變。當時用“+”進行拼接,會產生一個新的對象。

StringPool運作流程:

String a = "test";//創建String實例a,賦值時,查找StringPool中是否存在“test”值,不存在則寫入“test”並返回地址
String b = "test";//創建String實例b,賦值時,從StringPool中找到“test”值,返回地址

思考Object的equals()與String的equals()的區別

public boolean equals(Object obj)
{
  return (this == obj);//對比兩個對象的地址
}

intern()會返回String實例在String Pool中的引用。

a.intern() == b.intern();
等價於
a.equals(b);

二十一、StringBuffer(31)

StringBuffer sb = new StringBuffer();
sb.append("hello").append(" world");
sb = sb.append(" welcome");
//Result:hello world welcome

不同於String是不可變的,StringBuffer雖未final class,但可通過append進行信息追加。

String a = "hel";
a += "lo";//String的實例a在追加字符串操作後hashcode會產生變化,即產生一個新對象
StringBuffer sb = new StringBuffer();
sb.append("hel").append("lo");//StringBuffer的實例sb在追加字符串操作後hashcode不會產生變化

Notice:8種primitive爲了滿足某些對象只能對對象操作的需要,都有對應的Wrapper Class,比如int的Wrapper Class爲Integer。

int x = 5;//1
Integer y = new Integer(x);//2
int z = y.intValue();//3
//1到2的過程稱爲boxing,2到3的過程稱爲unboxing

二十二、Array(32)

Array是相同類型的數據的集合。

int[] a = new int[10];/Java推薦使用這種形式
//等價於
int a[] = new int[10];
//初始化並賦值
int[] a = {1,2,3};
int[] a = new int[]{1,2,3};

every Array have a attribute——length,表數組長度(不是數組內存在多少個對象,而是最多允許存在多少個對象昂)。

如果Array中裝的是對象,那一定是對象的引用。

思考:爲什麼有些地方用的是length(),而有些是length?

 二十三、2DArray(33)

int[][] a = new int[][]{{1,2},{3,4}};

二十四、Swap入門(34、36)(20130116)

int a = 4,b = 3;
a = a + b;
b = a - b;
a = a - b;

思考,爲什麼產生這樣的輸出?

public static void swap(char[] ch,char c)
{
  ch[0] = 'B';//數組作爲實參傳遞的是引用(地址),所以賦值會改變其值
  c = 'D';//primitive作爲實參傳遞的是值(把值copy給形參),因此不會改變其值
}
public static void main(String[] args)
{
  char[] ch = {'A','C'};
  swap(ch,ch[1]);
  for(int i = 0;i < ch.length;i++)
  {
    System.out.println(ch[i]);
  }
}//Result:B  C

JDK爲我們提供了一個爲Array查找、排序的類java.util.Arrays;

int[] a = {1,2,3};
int[] b = {1,2,3};
System.out.println(a.equals(b));//false
System.out.println(Arrays.equals(a,b));//true
int[] c = new int[3];
System.arrayCopy(a,0,c,0,3);//從a的第0個下標開始依次拷貝3個元素到c的第0個下標開始 

冒泡排序Example:

//BubbleSort core:
for(int i = 0;i < array.length-1;i++)
{
  for(int j = 0;j < array.length-i-1;j++)
  {
    if(array[j] > array[j+1])
    {
      array[j] = array[j] - array[j+1];
      array[j+1] = array[j] + array[j+1];
      array[j] = array[j+1] - array[j];
    }
  }
}

線性查找Example:

//LinearSearch core:
for(int i = 0;i < array.length;i++)
{
  if(key == array[i]
    return i;
}

二分查找Example:

//BinarySearch core:
int lh = 0;rh = array.length-1,middle;
while(lh <= rh)
{
  middle = (lh+rh)/2;
  if(array[middle] == key)
    return middle+1;//返回位置
  if(array[middle] > key)
    rh = middle-1;
  if(array[middle] < key)
    lh = middle+1;
}
return -1;//沒找到返回-1

 二十五、3DArray(35)

三維數組在Game Development中用得較多,如果想從事此行業對傅里葉、線性代數、概率論、統計、級數等要熟悉。

二十六、Math、Random(37、38)(20130117)

double ceil = Math.ceil(16.34);//返回大於指定數的最小整數
double floor = Math.floor(12.34);//返回小於指定數的最大整數
long round = Math.round(12.54);//四捨五入
double pow = Math.pow(2,3);//冪運算,2的3次方
double random1 = Math.random();//僞隨機數,大於等於0.0,小於1.0
int random2 = (int)(Math.random()*10 + 1);//隨機輸出1~10
Random r = new Random();//同爲僞隨機數
int random3 = r.nexInt(10);//無需強轉,直接返回int隨機數,括號內10爲“乘10”

Java中牽涉範圍的都會遵循min<=n<max

Constant命名規則:所有字母大寫、單詞間下劃線隔開、通常是public static final的。

二十七、Collection——ArrayList(39、40)

對於集合下的一些常用方法,應當熟記。

List兩大子類ArrayList、Linked。

ArrayList<Integer> list = new ArrayList<Integer>();
int x = 5;
Integer y = new Integer(x);//int==>Integer is boxing
list.add(y);//ArrayList只接受Object,因此對於primitives需要wrap後,才能存入。
Integer z = (Integer)list.get(0);
int f = z.intValue();//Integer==>int is unboxing

從Java5.0開始,boxing/unboxing已經由底層自動完成,即primitives也可直接俄存入ArrayList。

由ArrayList源碼可見,ArrayList的插入、刪除操作,需要對ArrayList整體進行復制操作,開銷較大。

二十八、數據結構簡介(41)

1、一般將數據結構分爲兩大類

1)線性數據結構:線性表、棧、隊列、串、數組、文件。

2)非線性數據結構:樹、圖。

2、線性表——n個數據元素的有限序列(a、a1、a2、a3、……an),必存在唯一一個“第一”和“最後”。同一線性表中,所有數據元素類型相同。

按存儲結構可分爲:

1)順序表,eg:ArrayList

2)鏈表:

(1)單向鏈表,date1|next==>date2|next==>date3|next

(2)循環鏈表,date1|next==>date2|next==>date3|next==>date1|……

(3)雙向循環鏈表,previous|date1|next<==>previous|date2|next<==>previous|date3|next<==>previous|date1|……

二十九、Collection——LinkedList(42)

LinkedList底層是用雙向鏈表實現的。

性能比較 ArrayList LinkedList
插入/刪除   yes
搜索 yes  

ArrayList與LinkedList雖然有着很多類似的方法,但這些方法的底層實現卻完全不同。

Notice:學習過程中,首要掌握的是分析源碼的能力,其次是編寫代碼的能力(創新),最後加上一定的經驗,才能更好的完成架構。

思考:分別用ArrayList&LinkedList實現Stack&Queue的機制。

三十、Stack&Queue(43、44)

Stack:一種特殊的線性表(Last In First Out),限定盡在表尾插入or刪除,物理存儲可以用順序or鏈式。

Queue:限定一端進入,另一端出去的線性表(First In First Out),插入端稱Rear,刪除端稱Front,物理存儲可以用順序or鏈式。

java.util包有Stack&Queue。

三十一、Collection——HashSet&TreeSet(45、46、47)

Set是無序的,不允許重複元素的。

HashSet.add()不會添加重複元素,它的執行過程是這樣的,比較兩個element的hashcode,不同直接加入,相同再用equals()進行第二次比較,不同加入,相同不加入。

HashSet.iterator()返回實現Iterator接口的一個實例,這個接口使用迭代器模式(行爲型模式分支)。

Notice:每個Collection類都提供了一個iterator函數,用於返回一個對類集頭的迭代接口。

HashSet內部是一個HashMap,TreeSet內部是一個TreeMap。

TreeSet是一種有序集合,爲加入的元素進行自動排序。

Pitfall:當加入的元素類型是primitives時,元素會被正常加入,因爲它們具有自然順序。當加入的元素類型是Object時,可能會拋出ClassCastException,解決方法是在TreeSet初始化時給出一套Sort規則。

MyComparator mc = new MyComparator();
TreeSet ts = new TreeSet(mc);//爲TreeSet限定一套Sort規則,就能正常對Object進行比較了
//MyComparator實現Comparator接口,至少實現compare(T e1,T e2)方法
class MyComparator implements Comparator
{
  public int compare(Object arg0,Object arg1)
  {
    String s1 = (String)arg0;
    String s2 = (String)arg1;
    return s2.compareTo(s2);//compareTo()以字典順序對兩個String進行比較
  }
}

Notice:相對於Array提供了一個工具類Arrays,Collection也有一個工具類Collections。

Comparator r = Collections.reverseOrder();//按自然順序的反序進行排序
Collections.sort(list,r);//按指定規則“r”對集合“list”進行排序
Collection.shuffle(list);//將集合順序打亂
Collection.min/max(list);//返回集合中最小/最大的數,該方法也有比較器形式min/max(list,comparator)

三十二、Collection——HashMap(48、49、50)

Map是無序的,一個key只能對應一個value。

思考:爲何keySet()返回Set<K>,而values()返回Collection<V>?

eg:使用HashMap對單詞出現頻率計數。

for(int i = 0;i < args.length;i++)
{
  if(!map.containsKey(args[i]))
  {
    map.put(args[i],1);
  }
  else
  {
    int count = map.get(args[i]);
    map.put(args[i],++count);
  }
}

Map.Entry<K,V>是Map的內部類

Set set = map.entrySet();
for(Iterator iter = set.iterator();iter.hashNext();)
{
  Map.Entry entry = (Map.Entry)iter.next();
  String key = (String)entry.getKey();
  Strng value = (String)entry.getValue();
}

思考:使用HashMap實現50個隨機數出現頻率的存取。

TreeMap是有序的,使用方法參照API與TreeSet,根據key爲Map內元素排序,同樣,允許在構造時限定Comparator排序規則。

三十三、Strategy Pattern(51)

Strategy Pattern策略模式(行爲型模式分支)

體現:封裝變化的概念,編程中使用接口。

定義:將每個算法封裝,使用時可互換,且互不影響。

意義:軟件各組成部分間是弱連接關係,且可替換(可重用性)。

三十四、Collection總結(52) 

List是有序的,允許重複的元素。

Set是無序的,不允許重複的元素。

Map是無序的,允許重複的元素,一個key只能對應一個value。

Tree前綴代表有序,且允許在構造時限定Comparator接口實現的排序規則。

性能比較 ArrayList LinkedList
插入/刪除   yes
搜索 yes  

ArrayList與LinkedList雖然有着很多類似的方法,但這些方法的底層實現卻完全不同。

LinkedList底層是用雙向鏈表實現的。

HashSet內部是一個HashMap(由於HashSet只用到HashMap的key,因此定義了一個Object常量用於填充value),TreeSet內部是一個TreeMap。

TreeSet是一種有序集合,爲加入的元素進行自動排序。

TreeMap是有序的,使用方法參照API與TreeSet,根據key爲Map內元素排序,同樣,允許在構造時限定Comparator排序規則。

loadfactor與hash有關,由於linearSearch開銷巨大,使用binarySearch前需要對Array進行sort,因此衍生了hash函數,其基本思想是:對輸入Data進行hash計算得出內存中存放地址。http://zh.wikipedia.org/wiki/Hash

HashMap底層是採用數組+鏈表實現的,當一個Data通過put方法添加到HashMap中,首先,會通過Hash函數計算出即將存入的Array[index],如果該index位置已被其他Data佔用,則將輸入的Data存入Array[index],將已存在的Data放置到new Data的next中,形成鏈表。

三十五、Vector(53、54、55、56)

Vector(同步)&ArrayList(不同步)相似,實際開發中,幾乎無人使用Vector,屬於逐漸淘汰對象。

HashTable(同步)&HashMap(不同步)相似,HashTable&Vector都是Java舊版本時代的產物。

Properties是HashTable的一個子類,經常使用。

類集框架提供了一個功能強大的設計方案,這些在今後開發中會頻繁使用,必須達到熟練使用的程度。


一、Generics(20130223修改)

JDK5.0 New Feature:

1)Generics、2)Enhanced For Loop、3)Autoboxing、4)Type Safe Enums、5)Static Import、6)Var Args。

其中Generics最爲重要,也會在今後開發中大量使用,編譯時類型安全,減少運行時ClassCastException拋出。

public class GenericFoo<T>
{
  private T foo;
  public void setFoo(T foo)
  {
    this.foo = foo;
  }
  public T getFoo()
  {
    return foo;
  }
}
GenericFoo foo = new GenericFoo();//“〈〉”中傳入何種類型,該類中的泛型就會轉化爲該類型,泛型:即變量類型參數化

Generic定義時,可使用extends關鍵字限制該Generic必須繼承某個類or實現某個接口。

public class genericFoo<T extends List>{//泛型T必須是List接口層次體系下的一員}
GenericFoo<? extends List> foo = null;//?爲通配符,聲明瞭foo可以指向實現List接口的對象,進一步將強了Generic的靈活性
foo = new GenericFoo<ArrayList>();
foo = new GenericFoo<LinkedList>();

Notice:GenericFoo<T extends List>與GenericFoo<? extends List>的區別,前者代表該泛型可以取繼承何種類&接口的子類,後者代表引用可以指向繼承了何種類&接口的子類。



二、Enhanced For Loop&Autoboxing&Varargs(57、58)
for(Type buf : src){//缺點:會丟失Array&Collection的subscript}

Example:使用new feature精簡單詞出現次數的Program

Map<String, Integer> map = new HashMap<String, Integer>();
for(String word : args)
{
  map.put(word, (null == map.get(word)) ? 1 : map.get(word)+1);
}



三、Enumeration(59、60)

Enum是一個特殊的類別,與class、interface作用相似,作用:限制某個特別用途的變量的取值範圍。

使用enum定義枚舉類型,實際上是定義了一個extends java.lang.Enum的類型。

每一個Enum成員都是該Enum的Instance,且自動預設爲public final static。

當Enum第一次使用時,會對其每一個成員進行初始化(調用其construction method),比如:enum WeekDay{ SUN,MON,TUE,WED,THU,FRI,SAT }第一次使用時,會調用7次construction method。

public enum Coin
{
  penny("PENNY"), nickel("NICKEL"), dime("DIME"),RMB{ public Coin nextCoin(){ return "人民幣" } };//每個枚舉成員都是一個對象(類似子類),可以在成員內定義方法,但不能是抽象方法
  private String value;
  public String getValue()
  {
    return value;
  }
  public abstract getCoin();
}


四、Reflection(61~71)(20130117)

1、Reflection反射機制:

動態獲取/調用一個類or任意一個對象的信息(屬性、方法等)的機制,通俗的講,就是把java類中的各種成分映射成相應的java類(eg:Field、Mehtod、Contructor、Package等對象都可以通過反射獲得)。

主要功能可在運行時:

1)判斷任意一個對象的所屬的類。

2)構造任意一個類的對象。

3)判斷任意一個類所具有的fields和methods。

4)調用任意一個對象的方法。

Reflection是Java被視爲準動態語言的一個關鍵性質。它允許我們對已知名稱的class的內部信息進行取得或在運行時加載一個編譯期未知的class。

Introspection - the ability of the program to examine itself.

Reflection&Introspection是常被並提的兩個術語。

API:java.lang.relect

Dynamic Programming language:允許程序在運行時改變其結構的編程語言;例如新的函數、對象,甚至代碼可以被引入,已有的函數可以被刪除或是其他結構上的變化。

Java中,無論生成某個類的多少個對象,這些對象都對應於同一class對象:

Object anClassInstance anClassInstance anClassInstance
Class AnClass

2、獲得這個class的方式有多種:

Class<?> clazz = AnClass.class;//通過Java內置對象
Class<?> clazz = Class.forName("com.relection.AnClass");
Object instance = clazz.newInstance();//創建該class對象所代表的類的實例
Mehotd method = clazz.getMehotd("methodName", new Class[]{int.class, int.class});//指定方法的pramType
Object result = method.invoke(instance, new Object[]{1, 3});//指定調用哪個實例的方法並傳參給方法
System.out.println((Integer)result);

獲得某個類or對象所對應的Class對象的3種常用的方式:

1)使用Class類的靜態方法Class.forName("java.lang.String");,這是常用的方式,因爲可以用字符串暫替類名,以便運行時決定具體類型。

2)使用類的內置對象String,class;。

3)使用Object的final方法str.getClass();區別於前兩者作用於類對象,getClass作用於已實例化對象。

Notic:通俗的講,無論你是“類名.class”還是“Class.forName(類名)”方式,獲得的都是該類在內存中的字節碼對象(取字節碼方式:直接從jvm中取,若沒有則加載進jvm再取)。

clazz.newInstance();等價於new MyClass();,但這種方式只能處理Constructor不帶參的類,對於處理帶參的要使用以下方式:

Class<?> clazz = obj.getClass();
COnstructor cons = clazz.getConstructor(new Class[]{String.class, int.class});
Object instance = cons.newInstance(new Object[]{"name", 25});
getFields()獲得對象的public的所有成員變量,getDeclaredFields()獲得對象所有已聲明的成員變量,包括private。
Field[] f = clazz.getDeclaredFields();
for(Field fbuf : f)
{
  String name = fbuf.getName();//獲得的方法名爲全小寫,所以要將其首字母轉換爲大寫
  String firstL = name.substring(0,1).toUpperCase();//截取方法名首字母並轉換爲大寫
  String getMN = "get" + firstL + name.substring(1);//拼接get方法
  String setMn = "set" + firstL + name.substring(1);//拼接set方法
  Method getM = clazz.getMethod(getMN, new Class[]{});//聲明get方法實例
  Method setM = clazz.getMethod(setMN, new Class[]{fbuf.getType()});//聲明set方法實例
  Object value = getM.invoke(obj, new Object[]{});//通過get方法實例調用方法,將返回值賦給value
  setM.invoke(instance, new Object[]{ value});//通過set方法實例調用方法,把value當作實參傳入
}
//用Reflection進行String對象中的字符替換
public static void compareTypeString(Object obj)
{
  Field[] fields = obj.getClass().getFields();
  for(Field field : fields)
  {
    if(field.getType() == String.class)
    {
      String buf = (String)field.get(obj);//從obj對象中取出field對應的值,downcast到String
      String nbuf = buf.replace("b", "a");//將String中的b替換爲a
      field.set(obj, nbuf);//將obj對象中field對應的值設置爲nbuf
    }
  }
}

Notice:加載config.properties時,利用反射AnClass.class.getClassLoader().getResourceAsStream(path/name);可以從classpath所在的根目錄尋找該配置文件

二、JavaBean(20130216)

IntroSpector(內省)==》JavaBean==》一種特殊的Java類==》主要用於傳遞數據信息==》JavaBean中的方法用於訪問私有的字段==》方法名符合某種命名規則(set/get)

JavaEE開發,很多環境中需要以JavaBean的方式進行開發。

以下是JavaAPI提供的對JavaBean操作的類PropertyDescript、BeanInfo、Introspector:

ReflectPoint rp1 = new ReflectPoint(3,5);//該類有一個構造方法接收兩個int賦給局部變量x、y,即x=3,y=5
String propertyName = "x";
//JavaBean的簡單內省操作,get方法
PropertyDescript pd = new PropertyDescript(propertyName,rp1.class);//Java提供的PropertyDescript屬性描述符
Method methodGetX = pd.getReadMethod();
Object retVal = methodGetX.invoke(rp1);
System.out.println(retVal)//result=3;
//JavaBean的簡單內省操作,set方法
PropertyDescript pd2 = new PropertyDescript(propertyName,rp1.class);
Method methodSetX = pd2.getWriteMethod();
methodSetX.invoke(rp1,8);//關於set的invoke方法調用,它本身是接收一個對象類型的Integer,但由於jdk5開始具備auto boxing功能,所以會把int型的8自動轉變爲Integer的8

以上方式,在對於不同JavaBean中的set/get方法的反射調用,具有一定程度的共通性,因此可以把其中的一些通用代碼提取出來寫成一個接收propertyName的方法。

public static Object getProperty(Object obj1, String propertyName)
{
  BeanInfo beanInfo = Introspector.getBeanInfo(obj1.getClass());
  PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
  Object retVal = null;
  for(PropertyDescriptor pd : pds)
  {
    if(pd.getName().equals(propertyName))
    {
      Method methodGetX = pd.getReadMethod();
      retVal = methodGetX.invoke(obj1);
      break;
    }
  }
  return retVal;
}

Apache的commons項目提供了BeanUtils類對類似以上的JavaBean的操作方式進行了封裝,因此,實際開發可用BeanUtils。



三、Annotation(20130217)

未來很多框架會越來越基於註解形式的配置,這是一種趨勢。

@Deprecated表示該類或方法“已過時”。

@SuppressWarnings("deprecation")消除”已過時“的方法或類提出的警告。

@Override重寫父類方法,若不符合重寫範式提出警告。

註解相當於一種標記,java編譯器或開發工具通過反射了解項目中的類或方法有無註解。

應用結構:

@interface A//註解類
{
}
==> @A
class B//應用註解的類
{
}
==> Class C//對“應用註解的類”進行反射操作的類
{
B.class.isAnnotationPresent(A.class)
A a = B.class.getAnnotation(A.class)
}

一個簡單的註解實例:

@Retention(RetentionPolicy.RUNTIME)//元註解,Retention有三種等級:SOURCE、CLASS、RUNTIME
@Target(ElementType.CLASS)//也是元註解,表示註解可用於什麼地方
public @interface AnnotationExample
{

}

Retention的三種等級,指明瞭註解的生命週期,SOURCE表示註解只保留在源碼階段,CLASS表示註解保留至class文件階段,RUMTIME表示註解保留至運行期。

如何在另一個類中應用註解:

@AnnotationExample
public class AnnotationApplication
{
  public static void main(String args[])
  {
    //檢查類上是否有註解對象
    if(AnnotationApplication.class.isAnnotationPresent(AnnotationExample.class))
    {
      //得到註解對象,只有當註解上被標記了元註解@Retention(RetentionPolicy.RUNTIME)時纔會被保留到運行期間
      AnnotationExample ae = (AnnotationExample)AnnotationApplication.class.getAnnotation(AnnotationExample.class);
    }
  }
}

爲註解增加屬性:

public @interface AnnotationExample
{
  String name() default "custom";//屬性默認值爲custom
  String value();//當且僅當註解只有一個屬性需要賦值時,可以直接用雙引號+值進行賦值
  MetaAnnotation annotationAttr() default @MetaAnnotation("change");;
}

使用帶屬性的註解:

@AnnotationExample("value")//如果要對兩個屬性都進行賦值,就必須這麼寫@AnnotationExample(annotationAttr=@MetaAnnotation("fix"),name="name",value="value")
public class AnnotationApplication
{

}



四、Thread

1、線程的概述(Introduction)

線程是一個程序的多個執行路徑,執行調度的單位,依託於進程存在。 線程不僅可以共享進程的內存,而且還擁有一個屬於自己的內存空間,這段內存空間也叫做線程棧,是在建立線程時由系統分配的,主要用來保存線程內部所使用的數據,如線程執行函數中所定義的變量。

Notice:Java中的多線程是一種搶佔機制而不是分時機制。搶佔機制指的是有多個線程處於可運行狀態,但是隻允許一個線程在運行,他們通過競爭的方式搶佔CPU。


2、線程的定義(Defining)

定義一個線程有兩種方法:繼承Thread類、實現Runnable接口

/**
 * 方式1:繼承Thread類
 */
public class ThreadTest extends Thread {
    /**
     * 重寫(Override)run()方法 JVM會自動調用該方法
     */
    @Override
    public void run() {
        System.out.println("I'm running!");
    }
    /**
     * 重載(Overload)run()方法 和普通的方法一樣,並不會在該線程的start()方法被調用後被JVM自動運行
     */
    public void run(int times) {
        System.out.println("I'm running!(Overload)");
    }
}

Notice:run()方法在該線程的start()方法被調用後,由JVM自動調用,但不建議使用此方法定義線程,因爲採用繼承Thread的方式定義線程後,你不能在繼承其他的類了,導致程序的可擴展性大大降低。

/**
 * 方式2:實現Runnable接口
 */
public class ThreadTest implements Runnable {
    public void run() {
            System.out.println("I'm running!");
    }
}


3、線程的啓動(Starting)

任何一個線程的執行的前提都是必須有Thread class的實例存在,並且通過調用run()方法啓動線程。

//1)如果線程是繼承Thread類,則創建方式如下:
ThreadTest1 tt = new ThreadTest1();
tt.start();
 //2)如果是實現Runnable接口,則創建方式如下:
ThreadTest2 tt = new ThreadTest2();
Thread t = new Thread(tt);
t.start();


4、線程的狀態(State)

1)新生狀態(New): 當一個線程的實例被創建即使用new關鍵字和Thread類或其子類創建一個線程對象後,此時該線程處於新生(new)狀態,處於新生狀態的線程有自己的內存空間,但該線程並沒有運行,此時線程還不是活着的(not alive);

2)就緒狀態(Runnable): 通過調用線程實例的start()方法來啓動線程使線程進入就緒狀態(runnable);處於就緒狀態的線程已經具備了運行條件,但還沒有被分配到CPU即不一定會被立即執行,此時處於線程就緒隊列,等待系統爲其分配CPCU,等待狀態並不是執行狀態; 此時線程是活着的(alive);

3)運行狀態(Running): 一旦獲取CPU(被JVM選中),線程就進入運行(running)狀態,線程的run()方法纔開始被執行;在運行狀態的線程執行自己的run()方法中的操作,直到調用其他的方法而終止、或者等待某種資源而阻塞、或者完成任務而死亡;如果在給定的時間片內沒有執行結束,就會被系統給換下來回到線程的等待狀態;此時線程是活着的(alive);

4)阻塞狀態(Blocked):通過調用join()、sleep()、wait()或者資源被暫用使線程處於阻塞(blocked)狀態;處於Blocking狀態的線程仍然是活着的(alive)

5)死亡狀態(Dead):當一個線程的run()方法運行完畢或被中斷或被異常退出,該線程到達死亡(dead)狀態。此時可能仍然存在一個該Thread的實例對象,當該Thready已經不可能在被作爲一個可被獨立執行的線程對待了,線程的獨立的call stack已經被dissolved。一旦某一線程進入Dead狀態,他就再也不能進入一個獨立線程的生命週期了。對於一個處於Dead狀態的線程調用start()方法,會出現一個運行期(runtime exception)的異常;處於Dead狀態的線程不是活着的(not alive)。


5、狀態圖



6、線程的方法(Method)、屬性(Property)

1)優先級(priority):每個類都有自己的優先級,一般property用1-10的整數表示,默認優先級是5,優先級最高是10;優先級高的線程並不一定比優先級低的線程執行的機會高,只是執行的機率高;默認一個線程的優先級和創建他的線程優先級相同;

2)Thread.sleep()/sleep(long millis):當前線程睡眠/millis的時間(millis指定睡眠時間是其最小的不執行時間,因爲sleep(millis)休眠到達後,無法保證會被JVM立即調度);sleep()是一個靜態方法(static method) ,所以他不會停止其他的線程也處於休眠狀態;線程sleep()時不會失去擁有的對象鎖。 作用:保持對象鎖,讓出CPU,調用目的是不讓當前線程獨自霸佔該進程所獲取的CPU資源,以留一定的時間給其他線程執行的機會。

3)Thread.yield():讓出CPU的使用權,給其他線程執行機會、讓同等優先權的線程運行(但並不保證當前線程會被JVM再次調度、使該線程重新進入Running狀態),如果沒有同等優先權的線程,那麼yield()方法將不會起作用。

4)thread.join():使用該方法的線程會在此之間執行完畢後再往下繼續執行。

5)object.wait():當一個線程執行到wait()方法時,他就進入到一個和該對象相關的等待池(Waiting Pool)中,同時失去了對象的機鎖—暫時的,wait後還要返還對象鎖。當前線程必須擁有當前對象的鎖,如果當前線程不是此鎖的擁有者,會拋出IllegalMonitorStateException異常,所以wait()必須在synchronized block中調用。

6)object.notify()/notifyAll():喚醒在當前對象等待池中等待的第一個線程/所有線程。notify()/notifyAll()也必須擁有相同對象鎖,否則也會拋出IllegalMonitorStateException異常。

7)Synchronizing Block:Synchronized Block/方法控制對類成員變量的訪問;Java中的每一個對象都有唯一的一個內置的鎖,每個Synchronized Block/方法只有持有調用該方法被鎖定對象的鎖纔可以訪問,否則所屬線程阻塞;機鎖具有獨佔性、一旦被一個Thread持有,其他的Thread就不能再擁有(不能訪問其他同步方法),方法一旦執行,就獨佔該鎖,直到從該方法返回時纔將鎖釋放,此後被阻塞的線程方能獲得該鎖,重新進入可執行狀態。


備註:所有代碼可能忽略異常(Exception)處理。(20130112)

一、基礎回顧:學會使用JavaAPI

Java提供了標準類庫,但對於初學者要一次掌握這樣龐大的類庫無異於學習C++那樣困難,因此,只需要掌握幾個基本、常用的即可。

然後,學會查找JavaAPI,在開發中需要使用其它類時再進行查詢。

Example:

1、System(該類無構造函數,說明不能new對象,那麼會想到該類中的方法都是靜態方法)

Properties pro = System.getProperties();//獲得系統屬性
System.setProperty("myKey","myValue');//設置系統屬性
for(Object obj : pro.keySet())//Properties是Hashtable的子類(通過JavaAPI查得),也是Map的子類,那麼可以通過Map的方法取出其中的元素
{
  String tmp = (String)pro.get(obj);//Properties集合中存儲的都是字符串
  System.out.println(obj + " :: " + tmp);//系統屬性用處很大,比如:可以通過系統屬性得知當前操作系統是什麼,然後判斷是否能安裝本軟件
}

2、Runtime(與System相同無構造函數,但也無靜態方法,說明該類會提供獲取本類對象的方法)

Runtime r = Runtime.getRuntime();//獲得Runtime對象(單例模式)
Process p = r.exec("winmine.exe");//啓動進程
Thread.sleep(4000);//線程休息4000毫秒
p.destroy();//殺死進程(只能殺死Runtime啓動的進程)

3、Date

Date d = new Date();
System.out.println(d);//默認以系統格式顯示,往往滿足不了我們的需要
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");//將模式封裝到SimpleDateFormat對象中,JavaAPI有詳盡的格式介紹
String time = sdf.format(d);//返回字符串,如要進行日期計算就需要額外轉換(麻煩)
System.out.println("time= " + time);

4、Calendar

Calendar c = Calendar.getInstance();
c.add(Calendar.MONTH,-1);//月份減1,eg:1月-1=12月
String[] mons = {"一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"};//使用下標法將數字轉換成中文
System.out.println(c.get(Calendar.YEAR) + "年" + mons[c.get(Calendar.MONTH)] + "月");

二、IO Stream(86~96)

Java通過IO Stream操作數據,IO Stream按數據分(字節流、字符流),按流方向分(輸入流、輸出流)。

常用基類:字節流的抽象基類(InputStream、OutputStream)、字符流的抽象基類(Reader、Writer)。

由此派生出來的子類名稱都是以他們四個父類作爲子類後綴名的。

Example:(20130113)

1、字符流的讀寫

//寫演示
FileWriter fw = null;
try
{
  fw = new FileWriter("demo.txt");//建立字符流,它會拋出一個IO異常
  //fw = new FileWriter("demo.txt",true);//FileWriter的第二個構造函數,指定爲true後啓用追加修改
  fw.write("Testing1");//寫入數據,暫存於緩存
  fw.flush();//刷新流,將緩衝區的數據寫入文件
  fw.write("Testing2");
}
catch(IOException e)//FileNotFoundException是IOException的一個子類
{
  System.out.println(e.toString());
}
finally
{
  try
  {
    if(fw != null)
      fw.close();//刷新流,並關閉流
  }
  catch(IOException e)//如果初始化拋出異常而沒創建成功,那麼執行fw.close()就會得到異常
  {
    System.out.println(e.toString());  
  }
}
//讀演示
FileReader fr = new FileReader("demo.txt");//讀取文件
int ch = 0;
while((ch = fr.read()) != -1)//讀取方式一,單個字符讀取
{
  System.out.print((char)ch);
}
char[] buf = new char[1024];
int num = 0;
while((num = fr.read(buf)) != -1)//讀取方式二,以定長字符數組作爲緩存讀取
{
  System.out.print(new String(buf,0,num));
}

2、字符流用於文件複製

FileReader fr = null;
FileWriter fw = null;
try
{
  fr = new FileReader("Source.txt");
  fw = new FileWriter("Destination.txt");
  char[] buf = new char[1024];
  int len = 0;
  while((len = (fr.read(buf)) != -1)//把字符讀取到緩衝字符數組buf中
  {
    fw.write(buf,0,len);//將緩衝字符數組buf的內容寫入
  }
}
catch(IOException e)
{
  throw new RuntimeException("複製失敗");
}
finally
{
  try
  {
    if(fr != null)
      fr.close();
  }
  catch(IOException e){}
  try
  {
    if(fw != null)
      fw.close();
  }
  catch(IOException e){}
}

3、字符流的緩衝區

緩衝區的出現提高了對數據的讀寫效率。

對應類:BufferedWriter、BufferedReader。

緩衝區要結合流纔可以使用,在流的基礎上對流的功能進行增強。

//寫演示
FileWriter fw = new FileWriter("demo.txt");
BufferedWriter bw = new BufferedWriter(fw);//緩衝技術的關鍵是在其中封裝了數組
bw.write("testing");
bw.newLine();//這是爲了避免不同操作系統中換行符表示不同所採用的
bw.flush();
bw.close();
fw.close();
//讀演示
FileReader fr = new FileReader("demo.txt");
BufferedReader br = new BufferedReader(fr);
String line = null;
while((line = br.readLine()) != null)
{
  System.out.println(line);
}
br.close();
fr.close();

4、自己封裝一個緩衝區

public class MyBufferedReader//這裏使用了“裝飾設計模式”,想要對已有對象進行增強時,將已有對象傳入,基於已有功能,提供加強功能,那麼自定義的該類稱爲裝飾類

{
  private Reader r = null;
  public MyBufferedReader(Reader r)//原有類通過構造方法傳入
  {
    this.r = r;
  }
  public String myReadLine() throws IOException//對原有類進行增強
  {
    StringBuilder sb = new StringBuilder();
    int ch = 0;
    while((ch = r.read()) != -1)
    {
      if(ch == '\r')
        continue;
      if(ch == '\n')
        return sb.toString();
      else
        sb.append((char)ch);
    }
    if(sb.length != 0)
      return sb.toString();
    return null;
  }
  public void myClose() throws IOException
  {
    r.close();
  }
}

裝飾模式VS繼承:如果使用繼承,對於每一種形式的流操作都需要爲它寫一個子類,這意味着可能要寫多個子類,但如果使用裝飾模式,則可以只寫一個子類,根據需要把不同形式的流操作通過構造函數傳入,進行功能增強。

LineNumberReader跟蹤行號的緩衝字符輸入流,增加了setLineNumber()(設置起始行號)和getLineNumber()(獲得當前行號)兩個方法,使用方法與上類似。

5、字節流的讀寫

//讀操作1
FileInputStream fis = new FileInputStream("demo.txt");
byte[] buf = new byte[fis.available()];
fis.read(buf);
System.out.println(new String(buf));
fis.close();
//讀操作2
FileIutputSteam fis = new FileIutputStream("demo.txt");
byte[] buf = new byte[1024];
int len = 0;
while((len = fis.read(buf)) != -1)
{
  System.out.println(new String(buf,0,len));
}
fis.close();
//寫操作
FileOutputStream fos = new FileOutputStream("demo.txt");
fos.write("abcdefg".getBytes());
fos.close();

6、字節流用於複製圖片

FileOutputStream fos = new FileOutputStream("c:\\2.bmp");
FileInputStream fis = new FileInputStream("c:\\1.bmp");
byte[] buf = new byte[1024];
int len = 0;
while((len = fis.read(buf)) != -1)
{
  fos.write(buf,0,len);
}

7、字節流緩衝區

BufferedInputStream bis = new BufferedInputStream(new FileInputStream("demo.txt"));//不難發現使用方式與字符流類似
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("demo2.txt"));
int buf = 0;
while((buf = bis.read()) != -1)
{
  fos.write(buf);
}
bis.close();
bos.close();

8、從鍵盤錄入

InputStream in = System.in;//in標準“輸入流”,從鍵盤   out標準“輸出流”,到控制檯
int buf = in.read();//等待用戶輸入
System.out.println(buf);

9、鍵盤錄入轉換

通過鍵盤錄入一行數據,就類似readLine()方法,但readLine()方法是字符流BufferedReader中的方法,而鍵盤錄入的read方法是字節流InputStream的方法,那麼該如何轉換呢?

InputStream in = System.in;
InputStreamReader isr = new InputStreamReader(in);//將字節流轉換爲字符流
BufferedReader br = new BufferedReader(isr);//br.read()
//之後操作參考上面的例子,同樣,也有字符流轉換字節流的OutputStreamWriter
OutputStream out = System.out;
OutputStreamWriter osw = new OutputStreamWriter(out);
BufferedWriter bw = new BufferedWriter(osw);

10、流操作規律(20130114)

思考I/O Stream如何使用時,應以Java程序本身爲第一人稱,比如我們鍵盤錄入數據到文本,鍵盤錄入System.in,需要把數據寫入Java程序(InputStream或Reader),Java程序再把數據輸出到文本(OutputStream或Writer)。

System.setIn(new FileInputStream("Demo.txt"));//該方法可改變輸入源,同樣也有改變輸出源的System.setOut(new PrintStream("demo.txt"));

11、維護日誌

利用流把錯誤信息輸出到維護日誌

//實現一
catch(Exception e)
{
  e.printStackTrace(new PrintStream("Exception_log.txt"));//將錯誤信息輸出到Exception_log.txt日誌中,注意:PrintStream本身還會拋出異常需要處理
}
//實現二
catch(Exception e)
{
  Date d = new Date();
  PrintStream ps = new PrintStream("Exception_log.txt");
  ps.print(d.toString());//在輸出錯誤信息前加入時間信息
  System.setOut(ps);
  e.printStackTrace(System.out);//通過改變輸出源,將錯誤信息保存進日誌,也可以在此加入時間
}

有時也需要將系統信息輸出到維護日誌

Properties p = System.getProperties();
p.list(new PrintStream("Exception_log.txt"));

12、File

不要被File的名字騙了,File即可以代表一個文件,也可以代表一系列文件的集合。

該類彌補了I/O Stream的不足,因爲流只能操作文件中的數據,想要操作文件本身標的信息就要依靠File對象。

File f = new File("c:"+File.separator()+"demo.txt");//separator方法提供跨平臺文件的分隔符
f.createNewFile();//創建成功返回true
f.delete();//刪除文件
f.createTempFile();//創建臨時文件,用於保存程序運行發生的臨時數據(故障恢復時用)
f.deleteOnExit();//在程序退出時刪除文件,通常和創建臨時文件一起使用
f.getPath();//獲得相對路徑,Result:demo.txt
f.getAbsolutePath();//獲得絕對路徑,Result:c:\demo.txt
//文件過濾
File f = new File("c:\\");
String[] arr = f.list(new FilenameFilter(){
  public boolean accept(File dir,String name)
  {
    System.out.println(dir+"   "+name);
    /*以下是錯誤的示範
    if(name.endsWith(".bmp"))
      return true;
    else
      return false;
    */
    return name.endsWith(".bmp");
  }
});
//學會查看JavaAPI,需要時查閱,再做一些簡單的測試既能掌握它的用法,Java的產生主旨就是要讓編程變得簡單!

13、遞歸顯示文件列表

14、刪除文件

原理:window中,文件刪除必須從裏向外,只有把一個文件夾裏的內容全部刪除才能刪除該文件夾,以此類推。

15、Properties

Properties以鍵值對的形式保存信息(鍵=值,“=”爲分隔符,#爲註釋標記),使用Properties對象創建配置文件

Properties p = new Properties();
FileInputStream fis = new FileInputStream("config.txt");
p.load(fis);//load方法通過流讀取某一配置文件中的配置信息(載入配置文件)
p.list(System.out);//將信息輸出到控制檯
p.setProperty("myKey","myValue);//修改某一屬性值
FileOutputStream fos = new FileOutputStream("config.txt");
p.store(fos);//保存配置信息

16、特殊流

打印流的便捷之處在於可以將各種數據類型的數據都原樣打印。

字節輸出流PrintStream(可接收File、String、OutputStream),字符輸出流PrintWriter(可接收File、String、OutputStream、Writer)。

BufferedReader br = new BufferedReader(new InputStream(System.in));
PrintWriter pw = new PrintWriter(System.out,true);
String line = null;
while((line = br.readLine()) != null)
{
  pw.println(line);
}
pw.close();
br.close();

合併流SequenceInputStream

Vector<FileInputStream> v = new Vector<FileInputStream>();
v.add(new FileInputStream("demo1.txt"));
v.add(new FileInputStream("demo2.txt"));
v.add(new FileInputStream("demo3.txt"));
Enumeration<FileInputStream> enum = v.elements();
SequenceInputStream sis = new SequenceInputStream(enum);
FileOutputStream fos = new FileOutputStream("Destination.txt");
byte[] buf = new byte[1024];
int len = 0;
while((len = sis.read(buf)) != -1)
{
  fos.write(buf,0,len);
}
sis.close();
fos.close();

切割文件

FileInputStream fis = new FileInputStream("Source.txt");
FileOutputStream fos = null;
byte[] buf = new byte[1024*1024];
int len = 0,count = 0;
while((len = fis.read(buf)) != -1)
{
  fos = new FileOutputStream((count++) + ".part");//以文件切割次數作爲碎片的序列名稱
  fos.write(buf,0,len);
  fos.close();
}

17、對象的序列化

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj_tmp.txt"));
oos.writeObject(new ObjDemo("value",111));//向文本寫入一個對象
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj_tmp.txt"));
ObjDemo od1 = (ObjDemo)ois.readObject();
System.out.println(od1);
ois.close();
//至此,可以進行初步的對象序列化(即存入磁盤),但當原有的ObjDemo發生改變(比如其中的name屬性由public變爲private),被序列化的obj_tmp就無法匹配新對象了。此時,讓ObjDemo繼承Serializable對象,並且給定固定標識(static final long serialVersionUID = 42L),就能保證新類也能匹配舊的序列化對象
//注意:transient可關鍵詞用於修飾不想被序列化的屬性

18、管道流

class Read implements Runable
{
  private PipedInputStream in;
  Read(PipedInputStream in)
  {
    this.in = in;
  }
  public void run()
  {
    byte[] buf = new byte[1024];
    System.out.println("Read:start reading...");
    int len = in.read(buf);//阻塞讀取數據,若無數據進入等待狀態
    System.out.println("Read:read over.");
    String s = new String(buf,0,len);
    System.out.println(s);
    in.close();
  }
}
class Write implements Runable
{
  private PipedOutputStream out;
  Write(PipedOutputStream out)
  {
    this.out = out;
  }
  public void run()
  {
    System.out.println("Write:stop 6ms...")
    Thread.sleep(6000);
    out.write("piped is come in".getBytes());//寫數據
    out.close();
  }
}
main
{
  PipedInputStream in = new PipedInputStream();
  PipedOutputStream out = new PipedOutputStream();
  in.connect(out);//連接管道
  Read r = new Read();
  Write w = new Write();
  new Thread(r).start();
  new Thread(w).start();
}

19、RandomAccessFile

該類不屬於I/O體系中一員,直接繼承自Object,但該類是I/O包中成員,因爲該類自身擁有通過指針對文件隨機讀寫的能力(內部封裝了字節輸入&輸出流)。

通過構造函數可以看出該類只能對文件操作,且具有固定模式。

RandomAccessFile raf = new RandomAccessFile("demo.txt","rw);
raf.write("value1".getBytes());
raf.writeInt(97);
raf.write("value2".getBytes());
raf.writeInt(99);
raf.close();
//輸出完成,下面開始輸入
RandomAccessFile raf = new RandomAccessFile("demo.txt","rw);
byte[] buf = new byte[4];
raf.read(buf);
String name = new String(buf);
int age = raf.readInt();
System.out.println(name+" "+age);
//若只想取第二個數據
RandomAccessFile raf = new RandomAccessFile("demo.txt","rw);
raf.seek(8);//指針向前偏移8個字節,使用此方法的前提是數據必須具有規律性!
//raf.skipBytes(8);效果同seek,但seek可以前移後移,但skipBytes只能後移!
byte[] buf = new byte[4];
raf.read(buf);
String name = new String(buf);
int age = raf.readInt();
System.out.println(name+" "+age);
//流在操作數據時只能按順序讀或寫,但RandomAccessFile允許跳至任何位置寫(即隨機寫),就是使用seek&skipBytes方法實現的!

注意:流對象允許在構建時指定編碼表(UTF-8、ASCII、GB2312、Unicode、ISO8859-1、GBK)。

三、網絡編程(20130128)(106~112)

1、網絡模型:OSI(Open System Interconnection)參考模型、TCP/IP(Transmission Control Protocol/Internet Protocol)模型。

OSI參考模型 TCP/IP參考模型 各層作用
應用層 應用層 處理網絡應用
表示層 數據表示
會話層 主機間通訊
傳輸層 傳輸層 端到端的連接
網絡層 網際層 尋址&最短路徑
數據鏈路層 主機至網絡層 介質訪問(接入)
物理層 二進制傳輸

Notice:除了TCP/IP協議,還有其他協議可以使多機通訊,但前提是通訊雙方的主機所安裝的協議需要一致。因此,一些特別組織會安裝特殊協議以保證網絡安全性。

2、網絡通訊要素:

1)IP

本地迴環地址:127.0.0.1,可以通過ping本地迴環地址測試網卡是否正常。主機名:localhost。

192.168.1段的地址是作爲局域網的最常用的保留地址段。

子網掩碼:http://zh.wikipedia.org/wiki/子網

2)Prot

用於標識進程的邏輯地址,不同進程的標識。

0~6535是端口允許的範圍,其中0~1024被系統使用。

3)Protocol

兩種常用的傳輸協議:

TCP,Transfer Control Protocol——是一種面向連接的保證可靠傳輸的協議,通過TCP傳輸,得到的是一個順序無差錯的數據流,發送者&接收者之間必須建立成對的Socket連接。

UDP,User Datagram Protocol——是一種無連接協議,每一個數據報(限制64KB內)都是一個獨立的信息,包括完整的源地址或目的地址、時間及內容正確性不能保證,通常用於音頻/視頻傳輸。

3、Java的TCP&UDP實現

java支持TCP&UDP通過java.net包中的類進行通訊。

1)InetAddress是java的IP對象,Example:

InetAddress address = InetAddress.getLocalHost();
address = InetAddress.getByName("www.infoq.com");
InetAddress[] address = InetAddress.getAllByName("www.baidu.com");//獲得該主機名的所有IP
//以上3個方法都是創建InetAddress實例(工廠方法)
System.out.println(address.getHostAddress());
System.out.println(address.getHostName());

2)Socket

網絡編程指的就是Socket編程,而一些教程書上的寫的JSP、Servlet、SSH等技術是JavaEE對網絡應用框架的封裝,雖然底層是Socket,但它們並不是網絡編程,而是網絡應用編程。

Socket就是爲網絡服務提供的一種機制,通訊兩端都有Socket,網絡通訊就是Socket間的通訊,數據在兩個Socket間通過I/O傳輸。

3)UDP Example:

//數據發送
DatagramSocket ds_send = new DatagramSocket();//1、通過DatagramSocket對象創建UDP服務(發送端),若不指定端口,系統會自動分配端口
byte[] buf = "this is a udp package.".getBytes();//2、確定數據,並封裝成數據包
DatagramPacket dp_send = new DatagramPacket(buf, buf.length, InetAddress.getByName("127.0.0.1"), 10000);
ds_send.sent(dp_send);//3、通過Socket服務,將已有數據包通過send()發送出去
ds_send.close();//4、關閉資源
//數據接收
DatagramSocket ds_receive = new DatagramSocket(10000);//1、通過DatagramSocket對象創建UDP服務(接收端),指定監聽端口
byte[] buf = new bytes[1024];//2、定義數據包,用於存儲數據
DatagramPacket dp_receive = new DatagramPacket(buf, buf.length);
ds_receive.receive(dp_receive);//3、通過receive()將數據接收到數據包中,阻塞式方法,未接收到數據就等待
String ip = dp_receive.getAddress().getHostAddress();//4、通過數據包的方式獲取其中的數據
String data = new String(dp_receive.getData(), 0, dp_receive.getLength());
int port = dp_receive.getPort();
System.out.println(ip + " : " + prot + " :: " + data);
ds_receive.close();

用UDP通訊的聊天室(無線程版)Example:

//發送端
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
DatagramSocket ds_send = new DatagramSocket(10002);
String line = null;
while(null != (line = br.readLine))
{
  if(line.equals("886"))
    break;
  byte buf = line.getBytes();
  DatagramPacket dp_send = new DatagramPacket(buf, buf.length, InetAddress.getByName("127.0.0.1"), 10001);
  ds_send.sent(dp_send);
}
ds_send.close();
//接收端
DatagramSocket ds_receive = new DatagramSocket(10001);
while(true)
{
  byte[] buf = new bytes[1024];
  DatagramPacket dp_receive = new DatagramPacket(buf, buf.length);
  ds_receive.receive(dp_receive);
  String ip = dp_receive.getAddress().getHostAddress();
  String data = new String(dp_receive.getData(), 0, dp_receive.getLength());
  int port = dp_receive.getPort();
  System.out.println(ip + " : " + prot + " :: " + data);
}
ds_receive.close();

用UDP通訊的聊天室(線程版)Example:

//發送線程
class Send implements Runnable
{
  private DatagramSocket ds;
  public Send(DatagramSocket ds)
  {
    this.ds = ds;
  }
  public void run()
  {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    while(null != (line = br.readLine()))
    {
      if("886".equals(line))
        break;
      byte[] buf = line.getBytes();
      DatagramPacket dp = new DatagramPocket(buf, buf.length, InetAdress.getByName("127.0.0.1"), 10002);
      ds.send(dp);
    }
    ds.close();
  }
}
//接收線程
class Receive implements Runnable
{
  private DatagramSocket ds;
  public Receive(DatagramSocket ds)
  {
    this.ds = ds;
  }
  public void run()
  {
    while(true)
    {
      byte[] buf = new byte[1024];
      DatagramPocket dp = new DatagramPocket(buf, buf.length);
      ds.receive(dp);
      String ip = dp.getAddress().getHostAddress();
      String data = new String(dp.getData(), 0, dp.getLength());
      System.out.println(ip + " : " + data);
    }
    ds.close();
  }
}
//主線程
public class ChatDemo
{
  public static void main(String[] args)
  {
    DatagramSocket sendSocket = new DatagramSocket();
    DatagramSocket receiveSocket = new DatagramSocket(10002);
    new Thread(new Send(sendSocket)).start();
    new Thread(new Receive(receiveSocket)).start();
  }
}

4)TCP Example:

//客戶端
Socket s = new Socket("127.0.0.1", 10003);//創建客戶端的Socket,指定目的主機和端口
OutputStream os = s.getOutputStream();//爲了發送數據,應該獲取Socket流中的輸出流
os.write("this is a tcp message.".getBytes());
InputStream is = s.getInputStream();//阻塞式方法等待數據
byte[] buf = new byte[1024];
int len = is.read(buf);
System.out.println(new String(buf, 0, len));
os.close();
is.close();
s.close();
//服務端
ServerSocket ss = new ServerSocket(10003);//建立服務端Socket,並監聽一個端口
Socket s = ss.accept();//通過accept()獲取連接過來的客戶端對象
InputStream is = s.getInputStream();//使用讀取流獲取客戶端發送來的數據,阻塞式方法等待數據
byte buf = new byte[1024];
int len = is.read(buf);
System.out.println(s.getInetAddress().getHostAddress() + " : " + new String(buf, 0, len));
OutputStream os = s.getOutputStream();
os.write("recevie success!".getBytes());
is.close();
os.close();
s.close();//關閉該客戶端Socket,釋放資源

併發式(多線程)上傳圖片Example:

//客戶端
class PicClient
{
  public static void main(String[] args)
  {
    Socket s = new Socket("127.0.0.1", 10005);
    FileInputStream fis = new FileInputStream("c:\\test.bmp");
    OutputStream os = s.getOutputStream();
    byte[] buf = new byte[1024];
    int len = 0;
    while((len = fis.read(buf)) != -1)
    {
      os.write(buf, 0, len);
    }
    s.shutdownOutput();//告訴服務端數據已寫完
    InputStream is = s.getInputStream();
    len = is.read(buf);
    System.out.println(new String(buf, 0, len));
    fis.close();
    os.close();
    is.close();
    s.close();
  }
}
//服務端
class PicServerThread implements Runnable
{
  private Socket s;
  PicServerThread(Socket s)
  {
    this.s = s;
  }
  public void run()
  {
    int count = 1;
    String ip = s.getInetAddress().getHostAddress();
    InputStream is = s.getInputStream();
    File file = new File(ip + "(" + count + ")" + "_test.bmp");
    while(file.exists())//如果名字存在,計數+1
      file = new File(ip + "(" + count + ")" + "_test.bmp");
    FileOutputStream fos = new FileOutputStream(ip + "_test.bmp");
    byte[] buf = new byte[1024];
    int len = 0;
    while((len = is.read(buf)) != -1)
    {
      fos.write(buf, 0, len);
    }
    OutputStream os = s.getOutputStream();
    os.write("receive success!".getBytes());
    fos.close();
    os.close();
    is.close();
    s.close();
  }
}
class PicServer
{
  public static void main(String[] args)
  {
    ServerSocket ss = new ServerSocket(10005);
    while(true)
    {
      Socket s = ss.accept();
      new Thread(new PicServerThread(s)).start();
    }
    ss.close();
  }
}

4、Browser/Server架構

1)通過Browser訪問ServerSocket,Example:

ServerSocket ss = new ServerSocket(11000);
Socket s = ss.accept();
System.out.println(s.getInetAddress().getHostAddress());
PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
pw.println("<font color='red' size='7'>Hello,Client!</font>");
s.close();
ss.close();
啓動以上服務端Socket,然後在瀏覽器地址欄鍵入http://127.0.0.1:11000/,回車就會收到反饋信息。

也可以在DOS下通過telnet 127.0.0.1 11000命令訪問主機。

2)通過自定義瀏覽器訪問Tomcat,Example:

String path = "/myweb/demo.html";//如果使用圖形化界面,可通過修改path改變訪問地址
Socket s = new Socket("192.168.1.1", 8080);
PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
out.println("GET + path + HTP/1.1");
out.println("Accept: */*");
out.println("Accept-Language: zh-cn");
out.println("Host: 192.168.1.1:11000");
out.println("Connection: closed");
out.println();
out.println();
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = null;
while((line = br.read()) != null)
{
  System.out.println(line);
}
s.close();

打開Tomcat服務器,然後通過自定義瀏覽器(報頭遵循國際標準)訪問Tomcat查看接收到什麼數據。

3)URL,Uniform Resource Locator統一資源定位符,包括兩個主要部分:協議標識符(http、ftp、file等)、資源名稱(主機名.文件名.端口號.引用)。

Exampe:

URL url = new URL("http://java.sun.com:80/docs/books/demo.html?name=test&age=30");
String protocol = url.getProtocol();
String host = url.getHost();
String file = url.getFile();//獲得目錄/docs/books/demo.html?name=test&age=30
String query = url.getQuery();//獲得?後邊的參數name=test&age=30
int port = url.getPort();
String ref = url.getRef();
//將infoq的頁面通過I/O Stream保存至本地
URL url = new URL("http://www.infoq.com");
URLConnection conn = url.openConnection();
InputStream is = conn.getInputStream();
//以上2句等價於InputStream is = url.openStream();
OutputStream os = new FileOutputStream("c:\\infoq.txt");
byte[] buf = new byte[2048];
int length = 0;
while(-1 != (length = is.read(buf, 0, buf.length)))
  os.write(buf, 0, length);
is.close();
os.close();
//使用字符流+緩衝流
BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
String line = null;
while(null != (line = br.readLine()))
  System.out.println(line);

5、小知識

InetSocketAddress(extends SocketAddress)封裝了IP+Port,InetAddress封裝了IP。

ServerSocket在對象建立的時候,有一個構造方法ServerSocket(int port, int backlog),backlog允許指定同時接入客戶端的最大數。

域名解析過程:查看傳輸協議(http)==》公網DNS服務器==》查找域名&IP映射表==》域名對應IP被返回客戶端==》通過IP訪問目標服務端

那麼,當訪問localhost時,電腦並未連入公網,又是如何查找到對應IP127.0.0.1的呢?因爲,localhost與127.0.0.1的映射關係被保存在本機了(位置:C:\Windows\System32\drivers\etc\hosts)。

爲了提高訪問速度&屏蔽一些惡意網站,可以把常用的域名&IP映射表保存在hosts文件中。

五、JDBC

JDBC(Java Data Base Connectivity),創建一個JDBC程序,包含7個步驟:

/**
 * 1、加載JDBC驅動程序:<br/>
 * 在連接數據庫之前,首先要加載想要連接的數據庫的驅動到JVM(Java虛擬機),<br/>
 * 這通過java.lang.Class類的靜態方法forName(String className)實現。
 */
try
{
	// 加載MySql的驅動類
	Class.forName("com.mysql.jdbc.Driver");
}
catch (ClassNotFoundException e)
{
	System.out.println("找不到驅動程序類 ,加載驅動失敗!");
	e.printStackTrace();
}
// 成功加載後,會將Driver類的實例註冊到DriverManager類中。
/**
 * 2、提供JDBC連接的URL:<br/>
 * 連接URL定義了連接數據庫時的協議、子協議、數據源標識。 <br/>
 * 書寫形式——協議:子協議:數據源標識<br/>
 * 協議:在JDBC中總是以jdbc開始。<br/>
 * 子協議:是橋連接的驅動程序或是數據庫管理系統名稱。<br/>
 * 數據源標識:標記找到數據庫來源的地址與連接端口。
 */

String url = "jdbc:mysql://192.168.137.211/test?useUnicode=true&characterEncoding=utf-8";
// useUnicode=true:表示使用Unicode字符集。如果characterEncoding設置爲gb2312或GBK,本參數必須設置爲true。characterEncoding=utf-8:字符編碼方式。
/**
 * 3、創建數據庫的連接:<br/>
 * 要連接數據庫,需要向java.sql.DriverManager請求並獲得Connection對象,該對象就代表一個數據庫的連接。<br/>
 * 使用DriverManager的getConnectin(String url , String username , String
 * password )方法傳入指定的欲連接的數據庫的路徑、數據庫的用戶名和 密碼來獲得。
 */
String username = "root";
String password = "root";
Connection conn = null;
try
{
	conn = DriverManager.getConnection(url, username, password);
}
catch (SQLException se)
{
	System.out.println("數據庫連接失敗!");
	se.printStackTrace();
}
/**
 * 4、創建一個Statement:<br/>
 * 要執行SQL語句,必須獲得java.sql.Statement實例,Statement實例分爲以下3 種類型:<br/>
 * 1、執行靜態SQL語句。通常通過Statement實例實現。<br/>
 * 2、執行動態SQL語句。通常通過PreparedStatement實例實現。<br/>
 * 3、執行數據庫存儲過程。通常通過CallableStatement實例實現。
 */

Statement stmt = conn.createStatement();
PreparedStatement pstmt = conn.prepareStatement("sql");
CallableStatement cstmt = conn.prepareCall("{CALL demoSp(? , ?)}");
/**
 * 5、執行SQL語句:<br/>
 * Statement接口提供了三種執行SQL語句的方法:executeQuery、executeUpdate和execute。<br/>
 * 1、ResultSet executeQuery(String sqlString):執行查詢數據庫的SQL語句
 * ,返回一個結果集(ResultSet)對象。<br/>
 * 2、int executeUpdate(String sqlString):用於執行INSERT、UPDATE或
 * DELETE語句以及SQL DDL語句,如:CREATE TABLE和DROP TABLE等。<br/>
 * 3、execute(sqlString):用於執行返回多個結果集、多個更新計數或二者組合的語句。
 */

ResultSet rs = stmt.executeQuery("SELECT * FROM ...");
int rows = stmt.executeUpdate("INSERT INTO ...");
boolean flag = stmt.execute("sql");
/**
 * 6、處理結果 兩種情況:<br/>
 * 1、執行更新返回的是本次操作影響到的記錄數。<br/>
 * 2、執行查詢返回的結果是一個ResultSet對象。
 * ResultSet包含符合SQL語句中條件的所有行,並且它通過一套get方法提供了對這些 行中數據的訪問。
 * 使用結果集(ResultSet)對象的訪問方法獲取數據:
 */

while (rs.next())
{
	String name = rs.getString("name");
	String pass = rs.getString(1); // 此方法比較高效
}
// (列是從左到右編號的,並且從列1開始)
/**
 * 7、關閉JDBC對象:<br/>
 * 操作完成以後要把所有使用的JDBC對象全都關閉,以釋放JDBC資源,關閉順序和聲明順序相反:<br/>
 * 1、關閉記錄集。2、關閉聲明。3、關閉連接對象。
 */

if (rs != null)
{ // 關閉記錄集
	try
	{
		rs.close();
	}
	catch (SQLException e)
	{
		e.printStackTrace();
	}
}
if (stmt != null)
{ // 關閉聲明
	try
	{
		stmt.close();
	}
	catch (SQLException e)
	{
		e.printStackTrace();
	}
}
if (conn != null)
{ // 關閉連接對象
	try
	{
		conn.close();
	}
	catch (SQLException e)
	{
		e.printStackTrace();
	}
}


一、Proxy(20130223)

1、Proxy Pattern

1)Proxy Pattern(結構型模式分支):爲其他對象提供一種代理以控制對這個對象的訪問權,比如:spring。

2)Proxy Pattern組件:

抽象角色(聲明真實對象和代理對象的共同接口)

代理角色(內部含有對真實對象的引用,通過引用操作真實對象,對外提供與真實對象的接口,可附加對真實對象調用執行時的額外行爲)

真實角色(代理角色所代表的真實角色,是我們最終要引用的)

3)Proxy Pattern分類:

Static Proxy Pattern具有明顯的缺點:真實角色必須事先存在,並將其作爲代理對象的內部屬性實際使用,真實角色過多會造成過度膨脹。

Dynamic Proxy Pattern填補了上述缺點,所有代理類都由JVM在運行期動態生成,在Java中提供了實現這一功能的兩個類,InvocationHandler在代理中動態實現、Proxy動態代理類。

要充分理解Dynamic Proxy就必須打破傳統Java思維方式,因爲它是通過ClassLoader、Interface、InvcationHandler在運行時動態生成代理類的技術。

4)Proxy Pattern的作用

爲已存在的多個具有相同接口的目標類的各個方法增加一些系統功能,比如:異常處理、日誌、運行時間等。但問題是我們沒有源碼,那該怎麼做呢?

Proxy編程應用非常廣泛,漸漸衍生出一個術語AOP(Aspect Oriented Program),其目的是爲了讓交叉業務模塊化,即用統一方式,在目標方法執行到特定步驟的時候自動加入某一操作,而非直接頻繁地修改目標方法內部,比如Spring通過xml配置對Java Beans進行實例注入。

5)A simple Dynamic Proxy Example

Class clazzProxy1 = Proxy.getProxyClass(
		Collection.class.getClassLoader(), Collection.class);
System.out.println(clazzProxy1.getName());
System.out
		.println("------------begin constructor list------------------");
Constructor[] constructors = clazzProxy1.getConstructors();
for (Constructor constructor : constructors)
{
	String name = constructor.getName();
	StringBuilder sb = new StringBuilder(name);
	// StringBuilder&StringBuffer的區別,應用上都是動態往字符串上加內容,
	// 但StringBuffer是Thread Safe的,因此StringBuilder在Single Thread情況下效率高,
	// 而Multi Thread情況下StringBuffer效率高
	sb.append('{');
	Class[] clazzParams = constructor.getParameterTypes();
	for (Class clazzParam : clazzParams)
	{
		sb.append(clazzParam.getName()).append(',');
	}
	if (clazzParams != null && clazzParams.length != 0)
		sb.deleteCharAt(sb.length() - 1);
	sb.append('}');
	System.out.println(sb);
}
System.out.println("------------begin method list------------------");
Method[] methods = clazzProxy1.getMethods();
for (Method method : methods)
{
	String name = method.getName();
	StringBuilder sb = new StringBuilder(name);
	sb.append('{');
	Class[] clazzParams = method.getParameterTypes();
	for (Class clazzParam : clazzParams)
	{
		sb.append(clazzParam.getName()).append(',');
	}
	if (clazzParams != null && clazzParams.length != 0)
		sb.deleteCharAt(sb.length() - 1);
	sb.append('}');
	System.out.println(sb);
}
System.out
		.println("------------begin create instance-------------------");
Constructor constructor = clazzProxy1
		.getConstructor(InvocationHandler.class);
class MyInvocationHandler1 implements InvocationHandler
{

	@Override
	// 參數的含義:proxy當前的代理對象,method代理對象的哪個方法,args方法所需參數
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable
	{
		// TODO Auto-generated method stub
		return null;
	}

}
Collection proxy1 = (Collection) constructor
		.newInstance(new MyInvocationHandler1());
System.out.println(proxy1);// null
proxy1.clear();// invoked
// proxy1.size();// java.lang.NullPointerException

Collection proxy2 = (Collection) constructor
		.newInstance(new InvocationHandler()
		{

			@Override
			public Object invoke(Object proxy, Method method,
					Object[] args) throws Throwable
			{
				// TODO Auto-generated method stub
				return null;
			}

		});

Collection proxy3 = (Collection) Proxy.newProxyInstance(
		Collection.class.getClassLoader(),
		new Class[] { Collection.class }, new InvocationHandler()
		{
			ArrayList target = new ArrayList();

			@Override
			public Object invoke(Object proxy, Method method,
					Object[] args) throws Throwable
			{
				long beginTime = System.currentTimeMillis();//這種取得時間的方式是硬編碼
				Object retVal = method.invoke(target, args);
				long endTime = System.currentTimeMillis();//一旦代碼被封裝就無法更改,後續會進行更改
				System.out.println(method.getName()
						+ " time-consuming: " + (endTime - beginTime));
				return retVal;
			}
		});
proxy3.add("111");// 若target對象寫在invoke方法內部就會發生java.lang.NullPointerException
proxy3.add("222");// 我們調用proxy的add方法,proxy內部調用invoke方法,獲得返回值retVal
System.out.println(proxy3.size());
// 疑問:proxy3內部target是一個ArrayList,但爲何打印其名結果仍是sun.proxy.$Proxy0呢?
// 答案:所有Proxy都繼承自Java根類Object,而Java只把hashCode、equals、toString三個方法交由Handler實現。
// 除這三個方法以外的Object中的其他方法都有自己的實現,因此輸出結果是sun.proxy.$Proxy0
System.out.println(proxy3.getClass().getName());

例子中,動態生成的類實現了Collection接口,生成的類有Collection接口中的所有方和一個如下接受InvocationHandler參數的構造方法。構造方法接受一個InvocationHandler對象,它的內部實現是怎樣的?

既然Proxy構造方法接收了一個InvocationHandler(實現該接口必須重寫invoke方法),Proxy內部必定有一個InvocationHandler的成員變量,而後,通過例子又看到我們在invoke接口中增加的額外操作會在每次執行Proxy的任何方法時被調用,自然可以想到它內部(通過reflect機制)調用了invoke方法。

爲了解決硬編碼的問題,以上例中proxy3爲例抽取其作爲一個方法:

//Advice.java
public interface Advice
{
	// Advice代表Java的系統建議,一般有5個方法,異常、方法內前後、方法外前後
	public void beforeMethod(Method method);
	public void afterMethod(Method method);
}
//MyAdvice.java
public class MyAdvice implements Advice
{
	long beginTime;
	long endTime;
	@Override
	public void beforeMethod(Method method)
	{
		beginTime = System.currentTimeMillis();
	}
	@Override
	public void afterMethod(Method method)
	{
		endTime = System.currentTimeMillis();
		System.out.println(method.getName() + " time-consuming: "
				+ (endTime - beginTime));
	}
}
//ProxyTest.java
public class ProxyTest
{
	public static void main(String[] args) throws Exception
	{
		Collection proxy3 = (Collection) getProxy(new ArrayList(),
				new MyAdvice());
	}
	private static Object getProxy(final Object target, final Advice advice)
	{
		Object proxy3 = Proxy.newProxyInstance(target.getClass()
				.getClassLoader(), target.getClass().getInterfaces(),
				new InvocationHandler()
				{
					@Override
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable
					{
						// long beginTime = System.currentTimeMillis();
						advice.beforeMethod(method);
						Object retVal = method.invoke(target, args);
						// long endTime = System.currentTimeMillis();
						// System.out.println(method.getName()+" time-consuming: " + (endTime - beginTime));
						advice.afterMethod(method);

						return retVal;
					}
				});
		return proxy3;
	}
}

6)Dynamic Proxy Summary

嵌套結構圖:Client==>Proxy.add()==>add方法內invocationHandler.invoke()==>invoke方法內(還可增加一些額外操作比如log()等)method.invoke()==>invoke方法內target.add()

創建一個被代理類(真實角色)及接口(抽象角色)

創建一個實現InvocationHandler接口的類(代理角色)必須實現invoke

通過Proxy.newProxyInstance(loader,interface,handler)創建一個代理

通過代理調用方法



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章