日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

疯狂Java讲义笔记

發布時間:2023/12/10 java 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 疯狂Java讲义笔记 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、Java概述

1.java編譯產生與平臺無關的字節碼(*.class文件),再在JVM里面執行。

2.JVM是一個抽象的計算機,具有指令集并使用不同的存儲區,負責執行指令,還要管理數據、內存和寄存器。

3.JVM細節:指令集、寄存器、類文件的格式、棧、垃圾回收堆、存儲區。

4.只運行java程序可以只安裝JRE,若要開發則要JDK

5.bin路徑下的絕大部分命令都是包裝了tools.jar文件里的工具類。

6.JDK1.5之后可以不用設置CLASSPATH環境變量

7.main方法的 public 和 static可以交換位置。

8.垃圾回收相關:將對象引用設置為null將加快垃圾回收;可以通過System.gc()來建議系統進行垃圾回收;垃圾回收的精確性主要包括a.精確標記或者的對象b.精確地定位對象之間的引用關系。


二、理解面向對象

1.基本特征:

? ?a.封裝:將對象實現細節隱藏起來,然后通過一些共用方法來暴露該對象的功能

? ?b.繼承:

? ?c.多態:子類對象可以直接賦給父類變量,運行時依然表現出子類的行為特征,這意味著同一個類型的對象在執行同一個方法時,可能表現出多種行為特征。


三、數據類型和運算符

1.注釋,單行注釋// ?,多行注釋 /* .... */,文檔注釋/** ?... */

2.注釋中常用標記@author指定java程序作者,@version指定源文件的版本,@deprecated不推薦使用的方法,@param方法的參數說明信息,@return方法的返回值說明信息,@see參見,關于指定交叉參考的內容,@exception拋出異常類型,@throws拋出的異常,和exception同意。

類或接口文檔中的標記@see、@deprecated、@author、@version;方法或構造器文檔中的標記@see、@deprecated、@param、@return、@throws和@exception等;出現在Field文檔注釋中的有@see、@deprecated等

package yeeku;/*** Description:* <br/>網站: <a href="http://www.crazyit.org">瘋狂Java聯盟</a> * <br/>Copyright (C), 2001-2012, Yeeku.H.Lee* <br/>This program is protected by copyright laws.* <br/>Program Name:* <br/>Date:* @author Yeeku.H.Lee kongyeeku@163.com* @version 1.0*/ public class JavadocTagTest {/*** 一個得到打招呼字符串的方法。* @param name 該參數指定向誰打招呼。* @return 返回打招呼的字符串。*/public String hello(String name){return name + ",你好!";} }


3. 標識符中可以包含$符號

4.Java語言指出的類型,基本類型(primitive type):int等,引用類型(reference type):類、接口、數組、null


5.java7中新增了二進制類型。以0b或者0B開頭

6.特殊的三個浮點數:正無窮大Double或Float的POSITIVE_INFINITY表示,負無窮大NEGATIVE_INFINITY表示,非數NaN

7.java7中可以在數值中使用下劃線。int binVal = 0B1000_0000_0000_0011;double pi = 3.14_15_92;


四、數組

1.數組是一種數據類型,它本身市一中引用類型。 int[]就是一種類型

2.java數組定義格式 ?type[] arrayName 和 type arrayName[]; 推薦第一種。這樣只定義了一個引用變量,沒有分配內存空間。

3.初始化:

? ? a.靜態初始化arrayName= new type[] {element1,element2...},type與element類型相同。也可以arrayName = {element1, element2...}

? ? b.動態初始化arrayName= new type[length]

4.foreach用于遍歷數組和集合,for (type variableName : array | collection) { .................},只提供訪問,不提供修改,即修改無效。

5.基本數據類型數組直接在數組引用的內存存儲值,而引用類型數組數組元素是引用,需要再次申請堆內存。

6.二維數組的初始化

public class TwoDimensionTest {public static void main(String[] args) {//定義一個二維數組int[][] a;//把a當成一維數組進行初始化,初始化a是一個長度為4的數組//a數組的數組元素又是引用類型a = new int[4][];//把a數組當成一維數組,遍歷a數組的每個數組元素for (int i = 0 , len = a.length; i < len ; i++ ){System.out.println(a[i]);}//初始化a數組的第一個元素a[0] = new int[2];//訪問a數組的第一個元素所指數組的第二個元素a[0][1] = 6;//a數組的第一個元素是一個一維數組,遍歷這個一維數組for (int i = 0 , len = a[0].length ; i < len ; i ++ ){System.out.println(a[0][i]);}//同時初始化二維數組的2個維數int[][] b = new int[3][4];//使用靜態初始化的語法來初始化一個二維數組String[][] str1 = new String[][]{new String[3] , new String[]{"hello"}};//使用簡化的靜態初始化語法來初始化二維數組String[][] str2 = {new String[3] , new String[]{"hello"}};System.out.println(str1[1][0]);System.out.println(str2[1][0]);} }

7.數組操作的工具類Arrays


五、面向對象(上)

1.一個類中常見的三種成員:構造器、Field和方法

2.static修飾的成員不能訪問沒有static修飾的成員

3.方法傳遞參數凡是只有一種:值傳遞。

4.形參個數可變的方法,在最后一個形參的類型后增加三點...,表示該形參可以接受多個參數值,多個參數的值被當成數組傳入。

public class Varargs {//定義了形參個數可變的方法public static void test(int a , String... books){//books被當成數組處理for (String tmp : books){System.out.println(tmp);}//輸出整數變量a的值System.out.println(a);}public static void main(String[] args) {//調用test方法test(5 , "瘋狂Java講義" , "輕量級Java EE企業應用實戰");} }5.重載:同一個類中包含了兩個或兩個以上方法的方法名相同,但是形參列表不同。方法返回值類型、修飾符等,與方法重載沒有任何關系。可變參數方法重載時,優先選擇不可變參數的方法。

6.雖然類實例可以訪問static Field,但是建議用類.StaticField來訪問。

7.訪問控制權限。

8.希望子類重寫的方法,可以用protected修飾符

9.父包中要使用子包中的類需要寫成完整包路徑加類名,使用import后可以直接使用類名。

10.Java默認為所有源文件導入java.lang包下的所有類。

11.import static 可以導入類的static Field

import static java.lang.System.*; import static java.lang.Math.*;public class StaticImportTest {public static void main(String[] args) {//out是java.lang.System類的靜態Field,代表標準輸出//PI是java.lang.Math類的靜態Field,表示π常量out.println(PI);//直接調用Math類的sqrt靜態方法out.println(sqrt(256));} }12. 一旦提供了構造器,則不再使用默認的構造器,通常在提供構造器后要提供一個無參構造器

13.幾個構造器中與重疊代碼時,可以用一個構造器通過this(x,y)調用另一個構造器

public class Apple {public String name;public String color;public double weight;public Apple(){}//兩個參數的構造器public Apple(String name , String color){this.name = name;this.color = color;}//三個參數的構造器public Apple(String name , String color , double weight){//通過this調用另一個重載的構造器的初始化代碼this(name , color);//下面this引用該構造器正在初始化的Java對象this.weight = weight;} }14.子類包含與父類同名方法的現象被稱為方法重寫,重寫要遵循“兩同兩小一大”規則,“兩同”即方法名相同、形參列表相同;“兩小”指的是子類方法返回值類型應比父類方法返回值類型更小或者相等,子類方法聲明拋出的異常類應比父類方法聲明拋出的異常類更小或相等;"一大“,子類方法的訪問權限應比父類方法更大或者相等

15.父類中的private方法對子類是隱藏的,因此其子類無法訪問該方法,也就是無法重寫該方法,即使子類中有相同的private方法,也不是重寫

16.子類Field定義了與父類Field同名成員變量,那么子類成員變量會覆蓋父類成員變量,但是子類對象初始化時也會為父類成員變量分配存儲空間,必須通過super訪問

17.子類構造器總會調用一次父類構造器,如果沒有顯示調用父類構造器,則調用默認的父類構造器,super顯示調用父類構造器,必須放在子類構造器的第一行

18.創建任何java對象總是先調用object類的構造器,自頂向下調用構造器

19.Java引用變量有編譯時類型和運行時類型兩類,編譯時類型由定義該變量時使用的類型決定,運行時類型由實際賦給該變量的對象決定。編譯時類型和運行時類型不一致,就可能出現所謂的多態性

20.對象的Field并不具有多態性,總是訪問它編譯時類型所定義的Field,而不是它運行時類型所定義的Field

21.將父類引用強制轉換成子類引用時,需要先用instanceof進行預判

22.設計父類的原則:a.盡量隱藏父類的內部數據,多用private,不讓子類直接訪問父類;b.不要讓子類可以隨意訪問、修改父類的方法,輔助方法用private,不希望改寫用final,希望重寫,不希望其他類訪問,可以用protected。c.盡量不要在構造器中調用將要被子類重寫的方法。

class Base {public Base(){test();}public void test() //①號test方法{System.out.println("將被子類重寫的方法");} } public class Sub extends Base {private String name;public void test() //②號test方法{System.out.println("子類重寫父類的方法,"+ "其name字符串長度" + name.length());}public static void main(String[] args){//下面代碼會引發空指針異常Sub s = new Sub();} }當創建Sub對象時,先執行父類構造器,父類構造器調用了被子類重寫方法,則變成調用被子類重寫后的方法。即執行2號test方法,此時name Field是null

23.使用final或者將構造器設置為private,則該類無法成為父類,對于后者可以提供靜態方法以創建該類的實例

24.當子類需要增加額外屬性或者行為方式時,就使用繼承

25.初始化塊是Java類的第四種成員(Field、方法和構造器),先定義的初始化塊先執行,后定義的后執行,用{}表示,初始化塊在構造器之前執行

26.創建Java對象時,先為該對象的所有實例Field分配內存(前提是該類已經被加載過)接著成語開始對這些實例變量執行初始化,先執行初始化塊或者聲明Field時指定的初始值,在執行構造器里指定的初始值。

public class InstanceInitTest {//為a分配內存,再執行初始化塊將a Field賦值為6{a = 6;}//再執行將a Field賦值為9int a = 9;public static void main(String[] args) {//下面代碼將輸出9。System.out.println(new InstanceInitTest().a);} }

27.將多個構造器中相同的代碼提取到初始化塊中定義,能更好地提高初始化代碼的復用,提高整個應用的可維護性

28.在存在繼承關系中,先執行父類的初始化塊,父類的構造器,再執行子類的初始化塊和子類構造器

29.靜態初始化代碼塊使用static,系統將在類初始化階段執行靜態初始化塊,而不是在創建對象時才執行,靜態初始化塊總是比普通初始化塊先執行。通常用于對類Field執行初始化處理,而不能對實例Field進行初始化處理。不能訪問非靜態的Field。靜態初始化塊也要上溯父類。

class Root {static{System.out.println("Root的靜態初始化塊");}{System.out.println("Root的普通初始化塊");}public Root(){System.out.println("Root的無參數的構造器");} } class Mid extends Root {static{System.out.println("Mid的靜態初始化塊");}{System.out.println("Mid的普通初始化塊");}public Mid(){System.out.println("Mid的無參數的構造器");}public Mid(String msg){//通過this調用同一類中重載的構造器this();System.out.println("Mid的帶參數構造器,其參數值:"+ msg);} } class Leaf extends Mid {static{System.out.println("Leaf的靜態初始化塊");}{System.out.println("Leaf的普通初始化塊");} public Leaf(){//通過super調用父類中有一個字符串參數的構造器super("瘋狂Java講義");System.out.println("執行Leaf的構造器");} } public class Test {public static void main(String[] args) {new Leaf();new Leaf();} }30.當JVM第一次主動使用某個類時,系統為在類準備階段為該類的所有靜態Field分配內存;在初始化階段則負責初始化這些靜態Field,初始化靜態Field就是執行類初始化代碼或者聲明類Field時指定的初始值,他們的執行順去與源代碼中的排列順序相同。

public class StaticInitTest {//先執行靜態初始化塊將a靜態Field賦值為6static{a = 6;}//再執行將a靜態Field賦值為9static int a = 9;public static void main(String[] args) {//下面代碼將輸出9。System.out.println(StaticInitTest.a);} }

即先分配空間,再按排列順序執行賦值。


六、面向對象(下)

1.==和equals,==用來判斷兩個對象是否相等(基本類型時,只要值相等,引用類型時,必須指向同一個對象),equals用于判斷引用對象值是否相等,通常需要重寫,默認的是比較對象的地址,String已經重寫了

2.final修飾的類、變量和實例變量不可改變。final修飾的類Field必須在靜態初始化塊中或聲明該Field時指定初始值;實例Field必須在非靜態初始化塊、聲明該Field或構造器中指定初始值。final變量必須初始化后才可以訪問。final局部變量,只能初始化一次

3.當final修飾基本類型變量,不能對基本類型變量重新賦值,但對于引用類型變量,只保證這個引用類型變量所引用的地址不會改變,即一直引用同意對象,但這個對象完全可以發生改變

4.當類Field、實例Field或者局部變量使用了final修飾符,并且在定義該變量時指定了初始值,該初始值在編譯時就被確定下來,那么就相當于是一個直接量(宏替換)

public class FinalLocalTest {public static void main(String[] args) {//定義一個普通局部變量final int a = 5;System.out.println(a);} }變量a其實并不存在,執行System.out.println(a)時轉換為System.out.println(5)

5.final修飾的方法不可被重寫(不希望子類重寫父類的某個方法),final修飾的類不能有子類

6.創建不可變類的規則(類似與String,Double):使用private和final修飾符來修飾該類的Field;提供帶參數構造器,用于根據傳入參數來初始化類里的Field;僅為該類的Field提供Getter方法,不提供Setter方法;如果有必要,重寫Object類的hashCode和equals方法,還要保證用equals判斷相等的對象的hashCode也相等。

public class Address {private final String detail;private final String postCode;//在構造器里初始化兩個實例Fieldpublic Address(){this.detail = "";this.postCode = "";}public Address(String detail , String postCode){this.detail = detail;this.postCode = postCode;}//僅為兩個實例Field提供getter方法public String getDetail(){return this.detail;}public String getPostCode(){return this.postCode;}//重寫equals方法,判斷兩個對象是否相等。public boolean equals(Object obj){if (this == obj){return true;}if(obj != null && obj.getClass() == Address.class){Address ad = (Address)obj;// 當detail和postCode相等時,可認為兩個Address對象相等。if (this.getDetail().equals(ad.getDetail()) && this.getPostCode().equals(ad.getPostCode())){return true;}}return false;}public int hashCode(){return detail.hashCode() + postCode.hashCode() * 31;} }

7.通過引用新的實例對象,就可以避免不可變類的引用類型的Field域被修改

class Name {private String firstName;private String lastName;public Name(){}public Name(String firstName , String lastName){this.firstName = firstName;this.lastName = lastName;}public void setFirstName(String firstName){this.firstName = firstName;}public String getFirstName(){return this.firstName;}public void setLastName(String lastName){this.lastName = lastName;}public String getLastName(){return this.lastName;} } public class Person {private final Name name;public Person(Name name){this.name = name;}public Name getName(){return name;}public static void main(String[] args){Name n = new Name("悟空", "孫");Person p = new Person(n);// Person對象的name的firstName值為"悟空"System.out.println(p.getName().getFirstName());// 改變Person對象name的firstName值n.setFirstName("八戒");// Person對象的name的firstName值被改為"八戒"System.out.println(p.getName().getFirstName());} }修改代碼為:

public class Person {private final Name name;public Person(Name name){ this.name =new Name(name.getFirstName()name.getLastName()); }public Name getName(){return newName(name.getFirstName()name.getLastName());}public static void main(String[] args){Name n = new Name("悟空", "孫");Person p = new Person(n);// Person對象的name的firstName值為"悟空"System.out.println(p.getName().getFirstName());// 改變Person對象name的firstName值n.setFirstName("八戒");// Person對象的name的firstName值被改為"八戒"System.out.println(p.getName().getFirstName());} }8.含有抽象方法的類只能被定義成抽象類(直接定義一個抽象方法;繼承了一個抽象父類沒有完全實現父類包含的抽象方法,以及實現了一個接口,但沒有完全實現接口包含的抽象方法),final和abstract不能同時使用,static不能和abstract同時使用

9.接口中不能包含普通方法,接口中所有方法都是抽象方法。一個接口可以有多個直接父接口,但接口只能繼承接口,不能繼承類。接口里可以包含Field(只能是常量)、方法(只能是抽象實例方法)、內部類(包括內部接口、枚舉)定義。接口中的成員(包括常量、方法、內部類和枚舉)都是public訪問權限,可以省略public,如果指定訪問修飾則只能用public。接口中的Field會自動添加static 和 final修飾符,即自動添加public static final,而且接口中沒有構造器和初始化塊,必須在定義時指定默認值。接口里的方法總是public abstract來修飾。接口里定義的內部類、接口、枚舉類默認都采用public static 兩個修飾符

10.一個類可以繼承一個父類,實現多個接口

11.內部類成員可以直接訪問外部類的私有數據,因為內部類被當成其外部類成員,同一個類成員之間可以相互訪問,但外部類不能訪問內部類的實現細節

12.內部類的文件形式,B是A的內部類,則會生成A.class和A$B.class

13.外部類成員變量、內部類成員變量與內部類里方法的局部變量同名,則可通過使用this,外部類類名.this作為限定來區分。同過OutterClass.this.propName的形式訪問外部類的實例Field,通過this.propName的形式訪問非靜態內部類的實例Field

public class DiscernVariable {private String prop = "外部類的實例變量";private class InClass{private String prop = "內部類的實例變量";public void info(){String prop = "局部變量";//通過 外部類類名.this.varName 訪問外部類實例FieldSystem.out.println("外部類的Field值:" + DiscernVariable.this.prop);//通過 this.varName 訪問內部類實例的FieldSystem.out.println("內部類的Field值:" + this.prop);//直接訪問局部變量System.out.println("局部變量的值:" + prop);}}public void test(){InClass in = new InClass();in.info();}public static void main(String[] args) {new DiscernVariable().test();} }14.內部類也滿足靜態關系,即靜態成員不能訪問非靜態成員,不允許在非靜態內部類里定義靜態成員

15.static修飾的內部類,屬于外部類本身,稱為靜態內部類。外部類不能直接訪問靜態內部類的成員,但可以使用靜態內部類的類名作為調用者來訪問靜態內部類的類成員,也可以使用靜態內部類對象作為調用者來訪問靜態內部類的實例成員。

public class AccessStaticInnerClass {static class StaticInnerClass{private static int prop1 = 5;private int prop2 = 9;}public void accessInnerProp(){//System.out.println(prop1);//上面代碼出現錯誤,應改為如下形式://通過類名訪問靜態內部類的類成員System.out.println(StaticInnerClass.prop1);//System.out.println(prop2);//上面代碼出現錯誤,應改為如下形式://通過實例訪問靜態內部類的實例成員System.out.println(new StaticInnerClass().prop2);} }

16.接口中定義的內部類,默認使用public static 修飾

17.在外部類以外使用非靜態內部類:如果在外部類以外的地方訪問內部類(包括靜態和非靜態),則內部類不能使用private訪問控制權限,private修飾的內部類只能在外部類內部使用,對于使用其他訪問控制符修飾的內部類,則能在訪問控制符對應的訪問權限內使用。

OuterClass.InnerClass varName //在外部類意外定義內部類變量在外部類以外的地方創建非靜態內部類實例

OuterInstance.new InnerConstructor(); class Out {//定義一個內部類,不使用訪問控制符,//即只有同一個包中其他類可訪問該內部類class In{public In(String msg){System.out.println(msg);}} } public class CreateInnerInstance {public static void main(String[] args) {Out.In in = new Out().new In("測試信息");/*上面代碼可改為如下三行代碼:使用OutterClass.InnerClass的形式定義內部類變量Out.In in;創建外部類實例,非靜態內部類實例將寄存在該實例中Out out = new Out();通過外部類實例和new來調用內部類構造器創建非靜態內部類實例in = out.new In("測試信息");*/} } 非靜態內部類構造器必須使用外部類對象來調用。

18.在外部類以外使用靜態內部類

class StaticOut {//定義一個靜態內部類,不使用訪問控制符,//即同一個包中其他類可訪問該內部類static class StaticIn{public StaticIn(){System.out.println("靜態內部類的構造器");}} } public class CreateStaticInnerInstance {public static void main(String[] args) {StaticOut.StaticIn in = new StaticOut.StaticIn();/*上面代碼可改為如下兩行代碼:使用OutterClass.InnerClass的形式定義內部類變量StaticOut.StaticIn in;通過new來調用內部類構造器創建靜態內部類實例in = new StaticOut.StaticIn();*/} }靜態內部類只需使用外部類即可調用構造器,而非靜態內部類必須使用外部類對象來調用構造器

使用靜態內部類只要把外部類當成靜態內部類的包空間即可。
19.匿名內部類適合創建那種只使用一次的類,匿名內部類會立即創建一個該類的實例,匿名內部類必須繼承一個父類,或(最多)實現一個接口。匿名內部類不能是抽象類(需要對象實例),匿名內部類不能定義構造器(無類名),可以定義實例初始化塊

new 父類構造器(實參列表) | (實現接口){ 匿名內部類的類體} interface Product {public double getPrice();public String getName(); } public class AnonymousTest {public void test(Product p){System.out.println("購買了一個" + p.getName() + ",花掉了" + p.getPrice());}public static void main(String[] args) {AnonymousTest ta = new AnonymousTest();//調用test方法時,需要傳入一個Product參數,//此處傳入其匿名實現類的實例ta.test(new Product(){public double getPrice(){return 567.8;}public String getName(){return "AGP顯卡";}});} }20.通過接口創建匿名內部類時,不能顯示創建構造器,只有一個隱式的無參構造器,new 接口名后的括號里不能傳入參數值;通過繼承父類來創建匿名內部類時,匿名內部類自動擁有和父類相似(相同的形參列表)的構造器。
abstract class Device {private String name;public abstract double getPrice();public Device(){}public Device(String name){this.name = name;}//此處省略了name的setter和getter方法public void setName(String name){this.name = name;}public String getName(){return this.name;} } public class AnonymousInner {public void test(Device d){System.out.println("購買了一個" + d.getName()+ ",花掉了" + d.getPrice());}public static void main(String[] args) {AnonymousInner ai = new AnonymousInner();//調用有參數的構造器創建Device匿名實現類的對象ai.test(new Device("電子示波器"){public double getPrice(){return 67.8;}});//調用無參數的構造器創建Device匿名實現類的對象Device d = new Device(){//初始化塊{System.out.println("匿名內部類的初始化塊...");}//實現抽象方法public double getPrice(){return 56.2;}//重寫父類的實例方法public String getName(){return "鍵盤";}};ai.test(d);} } 21. 匿名內部類內部只能訪問外部類的final變量

22.回調就是某一個方法一旦獲得了內部類對象的引用后,就可以在合適的時候反過來去調用外部類實例的方法。所謂回調,就是允許客戶類通過內部類引用來調用其外部類的方法

public class TeachableProgrammer extends Programmer {public TeachableProgrammer(){}public TeachableProgrammer(String name){super(name);}//教學工作依然由TeachableProgrammer類定義private void teach(){System.out.println(getName() + "教師在講臺上講解...");}private class Closure implements Teachable{/*非靜態內部類回調外部類實現work方法,非靜態內部類引用的作用僅僅是向客戶類提供一個回調外部類的途徑*/public void work(){teach();}}//返回一個非靜態內部類引用,允許外部類通過該非靜態內部類引用來回調外部類的方法public Teachable getCallbackReference(){return new Closure();} } public class TeachableProgrammerTest {public static void main(String[] args) {TeachableProgrammer tp = new TeachableProgrammer("李剛");//直接調用TeachableProgrammer類從Programmer類繼承到的work方法tp.work();//表面上調用的是Closure的work方法,//實際上是回調TeachableProgrammer的teach方法tp.getCallbackReference().work();} }23.用enum定義枚舉類,默認繼承了Enum類,Enum類實現了Serializable和Comparable兩個接口。使用enum定義、 非抽象的枚舉類默認會使用final修飾,構造器只能使用private訪問控制符;枚舉類的所有實例必須在枚舉類第一行顯示列出,系統自動添加public static final修飾;枚舉類提供了一個values方法。
public enum SeasonEnum {// 在第一行列出4個枚舉實例SPRING,SUMMER,FALL,WINTER; } public class EnumTest {public void judge(SeasonEnum s){//switch語句里的表達式可以是枚舉值switch (s){case SPRING:System.out.println("春暖花開,正好踏青");break;case SUMMER:System.out.println("夏日炎炎,適合游泳");break;case FALL:System.out.println("秋高氣爽,進補及時");break;case WINTER:System.out.println("冬日雪飄,圍爐賞雪");break;}}public static void main(String[] args){//所有枚舉類都有一個values方法,返回該枚舉類的所有實例for (SeasonEnum s : SeasonEnum.values()){System.out.println(s);}//平常使用枚舉實例時,//總是通過EnumClass.variable形式來訪問new EnumTest().judge(SeasonEnum.SPRING);} }23.枚舉類的實例只能是枚舉值,而不是隨意通過new來創建枚舉對象。每一個實例擁有一套定義的Field

better

public enum Gender {MALE,FEMALE;private String name;public void setName(String name){switch (this){case MALE:if (name.equals("男")){this.name = name;}else{System.out.println("參數錯誤");return;}break;case FEMALE:if (name.equals("女")){this.name = name;}else{System.out.println("參數錯誤");return;}break;}}public String getName(){return this.name;} } public class GenderTest {public static void main(String[] args) {Gender g = Enum.valueOf(Gender.class , "FEMALE");g.setName("女");System.out.println(g + "代表:" + g.getName());//此時設置name值時將會提示參數錯誤。g.setName("男");System.out.println(g + "代表:" + g.getName());} }best

public enum Gender {//此處的枚舉值必須調用對應構造器來創建MALE("男"),FEMALE("女");private final String name;//枚舉類的構造器只能使用private修飾private Gender(String name){this.name = name;}public String getName(){return this.name;} }24. 枚舉類實現接口與普通類一樣。這樣每個實例都有同樣的方法。如果要實現實例方法的差異化,需要讓每個枚舉值分別來實現該方法

public interface GenderDesc {void info(); } public enum Gender implements GenderDesc {//此處的枚舉值必須調用對應構造器來創建MALE("男")//花括號部分實際上是一個類體部分{public void info(){System.out.println("這個枚舉值代表男性");}},FEMALE("女"){public void info(){System.out.println("這個枚舉值代表女性");}};//其他部分與codes\06\6.8\best\Gender.java中的Gender類完全相同 }匿名內部類的原理,創建的是Gender的子類實例
25.在枚舉類里定義抽象方法,為不同枚舉值提供不同實現,也是匿名內部類的原理 public enum Operation2 {PLUS{public double eval(double x , double y){return x + y;}},MINUS{public double eval(double x , double y){return x - y;}},TIMES{public double eval(double x , double y){return x * y;}},DIVIDE{public double eval(double x , double y){return x / y;}};//為枚舉類定義一個抽象方法,//這個抽象方法由不同枚舉值提供不同的實現public abstract double eval(double x, double y);public static void main(String[] args){System.out.println(Operation2.PLUS.eval(3, 4));System.out.println(Operation2.MINUS.eval(5, 4));System.out.println(Operation2.TIMES.eval(5, 4));System.out.println(Operation2.DIVIDE.eval(5, 4));} }6.9 垃圾回收機制回收對象前,會先調用它的finalize方法,該方法可能使該對象重新復活。不要主動調用某個對象的finalize方法;finalize方法何時被調用是否被調用具有不確定性,不一定會執行;finalize方法出現異常時,垃圾回收機制不會報告異常;finalize方法可能使該對象或系統中的其他對象重新可達。

七、與運行環境交互

1.Scanner類是一個基于正則表達式的文本掃描器;System類和Runtime類與程序的運行平臺進行交互

2.StringBuilder和StringBuffer是可變字符串,不同的是StringBuffer是線程安全的,而StringBuilder沒有實現線程安全,性能更高

3.Random類專門用于生成一個偽隨機數,一個構造器使用默認的種子(當前時間),另一個構造器需要顯示傳入long型整數種子。ThreadLocalRandom是Random的增強版,在并發環境下,使用ThreadLocalRandom來代替Random可以減少多線程資源競爭,保證更好的線程安全性

4.Random產生的是偽隨機數,相同的種子,會產生相同的隨機數,推薦使用當前時間作為Random對象的種子

5.BigDecimal用于避免精度丟失

6.Date類在設計上存在缺陷,Calender類能更好的處理日期和時間,其中add會進退位,而roll不會,setLenient(false)關閉容錯性,Month設置13時不會進位。lenient模式可接受超出范圍的值,而non-lenient模式則不能,否則將拋出異常。set方法會延遲修改,到真正訪問時才更新。TimeZone類用于設置時區。

7.Java國際化與格式化


八、Java集合

1.Java集合類主要由兩個接口派生:Collection和Map



2.使用Iterator接口遍歷集合元素,Iterator對象依附于Collection對象。使用Iterator迭代訪問Collection集合元素時,集合里的元素不能被改變

3.foreach,也不能修改迭代變量的值

public class ForeachTest {public static void main(String[] args) {//創建一個集合Collection books = new HashSet();books.add(new String("輕量級Java EE企業應用實戰"));books.add(new String("瘋狂Java講義"));books.add(new String("瘋狂Android講義"));for (Object obj : books){//此處的book變量也不是集合元素本身String book = (String)obj;System.out.println(book);if (book.equals("瘋狂Android講義")){//下面代碼會引發ConcurrentModificationException異常books.remove(book); //①}}System.out.println(books);} } 4.Set集合中判斷兩個對象是否相同不是使用==運算,而是根據equals

5.HashSet需要通過代碼來保證同步性,集合元素值可以是null,HashSet是通過equals和hashCode中的一個或者全部來判斷元素相同,如果需要把某個類的對象保存到HashSet集合中,重寫這個類的equals方法和hashCode方法時,應盡量保證兩個對象通過equals方法比較是返回true時,它們的hashCode方法返回值也相等

6.hashCode方法基本規則:同一個對象多次調用hashCode方法應該返回相同的值;equals方法比較返回true時,這兩個對象的hashCode方法應該返回相等的值;equals方法比較標準的Field,都應該用來計算hashCode值


將對象內每個有意義的Field計算出一個int類型的hashCode;組合之前計算的多個hashCode值計算出一個hashCode值返回(可以通過為各Field乘以任意一個質數后再相加來避免直接想家產生偶然相等。當向hashSet中添加可變對象時可能導致該獨享與集合中的其他對象相等,從而導致HashSet無法準確訪問該對象

class R {int count;public R(int count){this.count = count;}public String toString(){return "R[count:" + count + "]";}public boolean equals(Object obj){if(this == obj)return true;if (obj != null && obj.getClass() == R.class){R r = (R)obj;if (r.count == this.count){return true;}}return false;}public int hashCode(){return this.count;} } public class HashSetTest2 {public static void main(String[] args) {HashSet hs = new HashSet();hs.add(new R(5));hs.add(new R(-3));hs.add(new R(9));hs.add(new R(-2));//打印HashSet集合,集合元素沒有重復System.out.println(hs);//取出第一個元素Iterator it = hs.iterator();R first = (R)it.next();//為第一個元素的count實例變量賦值first.count = -3; //①//再次輸出HashSet集合,集合元素有重復元素System.out.println(hs);//刪除count為-3的R對象hs.remove(new R(-3)); ////可以看到被刪除了一個R元素System.out.println(hs);//輸出falseSystem.out.println("hs是否包含count為-3的R對象?"+ hs.contains(new R(-3)));//輸出falseSystem.out.println("hs是否包含count為5的R對象?"+ hs.contains(new R(5)));} }

先計算hashcode找到桶,再按照equals來判斷是否相等,hs.remove(new R(-3));計算該對象的hashcode后與第三個元素所在桶一致,equals也相等則包含。hs.contains(new R(5))計算hashcode與第一個元素所在桶一直,equals則不想等,所以不包含

7.LinkedHashSet是HashSet子類,需要使用鏈表維護元素的次序;TreeSet是SortedSet接口的實現類,可以確保集合元素處于排序狀態;通過實現Comparable接口,來實現對該類對象的比較,在TreeSet中插入元素時,會自動調用元素的compareTo方法,如果添加到TreeSet中的類對象沒有compareTo方法,會拋出異常。equals方法返回true時,comparaTo應該返回0

8.可以通過Comparator接口實現定制排序

class M {int age;public M(int age){this.age = age;}public String toString(){return "M[age:" + age + "]";} } public class TreeSetTest4 {public static void main(String[] args) {TreeSet ts = new TreeSet(new Comparator(){//根據M對象的age屬性來決定大小public int compare(Object o1, Object o2){M m1 = (M)o1;M m2 = (M)o2;return m1.age > m2.age ? -1: m1.age < m2.age ? 1 : 0;}}); ts.add(new M(5));ts.add(new M(-3));ts.add(new M(9));System.out.println(ts);} }創建了一個Comparator接口的匿名內部類對象,該對象負責ts集合的排序,可以無需實現Comparable接口,由TreeSet關聯的Comparator對象負責集合元素的排序

9.EnumSet是專為枚舉類設計的集合類,其中的元素必須是指定枚舉類型的枚舉值,其中的元素是有序的,以枚舉值在Enum類內的定義順序來決定集合元素的順序。不允許加入null

10.HashSet、TreeSet和EnumSet都是線程不安全的,必須手動保證該Set集合的線程安全,通常可以通過Collections工具類sysnchronizedSortedSet方法來“包裝”該Set集合

SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...))

11.List是Collection接口的子接口,List提供了額外的listIterator()方法,返回一個ListIterator對象,在Iterator基礎上增加了一些用于List的方法

12.ArrayList和Vector是List類的兩個典型實現,initialCapacity控制內部數組的長度,會自增長,如果知道要保存多少元素可以指定initialCapacity大小。ArrayList是線程不安全的,而Vector是線程安全的,Vector性能較低,使用Collections工具類可以將ArrayList編程線程安全的

13.Arrays.ArrayList是固定長度的List集合,只能訪問,不可增加、刪除該集合里的元素

14.Queue接口有一個PriorityQueue實現類,元素按大小進行排序,可以通過插入元素實現Comparable接口實現自然排序,也可以創建PriorityQueue隊列時傳入一個Comparator對象實現定制排序;Queue還有一個Deque接口(雙端隊列,可以當隊列和棧使用),有ArrayDeque和LinkedList兩個實現類

15.HashMap和Hashtable實現了Map接口,Hashtable線程安全,且不能使用null作為key和value,HashMap可以使用null作為key或value,HashMap和Hashtable通過key的equals返回true判斷兩個key是否相等

16.LinkedHashMap鏈表維護Map的迭代順序,與插入順序保持一致

17.EnumMap是一個與枚舉類一起使用的Map實現,所有key都必須是單個枚舉類的枚舉值。根據key的自然順序來維護key-value對的順序。創建EnumMap時必須指定一個枚舉類

enum Season {SPRING,SUMMER,FALL,WINTER } public class EnumMapTest {public static void main(String[] args) {//創建一個EnumMap對象,該EnumMap的所有key//必須是Season枚舉類的枚舉值EnumMap enumMap = new EnumMap(Season.class);enumMap.put(Season.SUMMER , "夏日炎炎");enumMap.put(Season.SPRING , "春暖花開");System.out.println(enumMap);} } 18.HashSet與HashMap的性能選項:容量-capacity,初始化容量-initial capacity,尺寸-size,負載因子-load factor

19.Collections工具類,提供了排序、查找、替換操作和同步控制(通過synchronizedXxx()方法將指定集合包裝成線程同步的集合,解決多線程并發訪問集合時的線程安全問題)

public class SynchronizedTest {public static void main(String[] args){//下面程序創建了四個同步的集合對象Collection c = Collections.synchronizedCollection(new ArrayList());List list = Collections.synchronizedList(new ArrayList()); Set s = Collections.synchronizedSet(new HashSet()); Map m = Collections.synchronizedMap(new HashMap()); } }返回不可變集合:emptyXxx()、singletonXxx()、unmodifiableXxx()

public class UnmodifiableTest {public static void main(String[] args){//創建一個空的、不可改變的List對象List unmodifiableList = Collections.emptyList();//創建一個只有一個元素,且不可改變的Set對象Set unmodifiableSet = Collections.singleton("瘋狂Java講義");//創建一個普通Map對象Map scores = new HashMap();scores.put("語文" , 80);scores.put("Java" , 82);//返回普通Map對象對應的不可變版本Map unmodifiableMap = Collections.unmodifiableMap(scores);//下面任意一行代碼都將引發UnsupportedOperationException異常unmodifiableList.add("測試元素"); //①unmodifiableSet.add("測試元素"); //②unmodifiableMap.put("語文" , 90); //③} }


九、泛型

1.創建了帶泛型聲明的接口、父類之后,可以為該接口創建實現類,或從該父類派生子類,父類不能再包含類型形參,必須指定

public class A extends Apple<T> {...} \\錯誤 public class A extends Apple<String> {...} \\正確子類重寫父類方法時需要將T換成具體的類型

public class A1 extends Apple<String> {// 正確重寫了父類的方法,返回值// 與父類Apple<String>的返回值完全相同public String getInfo(){return "子類" + super.getInfo();}/*// 下面方法是錯誤的,重寫父類方法時返回值類型不一致public Object getInfo(){return "子類";}*/ }如果使用Apple時沒有傳入實際的類型參數,將默認使用Object

2.不管為泛型類傳入哪一種類型實參,對于Java來說,他們依然被當成同一個類處理,在內存中也只占用一塊內存空間,因此在靜態方法、靜態初始化塊或者靜態變量的聲明和初始化中不允許使用類型形參

List<String> l1 = new ArrayList<>(); List<Integer> l2 = new ArrayList<>(); System.out.println(l1.getClass() == l2.getClass()); //實際輸出為true

由于系統并不會真正生成泛型類,所以instanceof運算符后不能使用泛型類

3.如果Foo是Bar的一個子類型,而G是具有泛型聲明的類或接口, G<Foo>并不是G<Bar>的子類,即List<String>不是List<Object>子類,而數組中Foo[]是Bar[]的子類型

4.類型通配符?,List<?>位置類型的List,元素類型可以匹配任何類型,元素為Object,這個List集合可以是任何泛型List的父類

5.被限制的通配符,List<? extends Shape>表示元素為Shape的子類,未知類型一定是Shape子類,Shape為通配符的上限
6.定義類型形參時設定上限,用于表示傳給該類型形參的實際類型要么是該上限類型,要么是該上限類型的子類。

public class Apple<T extends Number> {T col;public static void main(String[] args){Apple<Integer> ai = new Apple<>();Apple<Double> ad = new Apple<>();// 下面代碼將引起編譯異常,下面代碼試圖把String類型傳給T形參// 但String不是Number的子類型,所以引發編譯錯誤Apple<String> as = new Apple<>(); //①} }更極端的情況需要為類型形參設定多個上限( 至多有一個父類上限,可以有多個接口上限) public class Apple<T extends Number & java.io.Serializabel> {...}7.泛型方法需要在方法前指明類型參數

修飾符 <T,S> 返回值類型 方法名(形參列表) { ... }

public class GenericMethodTest {// 聲明一個泛型方法,該泛型方法中帶一個T類型形參,static <T> void fromArrayToCollection(T[] a, Collection<T> c){for (T o : a){c.add(o);}}public static void main(String[] args) {Object[] oa = new Object[100];Collection<Object> co = new ArrayList<>();// 下面代碼中T代表Object類型<Object> fromArrayToCollection(oa, co);String[] sa = new String[100];Collection<String> cs = new ArrayList<>();// 下面代碼中T代表String類型fromArrayToCollection(sa, cs);// 下面代碼中T代表Object類型fromArrayToCollection(sa, co);Integer[] ia = new Integer[100];Float[] fa = new Float[100];Number[] na = new Number[100];Collection<Number> cn = new ArrayList<>(); // 下面代碼中T代表Number類型fromArrayToCollection(ia, cn);// 下面代碼中T代表Number類型fromArrayToCollection(fa, cn); // 下面代碼中T代表Number類型fromArrayToCollection(na, cn);// 下面代碼中T代表Object類型fromArrayToCollection(na, co);// 下面代碼中T代表String類型,但na是一個Number數組,// 因為Number既不是String類型,// 也不是它的子類,所以出現編譯錯誤//fromArrayToCollection(na, cs);} } <>中的T代表泛型參數,而數組的T不代表泛型參數,編譯器通過泛型參數來確定類型。不要制造迷惑,系統一旦迷惑,就出錯了

public class ErrorTest {// 聲明一個泛型方法,該泛型方法中帶一個T類型形參static <T> void test(Collection<T> from, Collection<T> to){for (T ele : from){to.add(ele);}}public static void main(String[] args) {List<Object> as = new ArrayList<>();List<String> ao = new ArrayList<>();// 下面代碼將產生編譯錯誤test(as , ao);} }改為如下形式即可?static <T> void test( Collection<? extends T> from , Collection<T> to)

public class RightTest {// 聲明一個泛型方法,該泛型方法中帶一個T形參static <T> void test(Collection<? extends T> from , Collection<T> to){for (T ele : from){to.add(ele);}}public static void main(String[] args) {List<Object> ao = new ArrayList<>();List<String> as = new ArrayList<>();// 下面代碼完全正常test(as , ao);} }8.大多數時候可以使用泛型方法來代替類型通配符

9.<? super Type>這個通配符表示它必須是Type本身,或是Type的父類,即設定了通配符的下限

public class MyUtils {// 下面dest集合元素類型必須與src集合元素類型相同,或是其父類public static <T> T copy(Collection<? super T> dest , Collection<T> src){T last = null;for (T ele : src){last = ele;dest.add(ele);}return last;}public static void main(String[] args) {List<Number> ln = new ArrayList<>();List<Integer> li = new ArrayList<>();li.add(5);// 此處可準確的知道最后一個被復制的元素是Integer類型// 與src集合元素的類型相同Integer last = copy(ln , li); // ①System.out.println(ln);} }這樣不會丟失返回類型


十、異常處理

1.Checked異常都是可以在編譯階段被處理的異常,必須強制處理,而Runtime異常無需處理

2.常見的異常類之間的繼承關系


Error錯誤一般指與虛擬機相關的問題,這種錯誤無法回復或不可能捕獲,將導致應用程序中斷
3.java7支持多異常捕獲,多種類型異常之間用 ?| ?間隔,捕獲多種類型的異常時,異常變量有隱式的final修飾

public class MultiExceptionTest {public static void main(String[] args) {try{int a = Integer.parseInt(args[0]);int b = Integer.parseInt(args[1]);int c = a / b;System.out.println("您輸入的兩個數相除的結果是:" + c );}catch (IndexOutOfBoundsException|NumberFormatException|ArithmeticException ie){System.out.println("程序發生了數組越界、數字格式異常、算術異常之一");// 捕捉多異常時,異常變量默認有final修飾,// 所以下面代碼有錯:ie = new ArithmeticException("test"); //①}catch (Exception e){System.out.println("未知異常");// 捕捉一個類型的異常時,異常變量沒有final修飾// 所以下面代碼完全正確。e = new RuntimeException("test"); //②}} } 4.除非在try、catch塊中調用了退出虛擬機的方法,否則finally塊總會被執行,即使包含return和throw語句,也要先執行finally后再來執行return或throw,如果finally塊里也使用了return或throw等導致方法終止的語句,finally塊已經終止了方法,系統將不會跳回去執行try塊、catch塊里的任何代碼

5.在java7中增強了try語句,在try關鍵字后緊跟一對圓括號,圓括號可以聲明、初始化一個或多個資源(必須在程序結束時顯示關閉的資源如,數據庫鏈接、網絡鏈接等),try語句在該語句結束時自動關閉這些資源。為了保證try語句可以正常關閉資源,這些資源實現類必須實現AutoCloseable或Closeable接口

public class AutoCloseTest {public static void main(String[] args) throws IOException{try (// 聲明、初始化兩個可關閉的資源// try語句會自動關閉這兩個資源。BufferedReader br = new BufferedReader(new FileReader("AutoCloseTest.java"));PrintStream ps = new PrintStream(newFileOutputStream("a.txt"))){// 使用兩個資源System.out.println(br.readLine());ps.println("莊生曉夢迷蝴蝶");}} } 隱式的finally塊存在,因此這個try語句可以既沒有catch塊,也沒有finally塊。在java7中所有的資源類進行了改寫,實現了AutoCloseable或Closeable接口

6.子類方法聲明拋出的異常類型應該是父類方法聲明拋出的異常類型的子類或相同,子類方法聲明拋出的異常不允許比父類方法聲明拋出的異常多。

7.自定義異常應該繼承Exception基類,自定義Runtime異常,應繼承RuntimeException基類。定義異常類時通常需要提供兩個構造器:一個是無參構造器;另一個是帶一個字符串參數的構造器

8.Java7增強throw

public class ThrowTest2 {public static void main(String[] args)// Java 6認為①號代碼可能拋出Exception,// 所以此處聲明拋出Exception // throws Exception// Java 7會檢查①號代碼可能拋出異常的實際類型,// 因此此處只需聲明拋出FileNotFoundException即可。throws FileNotFoundException{try{new FileOutputStream("a.txt");}catch (Exception ex){ex.printStackTrace();throw ex; //①}} }9.異常鏈,捕獲一個異常然后接著拋出另一個異常,把原始異常信息隱藏起來,僅向上提供必要的異常提示信息的處理方式,可以保證底層異常不會擴散到表現層。

十三、MySQL數據庫與JDBC編程

1.JDBC訪問示意圖


2.Java 7改寫了Connection、Statement、ResultSet等接口,它們都繼承了AutoCloseable接口,因此他們都可以由try語句來關閉

。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。


十四、Annotation(注釋)

1.Annotation是一個接口,可以通過反射來獲取指定程序元素的Annotation對象,然后通過Annotation對象來取得注釋里的元數據,訪問和處理Annotation的工具統稱APT

2.4個基本Annotation:@Override指定方法覆載(強制編譯器檢查子類必須覆蓋父類方法,避免參數錯誤導致編譯正確)@Deprecated表示某個程序元素(類、方法等)已過時,當其他程序使用已過時的類、方法時,編譯器將給出警告@SuppressWarnings取消顯示指定的編譯器警告,并且會一直作用于該元素的子元素@SafeVarargs抑制堆污染警告

3.元Annotation用于修飾其他的Annotation定義。

@Retention只能用于修飾一個Annotation定義,指定被修飾的Annotation可以保留多長時間,包含一個RetentionPolicy類型的value成員變量,必須指定value值為RetentionPolicy中3個靜態變量中,R.Class:將Annotation記錄在class文件中,運行JVM不再保留Annotation(默認);R.RUNTIME:Annotation記錄在class文件中,運行JVM保留Annotation,可通過反射獲取該Annotation信息;R.SOURCE:將Annotation保存在源代碼中,編譯器直接丟棄這種Annotation

@Retention(value=RetentionPolicy.RUNTIME) public @interface Testable{}也可以寫為(只有一個成員變量時)

@Retention(RetentionPolicy.RUNTIME) public @interface Testable{}@Target只能修飾一個Annotation定義,指定被修飾的Annotation能用于修飾哪些程序單元,包含一個value成員變量,取值ElementType.XXXX

@Target(ElementType.METHOD) public @interface Testable{}@Documented指定被修飾的Annotation將被javadoc工具提取成文檔
@Inherited指定被它修飾的Annotation將具有繼承性

4.自定義新的Annotation類型使用@interface關鍵字

public @interface Test { String name() default "jack"; int age();}@Test(name="xxx", age=6) //使用 public void info() { ... }..................................................


十五、輸入、輸出

1.File類與平臺無關的用來操作文件和目錄,不能訪問文件本身的內容。提供了訪問文件名、文件檢測、獲取常規文件信息、文件操作、目錄操作

2.FilenameFilter接口包含一個accept方法,該方法將依次對指定File的所有子目錄或者文件進行迭代

public class FilenameFilterTest {public static void main(String[] args) {File file = new File(".");String[] nameList = file.list(new MyFilenameFilter());for(String name : nameList){System.out.println(name);}} } // 實現自己的FilenameFilter實現類 class MyFilenameFilter implements FilenameFilter {public boolean accept(File dir, String name){// 如果文件名以.java結尾,或者文件對應一個路徑,返回truereturn name.endsWith(".java")|| new File(name).isDirectory();} }3.Java的流主要包括字節流(InputStream、OutputStream)和字符流(Reader、Writer),他們都是抽象的基類。節點流是指與特定的IO設備發生讀寫數據的流,也稱為低級流;處理流則用于對一個已存在的流進行連接或封裝,通過封裝后的流來實現讀寫功能,處理流也稱為高級流。

只要使用相同的處理流,程序就可以采用完全相同的輸入輸出代碼訪問不同的數據源,隨著處理流所包裝節點流的變化,程序實際所訪問的數據源也相應地發生變化。處理流主要以增加緩沖的方式來提高輸入輸出的效率并且可能提供了一系列便捷的方法來一次輸入輸出大批量的內容,處理流可以嫁接在任何已存在的流的基礎上

4.Java 7中的IO資源類都實現了AutoCloseable接口,可以通過try語句關閉IO流。使用Java的IO流執行輸出時,要關閉輸出流,關閉輸出流除了保證流的物理資源被回收外,可能還可以將輸出流緩沖區中的數據flush到物理節點里,因為在執行close方法之前,自動執行輸出流的flush方法。關閉輸入輸出資源時,只需關閉最上層的流即可,系統會自動關閉被該處理流包裝的節點流

5.Java輸入輸出流體系中常用的流分類


6.InputStreamReader和OutputStreamWriter將字節流轉換成字符流

7.System.in代表標準輸入,是InputStream類的實例,使用InputStreamReader將其轉換成字符輸入流,再次包裝成BufferedReader

public class KeyinTest {public static void main(String[] args) {try(// 將Sytem.in對象轉換成Reader對象InputStreamReader reader = new InputStreamReader(System.in);//將普通Reader包裝成BufferedReaderBufferedReader br = new BufferedReader(reader)){String buffer = null;//采用循環方式來一行一行的讀取while ((buffer = br.readLine()) != null){//如果讀取的字符串為"exit",程序退出if (buffer.equals("exit")){System.exit(1);}//打印讀取的內容System.out.println("輸入內容為:" + buffer);}}catch (IOException ioe){ioe.printStackTrace();}} }8.推回輸入流PushbackInputStream和PushbackReader,提供了unread方法,將字節、字符推回到緩沖區,從而允許重復讀取。這兩個推回輸入流都帶有一個推回緩沖區,讀取也是先從推回緩沖區讀取,完全讀取了推回緩沖區的內容后,還沒裝滿read所需的數組時才會從原輸入流中讀取。

public class PushbackTest {public static void main(String[] args) {try(// 創建一個PushbackReader對象,指定推回緩沖區的長度為64PushbackReader pr = new PushbackReader(new FileReader("PushbackTest.java") , 64)){char[] buf = new char[32];// 用以保存上次讀取的字符串內容String lastContent = "";int hasRead = 0;// 循環讀取文件內容while ((hasRead = pr.read(buf)) > 0){// 將讀取的內容轉換成字符串String content = new String(buf , 0 , hasRead);int targetIndex = 0;// 將上次讀取的字符串和本次讀取的字符串拼起來,// 查看是否包含目標字符串, 如果包含目標字符串if ((targetIndex = (lastContent + content).indexOf("new PushbackReader")) > 0){// 將本次內容和上次內容一起推回緩沖區pr.unread((lastContent + content).toCharArray());// 指定讀取前面len個字符int len = targetIndex > 32 ? 32 : targetIndex;// 再次讀取指定長度的內容(就是目標字符串之前的內容)pr.read(buf , 0 , len);// 打印讀取的內容System.out.print(new String(buf , 0 ,len));System.exit(0);}else{// 打印上次讀取的內容System.out.print(lastContent);// 將本次內容設為上次讀取的內容lastContent = content;}}}catch (IOException ioe){ioe.printStackTrace();}} }9.重定向標準輸入輸出的方法:

public void setErr(PringStream err): public void setIn(InputStream in): public void setOut(PrintStream out): public class RedirectOut {public static void main(String[] args) {try(// 一次性創建PrintStream輸出流PrintStream ps = new PrintStream(new FileOutputStream("out.txt"))){// 將標準輸出重定向到ps輸出流System.setOut(ps);// 向標準輸出輸出一個字符串System.out.println("普通字符串");// 向標準輸出輸出一個對象System.out.println(new RedirectOut());}catch (IOException ex){ex.printStackTrace();}} } public class RedirectIn {public static void main(String[] args) {try(FileInputStream fis = new FileInputStream("RedirectIn.java")){// 將標準輸入重定向到fis輸入流System.setIn(fis);// 使用System.in創建Scanner對象,用于獲取標準輸入Scanner sc = new Scanner(System.in);// 增加下面一行將只把回車作為分隔符sc.useDelimiter("\n");// 判斷是否還有下一個輸入項while(sc.hasNext()){// 輸出輸入項System.out.println("鍵盤輸入的內容是:" + sc.next());}}catch (IOException ex){ex.printStackTrace();}} }10.Prosess類提供了InputStream getErrorStream():獲取子進程的錯誤流;InputStream getInputStream():獲取子進程的輸入流;OutputStream getOutputStream():獲取子進程的輸出流,要站在本程序的角度看子程序來決定是輸入還是輸出。

public class ReadFromProcess {public static void main(String[] args)throws IOException{// 運行javac命令,返回運行該命令的子進程Process p = Runtime.getRuntime().exec("javac");try(// 以p進程的錯誤流創建BufferedReader對象// 這個錯誤流對本程序是輸入流,對p進程則是輸出流BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream()))){String buff = null;// 采取循環方式來讀取p進程的錯誤輸出while((buff = br.readLine()) != null){System.out.println(buff);}}} } public class WriteToProcess {public static void main(String[] args)throws IOException{ // 運行java ReadStandard命令,返回運行該命令的子進程Process p = Runtime.getRuntime().exec("java ReadStandard");try(// 以p進程的輸出流創建PrintStream對象// 這個輸出流對本程序是輸出流,對p進程則是輸入流PrintStream ps = new PrintStream(p.getOutputStream())){// 向ReadStandard程序寫入內容,這些內容將被ReadStandard讀取ps.println("普通字符串");ps.println(new WriteToProcess());}} } // 定義一個ReadStandard類,該類可以接受標準輸入, // 并將標準輸入寫入out.txt文件。 class ReadStandard {public static void main(String[] args){try(// 使用System.in創建Scanner對象,用于獲取標準輸入Scanner sc = new Scanner(System.in);PrintStream ps = new PrintStream(new FileOutputStream("out.txt"))){// 增加下面一行將只把回車作為分隔符sc.useDelimiter("\n");// 判斷是否還有下一個輸入項while(sc.hasNext()){// 輸出輸入項ps.println("鍵盤輸入的內容是:" + sc.next());}}catch(IOException ioe){ioe.printStackTrace();}} }11.RandomAccessFile可以自由訪問文件的任意位置,既可以輸入也可以輸出。文件位置指針定位后進行輸出會覆蓋原有內容,需要借用緩沖方案(數組、臨時文件等)來實現追加功能

public class InsertContent {public static void insert(String fileName , long pos, String insertContent) throws IOException{File tmp = File.createTempFile("tmp" , null);tmp.deleteOnExit();try(RandomAccessFile raf = new RandomAccessFile(fileName , "rw");// 創建一個臨時文件來保存插入點后的數據FileOutputStream tmpOut = new FileOutputStream(tmp);FileInputStream tmpIn = new FileInputStream(tmp)){raf.seek(pos);// ------下面代碼將插入點后的內容讀入臨時文件中保存------byte[] bbuf = new byte[64];// 用于保存實際讀取的字節數int hasRead = 0;// 使用循環方式讀取插入點后的數據while ((hasRead = raf.read(bbuf)) > 0 ){// 將讀取的數據寫入臨時文件tmpOut.write(bbuf , 0 , hasRead);}// ----------下面代碼插入內容----------// 把文件記錄指針重新定位到pos位置raf.seek(pos);// 追加需要插入的內容raf.write(insertContent.getBytes());// 追加臨時文件中的內容while ((hasRead = tmpIn.read(bbuf)) > 0 ){raf.write(bbuf , 0 , hasRead);}}}public static void main(String[] args) throws IOException{insert("InsertContent.java" , 45 , "插入的內容\r\n");} }12.序列化Serialize,反序列化Deserialize。要序列化必須實現Serializable(標記接口,無需實現任何方法)或Externalizable接口。反序列化要提供Java對象所屬類的class文件,無需調用構造器
public class Personimplements java.io.Serializable {private String name;private int age;// 注意此處沒有提供無參數的構造器!public Person(String name , int age){System.out.println("有參數的構造器");this.name = name;this.age = age;}// 省略name與age的setter和getter方法// name的setter和getter方法public void setName(String name){this.name = name;}public String getName(){return this.name;}// age的setter和getter方法public void setAge(int age){this.age = age;}public int getAge(){return this.age;}} public class WriteObject {public static void main(String[] args) {try(// 創建一個ObjectOutputStream輸出流ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"))){Person per = new Person("孫悟空", 500);// 將per對象寫入輸出流oos.writeObject(per);}catch (IOException ex){ex.printStackTrace();}} } public class ReadObject {public static void main(String[] args){try(// 創建一個ObjectInputStream輸入流ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"))){// 從輸入流中讀取一個Java對象,并將其強制類型轉換為Person類Person p = (Person)ois.readObject();System.out.println("名字為:" + p.getName()+ "\n年齡為:" + p.getAge());}catch (Exception ex){ex.printStackTrace();}} }使用序列化機制向文件中寫入了多個java對象,使用反序列化機制恢復對象時必須按實際寫入的順序讀取; 當一個可序列化類有多個父類時(直接或間接),這些父類要么有無參數的構造器,要么也是可序列化的,如果父類不可序列化,只是帶有無參數的構造器,則父類中定義的Field值不會序列化到二進制流中

13.如果類的Field類型是引用類型,那么這個引用類型必須是可序列化的,否則擁有該類型的Field的類也是不可序列化的。

14.當多個對象引用了同一個對象時,不會多次序列化引用對象,只是在初次序列化時會序列化該對象,之后直接輸出一個序列化編號

假設序列化順序為t1->t2->per

潛在的問題,先序列化,然后修改引用對象,再序列化時,只會輸出一個編號

public class SerializeMutable {public static void main(String[] args) {try(// 創建一個ObjectOutputStream輸入流ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("mutable.txt"));// 創建一個ObjectInputStream輸入流ObjectInputStream ois = new ObjectInputStream(new FileInputStream("mutable.txt"))){ Person per = new Person("孫悟空", 500);// 系統會per對象轉換字節序列并輸出oos.writeObject(per);// 改變per對象的name Fieldper.setName("豬八戒");// 系統只是輸出序列化編號,所以改變后的name不會被序列化oos.writeObject(per);Person p1 = (Person)ois.readObject(); //①Person p2 = (Person)ois.readObject(); //②// 下面輸出true,即反序列化后p1等于p2System.out.println(p1 == p2);// 下面依然看到輸出"孫悟空",即改變后的Field沒有被序列化System.out.println(p2.getName());}catch (Exception ex){ex.printStackTrace();}} }15.包含一些敏感信息,或者某個Field類型是不可序列化的,不希望對該Field進行序列化。可以在Field前面使用transient關鍵字修飾,這樣反序列化時該Field將返回0或者null等,就無法取得該Field值。

public class TransientTest {public static void main(String[] args) {try(// 創建一個ObjectOutputStream輸出流ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("transient.txt"));// 創建一個ObjectInputStream輸入流ObjectInputStream ois = new ObjectInputStream(new FileInputStream("transient.txt"))){Person per = new Person("孫悟空", 500);// 系統會per對象轉換字節序列并輸出oos.writeObject(per);Person p = (Person)ois.readObject();System.out.println(p.getAge());}catch (Exception ex){ex.printStackTrace();}} }16.Java提供了自定義序列化機制,可以控制如何序列化各Field,甚至完全不序列化某些Field。下列特殊方法用以實現自定義序列化


writeObject方法負責寫入特定類的實例狀態,以便相應的readObject方法可以恢復它,重寫該方法可以完全獲得對序列化機制的控制,可以自主決定哪些Field需要序列化,需要怎樣序列化,默認會調用out.defaultWriteObject來保存Java對象的各Field,從而可以實現序列化Java對象那個狀態的目的。readObject與此相反用于反序列化,默認調用in.defaultReadObjectlai恢復Java對象的非靜態和非瞬態Field。當序列化流不完整時,readObjectNoData方法可以用來正確地初始化反序列化的對象,例如接收方使用的反序列化類的版本不同于發送方,或者接收方版本擴展的類不是發送方版本擴展的類,或者序列化流被篡改時,系統都會調用readObjectNoData方法來初始化反序列化的對象。

public class Personimplements java.io.Serializable {private String name;private int age;// 注意此處沒有提供無參數的構造器!public Person(String name , int age){System.out.println("有參數的構造器");this.name = name;this.age = age;}// 省略name與age的setter和getter方法// name的setter和getter方法public void setName(String name){this.name = name;}public String getName(){return this.name;}// age的setter和getter方法public void setAge(int age){this.age = age;}public int getAge(){return this.age;}private void writeObject(java.io.ObjectOutputStream out)throws IOException{// 將name Field的值反轉后寫入二進制流out.writeObject(new StringBuffer(name).reverse());out.writeInt(age);}private void readObject(java.io.ObjectInputStream in)throws IOException, ClassNotFoundException{// 將讀取的字符串反轉后賦給name Fieldthis.name = ((StringBuffer)in.readObject()).reverse().toString();this.age = in.readInt();} }

17.Java的序列化機制保證在序列化某個對象之前,先調用該對象的writeReplace方法,如果該方法返回另一個Java對象,則系統轉為序列化另一個對象

public class Personimplements java.io.Serializable {private String name;private int age;// 注意此處沒有提供無參數的構造器!public Person(String name , int age){System.out.println("有參數的構造器");this.name = name;this.age = age;}// 省略name與age的setter和getter方法// name的setter和getter方法public void setName(String name){this.name = name;}public String getName(){return this.name;}// age的setter和getter方法public void setAge(int age){this.age = age;}public int getAge(){return this.age;}// 重寫writeReplace方法,程序在序列化該對象之前,先調用該方法private Object writeReplace()throws ObjectStreamException{ArrayList<Object> list = new ArrayList<>();list.add(name);list.add(age);return list;} } public class ReplaceTest {public static void main(String[] args) {try(// 創建一個ObjectOutputStream輸出流ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("replace.txt"));// 創建一個ObjectInputStream輸入流ObjectInputStream ois = new ObjectInputStream(new FileInputStream("replace.txt"))){Person per = new Person("孫悟空", 500);// 系統將per對象轉換字節序列并輸出oos.writeObject(per);// 反序列化讀取得到的是ArrayListArrayList list = (ArrayList)ois.readObject();System.out.println(list);}catch (Exception ex){ex.printStackTrace();}} }18.另一種自定義序列化機制,完全由程序員決定存儲和恢復對象數據,Java類必須實現Externalizable接口,接口中定義了readExternal和writeExternal方法。Externalizable接口強制自定義序列化
public class Personimplements java.io.Externalizable {private String name;private int age;// 注意此處沒有提供無參數的構造器!public Person(String name , int age){System.out.println("有參數的構造器");this.name = name;this.age = age;}// 省略name與age的setter和getter方法// name的setter和getter方法public void setName(String name){this.name = name;}public String getName(){return this.name;}// age的setter和getter方法public void setAge(int age){this.age = age;}public int getAge(){return this.age;}public void writeExternal(java.io.ObjectOutput out)throws IOException{// 將name Field的值反轉后寫入二進制流out.writeObject(new StringBuffer(name).reverse());out.writeInt(age);}public void readExternal(java.io.ObjectInput in)throws IOException, ClassNotFoundException{// 將讀取的字符串反轉后賦給name Fieldthis.name = ((StringBuffer)in.readObject()).reverse().toString();this.age = in.readInt();} } 19.對象的類名、Field都會被序列化;方法、static Field、transient Field都不會被序列化,反序列化對象時必須有序列化對象的class文件;通過文件、網絡來讀取序列化后的對象時,必須按實際寫入的順序讀取

20.通過添加private static final long serialVersionUID標識該Java類的序列化版本,不人為指定JVM根據類信息計算,而修改后的類的計算結果與修改前的類的計算結果往往不同。修改方法、靜態Field或瞬態Field,反序列化不受任何影響

21.Channel和Buffer是新IO中(NIO)的兩個核心對象。Channel是對傳統的輸入輸出系統的模擬,在新IO系統中所有的數據都需要通過通道傳輸;Channel與傳統的InputStream、OutputStream最大區別在于它提供了一個map方法將一塊數據映射到內存。Buffer可以理解成一個容器,本質是一個數組,發送到Channel或者從Channel中取出的數據必須先放到Channel中

22.Buffer是一個抽象類,有除boolean外的XxxBuffer類,通過static XxxBuffer allocate(int capacity):創建一個容量為capacity的XxxBuffer對象。ByteBuffer類還有一個子類MappedByteBuffer,用于表示Channel將磁盤文件的部分或全部內容映射到內存中后得到的結果,通常由Channel的map()方法返回。

23.Buffer中三個重要概念:容量capacity,表示Buffer的最大數據容量,創建后不能更改;界限limit,第一個不應該被讀出或者寫入的緩沖區位置索引;位置position,用于指明下一個可以被讀出或者寫入的緩沖區位置索引。flip()方法將limit設置為position所在的位置,并將position設為0為輸出做好了準備;clear()將position設置為0,將limit設為capacity為裝入數據做好準備。put()和get()方法,用于向Buffer中放入和取出數據

public class BufferTest {public static void main(String[] args){// 創建BufferCharBuffer buff = CharBuffer.allocate(8); //①System.out.println("capacity: " + buff.capacity());System.out.println("limit: " + buff.limit());System.out.println("position: " + buff.position());// 放入元素buff.put('a');buff.put('b');buff.put('c'); //②System.out.println("加入三個元素后,position = "+ buff.position());// 調用flip()方法buff.flip(); //③System.out.println("執行flip()后,limit = " + buff.limit());System.out.println("position = " + buff.position());// 取出第一個元素System.out.println("第一個元素(position=0):" + buff.get()); // ④System.out.println("取出一個元素后,position = " + buff.position());// 調用clear方法buff.clear(); //⑤System.out.println("執行clear()后,limit = " + buff.limit()); System.out.println("執行clear()后,position = "+ buff.position());System.out.println("執行clear()后,緩沖區內容并沒有被清除:"+ "第三個元素為:" + buff.get(2)); // ⑥System.out.println("執行絕對讀取后,position = "+ buff.position());} }24.程序不能直接訪問Channel中的數據,包括讀取、寫入都不行,Channel只能與Buffer進行交互。Channel是通過節點流進行的getChannel方法來返回對應的Channel。Channel最常用的3類方法map()映射到Buffer,read()讀取Buffer數據,write()將數據寫入Buffer中

public class FileChannelTest {public static void main(String[] args){File f = new File("FileChannelTest.java");try(// 創建FileInputStream,以該文件輸入流創建FileChannelFileChannel inChannel = new FileInputStream(f).getChannel();// 以文件輸出流創建FileBuffer,用以控制輸出FileChannel outChannel = new FileOutputStream("a.txt").getChannel()){// 將FileChannel里的全部數據映射成ByteBufferMappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY , 0 , f.length()); // ①// 使用GBK的字符集來創建解碼器Charset charset = Charset.forName("GBK");// 直接將buffer里的數據全部輸出outChannel.write(buffer); // ②// 再次調用buffer的clear()方法,復原limit、position的位置buffer.clear();// 創建解碼器(CharsetDecoder)對象CharsetDecoder decoder = charset.newDecoder();// 使用解碼器將ByteBuffer轉換成CharBufferCharBuffer charBuffer = decoder.decode(buffer);// CharBuffer的toString方法可以獲取對應的字符串System.out.println(charBuffer);}catch (IOException ex){ex.printStackTrace();}} }25.Java 7 新增了一個StandardCharsets類,包含了最常用字符集對應的Charset靜態對象 public class CharsetTransform {public static void main(String[] args)throws Exception{// 創建簡體中文對應的CharsetCharset cn = Charset.forName("GBK");// 獲取cn對象對應的編碼器和解碼器CharsetEncoder cnEncoder = cn.newEncoder();CharsetDecoder cnDecoder = cn.newDecoder();// 創建一個CharBuffer對象CharBuffer cbuff = CharBuffer.allocate(8);cbuff.put('孫');cbuff.put('悟');cbuff.put('空');cbuff.flip();// 將CharBuffer中的字符序列轉換成字節序列ByteBuffer bbuff = cnEncoder.encode(cbuff);// 循環訪問ByteBuffer中的每個字節for (int i = 0; i < bbuff.capacity() ; i++){System.out.print(bbuff.get(i) + " ");}// 將ByteBuffer的數據解碼成字符序列System.out.println("\n" + cnDecoder.decode(bbuff));} }也可以直接用Charset對象直接調用encode,decode方法進行編碼解碼(無需生成CharsetDecoder和CharsetEncoder對象)

26.FileLock來支持文件鎖定功能,FileChannel提供lock(),tryLock()方法可以獲得文件鎖對象,lock如果無法得到文件鎖將阻塞,而tryLock則返回null。release方法釋放文件鎖

public class FileLockTest {public static void main(String[] args) throws Exception{try(// 使用FileOutputStream獲取FileChannelFileChannel channel = new FileOutputStream("a.txt").getChannel()){// 使用非阻塞式方式對指定文件加鎖FileLock lock = channel.tryLock();// 程序暫停10sThread.sleep(10000);// 釋放鎖lock.release();}} }27.Java 7 中的NIO有重大改進,提供了全面的文件IO和文件系統訪問支持以及基于異步Channel的IO。Path接口代表一個平臺無關的平臺路徑;Files工具類包含了大量靜態的工具方法來操作文件;Paths包含了兩個返回Path的靜態工廠方法

28.Files工具類中的FileVisitor遍歷文件和目錄,可以通過繼承SimpleFileVisitor實現自己的文件訪問器。

public class FileVisitorTest {public static void main(String[] args)throws Exception{// 遍歷g:\publish\codes\15目錄下的所有文件和子目錄Files.walkFileTree(Paths.get("g:", "publish" , "codes" , "15"), new SimpleFileVisitor<Path>(){// 訪問文件時候觸發該方法@Overridepublic FileVisitResult visitFile(Path file , BasicFileAttributes attrs) throws IOException{System.out.println("正在訪問" + file + "文件");// 找到了FileInputStreamTest.java文件if (file.endsWith("FileInputStreamTest.java")){System.out.println("--已經找到目標文件--");return FileVisitResult.TERMINATE;}return FileVisitResult.CONTINUE;}// 開始訪問目錄時觸發該方法@Overridepublic FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException{System.out.println("正在訪問:" + dir + " 路徑");return FileVisitResult.CONTINUE;}});} }遍歷g:\publish\codes\15目錄下所有文件和子目錄,找到文件以FileVisitorTest.java結尾,停止遍歷

29.WatchService監控文件的變化

30.訪問文件的屬性,java 7 的NIO.2在java.nio.file.attribute包下提供了工具類可以簡單地讀取、修改文件屬性

十六、多線程

1.進程是系統進行資源分配和調度的一個獨立單位,線程是進程的執行單元,一個進程可以擁有多個線程,一個線程必須有一個父進程。線程可以擁有自己的堆棧、自己的程序計數器和自己的局部變量,但不擁有系統資源,它與父進程的其他線程共享該進程所擁有的全部資源。一個線程可以創建和撤銷另一個線程。Thread類代表線程,所有的線程對象都必須是Thread類或其子類的實例

2.繼承Thread類創建線程類:定義Thread類的子類,并重寫該類的run方法,run方法的方法體就代表了線程需要完成的任務;創建Thread子類的實例;調用線程對象的start方法來啟動該線程。主線程由main方法確定,子線程不能共享實例屬性。

public class FirstThread extends Thread {private int i ;// 重寫run方法,run方法的方法體就是線程執行體public void run(){for ( ; i < 100 ; i++ ){// 當線程類繼承Thread類時,直接使用this即可獲取當前線程// Thread對象的getName()返回當前該線程的名字// 因此可以直接調用getName()方法返回當前線程的名System.out.println(getName() + " " + i);}}public static void main(String[] args) {for (int i = 0; i < 100; i++){// 調用Thread的currentThread方法獲取當前線程System.out.println(Thread.currentThread().getName()+ " " + i);if (i == 20){// 創建、并啟動第一條線程new FirstThread().start();// 創建、并啟動第二條線程new FirstThread().start();}}} }3.實現Runnable接口創建線程類:定義Runnable接口的實現類,并重寫該接口的run方法;創建Runnable實現類的實例,并以此實例作為Thread的target來創建Thread對象,該Thread對象才是真正的線程對象,創建Thread對象時為該Thread對象指定一個名字;調用線程對象的start方法來啟動該線程。子線程可以共享實例屬性

public class SecondThread implements Runnable {private int i ;// run方法同樣是線程執行體public void run(){for ( ; i < 100 ; i++ ){// 當線程類實現Runnable接口時,// 如果想獲取當前線程,只能用Thread.currentThread()方法。System.out.println(Thread.currentThread().getName()+ " " + i);}}public static void main(String[] args) {for (int i = 0; i < 100; i++){System.out.println(Thread.currentThread().getName()+ " " + i);if (i == 20){SecondThread st = new SecondThread(); // ①// 通過new Thread(target , name)方法創建新線程new Thread(st , "新線程1").start();new Thread(st , "新線程2").start();}}} }4.使用Callable和Future創建線程:Callable接口是Runnable接口的增強版,提供了一個call方法作為線程執行體,call方法可以有返回值,也可以拋出異常。Future接口來代表Callable接口里call方法的返回值,并為Future接口提供了一個FutureTask實現類,該實現類實現了Future接口,并且實現了Runnable接口可以作為Thread類的target。

步驟如下:創建Callable接口的實現類,并實現call方法;創建Callable實現類的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call方法的返回值;使用FutureTask對象作為Thread對象的target創建并啟動新線程;調用FutureTask對象的get方法來獲得子線程執行結束后的返回值。

Callable接口有泛型限制,Callable接口里的泛型形參類型與call方法返回值類型相同

// 實現Callable接口來實現線程 public class ThirdThread implements Callable<Integer> {// 實現call方法,作為線程執行體public Integer call(){int i = 0;for ( ; i < 100 ; i++ ){System.out.println(Thread.currentThread().getName()+ " 的循環變量i的值:" + i);}// call()方法可以有返回值return i;}public static void main(String[] args) {// 創建Callable對象ThirdThread rt = new ThirdThread();// 使用FutureTask來包裝Callable對象FutureTask<Integer> task = new FutureTask<Integer>(rt);for (int i = 0 ; i < 100 ; i++){System.out.println(Thread.currentThread().getName()+ " 的循環變量i的值:" + i);if (i == 20){// 實質還是以Callable對象來創建、并啟動線程new Thread(task , "有返回值的線程").start();}}try{// 獲取線程返回值System.out.println("子線程的返回值:" + task.get());}catch (Exception ex){ex.printStackTrace();}} }4.創建線程的三種方式對比(將實現Runnable接口與實現Callable接口歸為一種方式后有兩種)

采用實現Runnable、Callable接口的方式創建多線程:線程類只是實現了Runnable接口或Callable接口,還可以繼承其他類;多個線程可以共享同一個target對象,非常適合
5.Thread提供join方法讓一個線程等待另一個線程完成。當某個程序執行流中調用其他線程的join方法時,調用線程將被阻塞,知道被join方法加入的join線程執行完為止

public class JoinThread extends Thread {// 提供一個有參數的構造器,用于設置該線程的名字public JoinThread(String name){super(name);}// 重寫run方法,定義線程執行體public void run(){for (int i = 0; i < 100 ; i++ ){System.out.println(getName() + " " + i);}}public static void main(String[] args)throws Exception{// 啟動子線程new JoinThread("新線程").start();for (int i = 0; i < 100 ; i++ ){if (i == 20){JoinThread jt = new JoinThread("被Join的線程");jt.start();// main線程調用了jt線程的join()方法,main線程// 必須等jt執行結束才會向下執行jt.join(); }System.out.println(Thread.currentThread().getName()+ " " + i);}} }main和新線程并發執行->新線程和被Join的線程并發執行,main等待

6.如果所有的前臺線程都死亡,后臺線程會自動死亡。Thread對象的setDaemon(true)可將指定線程設置成后臺線程

public class DaemonThread extends Thread {// 定義后臺線程的線程執行體與普通線程沒有任何區別public void run(){for (int i = 0; i < 1000 ; i++ ){System.out.println(getName() + " " + i);}}public static void main(String[] args) {DaemonThread t = new DaemonThread();// 將此線程設置成后臺線程t.setDaemon(true);// 啟動后臺線程t.start();for (int i = 0 ; i < 10 ; i++ ){System.out.println(Thread.currentThread().getName()+ " " + i);}// -----程序執行到此處,前臺線程(main線程)結束------// 后臺線程也應該隨之結束} }7.sleep方法讓正在執行的線程暫停一段時間,yield方法不會阻塞該線程,將該線程轉入就緒狀態,只是讓當前線程暫停一下,讓系統的線程調度器重新調度一次。只有優先級>=當前線程并且出于就緒狀態的線程才會獲得執行的機會

public class YieldTest extends Thread {public YieldTest(String name){super(name);}// 定義run方法作為線程執行體public void run(){for (int i = 0; i < 50 ; i++ ){System.out.println(getName() + " " + i);// 當i等于20時,使用yield方法讓當前線程讓步if (i == 20){Thread.yield();}}}public static void main(String[] args)throws Exception{// 啟動兩條并發線程YieldTest yt1 = new YieldTest("高級");// 將ty1線程設置成最高優先級 // yt1.setPriority(Thread.MAX_PRIORITY);yt1.start();YieldTest yt2 = new YieldTest("低級");// 將yt2線程設置成最低優先級 // yt2.setPriority(Thread.MIN_PRIORITY);yt2.start();} }8.線程的優先級在默認情況下與父進程的優先級相同。Thread提供了setPriority和getPriority方法來設置和返回指定線程的優先級,setPriority參數是一個1-10之間的整數,Thread類提供靜態常量MAX_PRIORITY=10,MIN_PRIORITY=1,NORM_PRIORITY=5,1-10與操作系統實現有關,最好是用靜態常量

9.synchronized(obj) {...}同步代碼快,obj為同步監視器。同步監視器用于阻止兩個線程對同一個共享資源進行并發訪問,因此通常使用可能被并發訪問的共享資源充當同步監視器。取錢程序中的賬戶可作為同步監視器

public class DrawThread extends Thread {// 模擬用戶賬戶private Account account;// 當前取錢線程所希望取的錢數private double drawAmount;public DrawThread(String name , Account account , double drawAmount){super(name);this.account = account;this.drawAmount = drawAmount;}// 當多條線程修改同一個共享數據時,將涉及數據安全問題。public void run(){// 使用account作為同步監視器,任何線程進入下面同步代碼塊之前,// 必須先獲得對account賬戶的鎖定——其他線程無法獲得鎖,也就無法修改它// 這種做法符合:“加鎖 → 修改 → 釋放鎖”的邏輯synchronized (account){// 賬戶余額大于取錢數目if (account.getBalance() >= drawAmount){// 吐出鈔票System.out.println(getName()+ "取錢成功!吐出鈔票:" + drawAmount);try{Thread.sleep(1);}catch (InterruptedException ex){ex.printStackTrace();}// 修改余額account.setBalance(account.getBalance() - drawAmount);System.out.println("\t余額為: " + account.getBalance()); }else{System.out.println(getName() + "取錢失敗!余額不足!");}}//同步代碼塊結束,該線程釋放同步鎖} }

10.使用synchronized修飾某個方法,則該方法稱為同步方法,同步方法的同步監視器為this(默認指定)。使用同步方法可以非常方便地實現線程安全的類,線程安全的類具有如下特征:該類的對象可以被多個線程安全的訪問;每個線程調用該對象的任意方法之后都將得到正確的結果;每個線程調用該對象的任意方法后,該對象狀態依然保持合理狀態。不可變類總是線程安全的,因為它的對象狀態不可改變,但可變對象需要額外的方法來保證其線程安全。在取款程序中,只需要將balance的方法修改成同步方法即可

public class Account {// 封裝賬戶編號、賬戶余額兩個Fieldprivate String accountNo;private double balance;public Account(){}// 構造器public Account(String accountNo , double balance){this.accountNo = accountNo;this.balance = balance;}// accountNo的setter和getter方法public void setAccountNo(String accountNo){this.accountNo = accountNo;}public String getAccountNo(){return this.accountNo;}// 因此賬戶余額不允許隨便修改,所以只為balance提供getter方法,public double getBalance(){return this.balance;}// 提供一個線程安全draw()方法來完成取錢操作public synchronized void draw(double drawAmount){// 賬戶余額大于取錢數目if (balance >= drawAmount){// 吐出鈔票System.out.println(Thread.currentThread().getName()+ "取錢成功!吐出鈔票:" + drawAmount);try{Thread.sleep(1);}catch (InterruptedException ex){ex.printStackTrace();}// 修改余額balance -= drawAmount;System.out.println("\t余額為: " + balance);}else{System.out.println(Thread.currentThread().getName()+ "取錢失敗!余額不足!");}}// 下面兩個方法根據accountNo來重寫hashCode()和equals()方法public int hashCode(){return accountNo.hashCode();}public boolean equals(Object obj){if(this == obj)return true;if (obj !=null&& obj.getClass() == Account.class){Account target = (Account)obj;return target.getAccountNo().equals(accountNo);}return false;} }任一時刻只能有一個線程獲得對Account對象的鎖定(同步監視器this),然后執行取錢操作
public class DrawThread extends Thread {// 模擬用戶賬戶private Account account;// 當前取錢線程所希望取的錢數private double drawAmount;public DrawThread(String name , Account account , double drawAmount){super(name);this.account = account;this.drawAmount = drawAmount;}// 當多條線程修改同一個共享數據時,將涉及數據安全問題。public void run(){// 直接調用account對象的draw方法來執行取錢// 同步方法的同步監視器是this,this代表調用draw()方法的對象。// 也就是說:線程進入draw()方法之前,必須先對account對象的加鎖。account.draw(drawAmount);} } 11.可變類的線程安全是以降低程序的運行效率為代價的,可采用如下策略減少線程安全所帶來的負面影響:不要對線程安全類的所有方法都進行同步,只對那些會改變競爭資源的方法進行同步;如果可變類有兩種運行環境:單線程和多線程,則應該為該可變類提供兩種版本,線程安全與線程不安全版本(如StringBuilder保證性能,StringBuffer保證線程安全)
12.同步監視器鎖定釋放:當前線程的同步方法、同步代碼快執行結束,當前線程即釋放同步監視器;當前線程在同步代碼庫、同步方法中遇到了break、return終止了該代碼塊、該方法的繼續執行,當前線程將會釋放同步監視器;當前線程在同步代碼塊、同步方法中出現了未處理的Error或Exception,導致了該代碼塊、該方法異常結束時,當前線程將會釋放同步監視器;當前線程執行同步代碼塊或同步方法時,程序執行了同步監視器對象的wait方法,則當前線程暫停,并釋放同步監視器。

13.如下情況,線程不會釋放同步監視器:線程執行同步代碼塊或同步方法時,程序調用sleep、yield方法來暫停當前線程的執行,當前線程不會釋放同步監視器;線程執行同步代碼快時,其他線程調用了該線程的suspend方法將該線程掛起,該線程不會釋放同步監視器。盡量避免使用suspend和resume方法來控制線程

14.同步鎖Lock提供了比syschronized方法和代碼塊更廣泛的鎖定操作,Lock實現允許更靈活的結構,可以具有差別很大的屬性,并且支持多個相關的Condition對象。Lock是控制多個線程對共享資源進行訪問的工具。通常,鎖提供了對共享資源的獨占訪問,每次只能有一個線程對Lock對象加鎖,線程開始訪問共享資源之前應先獲得Lock對象。某些鎖允許對共享資源并發訪問,如ReadWriteLock,Lock、ReadWriteLock是Java5新提供大的兩個根接口,并未Lock提供了ReentrantLock(可重入鎖)實現類;為ReadWriteLock提供了ReentrantReadWriteLock實現類

public class Account {// 定義鎖對象private final ReentrantLock lock = new ReentrantLock();// 封裝賬戶編號、賬戶余額兩個Fieldprivate String accountNo;private double balance;public Account(){}// 構造器public Account(String accountNo , double balance){this.accountNo = accountNo;this.balance = balance;}// accountNo的setter和getter方法public void setAccountNo(String accountNo){this.accountNo = accountNo;}public String getAccountNo(){return this.accountNo;}// 因此賬戶余額不允許隨便修改,所以只為balance提供getter方法,public double getBalance(){return this.balance;}// 提供一個線程安全draw()方法來完成取錢操作public void draw(double drawAmount){// 加鎖lock.lock();try{// 賬戶余額大于取錢數目if (balance >= drawAmount){// 吐出鈔票System.out.println(Thread.currentThread().getName()+ "取錢成功!吐出鈔票:" + drawAmount);try{Thread.sleep(1);}catch (InterruptedException ex){ex.printStackTrace();}// 修改余額balance -= drawAmount;System.out.println("\t余額為: " + balance);}else{System.out.println(Thread.currentThread().getName()+ "取錢失敗!余額不足!");}}finally{// 修改完成,釋放鎖lock.unlock();} }// 下面兩個方法根據accountNo來重寫hashCode()和equals()方法public int hashCode(){return accountNo.hashCode();}public boolean equals(Object obj){if(this == obj)return true;if (obj !=null&& obj.getClass() == Account.class){Account target = (Account)obj;return target.getAccountNo().equals(accountNo);}return false;} }每個Lock對象對應一個Account對象。ReentrantLock鎖具有可重入性,一個線程可以對已被加鎖的ReentrantLock鎖再次加鎖

15.死鎖案例

class A {public synchronized void foo( B b ){System.out.println("當前線程名: " + Thread.currentThread().getName()+ " 進入了A實例的foo方法" ); //①try{Thread.sleep(200);}catch (InterruptedException ex){ex.printStackTrace();}System.out.println("當前線程名: " + Thread.currentThread().getName()+ " 企圖調用B實例的last方法"); //③b.last();}public synchronized void last(){System.out.println("進入了A類的last方法內部");} } class B {public synchronized void bar( A a ){System.out.println("當前線程名: " + Thread.currentThread().getName()+ " 進入了B實例的bar方法" ); //②try{Thread.sleep(200);}catch (InterruptedException ex){ex.printStackTrace();}System.out.println("當前線程名: " + Thread.currentThread().getName() + " 企圖調用A實例的last方法"); //④a.last();}public synchronized void last(){System.out.println("進入了B類的last方法內部");} } public class DeadLock implements Runnable {A a = new A();B b = new B();public void init(){Thread.currentThread().setName("主線程");// 調用a對象的foo方法a.foo(b);System.out.println("進入了主線程之后");}public void run(){Thread.currentThread().setName("副線程");// 調用b對象的bar方法b.bar(a);System.out.println("進入了副線程之后");}public static void main(String[] args){DeadLock dl = new DeadLock();// 以dl為target啟動新線程new Thread(dl).start();// 調用init()方法dl.init();} }16.傳統的線程通信借助Object類提供的wait、notify、notifyAll這3個方法,必須由同步監視器對象來調用。syschronized同步方法,該類的默認實例this就是同步監視器,可以在同步方法中直接調用這3個方法;synchronized同步代碼塊,同步監視器是synchronized后括號里的對象,所以必須使用該對象調用這3個方法。

wait導致當前線程等待,直到其他線程調用該同步監視器的notify方法或notifyAll方法來喚醒該線程,調用wait方法當前線程會釋放對于該同步監視器的鎖定;

notify喚醒在此同步監視器上等待的單個線程,任意選擇一個喚醒,只有當前線程放棄對該同步監視器的鎖定后,才可以執行被喚醒的線程;

notifyAll喚醒在此同步監視器上等待的所有線程。只有當前線程放棄對該同步監視器的鎖定后,才可以執行被喚醒的線程。

public class Account {// 封裝賬戶編號、賬戶余額兩個Fieldprivate String accountNo;private double balance;//標識賬戶中是否已有存款的旗標private boolean flag = false;public Account(){}// 構造器public Account(String accountNo , double balance){this.accountNo = accountNo;this.balance = balance;}// accountNo的setter和getter方法public void setAccountNo(String accountNo){this.accountNo = accountNo;}public String getAccountNo(){return this.accountNo;}// 因此賬戶余額不允許隨便修改,所以只為balance提供getter方法,public double getBalance(){return this.balance;}public synchronized void draw(double drawAmount){try{// 如果flag為假,表明賬戶中還沒有人存錢進去,取錢方法阻塞if (!flag){wait();}else{// 執行取錢System.out.println(Thread.currentThread().getName() + " 取錢:" + drawAmount);balance -= drawAmount;System.out.println("賬戶余額為:" + balance);// 將標識賬戶是否已有存款的旗標設為false。flag = false;// 喚醒其他線程notifyAll();}}catch (InterruptedException ex){ex.printStackTrace();}}public synchronized void deposit(double depositAmount){try{// 如果flag為真,表明賬戶中已有人存錢進去,則存錢方法阻塞if (flag) //①{wait();}else{// 執行存款System.out.println(Thread.currentThread().getName()+ " 存款:" + depositAmount);balance += depositAmount;System.out.println("賬戶余額為:" + balance);// 將表示賬戶是否已有存款的旗標設為trueflag = true;// 喚醒其他線程notifyAll();}}catch (InterruptedException ex){ex.printStackTrace();}}// 下面兩個方法根據accountNo來重寫hashCode()和equals()方法public int hashCode(){return accountNo.hashCode();}public boolean equals(Object obj){if(this == obj)return true;if (obj !=null&& obj.getClass() == Account.class){Account target = (Account)obj;return target.getAccountNo().equals(accountNo);}return false;} } public class DrawTest {public static void main(String[] args) {// 創建一個賬戶Account acct = new Account("1234567" , 0);new DrawThread("取錢者" , acct , 800).start();new DepositThread("存款者甲" , acct , 800).start();new DepositThread("存款者乙" , acct , 800).start();new DepositThread("存款者丙" , acct , 800).start();} } public class DepositThread extends Thread {// 模擬用戶賬戶private Account account;// 當前取錢線程所希望存款的錢數private double depositAmount;public DepositThread(String name , Account account , double depositAmount){super(name);this.account = account;this.depositAmount = depositAmount;}// 重復100次執行存款操作public void run(){for (int i = 0 ; i < 100 ; i++ ){account.deposit(depositAmount);}} } public class DrawTest {public static void main(String[] args) {// 創建一個賬戶Account acct = new Account("1234567" , 0);new DrawThread("取錢者" , acct , 800).start();new DepositThread("存款者甲" , acct , 800).start();new DepositThread("存款者乙" , acct , 800).start();new DepositThread("存款者丙" , acct , 800).start();} }

17.Condition類讓已經得到Lock對象卻無法繼續執行的線程釋放Lock對象,Condition對象也可以喚醒其他處于等待的線程。Condition將同步監視器方法(wait、notify、notifyAll方法分解成截然不同的對象,以便通過將這些對象與Lock對象組合使用,為每個對象提供多個等待集。在這種情況下,Lock代替了同步方法或者同步代碼塊,Condition替代了同步監視器的功能。Condition實例被綁定在一個Lock對象上。要獲得特定Lock實例的Condition實例,調用Lock對象的newCondition方法即可。Condition類提供了如下3個方法。

await:類似于隱式同步監視器上的wait方法,導致當前線程等待,知道其他線程調用該Condition的signal方法或signalAll方法來喚醒該線程,await方法有更多變體。。。

signal:喚醒在此Lock對象上等待的單個線程。如果所有線程都在該Lock對象上等待,則會選擇喚醒其中一個線程。選擇是任意性的。只有當前線程使用await方法放棄對該Lock對象的鎖定后,才可以執行被喚醒的線程。

singalAll:喚醒在此Lock對象上等待的所有線程,只有當前線程使用await方法放棄對該Lock對象的鎖定后,才可以執行被喚醒的線程。

public class Account {// 顯式定義Lock對象private final Lock lock = new ReentrantLock();// 獲得指定Lock對象對應的Conditionprivate final Condition cond = lock.newCondition(); // 封裝賬戶編號、賬戶余額兩個Fieldprivate String accountNo;private double balance;//標識賬戶中是否已有存款的旗標private boolean flag = false;public Account(){}// 構造器public Account(String accountNo , double balance){this.accountNo = accountNo;this.balance = balance;}// accountNo的setter和getter方法public void setAccountNo(String accountNo){this.accountNo = accountNo;}public String getAccountNo(){return this.accountNo;}// 因此賬戶余額不允許隨便修改,所以只為balance提供getter方法,public double getBalance(){return this.balance;}public void draw(double drawAmount){// 加鎖lock.lock();try{// 如果flag為假,表明賬戶中還沒有人存錢進去,取錢方法阻塞if (!flag){cond.wait();}else{// 執行取錢System.out.println(Thread.currentThread().getName() + " 取錢:" + drawAmount);balance -= drawAmount;System.out.println("賬戶余額為:" + balance);// 將標識賬戶是否已有存款的旗標設為false。flag = false;// 喚醒其他線程cond.signalAll();}}catch (InterruptedException ex){ex.printStackTrace();}// 使用finally塊來釋放鎖finally{lock.unlock();}}public void deposit(double depositAmount){lock.lock();try{// 如果flag為真,表明賬戶中已有人存錢進去,則存錢方法阻塞if (flag) //①{cond.wait();}else{// 執行存款System.out.println(Thread.currentThread().getName()+ " 存款:" + depositAmount);balance += depositAmount;System.out.println("賬戶余額為:" + balance);// 將表示賬戶是否已有存款的旗標設為trueflag = true;// 喚醒其他線程cond.signalAll();}}catch (InterruptedException ex){ex.printStackTrace();}// 使用finally塊來釋放鎖finally{lock.unlock();}}// 下面兩個方法根據accountNo來重寫hashCode()和equals()方法public int hashCode(){return accountNo.hashCode();}public boolean equals(Object obj){if(this == obj)return true;if (obj !=null&& obj.getClass() == Account.class){Account target = (Account)obj;return target.getAccountNo().equals(accountNo);}return false;} }

顯示地使用Lock對象來充當同步監視器,需要使用Condition對象來暫停、喚醒指定線程

18.使用阻塞隊列控制線程通信BlockingQueue是Queue的子接口,作為線程同步的工具。當生產者線程試圖向BlockingQueue中放入元素時,如果該隊列已滿,則該線程被阻塞;當消費者線程試圖從BlockingQueue中取出元素時,如果該隊列已空,則該線程被阻塞


class Producer extends Thread {private BlockingQueue<String> bq;public Producer(BlockingQueue<String> bq){this.bq = bq;}public void run(){String[] strArr = new String[]{"Java","Struts","Spring"};for (int i = 0 ; i < 999999999 ; i++ ){System.out.println(getName() + "生產者準備生產集合元素!");try{Thread.sleep(200);// 嘗試放入元素,如果隊列已滿,線程被阻塞bq.put(strArr[i % 3]);}catch (Exception ex){ex.printStackTrace();}System.out.println(getName() + "生產完成:" + bq);}} } class Consumer extends Thread {private BlockingQueue<String> bq;public Consumer(BlockingQueue<String> bq){this.bq = bq;}public void run(){while(true){System.out.println(getName() + "消費者準備消費集合元素!");try{Thread.sleep(200);// 嘗試取出元素,如果隊列已空,線程被阻塞bq.take();}catch (Exception ex){ex.printStackTrace();}System.out.println(getName() + "消費完成:" + bq);}} } public class BlockingQueueTest2 {public static void main(String[] args){// 創建一個容量為3的BlockingQueueBlockingQueue<String> bq = new ArrayBlockingQueue<>(1);// 啟動3條生產者線程new Producer(bq).start();new Producer(bq).start();new Producer(bq).start();// 啟動一條消費者線程new Consumer(bq).start();} }

19.ThreadGroup表示線程組,對一批線程進行分類管理,用戶創建的所有線程都屬于指定線程組,未指定線程組則屬于默認線程組,默認情況下,子線程和父線程處于統一線程組。一旦指定線程組將無法修改,直到線程死亡。Thread類提供了構造器來設置新創建的線程屬于哪個線程組,ThreadGroup類提供了構造器來創建實例

class MyThread extends Thread {// 提供指定線程名的構造器public MyThread(String name){super(name);}// 提供指定線程名、線程組的構造器public MyThread(ThreadGroup group , String name){super(group, name);}public void run(){for (int i = 0; i < 20 ; i++ ){System.out.println(getName() + " 線程的i變量" + i);}} } public class ThreadGroupTest {public static void main(String[] args) {// 獲取主線程所在的線程組,這是所有線程默認的線程組ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();System.out.println("主線程組的名字:" + mainGroup.getName());System.out.println("主線程組是否是后臺線程組:" + mainGroup.isDaemon());new MyThread("主線程組的線程").start();ThreadGroup tg = new ThreadGroup("新線程組");tg.setDaemon(true);System.out.println("tg線程組是否是后臺線程組:" + tg.isDaemon());MyThread tt = new MyThread(tg , "tg組的線程甲");tt.start();new MyThread(tg , "tg組的線程乙").start();} }20.Thread.UncaughtExceptionHandler是Thread類的一個靜態內部接口,該接口只有一個方法uncaughtException。Thread類提供了setDefaultUncaughtExceptionHandler和setUncaughtExceptionHandler。ThreadGroup類實現了Thread.UncaughtExceptionHandler接口,所以每個線程所屬的線程組將會作為默認的異常處理器。當一個線程拋出未處理異常時,JVM會首先查找該異常對應的異常處理器,(setUn。。。方法設置的異常處理器),如果找到該異常處理器,則將調用該異常處理器處理該異常;否則,JVM將會調用該線程所屬的線程組對象的uncaughtException方法來處理該異常

class MyExHandler implements Thread.UncaughtExceptionHandler {//實現uncaughtException方法,該方法將處理線程的未處理異常public void uncaughtException(Thread t, Throwable e){System.out.println(t + " 線程出現了異常:" + e);} } public class ExHandler {public static void main(String[] args) {// 設置主線程的異常處理器Thread.currentThread().setUncaughtExceptionHandler(new MyExHandler());int a = 5 / 0; //①System.out.println("程序正常結束!");} } 當使用catch捕獲異常時,異常不會向上傳播給上一級調用者;但是使用異常處理器對異常進行處理后,異常依然會傳播給上一級調用者

21.線程池在系統啟動時即創建大量空閑的線程,程序將一個Runnable對象或者Callable對象傳給線程池,線程池就會啟動一個線程來執行他們的run或call方法,當run或call方法執行結束后,該線程并不會死亡,而是再次返回線程池中成為空閑狀態,等待執行下一個Runnable對象的run或call方法。線程池可以控制最大并發線程數。
通過Executors工廠類來產生線程池,newCachedThreadPool,newFixedThreadPool(int nThreads),new SingleThreadExecutor()返回ExecutorService對象,代表一個線程池;newScheduledThreadPool(int corePoolSize),new SingleThreadScheduledExecutor()返回一個ScheduledExecutorService線程池,它是ExecutorService的子類,指定延遲后執行線程任務。用完線程池后,調用線程池的shutdown方法啟動線程池的關閉序列,此后不能再接收新任務,但會將之前已經提交的任務執行完成;調用shutdownNow方法關閉線程池將試圖停止所有正在執行的活動任務,暫停處理正在等待的任務,并返回等待執行的任務。

22.使用線程池來執行線程任務的步驟如下:調用Executors類的靜態工廠方法創建一個ExecutorService對象;創建Runnable或Callable實現,作為線程執行任務;調用ExecutorService對象的submit方法來提交Runnable或Callable實例;不想提交任何任務時,調用Executorservice對象的shutdown方法來關閉線程池。

class MyThread implements Runnable {public void run(){for (int i = 0; i < 100 ; i++ ){System.out.println(Thread.currentThread().getName()+ "的i值為:" + i);}} } public class ThreadPoolTest {public static void main(String[] args) throws Exception{// 創建一個具有固定線程數(6)的線程池ExecutorService pool = Executors.newFixedThreadPool(6);// 向線程池中提交兩個線程pool.submit(new MyThread());pool.submit(new MyThread());// 關閉線程池pool.shutdown();} }23.Java7提供了ForkJoinPool來支持將一個任務拆分成多個小任務并行計算,再把多個小任務的結果合并成總的計算結果。ForkJoinPool是ExecutorService的實現類。提供了兩個常用的構造器,在創建了ForkJoinPool實例后,就可以調用submit(ForkJoinTask task)或invoke(ForkJoinTask task)方法來執行指定任務了。ForkJoinTask是一個抽象類,它還有兩個子類:RecursiveAction和RecursiveTask,后者有返回值

// 繼承RecursiveAction來實現"可分解"的任務 class PrintTask extends RecursiveAction {// 每個“小任務”只最多只打印50個數private static final int THRESHOLD = 50;private int start;private int end;// 打印從start到end的任務public PrintTask(int start, int end){this.start = start;this.end = end;}@Overrideprotected void compute() {// 當end與start之間的差小于THRESHOLD時,開始打印if(end - start < THRESHOLD){for (int i = start ; i < end ; i++ ){System.out.println(Thread.currentThread().getName()+ "的i值:" + i);}}else{// 如果當end與start之間的差大于THRESHOLD時,即要打印的數超過50個// 將大任務分解成兩個小任務。int middle = (start + end) /2;PrintTask left = new PrintTask(start, middle);PrintTask right = new PrintTask(middle, end);// 并行執行兩個“小任務”left.fork();right.fork();}} } public class ForkJoinPoolTest {public static void main(String[] args) throws Exception{ForkJoinPool pool = new ForkJoinPool();// 提交可分解的PrintTask任務pool.submit(new PrintTask(0 , 300));pool.awaitTermination(2, TimeUnit.SECONDS);// 關閉線程池pool.shutdown();} } class CalTask extends RecursiveTask<Integer> {// 每個“小任務”只最多只累加20個數private static final int THRESHOLD = 20;private int arr[];private int start;private int end;// 累加從start到end的數組元素public CalTask(int[] arr , int start, int end){this.arr = arr;this.start = start;this.end = end;}@Overrideprotected Integer compute(){int sum = 0;// 當end與start之間的差小于THRESHOLD時,開始進行實際累加if(end - start < THRESHOLD){for (int i = start ; i < end ; i++ ){sum += arr[i];}return sum;}else{// 如果當end與start之間的差大于THRESHOLD時,即要打印的數超過20個// 將大任務分解成兩個小任務。int middle = (start + end) /2;CalTask left = new CalTask(arr , start, middle);CalTask right = new CalTask(arr , middle, end);// 并行執行兩個“小任務”left.fork();right.fork();// 把兩個“小任務”累加的結果合并起來return left.join() + right.join();}} } public class Sum {public static void main(String[] args)throws Exception{int[] arr = new int[100];Random rand = new Random();int total = 0;// 初始化100個數字元素for (int i = 0 , len = arr.length; i < len ; i++ ){int tmp = rand.nextInt(20);// 對數組元素賦值,并將數組元素的值添加到total總和中。total += (arr[i] = tmp);}System.out.println(total);ForkJoinPool pool = new ForkJoinPool();// 提交可分解的CalTask任務Future<Integer> future = pool.submit(new CalTask(arr , 0 , arr.length));System.out.println(future.get());// 關閉線程池pool.shutdown();} }

24.ThreadLocal為線程局部變量,為每一個使用該變量的線程都提供一個變量的副本,每一個線程都可以獨立地改變自己的副本,而不會和其他線程的副本沖突

class Account {/* 定義一個ThreadLocal類型的變量,該變量將是一個線程局部變量每個線程都會保留該變量的一個副本 */private ThreadLocal<String> name = new ThreadLocal<>();// 定義一個初始化name屬性的構造器public Account(String str){this.name.set(str);// 下面代碼用于訪問當前線程的name副本的值System.out.println("---" + this.name.get());}// name的setter和getter方法public String getName(){return name.get();}public void setName(String str){this.name.set(str);} } class MyTest extends Thread {// 定義一個Account屬性private Account account;public MyTest(Account account, String name){super(name);this.account = account;}public void run(){// 循環10次for (int i = 0 ; i < 10 ; i++){// 當i == 6時輸出將賬戶名替換成當前線程名if (i == 6){account.setName(getName());}// 輸出同一個賬戶的賬戶名和循環變量System.out.println(account.getName()+ " 賬戶的i值:" + i);}} } public class ThreadLocalTest {public static void main(String[] args) {// 啟動兩條線程,兩條線程共享同一個AccountAccount at = new Account("初始名");/*雖然兩條線程共享同一個賬戶,即只有一個賬戶名但由于賬戶名是ThreadLocal類型的,所以每條線程都完全擁有各自的賬戶名副本,所以從i == 6之后,將看到兩條線程訪問同一個賬戶時看到不同的賬戶名。*/new MyTest(at , "線程甲").start();new MyTest(at , "線程乙").start ();} }

上例只有一個Account對象,但會有三個賬戶名副本

如果多個線程之間需要共享資源,以達到線程之間的通信功能,就使用同步機制;如果僅僅要隔離多個線程之間的共享沖突,則可以使用ThreadLocal

十七、網絡編程

1.三類端口號:公認端口0-1023,緊密綁定一些特定的服務;注冊端口1024-49151,松散綁定一些服務,通常是一些應用程序使用;動態或私有端口49152-65535,應用程序使用的動態端口,一般不會主動使用

2.InetAddress類代表IP地址,有Int4Address、Inet6Address兩個子類

public class InetAddressTest {public static void main(String[] args)throws Exception{// 根據主機名來獲取對應的InetAddress實例InetAddress ip = InetAddress.getByName("www.crazyit.org");// 判斷是否可達System.out.println("crazyit是否可達:" + ip.isReachable(2000)); // 獲取該InetAddress實例的IP字符串System.out.println(ip.getHostAddress());// 根據原始IP地址來獲取對應的InetAddress實例InetAddress local = InetAddress.getByAddress(new byte[]{127,0,0,1});System.out.println("本機是否可達:" + local.isReachable(5000)); // 獲取該InetAddress實例對應的全限定域名System.out.println(local.getCanonicalHostName());} }

3.URLDecoder和URLEncoder用于對鏈接中的字符轉碼,每個漢字占2個字節,每個字節轉換成2個十六進制的數字,每個中文字符將轉換成%XX%XX,也與字符集有關

public class URLDecoderTest {public static void main(String[] args) throws Exception{// 將application/x-www-form-urlencoded字符串// 轉換成普通字符串// 其中的字符串直接從圖17.3所示窗口復制過來String keyWord = URLDecoder.decode("%B7%E8%BF%F1java", "GBK");System.out.println(keyWord);// 將普通字符串轉換成// application/x-www-form-urlencoded字符串String urlStr = URLEncoder.encode("瘋狂Android講義" , "GBK");System.out.println(urlStr);} }4.URL類提供了多個構造器用于創建URL對象,URL實例的openConnection方法返回一個URLConnection對象,代表了與URL所引用的遠程對象的連接;openStream打開與此URL的鏈接,并返回一個用于讀取該URL資源的InputStream。如果只是發送GET方式請求,則使用connect方法建立和遠程資源之間的實際連接即可;如果需要發送POST方式的請求,則需要獲取URLConnection實例對應的輸出流來發送請求信息

public class GetPostTest {/*** 向指定URL發送GET方法的請求* @param url 發送請求的URL* @param param 請求參數,格式滿足name1=value1&name2=value2的形式。* @return URL所代表遠程資源的響應*/public static String sendGet(String url , String param) {String result = "";String urlName = url + "?" + param;try{URL realUrl = new URL(urlName);// 打開和URL之間的連接URLConnection conn = realUrl.openConnection();// 設置通用的請求屬性conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); // 建立實際的連接conn.connect(); // 獲取所有響應頭字段Map<String, List<String>> map = conn.getHeaderFields();// 遍歷所有的響應頭字段for (String key : map.keySet()){System.out.println(key + "--->" + map.get(key));}try(// 定義BufferedReader輸入流來讀取URL的響應BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream() , "utf-8"))){String line;while ((line = in.readLine())!= null){result += "\n" + line;}}}catch(Exception e){System.out.println("發送GET請求出現異常!" + e);e.printStackTrace();}return result;}/*** 向指定URL發送POST方法的請求* @param url 發送請求的URL* @param param 請求參數,格式應該滿足name1=value1&name2=value2的形式。* @return URL所代表遠程資源的響應*/ public static String sendPost(String url , String param){String result = "";try{URL realUrl = new URL(url);// 打開和URL之間的連接URLConnection conn = realUrl.openConnection();// 設置通用的請求屬性conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); // 發送POST請求必須設置如下兩行conn.setDoOutput(true);conn.setDoInput(true);try(// 獲取URLConnection對象對應的輸出流PrintWriter out = new PrintWriter(conn.getOutputStream())){// 發送請求參數out.print(param);// flush輸出流的緩沖out.flush();}try(// 定義BufferedReader輸入流來讀取URL的響應BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream() , "utf-8"))){String line;while ((line = in.readLine())!= null){result += "\n" + line;}}}catch(Exception e){System.out.println("發送POST請求出現異常!" + e);e.printStackTrace();}return result;}// 提供主方法,測試發送GET請求和POST請求public static void main(String args[]){// 發送GET請求String s = GetPostTest.sendGet("http://localhost:8888/abc/a.jsp", null);System.out.println(s);// 發送POST請求String s1 = GetPostTest.sendPost("http://localhost:8888/abc/login.jsp", "name=crazyit.org&pass=leegang");System.out.println(s1);} }5.shutdownInput和shutdownOutput表示輸出出具已經發送完畢,socket處于半關閉狀態


十八、類加載機制與反射

1.當程序主動使用某個類時,如果該類還未被加載到內存中,系統會通過加載、鏈接、初始化3個步驟來對該類進行初始化。類加載指的是將類的class文件讀入內存,并位置創建一個java.lang.Class對象,通常類的加載由系統類加載器完成,也可以通過繼承ClassLoader類來創建自己的類加載器。

2.鏈接階段負責把類的二進制數據合并到JRE中,包括驗證(內部結構是否正確與其他類協調一致)、準備(為靜態Field分配內存,并設置默認初始值)和解析(符號引用替換成直接引用)。

3.JVM初始化類步驟:如果這個類還沒有被加載和鏈接,則程序先加載并連接該類;該類的直接父類還沒有被初始化,則先初始化其直接父類;如果類中有初始化語句,則系統一次執行這些初始化語句

4.Java程序首次通過下面6種方式來使用某個類或接口時,系統就會初始化該類或接口:創建類的實例(使用new、反射以及反序列化創建實例);調用某個類的靜態方法;訪問某個類或接口的靜態Field、或為該靜態Field賦值;使用反射方式來強制創建某個類或接口對應的java.lang.Class對象(Class.forName("Person"));初始化某個類的子類;直接使用java.exe運行某個類。對于一個final型的靜態Field,如果該Field的值在編譯時就可以確定下來,那么這個Field相當于宏變量,編譯時直接替換,程序使用該靜態Field也不會導致該類的初始化。使用ClassLoader類的loadClass方法來加載某個類時,只是加載該類,并不會執行該類的初始化,使用Class的forName靜態方法才會導致強制初始化該類

class Tester {static{System.out.println("Tester類的靜態初始化塊...");} } public class ClassLoaderTest {public static void main(String[] args) throws ClassNotFoundException{ClassLoader cl = ClassLoader.getSystemClassLoader();// 下面語句僅僅是加載Tester類cl.loadClass("Tester");System.out.println("系統加載Tester類");// 下面語句才會初始化Tester類Class.forName("Tester");} }5.一個類用其全限定類名和其類加載器作為其唯一標識。例如,如果在pg的包中有一個名為Person的類,被類加載器ClassLoader的實例k1負責加載,則該Person類對應的Class對象在JVM中表示為(Person、pg、k1)。

6.當JVM啟動時,會形成由3個類加載器組成的初始類加載器層次結構:Bootstrap ClassLoader根類加載器,負責加載Java的核心類;Extension ClassLoader 擴展類加載器,負責加載JRE的擴展目錄(%JAVA_HOME%/jre/lib/ext或者java.ext.dirs)中JAR包的類;System ClassLoader 系統類加載器,負責在JVM啟動時加載來自java命令-classpath選項、java.class.path系統屬性,或CLASSPATH環境變量所指定的JAR包和類路徑

7.JVM的類加載機制:全盤負責,加載某個Class時,該Class所依賴的和引用的其他Class也將由該類加載器負責載入,除非顯示使用另外一個類加載器載入;父類委托,先讓父類加載器試圖加載該Class,只有在父類加載器無法加載該類時才嘗試從自己的類路徑中加載該類;緩存機制,所有加載過的Class都會被緩存,需要使用某個類時,先從緩存區中搜尋,緩存區不存在該Class對象時,才會讀取該類對應的二進制數據,并將其轉換成Class對象,存入緩存區中。

8.獲得Class對象:使用Class類的forName(String clazzName)靜態方法;調用某個類的class屬性(Person.class);調用某個對象的getClass()方法。第二種方式更安全,性能更好?

9.從Class中獲取信息

// 使用兩個注釋修飾該類 @SuppressWarnings(value="unchecked") @Deprecated public class ClassTest {// 為該類定義一個私有的構造器private ClassTest(){}// 定義一個有參數的構造器public ClassTest(String name){System.out.println("執行有參數的構造器");}// 定義一個無參數的info方法public void info(){System.out.println("執行無參數的info方法");}// 定義一個有參數的info方法public void info(String str){System.out.println("執行有參數的info方法"+ ",其str參數值:" + str);}// 定義一個測試用的內部類class Inner{}public static void main(String[] args) throws Exception{// 下面代碼可以獲取ClassTest對應的ClassClass<ClassTest> clazz = ClassTest.class;// 獲取該Class對象所對應類的全部構造器Constructor[] ctors = clazz.getDeclaredConstructors();System.out.println("ClassTest的全部構造器如下:");for (Constructor c : ctors){System.out.println(c);}// 獲取該Class對象所對應類的全部public構造器Constructor[] publicCtors = clazz.getConstructors();System.out.println("ClassTest的全部public構造器如下:");for (Constructor c : publicCtors){System.out.println(c);}// 獲取該Class對象所對應類的全部public方法Method[] mtds = clazz.getMethods();System.out.println("ClassTest的全部public方法如下:");for (Method md : mtds){System.out.println(md);}// 獲取該Class對象所對應類的指定方法System.out.println("ClassTest里帶一個字符串參數的info方法為:"+ clazz.getMethod("info" , String.class));// 獲取該Class對象所對應類的上的全部注釋Annotation[] anns = clazz.getAnnotations();System.out.println("ClassTest的全部Annotation如下:");for (Annotation an : anns){System.out.println(an);}System.out.println("該Class元素上的@SuppressWarnings注釋為:"+ clazz.getAnnotation(SuppressWarnings.class));// 獲取該Class對象所對應類的全部內部類Class<?>[] inners = clazz.getDeclaredClasses();System.out.println("ClassTest的全部內部類如下:");for (Class c : inners){System.out.println(c);}// 使用Class.forName方法加載ClassTest的Inner內部類Class inClazz = Class.forName("ClassTest$Inner");// 通過getDeclaringClass()訪問該類所在的外部類System.out.println("inClazz對應類的外部類為:" + inClazz.getDeclaringClass());System.out.println("ClassTest的包為:" + clazz.getPackage());System.out.println("ClassTest的父類為:" + clazz.getSuperclass());} }10.創建對象:使用Class對象的newInstance()方法來創建該Class對象對應的實例,這種方式要求該Class對象的對應類有默認構造器;使用Class對象獲取指定的Constructor對象,再調用Constructor對象的newInstance()方法來創建該Class對象對應類的實例 public class CreateJFrame {public static void main(String[] args)throws Exception{// 獲取JFrame對應的Class對象Class<?> jframeClazz = Class.forName("javax.swing.JFrame");// 獲取JFrame中帶一個字符串參數的構造器Constructor ctor = jframeClazz.getConstructor(String.class);// 調用Constructor的newInstance方法創建對象Object obj = ctor.newInstance("測試窗口");// 輸出JFrame對象System.out.println(obj);} }11.通過反射調用方法 import java.util.*; import java.io.*; import java.lang.reflect.*; public class ExtendedObjectPoolFactory {// 定義一個對象池,前面是對象名,后面是實際對象private Map<String ,Object> objectPool = new HashMap<>();private Properties config = new Properties();// 從指定屬性文件中初始化Properties對象public void init(String fileName){try(FileInputStream fis = new FileInputStream(fileName)){config.load(fis);}catch (IOException ex){System.out.println("讀取" + fileName + "異常");}}// 定義一個創建對象的方法,// 該方法只要傳入一個字符串類名,程序可以根據該類名生成Java對象private Object createObject(String clazzName)throws InstantiationException , IllegalAccessException , ClassNotFoundException{// 根據字符串來獲取對應的Class對象Class<?> clazz =Class.forName(clazzName);// 使用clazz對應類的默認構造器創建實例return clazz.newInstance();}// 該方法根據指定文件來初始化對象池,// 它會根據配置文件來創建對象public void initPool()throws InstantiationException ,IllegalAccessException , ClassNotFoundException{for (String name : config.stringPropertyNames()){// 每取出一對key-value對,如果key中不包含百分號(%)// 這個就標明是根據value來創建一個對象// 調用createObject創建對象,并將對象添加到對象池中if (!name.contains("%")){objectPool.put(name , createObject(config.getProperty(name)));}}}// 該方法根據指定文件來初始化對象池,// 它會根據配置文件來創建對象public void initProperty()throws InvocationTargetException,IllegalAccessException,NoSuchMethodException{for (String name : config.stringPropertyNames()){// 每取出一對key-value對,如果key中包含百分號(%)// 即可認為該key是用于為對象的Field設置值,// %前半為對象名字,后半為Field名// 程序將調用對應的setter方法來為對應Field設置值。if (name.contains("%")){// 將配置文件中key按%分割String[] objAndProp = name.split("%");// 取出需要設置Field值的目標對象Object target = getObject(objAndProp[0]);// 該Field對應的setter方法名:set + "屬性的首字母大寫" + 剩下部分String mtdName = "set" + objAndProp[1].substring(0 , 1).toUpperCase() + objAndProp[1].substring(1);// 通過target的getClass()獲取它實現類所對應的Class對象Class<?> targetClass = target.getClass();// 獲取該屬性對應的setter方法Method mtd = targetClass.getMethod(mtdName , String.class);// 通過Method的invoke方法執行setter方法,// 將config.getProperty(name)的屬性值作為調用setter的方法的實參mtd.invoke(target , config.getProperty(name));} }}public Object getObject(String name){// 從objectPool中取出指定name對應的對象。return objectPool.get(name);}public static void main(String[] args)throws Exception{ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory();epf.init("extObj.txt");epf.initPool();epf.initProperty();System.out.println(epf.getObject("a"));} } 通過Method的invoke方法來調用對應的方法時,Java會要求程序必須有調用該方法的權限。當確實需要調用某個對象的private方法,可以先調用setAccessible(boolean flag)取消訪問權限檢查,Constructor和Field都可以調用該方法

12.通過反射訪問屬性值:

class Person {private String name;private int age;public String toString(){return "Person[name:" + name + " , age:" + age + " ]";} } public class FieldTest {public static void main(String[] args) throws Exception{// 創建一個Person對象Person p = new Person();// 獲取Person類對應的Class對象Class<Person> personClazz = Person.class;// 獲取Person的名為name的Field// 使用getDeclaredField,表明可獲取各種訪問控制符的fieldField nameField = personClazz.getDeclaredField("name");// 設置通過反射訪問該Field時取消訪問權限檢查nameField.setAccessible(true);// 調用set方法為p對象的name Field設置值nameField.set(p , "Yeeku.H.Lee");// 獲取Person類名為age的屬性Field ageField = personClazz.getDeclaredField("age");// 設置通過反射訪問該Field時取消訪問權限檢查ageField.setAccessible(true);// 調用setInt方法為p對象的age Field設置值ageField.setInt(p , 30);System.out.println(p);} }引用對象直接用set,而基本數據類型用setXxx(如setInt)

13.操作數組

public class ArrayTest1 {public static void main(String args[]){try{// 創建一個元素類型為String ,長度為10的數組Object arr = Array.newInstance(String.class, 10);// 依次為arr數組中index為5、6的元素賦值Array.set(arr, 5, "瘋狂Java講義");Array.set(arr, 6, "輕量級Java EE企業應用實戰");// 依次取出arr數組中index為5、6的元素的值Object book1 = Array.get(arr , 5);Object book2 = Array.get(arr , 6);// 輸出arr數組中index為5、6的元素System.out.println(book1);System.out.println(book2);}catch (Throwable e){System.err.println(e);}} } public class ArrayTest2 {public static void main(String args[]){/*創建一個三維數組。根據前面介紹數組時講的:三維數組也是一維數組,是數組元素是二維數組的一維數組,因此可以認為arr是長度為3的一維數組*/Object arr = Array.newInstance(String.class, 3, 4, 10);// 獲取arr數組中index為2的元素,應該是二維數組Object arrObj = Array.get(arr, 2);// 使用Array為二維數組的數組元素賦值。// 二維數組的數組元素是一維數組,所以傳入Array的set()方法的// 第三個參數是一維數組。Array.set(arrObj , 2 , new String[]{"瘋狂Java講義","輕量級Java EE企業應用實戰"});// 獲取arrObj數組中index為3的元素,應該是一維數組。Object anArr = Array.get(arrObj, 3);Array.set(anArr , 8 , "瘋狂Android講義");// 將arr強制類型轉換為三維數組String[][][] cast = (String[][][])arr;// 獲取cast三維數組中指定元素的值System.out.println(cast[2][3][8]);System.out.println(cast[2][2][0]);System.out.println(cast[2][2][1]);} }14.Proxy提供了用于創建動態代理類和代理對象的靜態方法,它也是所有動態代理類的父類

15.使用反射來獲取泛型信息:

public class GenericTest {private Map<String , Integer> score;public static void main(String[] args)throws Exception{Class<GenericTest> clazz = GenericTest.class;Field f = clazz.getDeclaredField("score");// 直接使用getType()取出Field類型只對普通類型的Field有效Class<?> a = f.getType();// 下面將看到僅輸出java.util.MapSystem.out.println("score的類型是:" + a);// 獲得Field實例f的泛型類型Type gType = f.getGenericType();// 如果gType類型是ParameterizedType對象if(gType instanceof ParameterizedType){// 強制類型轉換ParameterizedType pType = (ParameterizedType)gType;// 獲取原始類型Type rType = pType.getRawType();System.out.println("原始類型是:" + rType);// 取得泛型類型的泛型參數Type[] tArgs = pType.getActualTypeArguments();System.out.println("泛型類型是:");for (int i = 0; i < tArgs.length; i++) {System.out.println("第" + i + "個泛型類型是:" + tArgs[i]);}}else{System.out.println("獲取泛型類型出錯!");}} }


































總結

以上是生活随笔為你收集整理的疯狂Java讲义笔记的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。