public在一個文件中只能有一個,能夠是一個類class或者一個接口interface >一旦建立一個引用,就但願它能與一個新的對象相關聯: String s = "hello"; String s = new String("hello"); s:遙控器(引用) 「hello」:電視機(對象) 數據存儲在: 寄存器:最快的存儲區,在處理器內部 堆棧:RAM(隨機訪問存儲器),棧底在上棧頂在下,指針向下移動->分配新的內存 堆:一種通用的內存池(RAM),用於存放全部Java對象,當 new 一個對象,代碼執行到的時候自動在堆裏進行存儲分配 常量存儲:一般直接存放在程序代碼內部 非RAM存儲:若是數據徹底存活與程序以外,那它能夠不受程序的任何控制,在程序沒有運行時也能夠存在。例:流對象,持久化對象 記住: new 將對象存儲在「堆」中 基本類型:new不是頗有效,不用new建立,非引用的「自動」變量,直接存儲「值」於棧內 Java 基本數據類型佔用存儲空間大小不受機器限制 全部數值類型都有正負號,沒有無符號數 byte 1 char 2 short 2 int 4 long 8 float 4 double 8 boolean 所佔的存儲空間沒有明確指定,僅定義爲可以取字面值 true 和 false 高精度數字: BigInteger 支持任意精度的整數 BigDecimal 支持任何精度的定點數 引用的空值:null Java 的垃圾回收器存在,使得咱們不須要在用完某一對象後進行手動銷燬(釋放內存) 類中定義的成員是基本類型會自動初始化爲 0 boolean值初始化false 方法中定義的基本變量要手動進行初始化,否則會報:變量沒有初始化的 錯誤 int x; System.out.println(x);//報錯:x沒有初始化 字符串的length()方法返回的是字符串中字符個數 定義包名(指定名字空間):使用反轉的域名,所有小寫 import用於導入類庫 執行 new 來建立對象時,數據才存儲空間才被分配,其方法才供外界調用 static設計的目的: 1、只想爲某特定域分配單一存儲空間,而不去考慮究竟要建立多少對象,甚至根本不建立對象 2、但願某個方法不與包含它的類的任何對象關聯在一塊兒,沒有建立對象也能夠使用此方法 記住:static是類成員 調用非類方法(沒有static修飾)必須使用此類的某一對象 javadoc的使用(註解): 第一行: //:文件路徑/文件名 @author @version @classname @description 用於方法文檔: @param 參數列表中的標識符 @return 返回值含義 @throws 在別處定義的異常的無歧義名字 @deprecated //表示方法過期,可能在後續版本中廢棄 最後一行: ///:~ Java 編碼風格:駝峯風格 類名所有單詞首字母大寫 方法、字段、對象引用名、變量名 第一個單詞首字母小寫,其餘單詞首字母大寫 操做符中一元減號起到轉變數據的符號的做用: -a 就是a的相反數 與、或、非 邏輯運算符 只適用於布爾值 不會像C同樣適用於數值 !1 是非法的 java中增長了無符號右移操做符 >>> 空位所有補0 數字的二進制表示形式稱爲:有符號的二進制補碼 int x = 1, y = 2, z = 3; 是合法的 char byte short 在使用算術運算符中數據類型被提高爲int 獲得的結果是int類型 foreach語句中迭代變量僅在for存活 多個foreach語句內部的迭代變量徹底能夠相同 for(int i: iArray){ System.out.println(i); } 在Java裏須要使用標籤的惟一理由就是由於循環嵌套存在,並且想從多層嵌套中break或continue break label; continue label;
構造器(constructor) 調用構造器是編譯器的責任 構造器沒有返回值,這與返回值爲void徹底不一樣的 java中,「初始化」和「建立」捆綁在一塊兒,二者不能分離 方法重載:在一個類內能夠定義多個含有不一樣參數類型列表的同名的方法來實現方法的重載 構造器是典型的例子 默認構造器在類內沒有明確指定一個構造器時編譯器會自動添加,若是本身提供了構造器編譯器就不會在自做多情 this 表示對「調用方法的那個對象」的引用 方法內部調用同一類的另外一個方法,沒必要使用 this 直接調用便可 同一類內成員相互使用時,沒必要加this 只有當要明確指出對當前對象的引用時才使用this this能夠用來調用一個構造器,理解同與super 在構造器中調用構造器 除了構造器,其餘地方都不能夠調用構造器 static 方法就是沒有 this 的方法 static內部不能直接調用非靜態方法,反之能夠 在代碼中出現大量的static方法,就該從新考慮本身的設計了 清理:終極處理和垃圾回收 1、對象可能不被垃圾回收 2、垃圾回收並不等於「析構」 3、垃圾回收只與內存有關 當垃圾回收時調用finalize()方法,finalize()方法不是進行普通的清理工做的合適場所 記住:不管是「垃圾回收」仍是「終結」,都不保證必定會發生。若是Java虛擬機(JVM)並無面臨內存耗盡的情形 它是不會浪費時間去執行垃圾回收以恢復內存的 垃圾回收器對於提升對象的建立速度具備明顯的效果 「自適應的、分代的、中止-複製、標記-清掃」式垃圾回收器 成員初始化: 類內成員變量初始化默認值爲 0 (引用是null) 記住:構造器進行的類成員變量初始化沒法阻止自動初始化的進行,它在構造器被調用以前發生 在類的內部,變量定義的前後順序決定了初始化的順序,但全部的初始化都優先與構造器內的初始化 靜態(static)初始化僅執行一次,靜態初始化只在class對象首次加載的時候進行一次 顯示的靜態初始化: public class Spoon{ static int i; //靜態初始化塊 static{ i = 1; } } 數組初始化 int[] intArray = {1, 2, 3, }; int[] intArray = new int[]{1, 2, 3, }; int[] intArray = new int[3];//默認初始化爲0 可變參數列表 public static void main(String[] args){} public void print(Object... args){} //其中Object能夠爲具體的類型 args仍舊是一個數組 調用print: print(1, 2, "hello"); print((Object[])new Integer[]{1, 2, 3}); 對一個類的實例直接打印:獲得 實例名@地址 枚舉類型 enum public enum Months{ JANUARY, FEBRUARY, } 能夠與switch完美契合 enum類有 toString()、ordinal()(獲取某個特定enmu常量的聲明順序)、static values()(存貯所有enum值的一個數組) for(Months month: Months.values){ System.out.println(month + "," + month.ordinal); }
包 -> 庫單元 |package| |... |-package| |... |.java java可運行程序是一組可打包並壓縮爲一個Java文檔文件(Jar包)的.class文件 機器上要配置環境變量 CLASSPATH java解析器能夠將報名 com.package1.xlc 中的 . 替換爲 / 來構成一個路徑名稱 對於jar包,路徑要包括jar包的具體名 : com/package1/xlc/myjar.jar 導入類內靜態方法: import static com.package1.xlc.MyClass.*; 各訪問修飾詞: 默認:包訪問權限 private:你沒法訪問 protected:繼承訪問權限 public:接口訪問權限 class Sundae{ private Sundae(){} static Sundae makeASundae(){ return new Sundae(); } } 這個類是沒法被繼承的,並且沒法經過 new Sundae();來建立一個對象 能夠使用 Sundae sundae = Sundae.makeASundae(); 類訪問權限只有兩種選擇: 接口訪問權限(public) 包訪問權限(默認、不加修飾) 應用實例: >沒法直接建立類的對象、不能用於繼承 class Class1{ private Class1(){} public static Class1 makeAClass1(){ return new Class1(); } } >沒法直接建立類的對象、只能建立一個實例 class Class2{ private Class2(){} private static Class2 class2 = new Class2();//定義一個Class2類型的私有靜態成員並初始化爲Class2的一個實例 public static Class2 returnAClass2(){ return class2; } } 記住:相同目錄下的全部不具備明確package聲明的文件,都被視做是該目錄下默認包的一部分
每個非基本類型的對象都有一個 toString() 方法,能夠在類內重寫它來知足本身的需求 java的OOP:當建立一個類時,老是在繼承,除非已經明確指出要從其餘類中繼承,不然就是在隱式地從java的標準根類 Object 進行繼承 在多個類中能夠都含有 main() 方法,當命令行 java 類名 此類的main會被調用 一個有用的規則:爲了繼承,通常的規則是將全部的數據成員都指定爲private,全部的方法都指定爲public super.fatherMethod() 調用父類的方法 子類的對象在 new 的時候 構建過程是從基類「向外」擴散的,基類在導出類構造器能夠訪問它以前,已經完成初始化 class Father{ Father(int i){ System.out.println("Father Constructor"); } } class Son extends Father{ Son(int i){ //沒有默認的基類構造器,或者想調用一個帶參數的基類構造器,必須用關鍵字 super 顯示的調用,要放在子類構造器的首行 super(i); System.out.println("Son Constructor"); } } 代理:將一個成員對象置於所要構造的類中 public class AObject{ private String name; private AObject aobject = new AObject(); public AObject(String name){ this.name = name; } public void aMethod(int i){ aobject.aMethod(i); } } 確保正確清理:首先,執行類的全部特定的清理工做,其順序同生成順序相反;而後調用基類的清理方法 is a 是繼承 has a 是組合 向上轉型:子類實例轉型爲父類實例 (較專用類型轉較通用類型) 經常使用的: 直接將數據和方法包裝進一個類,並使用該類的對象 運用組合技術使用現有類來開發新的類 繼承技術其實不太經常使用,到底該不應用繼承的一個問題:須要重新類向基類進行向上轉型麼? final: 命名,使用全大寫形式,多個單詞采用下劃線 一個永不改變的編譯時常量 一個在運行時被初始化的值,可是不但願它被改變 final修飾的基本類型值沒法改變,修飾一個引用則這個引用的值沒法改變,可是指向的對象可能會變 static final 只佔據一段不能改變的存儲空間(強調一個、不變) final變量必須在使用前進行初始化,此時出現空白final 一個類中的final域能夠作到根據對象而有所不一樣,卻又保持其恆定不變的特性: class Aaa{ private final int i; //每次實例化的時候均可以指定i的值,一旦指定後就沒法在修改 Aaa(int i){ this.i = i; } } final參數: void f(final int i){ i++;//錯誤 i不能夠被改變 } void g(final int i){ System.out.println(i);//能夠取值 } final方法:確保在繼承中使用方法行爲保持不變,而且不會被覆蓋 繼承的時候,「覆蓋」只有在某方法是基類的 接口 的一部分時纔會出現,private不是基類的接口的一部分 基類中有一個private方法aMethod,導出類繼承與此基類而且也有一個aMethod的方法,這裏只是新建立一個方法,不存在Override問題 private 修飾的做用:隱藏 finale修飾一個類:目的在於對該類的設計永不須要作任何改動,或者出於安全的考慮,不但願它有子類(不能被繼承) 繼承與初始化: 在一個類上運行Java,第一件事就是試圖訪問此類的 main() 方法 加載器開始啓動並找出此類的編譯代碼(在名爲 .class文件內) 在對其加載的過程當中,碰見 extends 關鍵字,找出其基類,繼續加載基類,基類還有基類以此類推 接下來,根基類中的static開始初始化,而後是其導出類的static,以此類推 如今對象能夠被建立了, 首先對象中全部基本類型初始化爲0,引用初始化爲null 而後基類的構造器被調用,導出類構造器和基類構造器同樣,以相同的順序經歷相同的過程 構造器完成以後,實例變量按其次序被初始化,最後構造器的其他部分被執行
java中除了static方法和final方法(static方法屬於final方法)是前期綁定以外,其餘全部方法都是後期綁定 一種工廠: public class Factory{ private Random rand = new Random(10); public Father next(){ switch(rand.nextInt(3)){ default: case 0: return new Son1(); case 1: return new Son2(); case 3: return new Son3(); } } } 多態的體現: Father的三個導出類Son一、Son二、Son3都重寫了Father的 aMethod()方法 Father[] fathers = new Father[]{new Son1(), new Son2(), new Son3(),}; for(Father father: fathers){ father.aMathod(); } 因爲動態綁定,能夠成功的調用各種實例本身的aMethod()方法 不想讓導出類覆蓋的方法,指定爲private: public class PrivateOverride{ private void f(){ System.out.println("private f()"); } public static void main(String[] args){ PrivateOverride po = new Derived(); po.f(); } } public class Derived extends PrivateOverride{ public void f(){ System.out.println("public f()"); } } 輸出結果是:private f() //基類中的f()方法沒法被覆蓋,向上轉型後調用po的f()方法是基類的 基類中已經有的成員,導出類再次定義則不會覆蓋,它們各自有本身的存儲空間導出類中要得到基類對象的屬性應顯示的使用 super.value 構造器是static方法,static的聲明是隱式的 初始化順序: 0、在其餘任何事物發生以前,將分配給對象的存儲空間初始化成二進制的 0 1、調用基類的構造器,反覆遞歸,首先構造這種層次結構的根,而後是下一層導出類,直到最底層的導出類 2、按聲明順序調用成員的初始化方法 3、調用導出類構造器的主體 若是要手動進行清理工做,則銷燬順序與初始化順序相反,導出類先進行清理而後纔是基類,覺得導出類的清理工做可能須要基類中的某些方法 編寫構造器的一條有效準則:用盡量簡單的方法使對象進入正常狀態;若是能夠的話,避免調用其餘方法 一條通用準則:用繼承表達行爲間的差別,用字段表達狀態上的變化 class Actor{ public void act(){} } //經過繼承表達 act 的不一樣 class HappyActor extends Actor{ public void act(){ System.out.println("HappyActor"); } } class SadActor extends Actor{ public void act(){ System.out.println("SadActor"); } } //使用組合使本身狀態發生變化 class Stage{ private Actor actor = new HappyActor(); //狀態發生變化 public void change(){ actor = new SadActor(); } public void performPlay(){ actor.act(); } } 抽象類: 關鍵字 abstract 抽象方法:僅聲明,沒有方法體 public abstract void abMethod(); 包含抽象方法的類叫作抽象類: abstract class AbClass{ private i; public abstract void abMethod(); public String method(){ return "hello world"; } } 若是從一個抽象類繼承,並想建立該新類的對象,那麼就必須爲基類中 全部 抽象方法提供方法定義,若是不這樣則導出類也是抽象類
關鍵詞 interface 能夠認爲是一種特殊的類,可是其支持多引用(implements) 像類同樣,能夠在定義時 interface前加public(可是僅限在與其同名的文件內),不加public則此接口只具備報訪問權限 接口內也能夠包含域,可是這些域隱式的是static和final的 能夠顯示的聲明接口內的方法爲public 若是不這樣,它們也是public的 interface Interface{ int value = 1;//static && final void method1(); String returnString(Stirng str); } 一個類能夠實現多接口:必需要實現兩個接口的全部方法,若是不則應將Class定義爲abstract class Class implements Interface1, Interface2{ } 向上轉型時,只可以使用轉型的那個接口內還有的方法 接口可繼承: 實現擴展接口 interface Interface2 implements Interface1, Interface{ void method(); } 適配: interface Interface{ Object method(Object value);//此方法接收任何類型,並支持返回任何類型 } 接口能夠嵌套在一個類或接口中 設計中,恰當的原則應該是優先選擇類而不是接口,從類開始,若是接口的必需性變得很是明確,那麼就進行 重構 工廠型設計的一個例子: interface Car{ void setname(String str); } interface CarFactory{ Car getCar(); } class Car1 implements Car{ private String name; public void setname(String name){ this.name = name; } } class Car2 implements Car{ private String name; public void setname(String name) { this.name = name; } } class Car1Factory implements CarFactory{ public Car getCar() { return (new Car1()); } } class Car2Factory implements CarFactory{ public Car getCar() { return (new Car2()); } }
成員內部類: 當生成一個內部類的對象時,此對象與製造它的外圍對象之間有一種聯繫,它不須要任何特殊條件就能夠訪問其外圍對象的全部成員,如同它擁有外部的成員同樣 在內部類中生成對外部類對象的引用以及經過外部類對象建立內部類的對象: public class Outer{ void f(){ System.out.println("outer.f()"); } public class Inner{ public Outer outer(){ //在內部類中生成對外部類對象的引用 return Outer.this; } } public static void main(String[] args){ //經過外部類對象建立內部類的對象 Outer.Inner outerInner = new Outer().new Inner(); outerInner.outer.f(); } } 記住:成員內部類對象的建立依賴與外部類的 對象 ,沒有外部類 對象 以前是不可能建立內部類 對象 的 成員內部類是private的,相同外部類內的非private成員內部類是沒法訪問的,只有外部類有其訪問權限 局部內部類: 定義在某方法內部的類 / 定義在方法內的某做用域內 有效範圍限制在這個域內 匿名內部類: 建立一個而繼承於基類的匿名類的對象(涵蓋的信息:它是一個子類,建立其對象時進行向上轉型) 幫助理解:看起來彷佛是你正要建立一個對象,可是而後你卻說:「等一等,我想在這裏插入一個類的定義」 interface Interface{} public class Class{ public Interface itf(){ return new Interface(){ private int i = 0; public int value(){ return i; } };//分號用來標記表達式的結束 } } ? 若是定義一個匿名內部類,並但願它使用一個其外部定義的對象,編譯器會要求其參數引用是 final 的(實驗中並無) 匿名內部類沒有構造器:由於它沒有名字! <<經過實例初始化可達到爲匿名內部類建立一個構造器的效果>> 匿名內部類能夠擴展接口和類,可是不能二者兼容,對於接口也只能實現一個 嵌套類: 聲明爲static的內部類是嵌套類,這樣的內部類對象與其外部類對象之間沒有聯繫 嵌套類意味着: 要建立嵌套類的對象,並不須要其外圍類的對象 不能從嵌套類的對象中訪問非靜態類的外圍類的對象 普通內部類不能有static數據和static字段,也不能包含嵌套類,可是嵌套類能夠包含全部這些東西 接口內部能夠定義類:默認是 public 和 static 的 不懂:閉包 回調 一個內部類無論嵌套多少層,均可以透明地訪問全部它嵌套入的外圍類的全部成員: class MNA{ private void f(){} class A{ private void g(){} public class B{ void h(){ g(); f(); } } } } 內部類的繼承: class WithInner{ class Inner{} } public class InheritInner extends WithInner.Inner{ //當要生成一個構造器時,必須使用這樣的語法 InheritInner(WithInner wi){ wi.super(); //!!!!!!!!!!! } public static void main(String... args){ WithInner wi = new WithInner(); InheritInner ii = new InheritInner(wi); } } 內部類能夠被覆蓋麼: 當繼承了某個外圍類的時候,內部類並無發生什麼特別神奇的變化,這兩個內部類是徹底獨立的兩個實體,各自在本身的命名空間內 外圍類繼承某一類後,其內部類能夠明確指定繼承於被繼承的類內的一個類,此時能夠覆蓋內部類對方法 局部內部類與匿名內部類的選擇: 若是須要一個已命名的構造器,或者須要重載構造器,就須要使用局部內部類,此時匿名內部類是沒法實現的(沒有名字),它只能用於實例初始化 還有就是一個局部內部類能夠建立多個其對象,匿名內部類只會建立一個 內部類標識符: 每個類都會產生一個.class文件,其中包含了如何建立該類型的對象的所有信息 內部類class文件命名:外圍類名字+$+內部類名字.class 若是是匿名內部類,則產生一個數字做爲其標識符
Java容器類類庫的用途是「保存對象」,劃分的兩個概念: Collection Map 泛型:一個例子 List<String> list = new ArrayList<String>();//指定list的存儲類型爲String,實例對象後向上轉型爲List接口,此時list可用的方法都是List接口中含有的 添加一組數據: Arrays.asList() 接受一個數組,或一個用逗號分割的元素列表,轉化爲List對象,可是其底層是數組,不能調整大小 Arrays.asList(1, 2, 3, 4); Integer[] intArray = {1, 2, 3, 4}; Arrays.asList(intArray); Collections.addAll() 接受一個Collection對象,以及一個數組或是用逗號分割的列表 Collection<Integer> collection = new ArrayList<Integer>(Arrays.asList(1, 2, 3));//Collection的構造器接受一個Collection用於本身的初始化 Collections.addAll(collection, intArray); Collections.addAll(collection, 1, 2, 3); collection.addAll(Arrays.asList(intArray));//此方法只能接受一個Collection對象做爲參數 建立一個空的Collection,而後使用對象的addAll()方法運行速度快 數組轉List的時候:Arrays.<MyType>asList(new MyType(1), new MyType(2)); 容器的打印很規範: List:[hello, world] Set:[hello, world] Map:{nihao=hello, shijie=world} Set: HashSet:元素存儲順序沒有意義,很快的查找速度 TreeSet:按照比較結果的升序保存對象 LinkedHashSet:按照被添加的順序保存對象 Map: HashMap:保存順序不是插入順序,基於key的某種算法存儲,很快的查找速度 TreeMap:按照比較結果的升序保存鍵(key) LinkedHashMap:按照插入順序保存鍵,並保留了HashMap的查詢速度 List: ArrayList 易於隨機訪問元素,可是在LIst的中間插入和移除元素時較慢 LinkedList 在List中間進行插入和移除代價會較低,提供了優化的順序訪問,可是隨機訪問會相對比較慢 一堆方法,用時查閱--224頁 二者都是按照插入順序保存元素,LinkedList包含更多的操做、 迭代器:Iterator 只能向前移動 使用iterator()方法返此容器的迭代器:Iterator<MyType> it = myArrayList.iterator(); 經過hasNext()方法檢查是否還有元素:it.hasNext() 經過next取出當前值,並將指針向後移動:it.next() 經過remove刪除容器中元素: it.next(); it.remove(); 迭代器統一了對容器的訪問方式: public void display(Iterator<Object> it){ if(it.hasNext()){ System.out.println(it.next());//取出當前值後指針自動後移 } } ListIterator: 只適用於各類List類的訪問,能夠雙向移動 建立List的專屬迭代器:ListIterator<MyType> it = myArrayList.listIterator();//從0開始 ListIterator<MyType> it = myArrayList.listIterator(3);//指定從index=3開始 next仍然適用於取出當前值,並指針前移:it.next() 當前指針位置:it.nextIndex() 當前指針前一個位置的索引:it.previousIndex() 使用set修改當前指針位置的元素:it.set(new MyType()) LinkedList: 它添加了做用於棧、隊列和雙端隊列的方法 取出第一個元素但不移除: getFirst();//List爲空拋出異常 element();//與getFirst徹底同樣 peek();//List爲空返回null 取出第一個元素並移除: removeFirst();//List爲空拋出異常 remove();//與removeFirst徹底同樣 poll();//List爲空返回null 添加一個元素: addFirst();//在表頭添加 add();//在表尾添加 addLast();//表尾添加 offer();//表尾添加 移除並返回最後一個元素: removeLast();//List爲空拋出異常? 棧(Stack):後進先出 採用LinkedList構建棧 public class Stack<T>{//重點:使用泛型,<T>告訴編譯器這是一個參數化類型 構建此類的對象時能夠採用泛型指定類型 private LinkedList<T> storage = new LinkedList<T>(); //增 public void push(T v){ storage.addFirst(v); } //查 public T peek(){ storage.getFirst(); } //刪 public T pop(){ storage.removeFirst(); } //判空 public boolean empty(){ return storage.empty(); } //轉串 public String toString(){ return storage.toString(); } } Set: HashSet Set最常被使用的是測試歸屬性:contains()方法 查找是Set中最重要的操做就是查找:基於對象的值 實際上Set就是Collection,只是行爲不一樣 HashSet內存儲的元素沒有順序:出於速度考慮使用了散列 TreeSet將元素存儲在紅-黑樹數據結構中,而HashSet使用的是散列函數,LinkedHashSet也使用了散列,可是看起來它使用了鏈表來維護元素的插入順序 TreeSet能夠根據對象的值在其插入的時候進行排序 Map: <key, value> 查看是否包含某個key或value:containsKey() containsValue() Map的value能夠其餘容器:例如Map<ClassName, List<MyType>> Map能夠返回他的鍵的Set:myMap.keySet() myMap.values()//返回值類型是Collection 以前沒見過:List<? extends MyType> //此處的泛型是全部繼承與MyType的類 Queue: 隊列常被看成一種可靠的將對象從程序的某一個區域傳輸到另一個區域的途徑 彩蛋:char的修飾類 Character PriorityQueue: 默認最小值擁有最高的優先級 能夠在構造隊列的時候調用含參構造器指定Comparator設定元素優先級 PriorityQueue<Integer> priorityQueue = new PriorityQueue<Integer>(3, Collections.reverseOrder()) Collection 和 Iterator: 實現Collection能夠輕鬆使用foreach進行遍歷 生成Iterator是將隊列與消費隊列的方法鏈接在一塊兒的耦合度最小的方式,並與實現Collection相比,它在序列類上施加的約束也少的多 ?? Iterable接口: 該接口包含一個可以產生Iterator的iterator()方法(匿名內部類實現) 而且Iterable接口被foreach用來在序列中移動,任何實現了Iterable的類,均可以將他用於foreach語句中 大量的類都是Iterable類型,主要包括有Collection類(除Map) foreach能夠用於數組,可是數組不能自動包裝成Iterable:直接做爲Iterable類型傳入會報異常 <T> void test(Iterable<T> ib){//指定泛型的方法 要在方法返回以前加 <T> for(T t: ib){ System.out.println(t); } } String[] strings = {"A", "B"}; test(strings);//!!!!!!!!!!失敗 new 出來的容器會從新給其要存儲的元素分配內存,若是將一個List做爲參數傳入則就是生成了一個副本,對其操做不會影響以前的內容 要進行大量隨機訪問就使用ArrayList,若是要常常從表中間插入或刪除元素則使用LinkedList 各類Queue和Stack的行爲由LinkedList提供支持 各類不一樣的容器類的方法:246頁
當拋出異常後,有幾件事會隨之發生。首先,同Java中其餘對象的建立同樣,將使用new在堆上建立異常對象。而後,當前的執行路徑(它不能繼續下去了) 被終止,而且從當前環境中彈出對異常對象的引用。此時,異常處理機制接管程序,並開始尋找一個恰當的地方來繼續執行程序。 這個恰當的地方就是異常處理程序,它的任務就是將程序從錯誤狀態中恢復,以使程序能要麼換一種方式運行,要麼繼續運行下去 拋出一個異常: throw new NullPointerException(); 全部標準異常類都有兩個構造器:一個是默認構造器,一個是接受字符串做爲參數來把相關信息放入異常對象的構造器 異常類型的根類:Throwable 捕獲異常: 監控區域:一段可能產生異常的代碼,而且後面跟着處理這些異常的代碼 若是在方法內部拋出了異常(或者在方法內部調用的其餘方法拋出了異常),這個方法將在拋出異常的過程當中結束 try{}catch(Type1 id1){} 終止:出現異常直接中止執行 恢復:出現異常,嘗試修復在繼續執行 一種方式是try放在while循環裏 自定義異常繼承於與其意思相近的異常(很難找),能夠繼承於Exception 輸出錯誤信息到控制檯能夠採用System.err.println();將錯誤發送給標準錯誤流 自定義異常並建立本身的構造器: class MyException extends Exception{//必定要繼承於基類異常 如Exception MyException(){} MyException(String msg){ super(msg);//直接調用父類構造器 } } 在捕獲異常後打印棧軌跡: try{ }catch(Exception e){ //能夠指定將信息發送的輸出流,好比System.out標準輸出流 默認是發送到標準錯誤流System.err e.printStackTrace(); } ???使用java.util.logging工具將輸出記錄到日誌 Throwable類中的getMessage()方法能夠得到異常的詳細信息,全部異常也都會有此方法 捕獲全部異常: 使用異常類型的基類Exception catch(Exception e){ System.out.println("Caught an exception"); } 能夠調用其從Throwable繼承來的: //獲取詳細信息 String getMessage(); String getLocalizedMessage(); String toString(); //打印棧軌跡 void printStackTrace(); void printStackTrace(PrintStream); void printStackTrace(java.io.PrintStream); Throwable fillInStackTrace(); 用於在Throwable對象的內部記錄棧幀的當前狀態 ---->更新異常 Object類中的 getClass() 能夠返回一個表示此對象類型的對象: 再 getName() getSimpleName() printStackTrace()方法所提供的信息能夠經過getStackTrace()方法直接訪問: try{ throw new Exception(); }catch(Exception e){ for(StackTraceElement ste: e.getStackTrace()){ //實際上還能夠打印整個StackTraceElement 包含其餘附件的信息 System.out.println(ste.getMethodName()); } } 一個被拋出的異常被捕獲後,printStackTrace()方法顯示的還是原來異常拋出點的 調用棧 信息,而並不是從新拋出點的信息 可是能夠在捕獲後調用異常的fillInStackTrace()方法,會將當前的調用棧信息填入原來那個異常對象,此行代碼成了異常的新發地 在捕獲到一個異常後能夠加以包裝再次拋出或處理,或從新拋出一個新的異常,而前一個異常對象因爲是new在堆上建立的,垃圾回收器會自動把它們清理掉 異常鏈: 在捕獲一個異常後拋出另外一個異常,而且但願把原始異常的信息保存下來 Trowable的子類在構造器中均可以接受一個 cause 對象做爲參數,cause表示原始異常 只有三個基本異常提供了帶 cause 參數的構造器:Error、Exception、RuntimeException 其餘的異常須要使用 initCause()方法而不是構造器: MyException myException = new MyException(); myException.initCause(new NullPointerException());//建立新異常並將舊異常包裝進來 catch(RuntimeException e){ throw new Exception(e);//自帶接受cause的構造器 } Throwable類被用來表示任何能夠做爲異常被拋出的類: Error:表示編譯時和系統錯誤 Exception:能夠被拋出的基本類型 特例異常:RuntimeException 它們會自動被Java虛擬機拋出,因此沒必要在異常說明中把它們列出來: void test1() extends Exception{//注意此處必定要有異常說明 throw new Exception(); } void test2(){//此處不須要異常說明,其輸出被報告給System.err throw new NullPointerException(); } 記住:只能在代碼中忽略RuntimeException類型的異常,其餘類型異常的處理都是由編譯器強制實施的,緣由:RuntimeException表明的是編程錯誤 使用finally進行清理:無論try內的代碼拋不拋出異常,finally塊的代碼都會執行 記住:finally子句不管如何都會被執行 何時使用finally:當要把除內存以外的資源恢復到它們的初始狀態時,就會使用finally,例如:關閉已經打開的文件或網絡鏈接等等 在return中使用finally: public class Return{ public static void f(int i){ try{ if(i == 1){return;} }finally{//此處的代碼仍然會被執行 System.out.println("print in finally"); } } public static void main(String[] args){ f(1);//此時仍然會輸出 print in finally } } 異常丟失: try{ throw new Exception("an exception"); }finally{ //拋出的異常沒有被處理和顯示,就好像沒有出現異常同樣 return; } 異常的限制:須要記住的幾點 1、基類方法聲明異常,派生類重寫此方法能夠不聲明異常或只聲明基類的異常 2、基類方法不聲明異常,派生類重寫的方法不可聲明異常 3、接口不能添加異常對於基類中已經存在的方法:接口與基類含有相同方法 4、構造器能夠聲明新的異常,但派生類的構造器必定要聲明基類構造器的異常 5、處理某一類對象,編譯器就會強制要求捕獲這個類所拋出的異常 6、對於某派生類將其向上轉型,編譯器就會要求捕獲基類的異常 7、派生類中的方法能夠聲明基類方法聲明的異常的派生異常 8、派生類構造器不能捕獲基類構造器拋出的異常 總結:基類的能力強於接口,因爲存在向上轉型,基類聲明的異常派生類能夠不聲明,但不能增長聲明新的異常(包括接口中的) 在建立須要清理的對象以後,當即進入try-finally語句塊 try{ //須要執行的任務 }finally{ //後續的清理工做 } 匹配異常:catch會按照代碼的書寫順序進行匹配捕獲,捕獲到當即處理後續再也不繼續查找 捕獲的某個異常能夠經過 getCause()方法得到異常信息而且能夠直接做爲異常被再次拋出: catch(RuntimeException re){ throw re.getCause(); } 技巧:在爲了簡化程序時,能夠將異常包裝成RuntimeException,由於此異常是不須要異常說明的
String對象是不可變的,每個看起來會修改String值的方法,實際上都是建立了一個全新的String對象來包含修改後的字符串內容 每當把String對象做爲方法的參數被傳入時,都會複製一份引用 對於方法而言,參數是爲該方法提供信息的,而並非想讓該方法改變本身的 String對象經過 += 拼接的本質: 編譯器自動引用java.lang.StringBuilder類,經過現有的String對象值生成Stringbuilder對象,而後在調用append()方法將字符串拼接,再調用toString()方法獲得新的String對象 無心識的遞歸: public class InfiniteRecursion{ public String toString(){ return "InfiniteRecursion address:" + this + "\n"; //此處會將嘗試將this轉爲String,那就要調用toString方法造成了遞歸調用 } public static void main(String[] args){ List<InfiniteRecursion> v = new ArrayList<InfiniteRecursion>(); for(int i = 0; i < 10; ++i){ v.add(new InfiniteRecursion); } System.out.println(v); } } 正確方式:調用Object.toString()方法:使用 super.toString()//由於它確定是一個Object類 String類的方法都會返回一個新的String對象,若是內容沒有發生改變則只是返回指向原對象的引用 格式化輸出://參數的格式書寫與C徹底同樣,包括寬度、左對齊仍是右對齊以及精度等等 System.out.format(); System.out.printf(); Formatter類://java.util.Formatter public class PrintFormat{ private Formatter f = new Formatter(System.out);//參數告訴它最終結果向哪輸出,經常使用PrintStream、OutputStream、File public void printF(){ f.format("%-15s %5s %10.2f\n", "Tom", "", 3.14);//此時調用printF方法就會將格式化字符串輸出在System.out } } String.format()方法:String類的static方法,接受與Formatter.format()一樣的參數,返回一個格式化後的String對象 Scanner類:控制太輸入 Scanner stdin = new Scanner(System.in);//接受任何類型的輸入對象 示例具備全部類型(除char)對應的next方法,以及hasNext方法用來判斷下一個輸入分詞是否是所需的類型 Scanner有一個假設,在輸入結束時會拋出IOException,因此Scanner會把IOException吞掉,能夠經過ioException()方法找到最近發生異常(這應該不會用到) Scanner能夠經過useDelimiter()方法指定定界符(就是用啥切分),默認是空白字符: Scanner scanner = new Scanner("12, 13, 14, 15, 16"); scanner.useDelimiter("\\s*,\\s*");//指定逗號(包括逗號先後任意的空白字符)做爲定界符
Java如何讓咱們在運行時識別對象和類的信息? 一、傳統的RTTI,它假定咱們在編譯時就已經知道了全部的類型(Run-Time Type Information) 在運行時,識別一個對象的類型 2、「反射」機制,它容許咱們在運行時發現和使用類的信息 「多態」是面向對象編程的基本目標 Class對象: 每一個類都有一個Class對象(被保存在.class文件) 類型信息如何表示:這項工做是由稱爲Class對象的[特殊對象]完成的,它包含了與類有關的信息 事實上,Class對象就是用來建立類的全部的「常規」對象的 !!若是很差理解能夠類比Object類,也就是會有一個Class類它就存在那裏,當建立一個類時就會獲得此類的Class對象並保存在相應的.class文件中 重點:全部的類都是在對其第一次使用時,動態加載到JVM的。 當程序建立第一個對類的靜態成員的引用時,就會加載這個類。 構造器也是類的靜態方法,即便在構造器以前並無使用static關鍵字 使用new操做符建立類的新對象也會被看成對類的靜態成員的引用(含義:new時候也會加載類到JVM) Java程序在它開始運行以前並不是被徹底加載,其各個部分是在必須時才加載的 類加載器首先檢查這個類的Class對象是否已經加載,若是沒有,默認的類加載器就會根據類名查找.class文件; 在這個類的字節碼被加載時,它們會接受驗證,以確保其沒有被破壞,而且不包含不良Java代碼 一旦某個類的Class對象被載入內存,它就被用來建立這個類的全部對象 靜態塊: class Test{ static {System.out.println("hello");}//沒有方法體 } Class類的衆多方法: forName("package.ClassName");//取得Class對象的引用,傳進去的是一個具體的類名,獲得一個其相應的Class對象的引用 若是已經有ClassName類的對象,還能夠使用Object的方法getClass()獲取一個Class對象的引用 getName();//產生全限定的類名(包括包名) getSimpleName();//產生不包含包名的類名 getCanonical();//產生全限定的類名(包括包名) isInterface();//判斷這個Class對象是否表示某個接口,接口.isInterface();纔會返回true getInterface();//返回Class對象,它們表示某個Class對象中所包含的接口 getSuperclass();//查找某Class對象的直接基類,返回的是基類的Class對象 newInstance();//不知道確切的類型,仍舊建立那種類型的實例對象,能夠用一個Object的引用來接收 使用此方法來建立實例的類必須帶有[默認的構造器] isInstance();//接受一個實例對象,判斷其是不是此類的一個實例 類字面常量: 另外一種方法來生成對Class對象的引用 能夠應用於普通的類,也能夠應用於接口、數組以及基本數據類型 基本數據類型的包裝類,還存在一個TYPE字段(一個引用),指向基本數據類型的Class對象:char.class(建議使用) <=> Character.TYPE 爲了使用類要作的準備工做: 1、加載 由類加載器執行 2、連接 驗證類中大的字節碼,爲靜態域分配存儲空間,若是必須則將解析這個類建立的對其餘類的全部引用 3、初始化 若是該類具備超類,則對其初始化,執行靜態初始化器和靜態初始化塊 僅使用.class語法來得到對類的引用不會引起初始化 Class.forName()會當即進行初始化 泛化的Class引用: 不指定類型,則理解相似於Object Class<Integer> intClass = Integer.class; //!intClass = Double.class; 錯誤 通配符 ---> ?:表示「任何事物」 Class<? extends Number> bounded = int.class; //指定類型爲全部繼承與Number類的類 //還有這種:Class<? super sonClass> up = son.class.getSuperclass(); 轉型語法: class Building{} class House extends Building{} public class ClassCasts{ public static void main(String[] args){ Building b = new House(); Class<House> houseType = House.class; House h = houseType.cast(b); // 等價於 h = (House)b; } } instanceof: if(x instanceof Dog){//判斷對象x是否爲Dog類的一個實例 ((Dog)x).bark(); } 「曲線救國」:簡介調用初始化塊 private static void init(){ System.out.println(); } static{init();} instanceof與Class的等價性: instanceof和isInstance()生成的結果徹底同樣,它們保持了類型的概念,指:你是這個類麼,或者你是這個類的派生類麼 比較用 equals() 和 == ,它們比較的就是實際的Class對象,不會考慮繼承->要麼是確切的類型,要麼不是 反射: java.lang.reflect類庫:Method、Constructor類等 RTTI與反射的真正區別在於,對於RTTI來講,編譯器在編譯時打開和檢查.class文件;對於反射,.class文件在編譯時是不可得到的,是在運行時打開和檢查.class文件 類方法提取器: 能夠提取出某個類的方法以及其從基類繼承來的方法 getMethods();//提取方法 getConstructors();//提取構造器 ???動態代理 337頁 空對象: public interface Null{}//建立一個標記接口 class Person{ public final String first; public final String last; public final String address; public Person(String first, String last, String address){ this.first = first; this.last = last; this.address = address; } public String toString(){ return "Person: " + first + " " + last + " " + address; } //***********內部的空類用於生成空對象*********** public static class NullPerson extends Person implements Null{ private NullPerson{ super("None", "None", "None"); } public String toString[(){ return "NullPerson"; } } //空對象 public static final Person NULL = new NullPerson(); } 經過反射能夠到達並調用那些非公共訪問權限的方法以及訪問私有的域: 例子 class WithPrivateFinalField{ private int i = 1; private final String s = "I am totally safe";//在使用反射嘗試修改其值時是安全的,但實際上不會發生任何修改 private String s2 = "Am I safe?"; } public class ModifyingPrivateFields{ public static void mian(String[] args){ WithPrivateFinalField pf = new WithPrivateFinalField(); Field f = pf.getClass().getDeclaredField("i"); //獲取私有的域 f.setAccessible(true); //設定此域能夠被到達 System.out.println("f.getInt(pf): " + f.getInt(pf));//會將i的值打印 f.setInt(pf, 47); //修改i的值爲47 ...等等 } }
泛型的主要目的之一就是用來指定 容器 要持有什麼類型的對象,並且由編譯器來保證類型的正確性 Java泛型的核心概念:告訴編譯器想使用什麼類型,而後編譯器幫你處理一切細節 元組類庫(tuple): 將多個返回值(可不一樣類型)包裝成一個元組,做爲return的值 public class Tuple<A, B>{ public final A a;//這裏雖然是public,可是因爲有final的修飾,仍能夠保證其被任意讀取而不能被修改 public final B b; public Tuple(A a, B b){ this.a = a; this.b = b; } } //還能夠在其基礎上建立更多元素的元組繼承與這個基類 一個堆棧類: public class LinkedStack<T> { //Node類也有本身的類型 public static class Node<U>{ U item; Node<U> next; Node(){ item = null; next = null; } Node(U item, Node<U> next){ this.item = item; this.next = next; } boolean end() { return (item == null && next == null); } } private Node<T> top = new Node<T>(); public void push(T item) { top = new Node<T>(item, top); } public T pop() { T result = top.item; if(!top.end()) { top = top.next; } return result; } public static void main(String[] args) { LinkedStack<String> lss = new LinkedStack<String>(); for(String str: "hello world !".split(" ")) { lss.push(str); } String s; while((s = lss.pop()) != null) { System.out.println(s); } } } 泛型接口: public interface Generator<T>{} Java泛型的侷限:基本類型沒法做爲類型參數 泛型方法:將泛型參數列表置於返回值前 public class GenericMethods{ //泛型方法,指定參數類型,啥都行 public <T> void f(T x){ System.out.println(x.getClass.getName()); } public static void main(String[] args){ GenericMethods gm = new GenericMethods(); gm.f(""); gm.f(1); gm.f(1.0); gm.f('c'); gm.f(gm); } } 泛型方法不受其類是不是泛型類的影響,單獨存在 使用泛型類時,必須在建立對象的時候指定類型參數的值,而使用泛型方法的時候,一般沒必要指明參數類型,由於編譯器會爲咱們找出具體的類型 槓桿利用類型參數推斷 public class New{ public static <K, V> Map<K, V> map(){ return new HashMap<K, V>(); } public static void main(String[] args){ Map<String, List<String>> sls = New.map();//類型參數推斷 } } 類型推斷只對賦值操做有效,其餘時候並不起做用:將方法調用結果看成參數傳遞給另外一個方法則不會進行推斷 public class LimitsOfInference{ static void f(Map<String, List<String>> map){} public static void main(String[] args){ f(New.map);//!!!!出錯,不會進行自動推斷 } } 顯示的類型說明(不多使用): 上例中的調用 f(New.<String, List<String>>map());//在點和方法名之間添加 擦除的神祕之處: public class Test{ public static void main(String[] args){ Class c1 = new ArrayList<String>().getClass(); Class c2 = new ArrayList<Integer>().getClass(); System.out.println(c1 == c2); } }//輸出:true 在泛型代碼內部,沒法得到任何有關泛型參數類型的信息 Java泛型使用擦除來實現,當使用泛型時任何具體的類型信息都被擦除,惟一知道的就是在使用一個對象,List<String>和List<Integer>在運行時其實是相同類型 指定泛型類邊界: class Test<T extends MyClass>{//已知Myclass類含有f()方法 此處指定邊界 private T obj; public Test(T x){ obj = x; } public void test(){ obj.f(); }//在這裏才能夠放心的調用f()方法 } 重點:即便擦除在方法或類內部移除了有關實際類型的信息,!!!編譯器仍舊能夠確保在方法或類中使用的類型的內部一致性 public class GenericHolder<T>{ private T obj; public void set(T obj){ this.obj = obj; } public T get(){ return obj; } public static void main(String[] args){ GenericHolder<String> holder = new GenericHolder<String>(); holder.set("hello world");//編譯器執行傳入參數檢查 String s = holder.get(); //字節碼中含有對get()返回的值進行轉型 } } 在泛型中的全部動做都發生在邊界處---對傳遞進來的值進行額外的 編譯器檢查,並插入對傳遞出去的值的轉型 擦除的補償----引入類標籤: class ClassAsFactory<T>{ T x; public ClassAsFactory(Class<T> kind){//指定類標籤 try{ x = kind.newInstance(); }catch(Exception e){ throw new RuntimeException(e); } } } 泛型數組: array = (T[])new Object[size];//泛型數組的強制類型轉換 Warning:unchecked cast 補償仍是使用類型標籤: Class<T> 邊界:重用 extends 關鍵字 class Test<T extends Class & Interface1 & Interface2>{}//類在前,接口在後,類只能有一個,接口能夠有不少 能夠在繼承的每一個層次添加邊界限制 interface HasColor{} class HolsItem<T>{ T item; HolsItem(){ this.item = item; } T getItem(){ return item; } } class Colored<T extends HasColor> extends HoldItem<T>{//繼承了HostItem的持有對象的能力,並要求其參數與HasColor一致 Colored(T item){ super(item); } } 通配符: //條件 class Fruit{} class Apple extends Fruit{} class Jonathan extends Apple{} class Orange extends Fruit{} //例子 Fruit[] fruit = new Apple[10]; fruit[0] = new Apple();//沒毛病 fruit[1] = new Jonathan();//繼承於Apple,沒毛病 fruit[2] = new Fruit();//ArrayStoreException fruit[3] = new Orange();//ArrayStoreException 解釋:編譯器認爲是合法的,可是運行時的數組機制知道它處理的是Apple[],所以會在向數組中放置異構類型時拋出異常 //編譯時錯誤,不相容類型 List<Fruit> flist = new ArrayList<Apple>();//Apple的List在類型上不等價於Fruit的List 這根本不是向上轉型 List<? extends Fruit> flist = new ArrayList<Apple>();//一旦執行這種類型的向上轉型,將丟掉向其中傳遞任何對象(set)的能力,甚至是Object 逆變: 超類型通配符,能夠聲明通配符是由某個特定類的任何基類來界定的 <? super MyClass> <? super T> 無界通配符: <?> 它是在聲明:我是想用Java的泛型來編寫這段代碼,我這裏並非要原生類型,可是在當前這種狀況下,泛型參數能夠持有任何類型 <?>表示一種特定的類型,可是我如今還不知道它是啥,原生類型則是任何Object類型 一個原則: <? extends MyClass> 指明的對象至少是MyClass,能夠使用get返回值並轉型爲MyClass(來自Myclass的兩個不一樣的東西不具備兼容性,無法存) <? super MyClass> 指明的對象是MyClass的某個基類,能夠使用set將MyClass存儲(根據多態必定能夠操做MyClass,取出來的類型有不少種,因此就無法取) 泛型使用時常見問題: 任何基本類型都不能做爲類型參數:new ArrayList<int>();//int是不被容許的 使用包裝類 轉型和警告:使用帶有泛型類型參數的轉型或 instanceof 不會有任何效果(因爲存在擦除) 泛型方法的重載: public class UseList<W, T>{ void f(List<T> v){}//因爲擦除會致使產生相同的類型簽名 void f(List<W> v)()//出現這中狀況就使用具備明顯區別的方法名 } 基類劫持接口: public class ComparableClass implements Comparable<ComparableClass>{ public int compareTo(ComparableClass arg){ return 0; } } class ExtendedClass extends ComparableClass implements Comparable<ExtendedClass>{}//錯誤,基類實現的是ComparableClass對象的比較,這裏沒法指定新的對象 自限定類型: 古怪的循環泛型(DRG):類至關古怪的出如今它本身的基類中 理解:我正在建立一個新類,它繼承自一個泛型類型,這個泛型類型接受個人類的名字做爲其參數 class GenericType<T>{} public class MyType extends GenericType<Mytype>{} 自限定: class SelfBounded<T extends SelfBounded<T>>{} class A extends SelfBounded<A>{}//基類中指定的類型要是繼承於基類的類型 class D{} class E extends SelfBounded<D>{}//這樣是不能夠的 若是不使用自限定,將重載參數類型,若是使用了自限定,只能得到 某個方法 的一個版本,它將接受確切的參數類型(使用自限定類型就肯定): 自限定類型的價值在於它能夠產生 協變參數類型 --- 方法參數類型會隨子類而變化 interface SelfBoundSetter<T extends SelfBoundSetter<T>>{ void set(T arg); } interface Setter extends SelfBoundSetter<Setter>{} public class SelfBoundingAndCovariantArguments{ void testA(Setter s1, Setter s2, SelfBoundSetter sbs){ s1.set(s2); //s1.set(sbs); //Error 自限定類型 } } 泛型異常---目前不會用吧 410頁 Java混型|--與接口混合 |--使用裝飾器模式 |--與動態代理混合 泛型的普遍應用:持有器(持有對象) 潛在類型機制:Java缺少潛在類型機制 對其的補償: 反射:經過Class對象嘗試獲取某對象的方法 問題:將全部的類型檢查都轉移到了運行時,所以在許多狀況下是咱們所不但願的 ???函數對象:就是某種程度上行爲像函數的對象---與普通方法不一樣,它們能夠傳遞出去,而且還能夠擁有在多個調用之間持久化的狀態 論點:使用泛型類型機制的最吸引人的地方,就是在使用容器類的地方,這些類包括各類List、各類Set、各類Map等等 當將一個對象放置到容器中時,這個對象就會被向上轉型爲Object,所以將會丟失類型信息 當將一個對象從容器中取回,用它去執行某些操做時,必須將其向下轉型回正確的類型
數組與其餘種類的容器之間的區別有三方面:效率、類型、保存基本類型的能力 數組是一種效率最高的存儲和隨機訪問對象引用序列的方式 使用數組和容器時,若是越界到會獲得一個 表示程序員錯誤的 RuntimeException Java返回數組(引用):無需考慮像C或C++那樣內存回收或內存泄漏問題(數據存在堆內的緣由?) 無需擔憂要爲數組負責,只要須要它就會一直存在,使用完後垃圾回收器會清理掉它 Arrays.deepToString()方法:將多維數組轉換爲多個String Arrays.deepToString(new int[][]{{1, 2, 3}, {4, 5, 6}});//輸出[[1, 2, 3], [4, 5, 6]] 數組中構成矩陣的每一個向量均可以具備任意長度---粗糙數組: int[][] is = new int[][]{ {1, 2, 3}, {1, 2}, {5, 6, 7, 8} }; 數組必須知道它們所持有的確切類型,以強制保證類型安全 數組與泛型不能很好的結合,不能實例化具備參數化類型的數組: !!!編譯器不容許實例化泛型數組 T[] t = new T[10];是不容許的 Peel<Banana>[] pells = new Peel<Banana>[10];//錯誤 能夠參數化數組自己的類型: class Class<T>{ public T[] f(T[] arg){ return arg; //doSomething }// } 還能夠建立數組引用: List<String>[] ls; List[] la = new List[10]; ls = (List<String>[])la;//"Unchecked" warning //數組是協變類型 List<String>[] 也是一個 Object[] Object[] objects = ls; //此時就能夠存入其餘類型 objects[1] = new List<Integer>(); 數組統一初始化:Arrays.fill();方法 int[] is = new int[6]; Arrays.fill(is, 2); Array.fill(is, 2, 5, 3);//index爲二、三、4的位置填充替換爲3 System.out.println(Arrays.toString(is));//[2, 2, 3, 3, 3, 2] 數據生成器:Generator Arrays使用功能:一套用於數組的static方法 equals(); ----比較兩個數據是否相等(deepEquals()用於多維數組) 基於內容的比較 Object.equals() fill(); ----填充數組 sort(); ----對數組排序 默認從小到大 須要排序的對象必須實現Comparable接口(重寫compareTo(MyType another)方法)sort須要將參數的類型轉變爲Comparable Arrays.sort(array, Collections.reverseOrder());//反序排列 Collections.reverseOrder()產生了一個Comparator 編寫本身的Comparator class MyTypeComparator implements Comparator<Mytype>{ public int compare(Mytype o1, MyType o2){ return (o1.i < o2.i ? -1 : (o1.i == o2.i ? 0 : 1)); } } Arrays.sort(array, new MytypeComparator()); 針對基本類型採用「快速排序」 針對對象採用「穩定歸併排序」 binarySearch(); ----在已經排序的數組中查找元素 也能夠使用Comparator: Arrays.binarySearch(array, array[3], String.CASE_INSENSITIVE_ORDER);//字符串忽略大小寫 toString(); ----數組轉換爲String hashCode(); ----產生數組的案列碼 asList(); ----接受任意的序列或數組爲參數,並將其轉變爲List容器 數組複製: Java標準類庫提供有static方法 System.arraycopy() 用法:System.arraycopy(源數組, 源數組偏移量, 目標數組, 目標數組偏移量, 複製元素的個數) 基本數組以及對象數組均可以複製,對象數組的複製只是複製引用(淺拷貝) 注:System.arraycopy 不會執行自動包裝和自動拆包,兩個數組必須具備相同的確切類型 總結:Java編程中,優先選用容器而不是數組,只有在已證實性能成爲問題(切換到數組會有大的提高)時,才應該將程序重構爲使用數組
填充容器: Collections.nCopies()建立傳遞給構造器的List: List<String> list = new ArrayList<String>(Collections.nCopies(4, new String("hello")));//之間填充4個相同的對象(引用) Collections.fill()只對List有用: Collections.fill(list, new String("world"));//只能替換已經在List中的元素,不能添加新的元素 直接輸出一個對象的引用獲得:類型@該對象的散列碼的無符號十六進制 全部的Collection子類型都有一個接收另外一個Collection對象的構造器,用所接受的Collection對象中的元素來填充新的容器 一種Generator解決方案: interface Generator<T>{ T next(); } public class CollectionData<T> extends ArrayList<T>{ public CollectionData(Generator<T> gen, int num){ for(int i = 0; i < num; ++i){ add(gen.next()); } } //通用便捷方法 傳遞進來任意實現生成器接口的類型,就能夠獲得持有此類型的CollectionData public static <T> CollectionData<T> list(Generator<T> gen, int num){ return new CollectionData<T>(gen, num); } } //一個實現生成器的類 class MyClass implements Cenerator<Integer>{ Random rand = new Random(49); public Integer next(){ return rand.nextInt(100); } } //在main中測試 Set<Integer> set = new LinkedHashSet<Integer>(new CollectionData<Integer>(new MyClass(), 3));//使用CollectionData進行填充 set.addAll(CollectionData.list(new MyClass(), 3));//使用便捷方式 static list()方法 再次複習Iterable接口的使用: class MyType implements Iterable<Integer>{ private int size = 9; private int number = 1; @Override //重點 public Iterator<Integer> iterator(){ return new Iterator<Integer>(){ public Integer next(){ return number++; } public boolean hasNext(){ return number < size; } public void remove(){ throw new UnsupportedOperationException(); } } } } 使用Abstract類:建立定製的Collection和Map實現本身需求(目前可能用不到) Collection的功能方法: boolean add(T);//添加元素 ^ boolean addAll(Collection<? extends T>);//添加全部元素 ^ void clear();//清空容器 ^ boolean contains(T);//是否持有參數 Boolean containsAll(Collection<?>);//是否持有全部元素 Iterator<T> iterator();//返回一個Iterator<T>,用於遍歷容器 Boolean remove(Object);//移除此元素的一個實例 ^ boolean removeAll(Collection<?>);//移除參數中全部元素 ^ Bollean retainAll(Collection<?>);//只保留參數中的元素 ^ int size();//容器中元素數目 Object[] toArray();//返回持有全部元素的數組 <T> T[] toArray(T[] a);//返回與a相同類型的數組 用法舉例:String[] strs = myList.toArray(new String[0]); 注意:這些都是Collection的方法,對於List和Set適用,Map不是繼承自Collection沒法使用 並且對於List和Set還會有特定的一些方法實現更多的功能 執行各類不一樣的添加和移除的方法在Collection接口中都是可選操做,這意味着實現類並不須要爲這些方法提供功能定義 ^:標註爲可選操做 經常使用的容器類內這些可選操做都獲得了具體實現,都支持全部操做 可選操做在運行時可能會拋出 UnsupportedOperationException 未獲支持的操做:add() addAll() retainAll() 等 來源於背後由固定尺寸的數據結構支持的容器 ---> Arrays.asList() 基於一個固定大小的數組,僅支持那些不會改變底層數組大小的操做,好比修改 set() 是能夠的 經過 Collections.unmodifiableList() 構建的不可修改的容器 因爲指定是不可修改,因此都不能夠 使用迭代器也能夠修改元素: public void iterTest(List<String> aList){ ListIterator<String> it = aList.listIterator(); it.add("47"); it.previous();//在前面添加了一個後把指針移到最前面 it.remove(); it.next();//把當前的元素刪除,指針移動到下一個 it.set("47");//修改當前指針位置的元素 } 全部在Set中存儲的類都必須具備 boolean equals() 方法 HashSet、LinkedHashSet中存儲的類都必須具備 hashCode() 方法(這裏看到的是返回 int類型) TreeSet中存儲的類都必須實現 Comparable接口 並實現 compareTo() 方法: 注意:不要使用簡明的 return (i - i2); 的形式 例如i是很大的正整數 i2是很大的負整數? i-i2可能會溢出返回負值 總結:全部存入Set類必須具備 equals() 方法 全部存入HashSet的類必須還要具備 hashCode() 方法 全部存入任何種類的排序容器中必須實現 Comparable 接口 SortedSet: 實現此接口的類能夠調用Comparator comparator() 方法返回當前Set使用的Comparator (返回null表示以天然方式排序) 還有: Object first();//返回容器中第一個元素 Object last();//返回容器中的最後一個元素 SortedSet subSet(fromElement, toElement);//生成Set的子集 左閉右開 SortedSet headSet(toElement);//生成子集,元素小於參數 SortedSet tailSet(fromElement);//生成子集,元素不小於參數 隊列: 兩個實現:LinkedList PriorityQueue 差別在於排序行爲而不是性能 優先級隊列 雙向隊列(沒有具體聲明 靠LinkedList實現) Map: HashMap//默認Map,速度最快 TreeMap LinkedHashMap WeakHashMap ConcurrentHashMap IdentityHashMap hashCode()方法是Object中的方法,全部Java對象都能產生散列碼 默認使用對象的地址計算散列碼 map.keySet();//返回Map的鍵的Set map.values();//返回Mao的值的Collection SortedMap(TreeMap是其惟一實現)的使用跟SortedSet很像,方法什麼的也都差很少 爲Map建立本身的key: 注意的兩個方法 hashCode() equals() //二者都在Object類中都有 不覆蓋會使用默認的形式 equals()方法必須知足的5個條件: 1、自反性 對於任意的x x.equals(x)必定返回true 2、對稱性 對於任意的x和y 若是y.equals(x)返回true,則x.equals(y)也必定返回true 3、傳遞性 對於任意x、y、z x.equals(y)返回true y.equals(z)也返回true 則x.equals(z)也返回true 4、一致性 對於任意的x和y,若是對象中用於等價比較的信息沒有改變,那麼不管調用 x.equals(y) 多少次返回的結果應該保持一致 五、對於任何 不是null的x,x.equals(null)必定返回false 線性查詢是最慢的查詢方式 爲速度而散列: 建立一個固定大小的 LinkeedList數組,每個數組位置稱爲桶位(bucket) 基於Key計算哈希值做爲數組的index,若是put時index位置爲null,則建立一個LinkedList 並將引用存儲在相應位置的桶位上 爲了散列分佈均勻,桶的數量一般使用質數 普遍測試結果:使用2的整數次方的散列表速度更快(掩碼代替耗時的除法) 散列碼沒必要是獨一無二的,散列碼關注的是生成速度 String的特色:若是程序中有多個String對象,都包含相同的字符串序列,那麼這些String對象都映射到同一塊內存區域 String的hashCode()是基於String的內容生成的(不一樣的引用指向贊成內存地址) 本身覆蓋hashCode()生成根據須要的哈希值 TreeSet 和 TreeMap都實現了元素的排序: 樹的行爲是老是保證有序,而且沒必要進行特殊的排序。一旦填充了一個TreeMap,就能夠調用keySet()方法獲取鍵的Set視圖,而後調用toArray()方法來 產生由這些鍵構成的數組,以後能夠使用Arrays.binarySearch()方法在排序的數組中快速查找對象 默認使用狀況:ArrayList HashSet HashMap HashMap的性能因子: *容量:桶個數 *初始容量:表在建立時擁有的同位數,HashMap和HashSet都具備指定初始容器容量的構造器 *尺寸:表中當前存儲的項數 *負載因子:尺寸/容量(HashMap默認負載因子 0.75 ) 大量的容器實用方法: java.util.Collections 類內部的靜態方法(static) 512頁 list的排序使用Comparator binarySearch()的時候也要使用相同的Comparator 不可修改容器: Collections.unmodifiable* (*: Collection Set List Map SortedSet SortedMap) 使用時能夠事先構造一個private的正常容器,填充好數據(設定爲不可修改的必須條件)後做爲參數傳遞給此方法 而後返回不可修改容器的引用 Collection和Map的同步控制: Collections類有辦法可以自動同步整個容器 Collections.synchronized*() *一樣表明各類容器 //直接將新生成的容器傳遞給適當的「同步」方法 保證沒有任何機會暴露出不一樣步的版本 Collection<String> c = Collections.synchronizedCollection(new ArrayList<String>()); 快速報錯: Java容器有一種保護機制,可以放置多個進程同時修改同一個容器的內容。 若是在迭代遍歷某個容器的過程當中,另外一個進程介入其中,而且插入、刪除、或修改此容器內某個對象,那麼就會出現問題 快速報錯機制(fail-fast)探查容器上的任何處理本身的進程所進行的操做之外的全部變換,一旦發現其餘進程修改容器,就會馬上拋出ConcurrentModificationException異常 對象是可得到的:指此對象可在程序中的某處找到 此對象的引用在棧內找到(或是存在中間連接 a->b->c) 對象是可得到的則垃圾回收器就不能釋放它,由於它仍然爲程序所用 若是是不可得到的則垃圾回收器將其回收是安全的 Reference類再研究(WeakHashMap)
File類: 幫助咱們處理文件目錄問題 目錄列表器 list() 方法 此方法接收空參數 以及一個實現了FilenameFilter接口的類的實例 public interface FilenameFilter{ boolean accept(File dir, String name);//list() 會回調accept方法 } 此處複習:某方法內部建立匿名內容類須要傳遞進參數,必須是final(編譯器強制要求 爲了保持應用的的正確性,不被修改) 流:它表明任何有能力產出數據的數據源對象或者是有能力接收數據的接收端對象 「流」屏蔽了實際的I/O設備中的處理細節 InputStream: 做用是用來表示那些從不一樣數據來源產生輸入的類 ByteArrayInputStream//容許將內存的緩衝區看成InputStream 構造器參數爲 緩衝區, 字節將從中取出 StringBufferInputStream//將String轉換成InputStream FileInputStream//文件 PipedInputStream//管道 SequenceInputStream//將多個InputStream對象轉換成單一InputStream FilterInputStream//抽象類,做爲「裝飾器」的接口 爲其餘的InputStream類提供有用的功能 OutputStream: 決定輸出所要去往的目標 ByteArrayOutputStream FileOutputStream PipedOutputStream FilterOutputStream 咱們幾乎每次都是對輸入進行緩衝---無論咱們正在鏈接的是什麼I/O設備,I/O類庫把無緩衝輸入做爲特殊狀況 FilterInputStream經常使用類型: DataInputStream//能夠按照可移植方式從流讀取基本數據類型(int, char, long等) BufferedInputStream//防止每次讀取時都得進行寫操做 DataOutputStream PrintStream BufferedOutputStream InputStream和OutputStream面向字符形式的I/O Reader和Writer提供兼容Unicode和麪向字符的I/O功能 InputStreamReader 將InputStream轉換爲Reader OutputStreamWriter 將OutputStream轉換爲Writer 總結一點:從面向字節流的InputStream / OutputStream 轉換爲面向字符的Reader / Writer 使用相應的適配器 *Reader *Writer 自我獨立的類:RandomAccessFile//只適用於文件 構造器須要額外參數: "r"//隨機讀 "rw"//隨機讀寫 getFilePointer()//查找當前所處的文件位置 seek()//在文件內移至新的位置 length()//判斷文件最大尺度 I/O流的典型使用方法: 緩衝輸入文件: 打開文件用於字符輸入 FileInputReader 爲了提升速度使用緩衝 將所產生的引用傳給一個BufferedReader構造器 BufferedReader也提供 readLine() 方法 到文件末尾返回null BufferedReader in = new BufferedReader(new FileReader(filename)); //BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(filename))); String s; while(s = in.readLine() != null){ System.out.print(s + "\n");//readLine() 會刪除掉每一行後面的換行符 } in.close();//不要忘了用完要關閉文件 從內存輸入//不經常使用 格式化的內存輸入//不經常使用 小竅門:跟內存/緩存有關的 都是Buffer 基本的文件輸出: PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(filename)));//PrintWriter提供格式化輸出 //BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filename))); 寫入文件時:PrintWriter提供print相關方法 BufferedWriter提供write方法(還有一個newLine) 特別推薦的快捷方式:直接PrintWriter out = new PrintWriter(filename);//仍舊會使用緩存 存儲和恢復數據:格式化讀與寫 DataInputStream DataOutputStream 含有針對不一樣內容的讀寫操做 如 readDouble() 讀寫隨機訪問文件: RandomAccessFile();//構造器接收文件名以及一個打開方式 "r" "rw" 它不支持裝飾,因此不能將其與InputStream以及OutputStream子類的任何部分組合起來 文件讀寫的實用工具: 確保處理文件後關閉文件: try{ PrintWriter out = new PrintWriter(new FileReader(filename)); try{ for(String str: strArrays){ out.println(str); } }finally{ //這裏保證了處理完文件後及時關閉文件 out.close(); } }catch(IOException e){ throw new RuntimeException(e); } 讀取二進制文件: 使用字節流 BufferedInputStream bf = new BufferedInputStream(new FileInputStream(bFile)); try{ byte[] data = new byte[bf.available()];//bf.available()返回文件佔有的byte大小 bf.read(data); return data; }finally{ //再次複習:上面return了 也會執行finally的代碼 bf.close(); } 標準I/O: System.out//事先被包裝成了printStream System.err//事先被包裝成了printStream System.in//沒有被包裝過的未經加工的InputStream 使用它以前必須對其進行包裝 //包裝如:BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); 標準I/O重定向://不太清楚怎麼用(沒有進行實驗) 靜態方法: System.setIn(); System.setOut(); System.setErr(); 通道與緩衝器:通道(Channel)->包含煤層(數據)的礦藏 緩衝器->用於礦藏與外界進行資源交互的卡車 惟一直接與通道交互的緩衝器是 ByteBuffer ByteBuffer是將數據移進移出通道的惟一方式 視圖緩衝器:針對基本類型 好比CharBuffer 內存映射文件:(MappedFile) 容許咱們建立和修改那些由於太大而不能放入內存的文件 FileChannel fc = new RandomAccessFile("temp.tmp", "rw").getChannel(); IntBuffer ib = fc.map(FileChannel.MapMode.READ_WRITE, 0, fc.size()).asIntBuffer(); for(int i; i < 10; ++i){ ib.put(i); } fc.close(); 文件加鎖: public class FileLocking{ public static void main(String[] args) throws Exception{ FileOutputStream fos = new FileOutputStream("file.txt"); FileLock fl = fos.getChannel().tryLock(); if(fl != null){ System.out.println("Locked File"); TimeUnit.MILLISECONDS.sleep(100); fl.release(); //釋放鎖 System.out.println("Released File"); } fos.close(); } } 經過對FileChannel調用tryLock()或者lock() 就能夠得到整個文件的FileLock 注:SockedChannel、DatagramChannel和ServerSocketChannel不須要加鎖,由於他們是從單進程實體繼承而來的 tryLock()是非阻塞的,它設法獲取鎖,可是若是不能得到(當其餘進程已經持有相同的所而且不共享時),它將直接從方法調用中返回 lock()是阻塞的,它要阻塞進程直至所能夠得到,或者調用lock()的線程中斷,或者調用lock()的通道關閉 能夠對文件的一部分上鎖: tryLock(long position, long size, boolean shared); lock(long position, long size, boolean shared);//加鎖區域爲 position ~ position+szie shared指定是否爲共享鎖 對 獨佔鎖 和 共享鎖 的支持必須由底層的操做系統提供,若是操做系統不支持共享鎖併爲每一個請求都建立一個鎖,那麼它就會使用獨佔鎖 經過 FileLock.isShared() 進行查詢鎖的類型 壓縮//應該不會用到 Zip GZIP JAR文件: 一種壓縮的文件格式 JAR文件是跨平臺的,因此沒必要擔憂跨平臺的問題 聲音和圖像文件能夠像類文件同樣被包含在其中 最實用: jar cf myJarFile.jar *.class //Jar文件包含全部的class文件 571頁 對象序列化與反序列化://目的就是將含有信息的對象存入磁盤 並在須要的時候將其恢復 序列化接口 Serializable 實現了此接口的類能夠支持序列化 序列化: public class Alien implements Serializable{} public class FreezeAlien{ public static void main(String[] args) throws Exception{ ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream("X.file")); Alien quellek = new Alien(); out.writeObject(quellek); } } 反序列化: public class ThawAlien{ public static void main(String[] args) throws Exception{ ObjectInputStream in = new ObjectInputStream( new FileInputStream(new File("..", "X.file"))); Object mystery = in.readObject(); System.out.println(mystery.getClass); } } 注:打開文件和讀取mystery對象中的內容都須要Alien的Class對象;若是Java虛擬機找不到Alien.class就會獲得ClassNotFoundException 必須保證Java虛擬機可以找到相關的.class文件 序列化的控制: Externalizable 繼承了Serializable 並添加了兩個方法:writeExternal() readExternal()//這兩個方法在序列化與反序列化的過程當中被自動調用來執行一些特殊操做 Externalizable對象在反序列化時 它的構造器必須是public//不然恢復時形成異常 注:Serializable對象的恢復徹底以它的二進制位爲基礎來構造,不調用構造器 Externalizable對象會調用默認構造器 Externalizable類內的域要在writeExternal() 以及 readExternal() 中進行顯示序列化與反序列化 兩種對象序的列化的區別:Serializable 所有會自動進行 Externalizable沒有任何東西能夠自動序列化 transient(瞬時)關鍵字: 若是使用的是Serializable構建類 可是但願其中的某一個子類(域)不被序列化存儲(如密碼)則須要在此域前加 transient關鍵字 Externalizable的替代方法: 實現Serializable接口的類中定義 writeObject() 以及 readObject()方法 方法必須具備準確的方法簽名: private void writeObject(ObjectOutputStream stream) throws IOException; private void readObject(ObjectOutputStream stream) throws IOException, ClassNotFoundException; 注:這兩個方法是 ObjectOutputStream 以及 ObjectInputStream 的writeObject() 和 readObject() 方法來調用的//private之因此能夠被調用使用的是 反射機制 能夠在本身的這兩個方法中調用defaultWriteObject() 和 defaultReadObject() 來選擇執行默認的write和read 使用「持久性」: 只要將任何對象序列化到單一流中,就能夠恢復出與咱們寫出時同樣的對象網,並無任何意外重複複製出的對象 若是咱們想保存系統狀態,最安全的作法就是將其做爲「原子」操做進行序列化: 將構成系統狀態的全部對象都置於單一容器內,並在一個操做中將該容器直接寫出 而後一樣只需一次方法調用便可將其恢復 對於類內static值不會被自動序列化,想要序列化static值,必須本身手動去實現 XML: import nu.xom.*; 能夠將對象保存爲規範格式的.xml文件 具體參考 586頁 讀取xml文件並解析對象//反序列化 public class People extends ArrayList<Persion>{ public People(String filename) throws Exception{//傳入文件名的構造器 Document doc = new Builder().builder(filename);//打開文件 基於xom類庫 Elements elements = doc.getRootElement().getChildElements();//獲得一個Elements列表 擁有get() 和 size() 方法 for(int i = 0; i < elements.size(); ++i){//size肯定大小 add(new Persion(elements.get(i))); //get獲取元素 } } public static void main(String[] args) throws Exception{ People p = new People("People.xml"); System.out.println(p); } } Preferences API: 588頁 他只能用於小的、受限的數據集合---咱們只能存儲基本類型和字符串(每一個字符串的存儲長度不能超過8K) Preferences是一個鍵-值集合(相似映射),存儲在一個節點層次結構中
enum基本特性: 建立enum時,編譯器會自動生成一個相關的類,這個類繼承於java.lang.Enum 方法values() 返回enum實例的數組 能夠用於遍歷 ordonal() 返回int值 對應於每個實例的聲明時的次序(從0開始) 能夠使用 == 比較實例 編譯器會自動提供equals() 和 hashCode() 方法 Enum類實現了Comparable接口 因此enum實例具備compareTo() 方法 enum實例上能夠調用 getDeclaringClass() 查看它所屬的類 Enum類的靜態方法valueOf() 根據給定的名字返回相應的enum實例: enum E { ONE, TWO, THREE} Enum.valueOf(E.class, "ONE");//獲得ONE對應的enum實例 將 import static 用於enum: 能夠將enum實例的標識符帶入當前的命名空間,無需在用enum類型來修飾enum實例: class Test{ E em; Test(E em){ this.em = em; } } 不使用靜態導入: new Test(E.ONE) 使用: new Test(ONE); 注意:在定義enum的同一個文件中 或者 在默認包中定義enum 沒法使用此技巧 enum不能被繼承,除了這一點幾乎能夠將其當作爲類,能夠在其中添加方法,甚至它能夠有main()方法 在enum中定義本身的方法: 注意幾點:建立enum實例的時候能夠採用括號後傳入字符串參數做爲描述//字符串做爲其構造器的參數 實例後面定義方法時,實例定義結束要加分號 構造器默認是private 一旦enum的定義結束,編譯器就不容許咱們再使用其構造器來建立任何實例 也能夠像正常的類同樣,覆蓋已有的方法 switch語句中能夠使用enum:正常狀況下,須要使用enum類型來修飾一個enum實例,可是在case語句中卻不須要 values()的神祕之處: enum E { ONE, TWO } 反編譯:java的代碼能夠調用系統命令 OSExecute.command("javap E"); 獲得: final class E extends java.lang.Enum{ public static final E ONE; public static final E TWO; public static final E[] values(); public static E valueOF(java.lang.String); static {};//靜態初始化子句 } 解釋:編譯器將 E 標記爲final類,因此沒法被繼承 values()是由編譯器添加的static方法 編譯器還添加了valueOf()方法//Enum類的valueOf()須要兩個參數 添加的這個須要一個 values()方法是由編譯器插入到enum定義中的static方法,向上轉型爲Enum後values()方法沒法訪問,能夠使用Class的getEnumConstants()方法獲得全部enum實例 enum繼承於Enum因此沒法再繼承其餘類,可是能夠implements接口 使用接口組織枚舉: 在一個接口的內部,建立實現該接口的枚舉,以此將元素進行分類 public interface Food{ enum Appetizer implements Food{ SALAD, SOUP, SPRING_ROLLS; } enum MainCourse implements Food{ LASAGNE, BURRITO, PAD_THAI; } enum Dessert implements Food{ TIRAMISU, GELATO, BLACK_FOREST_CAKE; } }//全部的枚舉實例都是Food類 它們又被進行了分類 EnumSet: EnumSet中的元素必須來自一個enum 使用一個long(64位)值做爲比特向量 一個enum實例只需一位bit表示其是否存在 EnumSet能夠應用於最多不超過64個元素的enum 當enum超過64個,EnumSet會在必要的時候增長一個long 向其中添加enum實例的順序並不重要,由於其輸出的次序決定於enum實例定義時的次序 各類方法查閱資料使用 EnumMap: EnumMap中的key必須來自一個enum 構建Map: EnumMap<MyEnum, MyClass> em = new EnumMap<MyEnum, MyClass>(MyEnum.class); enum的每個實例做爲一個鍵,老是存在 若是沒有爲這個鍵調用put()方法來存入相應的值的話,其對應的值就是null 常量相關方法: public enum ConstantSpecificMethod{ ONE{ void action(){ System.out.println("ONE"); } } TWO{ void action(){ System.out.println("TWO"); } }; abstract void action();//須要定義抽象方法 } 調用:ONE.action(); enum實例不能夠做爲一個類型來使用: //void f1(ConstantSpecificMethod.ONE instance){} ONE是一個靜態實例 不能做爲類型 除了實現abstract方法外 還能夠覆蓋常量相關方法 public enum ConstantSpecificMethod{ ONE, TWO{ void action(){ System.out.println("TWO"); } }; void action(){ ; } } Java只支持單路分發: 若是要執行的操做包含了不止一個類型未知的對象時,那麼Java的動態綁定機制只能處理其中一個的類型 因此咱們必須本身來斷定其餘的類型,從而實現本身的動態綁定行爲 使用enum分發:615頁代碼 不太理解構造器的代碼部分 private Outcome的 三個實例如何初始化(初始化爲啥呢!!??) 還有使用 常量相關方法 以及 EnumMap 和 數組:核心思路就是構建各類不一樣類型的表而已 「表驅動式編程」
註解爲咱們在代碼中添加信息提供了一種格式化的方法,是咱們在稍後某個時刻很是方便地使用這些數據 Java SE5內置三種註解,定義在java.lang中: @Override 表示當前的方法定義將覆蓋超類中的方法 @Deprecated(棄用) 若是程序員使用了註解爲Deprecated的元素,編譯器會發出警告 @SupressWarnings 關閉不當的編譯器警告 每當建立描述符性質的類或接口時,一旦其中包含了重複性的工做,就能夠考慮使用註解來簡化與自動化該過程 定義註解: 註解定義後,其中能夠包含一些元素來表示某些值 分析處理註解時,程序或工具能夠利用這些值 沒有元素的註解是 標記註解 例: @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Test{} 含有元素的註解定義: @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface UseCase{ public int id(); //元素定義相似於方法 public String description() default "no description";//設置默認值 若是添加註解時沒有指定 則使用默認值 } 用註解: @UseCase(id = 47, description = "whatever you want to write") public void oneMethod(){} 四種元註解 @Target 用來定義咱們本身定義的註解將應用於什麼地方(例如是一個方法或者一個域)(ElementType.) @Retention 用來定義該註解在哪個級別可用 源碼:OURCE 類:CLASS 運行時:RUNTIME(RetentionPolicy.) @Documented 將註解包含在Javadoc中 @Inherited 容許子類繼承父類中的註解 註解元素可用的類型: 基本類型 String Class enum Annotation 以上類型的數組 元素默認值限制: 元素必需要麼具備默認值,要麼在使用註解時提供元素的值 對於非基本類型的元素,默認值定義不能爲null 因此在定義時一般以負數或空字符串來表示某個元素不存在 元素命名爲value,使用註解時value是惟一須要賦值的元素則能夠直接在括號裏寫如值 而無需使用鍵=值的形式 注意:註解不支持繼承 基於註解的單元測試: 單元測試 是對類中的每一個方法提供一個或多個測試的一種實踐,其目的是爲了有規律地測試一個類的各個部分是否具有正確的行爲 基於註解的測試框架:@Unit 用的最多的@Test 用@Test標記測試方法 被標記的測試方法不帶參數 返回boolean 或 void 例子: @Test boolean assertAndReturn(){ assert 1 == 2: "What a surprise!"; return methodOne().equals("") } 注:使用@Unit進行測試必須定義在某個包中(即必須包括package聲明 例如java mywork.mypackage.xlc MyTest) 對於每個單元測試而言,@Unit都會用默認的構造器,爲該測試所屬的類建立出一個新的實例 並在此新建立的對象上運行測試,而後丟棄該對象,以避免對其餘測試產生反作用 若是沒有默認瀏覽器,或者新對象須要複雜的構造過程,能夠建立一個 static方法專門負責構造對象 使用 @TestObjectCreate註解標記此方法(必須是static的方法) 測試單元中添加額外的域 使用 @TestProperty註解(還能夠用來標記那些只在測試中使用的方法,而他們自己又不是測試方法) 進行清理工做使用 @TestObjectCleanup 標記static方法 經過繼承實現測試類 來完成@Unit對泛型的使用: 惟一缺點是 繼承使咱們失去了訪問被測試的類中的private方法的能力 解決辦法:設定爲protected 或 使用非private的@TestProperty方法調用private方法 總結:Java SE5僅提供了不多的內置的註解 若是咱們在別處找不到可用的類庫,就只能本身建立新的註解以及相應的處理器 藉助apt工具,咱們能夠同時編譯新產生的源文件,以及簡化構建過程 mirror API只能提供基本功能--幫助找到Java類定義中的元素 Javassist可以用來操做字節碼
用併發解決的問題大致上能夠分爲 速度 和 設計可管理性 阻塞: 若是程序中的某個任務由於該程序控制範圍以外的某些條件(一般是I/O)而致使不能繼續執行,那麼咱們就說這個任務或線程 阻塞 了 進程: 是運行在它本身的地址空間內的自包容的程序 多任務操做系統能夠經過週期性地將CPU從一個進程切換到另外一個進程,來實現同時運行多個進程(程序) 進程會被互相隔離開,所以它們不會彼此干涉 與在多任務操做系統中分叉外部進程不一樣,線程機制是在由執行程序表示的 單一進程 中建立任務 Java的線程機制是 搶佔式 的,這表示調度機制會週期性地中斷線程,將上下文切換到另外一個線程,從而爲每個線程都提供時間片 使得每一個線程都會分配到數量合理的時間去驅動它的任務 定義任務: 一種描述任務的方式:由Runnable接口來提供 要想定義任務,只須要實現Runnable接口並編寫run()方法,使得該任務能夠執行你的命令 public class RunClass implement Runnable{ ... public void run(){ while(...){ ... Thread.yield(); } } } 注:任務的run方法一般總會有某種形式的循環,使得任務一直運行下去直到再也不須要 因此要設定跳出循環的條件(有一種選擇是直接從run()返回) 一般run被寫成無限循環的形式 靜態方法Thread.yield()是對線程調度器的一種建議:我已經執行完最重要的部分了,此刻正是切換給其餘任務執行一段時間的大好時機 Thread類: 將Runnable對象轉變爲工做任務的傳統方式是把它提交給一個Thread構造器: public class BasicThreads{ public static void main(String[] args){ Thread t = new Thread(new RunClass());//Thread構造器只須要一個Runnable對象 t.start(); System.out.println("Waiting for RunClass"); } } 注:調用Thread對象的start()方法爲該線程執行必要的初始化操做,而後調用Runnable的run()方法 程序會同時運行兩個方法,main()和RunClass.run() 使用Executor: 用單個Executor 來建立和管理系統中全部的任務 例子: public class Executor{ ExecutorService exec = Executors.newCachedThreadPool(); for(int i = 0; i < 5; ++i){ exec.execute(new RunClass); } exec.shutdown();//防止新任務被被提交給這個Executor } newCachedThreadPool是首選 還有: FixedThreadPool(指定線程數) SingleThreadPool //至關於FixedThreadPool(1) 會序列化全部提交給它的任務 逐個完成 Runnable是執行工做的獨立任務,可是不會返回任何值 若是但願任務在完成時可以返回一個值,則能夠實現 Callable接口 Callable是一種具備參數類型的泛型,它的類型參數表示的是從方法call()中返回的值,必須使用ExecutorService.submit()方法調用它 例子: class TaskWithResult implements Callable<String>{ private int id; public TaskWithResult(int id){ this.id = id; } public String call(){ return "result of TaskWithResult " + id; } } public class CallableDemo{ public static void main(String[] args){ ExecutorService exec = Executors.newCachedThreadPool(); ArrayList<Future<String>> results = new ArrayList<Future<String>>(); for(int i = 0; i < 10; ++i){ results.add(exec.submit(new TaskWithResult(i))); } for(Future<String> fs: results){ try{ System.out.println(fs.get()); }catch(InterruptedException e){ System.out.println(e); return; }catch(ExecutionException e){ System.out.println(e); }finally{ exec.shutdown(); } } } } 注:submit()方法會產生Future對象,此處將指定類型的返回值做爲參數傳遞給submit()方法 //能夠使用 isDone()方法來查詢Future是否已經完成 //直接使用get()方法在Future沒有完成時會造成阻塞,直到結果準備就緒 休眠: sleep() 老式的sleep: Thread.sleep();//單位是毫秒 新式的sleep: TimeUnit.MILLISECONDS.sleep();//能夠指定sleep()延遲的時間單元,具備更好的可閱讀性 對sleep的調用會拋出InterruptedException 異常 異常不能跨線程傳播回主線程,因此必須在本地處理全部在任務內部產生的異常 優先級: 線程的優先級將該線程的重要性傳遞給調度器 調度器傾向於讓優先級最高的線程先執行 這並不意味着優先級較低的線程得不到執行(優先級不會致使死鎖) 只是優先級較低的線程執行的頻率較低 能夠使用getPriority()來讀取現有線程的優先級 setPriority()來修改線程的優先級 public void run(){ Thread.currentThread().setPriority(priority);//currentThread:當前線程 } 經常使用優先級: MAX_PRIORITY//最大 NORM_PRIORITY//標準 MIN_PRIORITY//最小 讓步: Thread.yield()方法 若是知道已經完成了在run()方法的循環的一次迭代過程當中所需的工做,就能夠給線程調度機制一個暗示: 你的工做已經作得差很少了,可讓別的線程使用CPU了 注意:對於任何重要的控制或在調整應用時,都不能依賴於yield() 後臺線程: 是指在程序運行的時候在後臺提供一種通用服務的線程,而且這種線程並不屬於程序中不可或缺的部分 使用Thread的setDaemon() 方法將線程設置爲後臺線程: Thread daemon = new Thread(new SimpleDaemon()); daemon.setDaemon(true);//設置爲後臺線程 daemon.start(); 被設置爲後臺線程的線程,其派生出來的其餘子線程,都是後臺線程(不須要顯示地設置爲後臺線程) 後臺線程中 不會執行finally子句的狀況下就會終止其run()方法 直接繼承與Thread類: public class SimpleThread extends Thread{ private int contDown = 5; private static int threadCount = 0; public SimpleThread(){ super(Integer.toString(++threadCount)); start(); } public String toString(){ return "#" + getName() + "(" + countDown + "),"; } public void run(){ while(true){ System.out.println(this); if(--countDown ==0){ return; } } } public static void main(String[] agrs){ for(int i = 0; i < 5; i++){ new SimpleThread(); } } } * 能夠定義內部類來將線程代碼隱藏在類中 * 還能夠在類的內部定義Thread引用: 採用匿名內部類的形式建立Thread子類並向上轉型爲Thread,並使用定義好的Thread引用指向它 * 也能夠將線程建立在方法內,當準備好運行線程時,就能夠調用這個方法,而在線程 開始以後,該方法將返回 加入一個線程: 在A線程中調用B的B.join()方法 A線程會被掛起 直達目標線程B結束才恢復 在調用join()時帶上一個超時參數(單位能夠是秒,毫秒或納秒) 這樣若是目標線程在這段時間到期時尚未結束的話 join()方法總能返回 對join()方法的調用能夠被中斷 使用 B.interrupt()方法 異常捕獲: 在線程中的run()方法內拋出的異常,並不會被外部線程捕獲到 專用: Thread.UncaughtExceptionHandler接口 共享受限資源: 對於併發工做,須要某種方式來防止兩個任務訪問相同的資源,至少在關鍵階段不能出現這種問題 採用:序列化訪問共享資源 的方案: 在給定時刻只容許一個任務訪問共享資源 共享資源通常是以對象形式存在的內存片斷,但也能夠是文件、輸如/輸出端口,或者打印機 要控制對共享資源的訪問,得先把它包裝進一個對象,而後把全部要訪問這個資源的方法標記爲synchronized synchronized void f(){} 全部對象都自動含有單一的鎖(也稱爲監視器),當在對象上調用任意synchronized方法的時候,此對象都被加鎖, 這時該對象上的其餘synchronized方法只有等到前一個方法調用完畢並釋放了鎖以後才能被調用 在併發時,將域設置爲private是很是重要的,不然,synchronized關鍵字就不能防止其餘任務直接訪問域,這樣就會發生衝突 針對每一個類,也有一個鎖(做爲Class對象的一部分),因此synchronized static方法能夠在類的範圍內防止對static數據的併發訪問 何時使用 同步: 若是你正在寫一個變量,它可能接下來將被另外一個線程讀取,或者正在讀取一個上一次已經被另外一個線程寫過的變量,那麼你必須使用同步 而且,讀寫線程都必須用相同的監視器鎖同步 重要的一點:每一個訪問臨界共享資源的方法都必須被同步,不然它們就不會正確的工做 顯示的Lock對象: java.util.concurrent.locks中顯示的互斥機制 Lock對象必須被顯示的建立、鎖定、釋放 例子: public class MyClass{ private int currentValue = 0; private Lock lock = new ReentrantLock(); public int next(){ /*慣用寫法 先加鎖 在採用try實現對競爭資源的操做 最後finally語句進行解鎖*/ lock.lock(); try{ ++currentValue; Thread.yield(); ++currentValue; return currentValue; }finally{ lock.unlock(); } } } 大致上,使用synchronized關鍵字時,須要寫的代碼量更少,而且用戶錯誤出現的可能性也會下降,所以一般只有在解決特殊問題時,才使用顯示的Lock對象 原子性與易變性: 原子操做是不能被線程調度機制中斷的操做,一旦操做開始就必定會在 切換到其餘線程執行 以前 執行完畢 原子性能夠應用於除long和double以外的全部基本類型上的「簡單操做」 對於讀取和寫入除long和double以外的基本類型變量這樣的操做,能夠保證它們會被看成不可分(原子)的操做來操做內存 易變性的關鍵字---volatile 若是將一個域聲明爲volatile的,那麼只要對這個域產生了寫操做,那麼全部的讀操做均可以看到這個修改 volatile域會當即被寫入主存中,讀取操做就是發生在主存中 i++這樣的操做在java中是非原子性的 基本上,若是一個域可能會被多個任務同時訪問,或者這些任務中至少有一個是寫入任務,那麼就應該將這個域設置爲volatile的 讀取和寫入都是直接針對內存的,而卻沒有緩存, 可是volatile並不能對遞增不是原子性操做這一事實產生影響: public class SerialNumberGenerator{ private static volatile int serialNumber = 0; public static int nextSerialNumber(){ return serialNumber++; //volatile不能阻止此操做是非原子性的 所以 此處是非線程安全的 } } 對基本類型的讀取和賦值操做被認爲是安全的原子性操做,當對象出於不穩定狀態時,仍舊有可能使用原子性操做來訪問它們: private int i = 0; public int getValue(){ return i; } public synchronized void iPlusOne(){ i++; i++; } 注:在某個線程中調用iPlusOne() 在另外一個線程中調用getValue() 因爲i++是原子性操做 而getValue()方法不是synchronized 致使獲取到奇數的中間狀態值 原子類: AtomicInteger AtomicLong 等等 Atomic類被設計用來構建java.util.concurrent中的類,所以只有在特殊狀況下才在本身的代碼中使用它們 臨界區: 但願防止多個線程同時訪問方法內部的 部分代碼 而不是防止訪問整個方法 --- 被分離出來的部分代碼段被稱爲臨界區(critical section) 使用sychronized關鍵字創建,synchronized被用來指定某個對象: synchronized(syncObject){ ...//臨界區---同步控制塊,進入此段代碼前必須獲得syncObject對象的鎖 } 具體使用案例687頁 還能夠使用顯示的Lock對象來建立臨界區://689頁的例子 class ExplicitPairManager2 extends PairManager{ private Lock lock = new ReentrantLock(); public void increment(){ Pair temp; lock.lock(); try{ p.incrementX(); p.incrementY(); }finally{ lock.unlock(); } store(temp); } } 在其餘對象上同步: synchronized塊最合理的使用方式是,使用其方法正在被調用的當前對象: synchronized(this) 這種方式下,若是得到了synchronized塊上的鎖,那麼該對象其餘的synchronized方法和臨界區就不能被調用了 class DualSynch{ private Object syncObject = new Object(); public synchronized void f(){ for(int i = 0; i < 5; ++i){ System.out.println("f()"); Thread.yield(); } } public void g(){ synchronized(syncObject){ //同步的對象爲另外一Object對象 並非this 這樣能夠保證兩個同步是相互獨立的 for(int i = 0; i < 5; ++i){ System.out.println("g()"); Thread.yield(); } } } } 線程本地存儲://這樣每一個線程都會有本身的變量 不會發生衝突 也就不須要進行同步 建立和管理線程本地存儲能夠由 java.lang.ThreadLocal類來實現 public class ThreadLocalVariableHolder{ private static ThreadLocal<Integer> value = new ThreadLocal<Integer>(){ private Random rand = new Random(47); protected synchronized Integer initialValue(){ return rand.nextInt(10000); } };//在一個類內調用使用此類時,每一個線程都會爲變量分配本身單獨的存儲 } 注:ThreadLocal對象一般看成靜態域存儲 在建立ThreadLocal時,只能經過get()和set()方法來訪問該對象的內容 無需使用synchronized 由於ThreadLocal保證不會出現競爭條件 終結任務: cancel()和isCanceled()方法被放到了一個全部任務均可以看到的類中 設置canceled變量人爲終止任務: public void run(){ while(!canceled){ ... } } 線程狀態: 一個線程能夠處於如下四種狀態之一: 1 新建(new):當線程被建立時,它只會暫時處於這種狀態,此時已經分配了必需的系統資源,並執行了初始化 2 就緒(Runnable):只要調度器把時間片分配給現線程,線程就能夠運行 3 阻塞(Blocked):線程可以運行,但有某個條件阻止它的運行。當線程處於阻塞狀態時,調度器將忽略線程,不會分配給線程任何CPU時間 4 死亡(Dead):處於死亡或終止狀態的線程將再也不是可調度的,而且不再會獲得CPU時間,任務已經結束再也不是可運行的 任務死亡的一般方式是從run()方法返回 進入阻塞狀態的可能緣由: 1 經過調用sleep()使任務進入休眠狀態 2 經過調用wait()使線程掛起 直到線程獲得了notify()或notifyAll()消息 (signal()或signalAll()消息) 線程會進入就緒狀態 3 任務在等待某個輸入/輸出完成 4 任務試圖在某個對象上調用其同步控制方法,可是對象鎖不可用(被另外一個任務獲取了鎖) 中斷: 若是一個線程已經阻塞,或者試圖執行一個阻塞操做,那麼設置這個線程的中斷狀態將拋出 InterruptedException Thread.interrupted() 使用Executor來執行操做時,調用Executor的shutdownNow()方法 它將發送一個interrupt()調用給它啓動的 全部線程 要中斷某個單一任務,則使用ExecutorService的submit()方法 代替execute()方法: submit()方法將返回一個泛型Future<?>,其中有一個未修飾的參數 --- 持有這種Future的關鍵字在於你能夠在其上調用cancel()方法來中斷某個特定任務 sleep引發的阻塞可中斷 I/O以及synchronized引發的阻塞不可中斷 關閉任務在其上發生阻塞的底層資源來中斷阻塞: * exec.shutdownNow() * 直接關閉流 * 調用Future的cancel() 一個任務應該可以調用在同一個對象中的其餘的synchronized方法,而這個任務已經持有鎖了 //699頁 中斷檢查: 將退出考慮全面:在阻塞時調用interrupt()方法拋出InterruptException進行catch處理 非此類狀況則須要第二種處理方式來退出 //考慮被阻塞和非阻塞的各類狀況 //結合使用try-finally一個典型的慣用法 701頁 線程之間的協做: 使用wait() notify() notifyAll() 協做 //三種方法都是Object類的 它們操做的鎖也是全部對象的一部分 wait()的調用有兩種 1 以毫秒爲單位的參數--表示掛起時間 2 沒有參數--表示一直掛起--無限等待 在wait()期間對象鎖被釋放 調用notify() 或 notifyAll()能夠是處於wait()的線程喚醒 notify():在衆多等待同一個鎖的任務中只有一個會被喚醒 notifyAll():喚醒多個處於wait()的任務 注意的是:wait() 和notify() 的使用針對的都是同一個對象 例如一個對象上使用notifyAll() 在另外一個對象上的wait()的任務是不會被喚醒的 生產者與消費者: 一個很好的例子//709頁 針對不一樣task對象設定不一樣的synchronized塊(不一樣任務不一樣的鎖) 當wait()被調用後,任務掛起(wait)並等待喚醒信號(notify/notifyAll) 再次強調:任務結束的方式---run()是如何返回的 使用顯示的Lock和Condition: 使用Condition和Lock對象,單個Lock將產生一個Condition對象,這個對象被用來管理任務間的通訊 可是Condition對象並不包含任何有關處理狀態的信息,須要額外的處理信息 使用的方法:await()來掛起 <-> signal() signalAll()來喚醒 使用的套路: class Car{//被操做的對象類 裏面定義必要域 以及 對象上的不一樣狀態 private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); private boolean waxOn = false; public void waxed(){ lock.lock(); try{ waxOn = true; condition.signalAll(); }finally{ lock.unlock(); } } //... } class WaxOn implements Runnable{ /*...*/ }//被操做對象上的具體操做任務 有Runnable編碼套路while(!Thread.interrupted()){...} 注:使用Lock和Condition可能會使編碼的複雜性變得更高但收益不多 --- Lock和Condition對象只有在更加困難的的多線程問題中才是必需的 生產者-消費者與隊列: 解決:wait()和notifyAll()方式一種很是低級的方式解決了任務互操做問題,即每次交互時都握手 使用 同步隊列 解決任務協做問題:java.util.concurrent.BlockingQueue接口 LinkedBlockingQueue ArrayBlockingQueue SynchronousQueue 經典例子://715頁的土司 沒有任何顯示的同步(使用Lock對象或者synchronized關鍵字的同步) 同步由隊列(其內部是同步的)和系統的設計隱式地管理 每個被操做的對象在任什麼時候刻都只由一個任務在操做 由於隊列的阻塞,使得處理過程將被自動地掛起和恢復 任務間使用管道進行輸入/輸出://piped PipedWriter:容許任務向管道寫 PipedReader:容許不一樣任務從同一管道中讀取 PipedReader的創建必須在構造器中與一個PipedWriter相關聯 使用 PipedReader in = new PipedReader(out); //其中out被定義爲 PipedWriter out = new PipedWriter(); 注意:BlockingQueue使用起來更加健壯而容易 死鎖: 任務循環等待下一個任務釋放鎖 必須同時知足四個條件纔會發生死鎖: 1 互斥條件 任務使用的資源中至少有一個是不能共享的 2 至少有一個任務它必須持有一個資源且正在等待獲取一個當前被別的任務持有的資源 3 資源不能被任務搶佔 任務必須把資源釋放看成普通事件 4 必須有循環等待 新類庫中的構建: java.util.concurrent庫引入大量設計用來解決併發問題的的新類 //用到時再仔細研究 CountDownLatch:被用來同步一個或多個任務,強制它們等待由其餘任務執行的一組操做完成 CyclicBarrier:適用於:建立一組任務,它們並行地執行工做,而後在進行下一步驟以前等待,直至全部任務都完成(/*賽馬*/) DelayQueue;延遲隊列 PriorityBlockingQueue:優先級隊列 ScheduledExecutor:預約義工具 ScheduledThreadPoolExcutor Semaphore:計數信號量容許n個任務同時訪問一個資源 Exchanger:在兩個任務之間交換對象的柵欄 典型應用場景:一個任務在建立對象,這些對象的生產代價很高昂,而另外一個任務在消費這些對象 能夠使得 有更多的對象在被建立的同時被消耗 當一個任務調用其Exchanger.exchange()方法時,它將阻塞直至對方任務調用它本身的exchange()方法,那時,這兩個exchange()方法將所有完成 對象被互換 性能優化: synchronized Lock Atomic 若是涉及多個Atomic對象,就有可能被強制要求放棄這種用法: Atomic對象只有在很是簡單的狀況下才有用//754頁 免鎖容器: 免鎖容器背後的通用策略:對容器的修改能夠與讀取操做同時發生,只要讀取者只能看到完成修改的結果便可 修改是在容器數據結構的某個部分的一個單獨的副本(有時是整個數據結構的副本)上執行的,而且這個副本在修改過程當中是不可視的, 只有當修改完成時,被修改的結構纔會自動地與主結構進行交換,以後讀取者就能夠看到這個修改了 CopyOnWriteArrayList CopyOnWriteArraySet ConcurrentHashMap ConcurrentLinkedDeque ReadWriteLock:對數據結構相對不頻繁的寫入,可是有多個任務要常常讀取這個數據結構的這類狀況進行了優化 僅容許一個寫入者持有鎖,容許多個讀取者同時讀取 活動對象:每一個對象都維護着它本身的工做器線程和消息隊列,而且全部對這種對象的請求都將進入隊列排隊,任什麼時候刻都只能運行其中的一個 有了活動對象://這段話我是沒咋明白意思 1 每一個對象均可以擁有本身的工做器線程 2 每一個對象都將維護對它本身的域的所有控制權 3 全部在活動對象之間的通訊都將以在這些對象之間的消息形式發生 4 活動對象之間的全部消息都要排隊 總結: 使用多線程的主要緣由: * 要處理不少任務,它們交織在一塊兒,應用併發可以更有效地使用計算機(包括在多個CPU上透明地分配任務的能力)//等待輸入/輸出時使用CPU * 要可以更好地組織代碼//仿真 * 要更便於用戶使用//長時間下載過程當中監視「中止」按鈕是否被按下 多線程的主要缺陷: 1 等待共享資源的時候性能下降 2 須要處理線程的額外CPU花費 3 糟糕的程序設計致使沒必要要的複雜度 4 有可能產生一些病態,如 餓死 競爭 死鎖 活鎖 5 不一樣平臺致使的不一致 ---------------------------------------------------------- 常見任務寫法: public class Task implements Runnable{ //... public void run(){ try{ while(!Thread.interrupted()){ //do something } }catch(InterruptedException e){ //... } } } 使用synchronized方法是最經常使用的方法
--------------------------------------------------------筆記底線------------------------------------------------------------------