-
當調用一個對象中的方法時,比如調用Dog類的實例dog的eat(),編譯器會把dog引用當成eat()的第一個參數傳入即(eat(this, otherParams))
-
在構造器中可以使用this關鍵字去顯式的調用構造器,並且只能位於構造器的第一行,其他地方都不能調用構造器
-
Java爲類做的準備工作包括以下三步,獲取類名.class、編譯器常量(static final並且編譯器就能確定值的字段)只會執行第一步加載,並不會進行鏈接和初始化,由於反射(Class.forName())是在運行時創建的Class對象因此通過該方法會進行鏈接和初始化
- 加載 ClassLoader在ClassPath下尋找到類的字節碼文件後,根據字節碼創建一個Class對象,也就是使用類名.class獲取的那個對象
- 鏈接 驗證類中的字節碼,爲靜態域分配存儲空間
- 初始化 如果該類有父類加載父類,然後初始化父類的靜態域,然後回來初始化子類靜態域
-
代碼的執行順序,創建對象執行1-5,調用靜態字段、方法執行1-2
- 當Java運行時需要某個類時(調用該類的靜態域或者構造器(其實其也屬於靜態方法),除去static final的編譯器常量),會觸發改類的加載->鏈接->初始化,如果其有父類則在子類初始化開始後,觸發父類的加載->鏈接->初始化,以此類推
- 從基類到子類初始化靜態字段和靜態代碼塊按照書寫順序
- 在堆中分配一塊空間,並把該空間都置於零,基本數據類型爲0,引用爲null
- 調用構造方法第一行直到Object
- 爲基類的非靜態字段進行初始化,然後執行剩餘的構造方法。下圖說明了這5點。
-
靜態字段屬於類,當類加載就會初始化,非靜態字段屬於對象,當對象不存在時是不會進行初始化的
-
垃圾回收機制
- 引用計數法 每個對象都有一個引用計數器,當有引用連接到該對象引用計數器++,當引用離開作用域或者置爲null引用計數器–,當垃圾回收器發現某個對象的引用計數器爲0時就釋放它,缺點:當對象相互引用的時候引用計數器不爲0,但是兩個對象都不再需要,這時候就沒法釋放了
- 自適應的、分代的、停止-複製、標記-清掃式垃圾回收器。停止-複製,先暫停程序從GCRoot(靜態區,堆棧引用)出發找到所有的活的對象,並且把這些對於一個緊挨一個放到新的一塊內存中,然後更新堆棧中和靜態區的引用然後刪除原來佔據的堆內存,缺點:需要第二塊內存,並且當可回收的對象過少的時候效率不高 優點:整理了堆中的對象可以有更多空間來存儲。標記-清掃,也是先暫停程序也從GCRoot出發找對象每找到一個活着的對象就把它標記,等所有的引用都遍歷結束後,刪除沒有被標記的對象。分代的,每個內存塊都有一個代數,大型對象不會被複制,內含小型對象的塊則會被複制和整理。自適應的,虛擬機會跟蹤標記-清掃的效果,要是堆空間中出現很多小碎片就會切換到停止-複製。
-
數組,數組初始化有以下兩種,其中第一種只能在定義時初始化,數組分爲基本數據類型數組和引用數組),數組本身也是一個引用(也是一個Object對象),可以通過調用Arrays.toString(arr)來打印數組
int[] iArr = {1,2,3,4,5}; //1 int[] bArr = new int[10]; //2 Obj[] arrs = new Obj[10]; //創建了一個引用數組,並沒有初始化裏面的引用,因此都是null
-
可變參數,使用方式如下,必須位於方法的最後一個參數,就相當於一個數組
static void print(Object... objects) { //可以傳入0-n個Object對象或者是一個引用數組(如果傳了基本數據類型數組,那麼相當於只是傳了一個Object對象) System.out.println(Arrays.toString(objects)); }
-
枚舉enum關鍵字,枚舉也是一個類,只是產生了某些編譯器行爲,編譯器會創建toString()、ordinal()方法,並且會給枚舉類創建一個values()靜態方法獲取所有的實例,代碼如下,並且枚舉裏面聲明的枚舉對象都是靜態的
enum PagerMoney { ONE_DOLLAR, FIVE_DOLLARS, TEN_DOLLARS, FIFTY_DOLLARS, ONE_HUNDRED_DOLLARS } for (PagerMoney money: PagerMoney.values()) { //返回一個PagerMoney數組,包含所有Dog類的實例 System.out.println(money + " " + money.ordinal()); //該方法獲取實例創建的順序從0開始 } /* outputs: ONE_DOLLAR 0 FIVE_DOLLARS 1 TEN_DOLLARS 2 FIFTY_DOLLARS 3 */
-
靜態導入,當靜態導入一個類後調用該類中的靜態方法就可以不寫類名了。
import static com.hfw.utils.CommonUtils.*; //靜態導入就不用寫類名了 public class TestUtils { public static void main(String[] args) { print("Hello,World"); } }
-
包名,包名必須與文件目錄一一對應,因爲java解釋器在加載類的時候會將包名的.替換成/,生成一個目錄,然後在所有的CLASSPATH下面的這個目錄(由於CLASSPATH中含有.因此也會查找當前位置)查找.class文件,所以如果包名與文件目錄不對應那麼java解釋器將找不到文件。
-
訪問控制,在創建類的時候最好把字段和方法按照public、protected、默認、private的順序書寫,外部類只有public和默認兩種訪問控制,內部類具有四種(如果設置爲private則只有同屬於一個最外部才能訪問,其默認構造方法也是private,如果設置爲protected,則其默認構造方法也是protected)也就是默認構造器的訪問控制符與類的訪問控制符一致
-
public 公開的,全局都可以訪問被public修飾的類或者字段或者方法
-
protected 受保護的,同包、同類、子類可以訪問該方法(如果從其他包繼承的一個方法,並且該方法不是public和protected那麼子類就會沒法訪問這個從父類繼承的方法)
-
默認 default,同包、同類中能訪問
-
private 私有的 只能在同屬於一個最外部類訪問
-
-
繼承 基本做法是基類把所有字段設置成private,而把所有方法設置爲public。當創建一個子類對象時,其實會先創建一個父類對象,相當於一個子類對象中包含一個父類對象(解釋了爲什麼子類對象不能調用父類對象的private字段和方法)並且在子類構造器中會首先調用父類構造器,父類構造器如果有參數則必須在子類構造器中明確使用super調用父類構造器(在父類構造器沒走完之前是不會初始化子類的非靜態的字段的,當父類構造器執行完後才初始化子類非靜態字段,然後繼續執行子類的構造器代碼如下所示)
package com.hfw.section7.practice; public class PracticeFive { public static void main(String[] args) { new CC(); } } class AA { AA() { System.out.println("AA"); } } class BB { BB() { System.out.println("BB"); } } class CC extends AA { BB bb = new BB(); CC() { System.out.println("CC"); } } //outputs: AA BB CC
-
代碼複用
-
組合 一個類中包含另一個類的引用,需要時從該類獲取引用調用其方法
-
繼承 調用時直接調用其方法
-
代理 結合前兩者,其擁有另一個類引用,並且創建對應的方法如f(),然後在該方法中調用另一個類對象的f(),idea可以自動生成代理方法
-
-
final,當使用final修飾基本數據類型時(名稱需要大寫),如果數值是確定的那麼就屬於編譯期常量可以減輕運行時的負擔。一般基本數據類型常量定義爲public static final,public表示其他包可以訪問,static表示只有一份。對基本數據類型使用final那麼它的值就不能發生變化,對引用使用final那麼引用不能發生變化但是它所指向的對象的內容可以發生變化
- 字段 表示其是一個常量。被final修飾的字段只能在定義時或者構造器中初始化,而一旦初始化就不能更改了。
- 方法參數 在方法內部無法更改參數,常用與方法內部的匿名內部類
- 方法 不可以被重寫,基類的private方法隱含final,因爲private方法不能被重寫,子類就算寫了一個與父類聲明一樣的方法也不是重寫此外static方法也不能被重寫
- 類 不可以被繼承
-
方法綁定 java中除了static、final(private也屬於final)方法外,其他所有方法都是動態綁定(也就是在運行時綁定),多態的實現就是依賴動態綁定,在運行時確定調用的方法。
-
多態 子類可以向上轉型成父類(將子類對象賦值給父類),並且在運行時程序能夠正確調用方法,書寫程序時最好只與基類進行通信而不依賴於某一個具體的實現,這樣程序就是可擴展的。多態是一項將改變的事物與未變的事物分離開來的重要技術,當基類中需要增加一些其他方法時完全不影響原來的代碼。注意點:1. 子類是無法重寫父類的私有方法的,因此上轉型到父類調用該方法會調用父類的方法(在子類中不要創建與父類的私有方法一樣的方法) 2. 如果父類擁有一個字段A,子類也擁有字段A,那麼將子類上轉型到父類獲取A得到的是父類的A字段,因爲這個訪問在編譯器進行,如果採用調用方法獲取則獲取的是子類的A字段(也就是字段不屬於多態)。舉例如下。3. 靜態方法不具有多態
public class TestPolymorphism { static class Super { int field = 0; } static class Sub extends Super { int field = 1; int getField() { return field; } int getSuperField() { return super.field; } } public static void main(String[] args) { Super su = new Sub(); System.out.println(su.field); //直接獲取得到的是父類的0,編譯時決定 System.out.println(su.getField()); //方法調用得到的是子類的1,運行時決定 } }
-
構造器 準則“用盡可能簡單的方法使對象進入正常狀態,如果可以的話避免調用其他方法,在構造器能安全的調用的方法只有當前類的final和private方法因爲它們不能被繼承”,如果不遵守可能會出現下面的問題。
//會輸出Circle print 0 因爲在堆中創建了空間後所有值都被置於0,而在Shape構造器執行的時候Circle的成員變量還沒賦值 public class PracticeFifteen { public static void main(String[] args) { Circle circle = new Circle(); } static class Shape { Shape() { print(); } void print() { println("Shape print"); } } static class Circle extends Shape { int radius = 2; Circle() { } void print() { println("Circle print" + radius); } } }
-
抽象類,如果一個類的父類不是抽現類,而該類聲明瞭abstract並且沒有任何抽象方法則該類不能被實例化
-
接口,接口的所有字段都是public static final的,方法都是public abstract的,所以要求實現類的方法也必須是public的。接口中的字段不能是空final的(聲明的時候不賦值,而在構造器賦值)。 接口可以繼承另一個接口(也可以繼承多個接口中間用逗號分割),一個類可以實現多個接口中間用逗號分割。在寫自己的類庫時方法最好是需要傳入一個實現了該類庫中的接口的實例(聲明爲你可以用任何你想用的對象來調用我,只要你的對象遵循我的接口)
-
內部類 分爲成員內部類、局部內部類、靜態內部類(嵌套類,非內部類一律不能聲明爲static class),接口中定義的內部類一定是靜態內部類,非靜態內部類對象含有一個外部類引用,所有能夠訪問外部類的所有成員包括private字段、方法(可以通過外部類類名.this.xxx來明確使用外部類的xxx)。要創建一個內部類對象必須要先有一個外部類對象(假設外部類名爲Outer對象爲outer內部類爲Inner),然後使用outer.new Inner(),外部類對象無法訪問內部類的任何字段。可以在以下地方定義內部類,類、方法(稱作局部內部類)、任意的{}作用域,但是不管內部類定義在哪裏在編譯時會一併被編譯成.class文件。匿名內部類如果用到了使用了一個在外部定義的變量,則該變量必須是final的,也就是說必須是常量。內部類也是可以繼承的,但是構造方法必須傳入外部類對象並且要調用父類對象.super()
public class PracticeNine extends Inner { PracticeNine(Outer outor) { outor.super(); //由於繼承了內部類這裏必須這麼調用,且構造器必須傳入outor對象 } public static void main(String[] args) { PracticeNine practiceNine = new PracticeNine(); practiceNine.generatorListener().onClick(); } // 在方法內部定義的內部類 private OnClickListener generatorListener() { class MyOnClickListener implements OnClickListener { @Override public void onClick() { System.out.println("OnClick"); } } return new MyOnClickListener(); } //在if{}裏面定義的內部類 private OnClickListener generatorListener(boolean b) { if (b) { class MyOnClickListener implements OnClickListener { @Override public void onClick() { System.out.println("OnClick"); } } return new MyOnClickListener(); } else { return null; } } class Outer { class Inner { } }
-
嵌套類(靜態內部類)創建不需要外部類引用,無法訪問外部類的非靜態成員(內部可以創建static變量和方法,其實就相當於一個與外部類沒關係的類了,普通內部類內部不能創建static方法和字段)
-
容器 打印容器不需要循環打印直接println(Collection),容器內不能存儲基本數據類型只能存儲對象,程序中不應該使用Vector、Hashtable、Stack
- Collections
- List(包括ArrayList長隨機讀取弱插入移除,LinkedList(長插入移除弱隨機讀取)方法offer()在尾部添加一個元素、peek()返回首個元素如爲空則返回null,poll()刪除尾部最後一個元素並返回,其實現了隊列,並且它也能當做棧使用雖然沒實現Stack
- Set(同一個元素只能出現一次,包括HashSet無序最快查找基於散列表實現、TreeSet升序、LinkedHashSet插入的順序)
- Queue(先進先出,主要方法poll(返回並刪除第一個元素)與remove()相同、peek(返回第一個元素)與element相同,區別在於空的時候一個返回null一個拋出異常、offer(添加一個元素)),PriorityQueue優先級隊列按照比較結果從小到大排列,最先出的就是最小的
- Stack(後進先出,可以使用LinkedList實現,主要需要實現方法由push()、pop()、peek()、isEmpty())
- 容器方法:Collections.addAll()、Collections.shuffle()洗牌、Collection.isEmpty()、Collection.clear()、Collection.retainAll()取交集、Collection.toArray()、List.set()替換某個index的值、List.sort()、List.subList()、Arrays.asList()注意該方法生成的會生成一個ArrayList(其內部類)該類並沒有重寫AbstractList中的刪除元素,增加元素的方法(比如add、remove、clear等)調用這些方法將包UnSupportException
- Map,字典(關聯數組),按照key查詢值。包括HashMap(HashTable也屬於Map)無序最快查找、TreeMap升序(基於紅黑樹實現)、LinkedHashMap插入的順序(通過構造器也可以實現LRU,三個參數的)
- Iterator 迭代器 方法(hasNext()、next()、remove()),當一個類需要能夠迭代時可以創建一個迭代器成員變量
- ListIterator List迭代器,通過listIterator(index)獲取index設置錨點起始值,可以做到倒序 比Iterator多的方法(hasPrevious()、Previous()、set()修改當前的值、nextIndex()、previousIndex())
- Collections
-
增強for循環(forEach)可以用於數組(沒有實現Iterable)、任何實現了Iterable的類(比如任何Collection) 注:可以自己實現Iterable用於定製,比如生成逆序迭代
public Iterable<Integer> reverseIterable() { //返回一個逆序的Iterable return new Iterable<Integer>() { int index = PracticeThirtyTwo.this.size(); @Override public Iterator<Integer> iterator() { return new Iterator<Integer>() { @Override public boolean hasNext() { return index > 0; } @Override public Integer next() { return get(--index); } }; } }; }
-
Exception 異常(方法printStackTraces(打印棧的軌跡可以傳入PrintStream或者WriteStream)在執行中會調用getMessage(),fillInStackTrace()一般用於捕獲了一個異常然後再次將其拋出,如果不調用該方法那麼棧的軌跡最終就到創建那個異常的地方,調用了該方法軌跡就到調用該方法的地方,當然也可以捕獲一個異常然後拋出另一個異常其棧軌跡將與調用了fillInStackTrace()一樣)異常對象一旦創建了堆棧信息就確定了,Logger(使用方法通過Logger.getLogger()獲取,然後調用logger.severe()等方法),異常鏈:Error、Exception的構造器都可以傳入一個Throwable,最終打印出來的會包含cause,也就是Android中常出現的上面異常由下面引起(可以捕獲異常調用getCause獲取異常鏈上一級),有些現有異常不包含參數爲throwable的構造器,可以調用initCause()達到同樣的目的,finally會在try catch執行完後執行,如果在try catch中執行了return,會先執行return,但是方法還沒結束方法會等到finally執行完後才結束
- RuntimeException 繼承於該類的異常(NullPointException、ArrayIndexOutOfBoundsException等等)屬於不受檢查的異常,方法不需要聲明throws,不進行catch也能夠通過編譯,一般是JVM拋出,如果調用鏈全都沒處理,那麼最後會傳到main(),然後把異常傳給System.err進行打印
- 普通異常 屬於受檢查的異常,如果一個方法拋出一個異常,那麼方法必須聲明throws,並且外層方法必須catch或者再拋給上層才能通過編譯。
- 異常丟失 兩種情況會導致異常丟失,一種是在finally語句中使用return,會丟失前面拋出的未捕獲異常,另一種是在子try代碼塊中拋出一個異常沒有catch,並且finally也拋出一個異常,這樣在外層catch中僅僅能捕獲到finally中拋出的異常,子try代碼塊中的異常將被忽略
- 繼承/實現帶有異常的方法 子類從父類或者接口繼承/實現的方法所能拋出的異常只能是接口/父類所拋出的異常的子集,注意構造器則正好相反,父類構造器拋出的異常是子類構造器的子集,如果上轉型到父類則必須捕獲父類該方法聲明的異常,否則就只需要捕獲當前類該方法聲明的異常
- 構造器拋出異常(並且當該類不用時需要關閉流) 需要嵌套try catch外層去創建該類的對象,內層去執行(比如I/O)然後執行完後內層finally關閉流,因爲當創建對象失敗不需要關閉流而進入了內層就需要關閉,所以使用了嵌套try catch,如果構造器不拋出異常則不需要嵌套
- 子類構造器不能捕獲父類構造器拋出的異常,因爲子類構造器中沒法用try catch包裹super(),super()必須位於第一行
- 異常匹配 當try代碼塊中拋出異常後,會尋找最近一個匹配的catch語句然後就認爲異常處理結束,所以Exception要放到最後,因爲它能匹配(父類可以匹配子類)所有異常
//代碼2處如果不調用fillInStackTrace,那麼異常的棧頂信息就指向代碼一的位置處否則指向代碼二的位置處 public class TestStackTrace { public static void main(String[] args) { secondThrowException(); System.out.println("After Exception"); } private static void secondThrowException() { try { firstThrowException(); } catch (Exception e) { e.printStackTrace(); //再次拋出的異常的棧軌跡中不包括firstThrowException中的再次throw } } private static void firstThrowException() throws MyException { try { throw new MyException(); //1 } catch (Exception e) { System.out.println("firstThrowException獲取到異常,再次拋出"); throw (MyException) e.fillInStackTrace();//2 } } } class MyException extends Exception { }
public class TextExceptionClean { public static void main(String[] args) { Test test1, test2; try { test1 = new Test(); try { test2 = new Test(); try { //執行一系列操作 } finally { test2.clean(); test1.clean(); //後創建先清理 } } catch (Exception e) { System.out.println("test2 constructor fail"); } finally { //關閉test1 test1.clean(); } } catch (Exception e) { System.out.println("test1 constructor fail"); //不需要關閉 } } } class Test { Test() throws MyException { } void clean() { //創建對象成功才需要清理 } } /* outputs matches = false firstFind = true Group = 34345 start = 4 end = 8 SecondFind = true Group = 234 start = 10 end = 12 ThirdFind = true Group = 123 start = 0 end = 2 firstLookingAt = true Group = 123 start = 0 end = 2 secondLookingAt = true Group = 123 start = 0 end = 2 firstFindWithParams = true Group = 4345 start = 5 end = 8 firstFindAfterResetWithParams = true Group = 987 start = 0 end = 2 */
-
String對象不可變(只讀),任何看起來修改String的方法都只是返回了一個新的String對象,String中的+/+=是java中唯一的兩個重載操作符,StringBuilder/SE5新加的(常用方法append、delete、toString)線程不安全,StringBuffer線程安全,不在Loop時由於編譯器會自動創建StringBuilder對象然後進行append,所以不用顯式創建StringBuilder對象,Loop時必須在Looper外面創建一個StringBuilder,然後在內部Append,如果不顯式創建那麼每進行一次循環都會創建一個StringBuilder對象影響性能,toString()如果想要獲取對象的內存空間可以使用super.toString(),判斷兩個字符串是否=,java中會有一個獨立於堆棧的String池,每次使用一個String如果在池中找不到就會創建,而使用new創建String對象,會在堆中創建一個對象所有使用new創建的和不用new創建的肯定是不相等的
- Formatter java中所有的格式化輸出都使用該類(%d,%s,%f,%c,%b,%x十六進制,%e科學技術,%h散列碼,%7s最小長度爲7位的String不足左補空格,%-4s不足右補空格,%04d最小長度4位不足左補零,%4.5f小數保留5位 注意.x不能應用於整數,否則會拋出異常),System.out.printf()、String.format(獲取格式化後的字符串)內部也是使用Formatter實現的。
public class TestFormat { public static void main(String[] args) { int age = 20; float price = 11567.2f; Formatter formatter = new Formatter(System.out); formatter.format("My age is %d and my price is %f", age, price); } }
- Scanner 掃描可以使用nextXXX()獲取(也可以在next方法中傳入一個正則),默認分割符爲空格,也可以使用正則表達式指定
//使用正則表達式作爲分界符 Scanner scanner = new Scanner("1, 2, 3, 4, 5, 6, 7"); scanner.useDelimiter("\\s*,\\s*"); while (scanner.hasNext()) { System.out.println(scanner.nextInt()); } //next使用正則表達式 String aim = "192.168.2.3\n" + "178.268.33.2\n" + "165.234.123.34"; Scanner scanner = new Scanner(aim); String pattern = "\\d+(\\.\\d+\\.)\\d+\\.\\d+"; while (scanner.hasNext(pattern)) { System.out.println(scanner.next(pattern)); MatchResult matchResult = scanner.match(); System.out.println("group0 = " + matchResult.group() + " group1 = " + matchResult.group(1)); }
-
正則表達式
- ^ 輸入序列的首個字符
- $ 輸入序列的最後一個字符
- \G 前一個匹配的結束??
- . 匹配任意除了\r\n以外的所有字符
- * 0次或多次匹配前面的字符或者表達式
- + 1次或多次匹配前面的字符或者表達式
- [A-Z] 匹配所有大寫字母
- [a-zA-Z] 匹配所有字母
- [abc[de]] 等價於[abcde],並集
- [a-z&&[abc]] 匹配a或b或c,交集
- ? 一個或零個
- | 或
- [xyz] 匹配裏面包含的任一字符
- [^xyz] 匹配除了裏面包含的任一字符
- B 指定字符B
- \t 製表符
- \n 換行符
- \r 回車符
- \f 換頁符
- \d 數字[0-9]
- \D 非數字[^0-9]
- \W 非詞字符等價於[^\w]
- \w 詞字符[a-zA-Z0-9]
- \s 空白符(空格、tab、換行、回車、換頁)
- \S 非空白符
- \b 詞的邊界
- \B 非詞的邊界
- X{n} 恰好n次X
- X{n,} 至少n次X
- X{n.m} n<=X出現次數<=m
- \xhh 十六進制值爲0xhh的字符
- \uhhhh 十六進制值爲0xhh的unicode字符
- \" 匹配雙引號因爲引號需要轉義
- 標記模式 前面的常量用於Pattern.compile(第二個參數可以使用|分割以支持多種標記),?!用於正則表達式,效果一致
- Pattern.CASE_INSENSITIVE(?!) 匹配忽略字母大小寫
- Pattern.DOTALL(?x) inputCharSequence中的空格已經以#開頭行將被忽略
- Pattern.MULTILINE(?m) 在該模式下^、$分別匹配一行的開頭和結束,默認兩兩個分別匹配inputCharSequence的首字符和尾字符
- 邏輯操作符 XY: Y字符跟在X字符後面 X|Y: X或Y
- 量詞 普通貪婪型、勉強型、佔有型尾部加+ 包括(?、*、+、{4,}、{4,7})這幾種默認是普通貪婪型的即匹配最多字符,如果在尾部加上? 則變成勉強型匹配最少字符,因爲上述幾種匹配字符數都不確定只是一個範圍所以需要有量詞
- group 正則字符串中每對括號表示一個group(?i、?m不算),groupId爲0表示整個表達式,groupId爲1表示從左到右遇見的第一個小括號內的內容,Matcher.getGroupCount()獲取正則中的所有group數量,默認的group不算
- ^|$ 可以匹配第-1位元素和s.length位元素所以“Hello”.replaceAll("^|$", “#”)會變成#Hello#
- 小括號的作用如XYZ+表示XY字符加上一個或多個Z,(XYZ)+表示一個或多個XYZ
- 將價格格式化爲每三個數子帶一個,可以使用該正則表達式,這裏的?!^不代表忽略大小寫,代表的是忽略首位,?=代表的是匹配的首個字符與前一個字符之間
String regex = "(?!^)(?=(\\d{3})+$)" "1234567".replaceAll(regex); // 1,234,567
- Matcher.appendReplacement(替換文本一次,可以指定任意的替換文本,然後把替換後的文本及其前面的文本(到上次替換的文本)寫入StringBuffer中)、Matcher.appendTail(把沒有替換的後面部分拼接到StringBuffer中)
public class TestReplacement { private static String aim = "/*! Here's a block of text to use as input to\n" + " the regular expression matcher. Note that we'll\n" + " first extract the block of text by looking for\n" + " the special delimiters, then process the\n" + " extracted block. !*/"; public static void main(String[] args) { StringBuffer sb = new StringBuffer(); Pattern pattern = Pattern.compile("[aeiou]"); Matcher m = pattern.matcher(aim); while (m.find()) { m.appendReplacement(sb, m.group().toUpperCase()); System.out.println(sb); } m.appendTail(sb); System.out.println("---------------------------------"); System.out.println(sb); } } /* outputs: /*! HE /*! HErE /*! HErE's A /*! HErE's A blO /*! HErE's A blOck O /*! HErE's A blOck Of tE /*! HErE's A blOck Of tExt tO */
//group相關,行尾指的是\n前 public class TestGroup2 { static public final String POEM = "Twas brillig, and the slithy toves\n" + "Did gyre and gimble in the wabe.\n" + "All mimsy were the borogoves,\n" + "And the mome raths outgrabe.\n\n" + "Beware the Jabberwock, my son,\n" + "The jaws that bite, the claws that catch.\n" + "Beware the Jubjub bird, and shun\n" + "The frumious Bandersnatch."; public static void main(String[] args) { Matcher m = Pattern.compile("(?m)(\\S+)\\s+((\\S+)\\s+(\\S+))$") .matcher(POEM); //意思就是必須包含句尾所以要從後找起相當於最後三個單詞 while(m.find()) { for(int j = 0; j <= m.groupCount(); j++) print("[" + m.group(j) + "]"); println(""); } } } /* Output: [the slithy toves][the][slithy toves][slithy][toves] [in the wabe.][in][the wabe.][the][wabe.] [were the borogoves,][were][the borogoves,][the][borogoves,] [mome raths outgrabe.][mome][raths outgrabe.][raths][outgrabe.] [Jabberwock, my son,][Jabberwock,][my son,][my][son,] [claws that catch.][claws][that catch.][that][catch.] [bird, and shun][bird,][and shun][and][shun] [The frumious Bandersnatch.][The][frumious Bandersnatch.][frumious][Bandersnatch.] *///:~
//量詞 public class TestMeasureWord { private static String aim = "H000helloWorld7"; public static void main(String[] args) { Pattern pattern = Pattern.compile("H\\w+?"); //在+後加了?表示勉強型會匹配最少字符也就是1個所以輸出H0,如果不加?則匹配最多字符輸出H000 Matcher matcher = pattern.matcher(aim); System.out.println(matcher.find() + matcher.group()); } }
^[A-Z].*\.$ //匹配一個句子首字符是大寫,以句號結尾 [aeiouAEIOU] //匹配所有的元音字母 \\w+ //匹配單詞 \\x61 //匹配a字符 // String中有關正則內部都是使用Pattern實現的 Pattern.matches(String regex, CharSequence input) Pattern pattern = Pattern.compile(regex); pattern.split(String, ?int) //第二個參數表示分割的字符串數量 Matcher matcher = pattern.matcher(aim); //matches()和lookingAt()都從第一個字符匹配,區別是前者是部分匹配後者是全匹配 match.matches() //matches:整個匹配,只有整個字符序列完全匹配成功,才返回True,否則返回False。但如果前部分匹配成功,下次匹配的位置將從這後面開始。 match.lookingAt() //lookingAt:部分匹配,前一個字符能不能匹配,如果不行則前兩個字符能不能匹配如果還不行前三個字符能不能匹配依次類推 matcher.find() //find:部分匹配,從當前位置開始匹配,找到一個匹配的子串,將移動下次匹配的位置。 matcher.group() matcher.start(?int) // 參數groupId matcher.end(?int) matcher.reset(CharSequence) //無參數表示,把遊標重置爲字符序列首位,有參數表示更改待匹配字符串
// 測試Matcher的方法,find(?int)、lookingAt()、matches()、reset(?CharSequence) public class TestMatcher { private static String charSequence = "123-34345-234-00"; private static String regex = "\\d{3,5}"; //[3,5]位數字 public static void main(String[] args) { Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(charSequence); System.out.println("matches = " + matcher.matches()); //沒有全部匹配,但是匹配了123,所以遊標位於-前面 //起始位置變了 System.out.println("firstFind = " + matcher.find() + " Group = " + matcher.group() + " start = " + matcher.start() + " end = " + (matcher.end() - 1)); // 找到34335 System.out.println("SecondFind = " + matcher.find() + " Group = " + matcher.group() + " start = " + matcher.start() + " end = " + (matcher.end() - 1)); matcher.reset(); System.out.println("ThirdFind = " + matcher.find() + " Group = " + matcher.group() + " start = " + matcher.start() + " end = " + (matcher.end() - 1)); //lookAt 與find一樣部分匹配,但是每次遊標都從第一個位置前開始 System.out.println("firstLookingAt = " + matcher.lookingAt() + " Group = " + matcher.group() + " start = " + matcher.start() + " end = " + (matcher.end() - 1)); System.out.println("secondLookingAt = " + matcher.lookingAt() + " Group = " + matcher.group() + " start = " + matcher.start() + " end = " + (matcher.end() - 1)); //相當於忽略前5個字符,從第五個字符開始匹配,同樣會移動遊標 System.out.println("firstFindWithParams = " + matcher.find(5) + " Group = " + matcher.group() + " start = " + matcher.start() + " end = " + (matcher.end() - 1)); //重置遊標並且替換待匹配字符串 matcher.reset("987-6543-21-0"); System.out.println("firstFindAfterResetWithParams = " + matcher.find() + " Group = " + matcher.group() + " start = " + matcher.start() + " end = " + (matcher.end() - 1)); } } /* outputs matches = false firstFind = true Group = 34345 start = 4 end = 8 SecondFind = true Group = 234 start = 10 end = 12 ThirdFind = true Group = 123 start = 0 end = 2 firstLookingAt = true Group = 123 start = 0 end = 2 secondLookingAt = true Group = 123 start = 0 end = 2 firstFindWithParams = true Group = 4345 start = 5 end = 8 firstFindAfterResetWithParams = true Group = 987 start = 0 end = 2 */
-
Class 對象擁有以下三種獲取方式(假設類名爲A,A類對象爲a),方法getName()、getCanonicalName()、getSimpleName()、getInterfaces()、isInterface()、getSuperclass()、newInstance(需要有無參構造器,並且類和無參構造器都要是能夠訪問的)、isInstance(Obj obj)判斷obj是否屬於這個Class對象代表的類的對象、isAssignableFrom()判斷參數Class對象是否繼承前者或者實現前者
- A.class
- Class.forName(A)
- a.getClass()
- 基本數據類型Class對象可以通過基本類型.class或其包裝類型的TYPE字段獲取
- Class泛型不支持上轉型比如這樣就不能編譯通過Class<Number> integerClass = Integer.class在表示一個Class引用時建議使用Class<?>,雖然其與Class效果一致,如果想要一個Class引用指向一系列繼承自某個類的Class對象可以使用Class<? extends Number>表示,這個Class<? extends Number> clazz = int.class;就能編譯通過,還有一種getSuperclass().newInstance()只能拿到Class<? super Number>
-
動態代理,常用的代理一般是有一個接口和一個已知實現類,然後想要修改那個實現類個某幾個方法,然後創建一個類實現接口,在構造器中傳入已知類對象,然後把不需要修改方法原樣調用已知類對象的對應方法。
public class TestDynamicProxy {
public static void main(String[] args) { //調用這個listener裏面的任何方法都會走到InvocationHandler的invoke方法
OnClickListener listener = (OnClickListener) Proxy.newProxyInstance(TestDynamicProxy.class.getClassLoader(), new Class[]{OnClickListener.class}, new MyInvocationHandler(new MyOnClickListener()));
listener.onClick("hfw");
}
}
interface OnClickListener {
void onClick(String name);
}
class MyInvocationHandler implements InvocationHandler {
private OnClickListener mListener;
MyInvocationHandler(OnClickListener mListener) { //把真正需要代理的對象傳過來
this.mListener = mListener;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("onClick".equals(method.getName())) { //過濾需要代理的對象
System.out.println("in handler invoke" + method);
}
// System.out.println(proxy); err死循環,調用proxy對象的toString()又會調用invoke()
return method.invoke(mListener, args);
}
}
class MyOnClickListener implements OnClickListener {
@Override
public void onClick(String name) {
System.out.println("onClick:" + name);
}
}
- 反射 getXXX與getDeclaredXXX的區別,前者返回本類及其父類的public域,後者返回當前類的所有域,getEnclosingClass獲取外部類的Class對象
- 泛型 定義了類類型參數,在實例化時要傳入對應的類型,不傳會報警告,泛型方法則無這個限制,參數化的引用可以賦值給非參數化的引用(反之會拋出警告),泛型在運行時不能new、instanceOf(左邊的參數如果爲null,則直接返回false),由於不能new那麼如何創建泛型對象解決方法之一是在構造器中傳入對應的Class對象,然後通過Class.newInstance()創建,在泛型中創建數組使用Array.newInstance(),int[]數組不能賦值或強轉成Integer數組反之亦然
class CreateGenObj<T> { private T mGenObj; void create() { // T genObj = new T(); //不能創建 mGenObj = (T) new Object(); } T get() { return mGenObj; } public static void main(String[] args) { CreateGenObj<String> genObj = new CreateGenObj<>(); genObj.create(); // 更加有問題了,Object怎麼可能強轉成String String s = genObj.get(); } } class CreateGenArray<T> { private T[] mGenArray; void create() { //創建泛型數組 // mGenArray = new T[10]; //不能創建 mGenArray = (T[]) new Object[10]; } T[] get() { return mGenArray; } public static void main(String[] args) { CreateGenArray<String> cga = new CreateGenArray<>(); cga.create(); //error Object[] 不能強轉成 String[], 原因在於數組內每個元素類型是在創建數組的時候確定下來的 //這裏創建了一個Object數組,所以每個元素就是Object,由於擦除的原因create方法相當於是 //Object[] mGenArray = (Object[]) new Object[10]; 而一旦調用get(編輯器在編譯階段加入了強轉成String[]的代碼) //這個Object[]數組強轉成String[]會報錯 String[] strings = cga.get(); } } //正確創建步驟 class CreateGenObjWithRight<T> { private Class<T> mClazz; private T obj; CreateGenObjWithRight(Class<T> clazz) { mClazz = clazz; } void create() { try { obj = mClazz.newInstance(); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); obj = null; } } T get() { return obj; } public static void main(String[] args) { CreateGenObjWithRight<String> genObj = new CreateGenObjWithRight<>(String.class); genObj.create(); String s = genObj.get(); System.out.println(s); } } class CreateGenArrayWithRight<T> { private Class<T> mClazz; private T[] array; CreateGenArrayWithRight(Class<T> clazz) { mClazz = clazz; } void create() { array = (T[]) Array.newInstance(mClazz, 10); } T[] get() { return array; } public static void main(String[] args) { CreateGenArrayWithRight<String> genObj = new CreateGenArrayWithRight<>(String.class); genObj.create(); String[] s = genObj.get(); } }
- 參數類型,也就是跟在類名後面的<A, B>裏面的類型,多個類型使用逗號分割(非靜態內部類可以訪問外部類的類型參數,靜態域不能訪問類型參數),基本數據類型無法作爲類型參數,可以使用對應的包裝類型替換它們,?代表通配符其只能擁有一個邊界
- 泛型方法
private static <T> void printClassName(T t) { System.out.println(t.getClass().getName()); }
- 泛型數組,把需要使用泛型數組的地方都替換成ArrayList
- 顯示的類型說明,在.與方法之間加上明確的類型
- 元組,有時候方法想要訪問一組不同類型的對象就可以創建元組對象,將其返回,使用public final修飾使客戶端可以讀取值但是不能修改內容,擴展也很容易直接繼承就行了
public class TwoTuple<A, B> { public final A a; public final B b; public TwoTuple(A a, B b) { this.a = a; this.b = b; } } class ThreeTuple<A, B, C> extends TwoTuple<A, B> { public final C c; public ThreeTuple(A a, B b, C c) { super(a, b); this.c = c; } }
- 擦除 如果聲明瞭泛型編譯器會做檢查,但是編程成的字節碼中並不包含泛型其會被擦除掉,運行時只會保留其原始類型比如List<String>就變成了List只是取值的時候自動做了一步強轉(邊界,對象進入和離開方法的地方就是發生動作的地方,傳入時編譯器檢查類型傳出時編譯器自動強轉,使用關鍵字extends定義邊界,可以定多個邊界使用&分割,由於編譯時已經把類型信息擦除,因此在運行時通過Class.getTypeParameters()只能獲取到參數標識符和其邊界,而無法獲取到確切的類型
ArrayList<String> list = new ArrayList<>(); list.add("Hello"); // list.add(123); error list.getClass().getMethod("add", Object.class).invoke(list, 2); for (String str: list) { //error ClassCastException System.out.println(str); }
- 協變與逆變 extends決定上界,super決定下界 在get時使用extends,set時使用super
- 原生類型與<?>並不等同,如List與List<?>,前者(相當於List)表示我是一個存儲Object的一個List(不需要編譯器做額外的檢查),什麼對象都可以往裏面add,後者表示我是某一個類型(使用泛型需要編譯器檢查)不接受其他的類型,裏面除了null什麼都不能添加因爲編譯器無法確定其下界
- 捕獲轉換 當一個容器由一個特定的類型參數轉換爲原型或者是通配符’?’時,可以通過先用一個使用’?’的方法保存容器,再將容器作爲參數傳遞給泛型方法。例如上面代碼中f2調用了f1,這樣就可以在泛型方法中使用確切的參數類型
- 類型檢查,SE5前如果方法接受原生類型比如List(實際傳遞給它的是List<String>)然後往裏面添加了一個元素,這時候並不會報錯,但是從List取值並賦值給String時會從報錯,如果使用Collections.checkedList()可以在其add一個錯誤元素時就報錯(內部使用代理模式實現)
- <T extends A>與<? extends A>的區別,前者只能在類型參數聲明的地方進行聲明,也就是類名後面或者方法返回值前,後者只能在方法參數中聲明
- 注意點
List<Number> list = new ArrayList<Integer>(); //error 泛型不能上轉型 Class<Number> clazz = new Clazz<Integer>(); //同上 List<? extends Number> list = new ArrayList<Integer>(); //合法但是不能調用List中參數涉及參數類型的方法了比如add Number[] numbers = new Integer[]; //數組這樣是合法的但是裏面只能存Integer List<String>[] genericArray = new ArrayList<String>[10]; //error 不能創建泛型數組,一般直接用容器代替
- 數組
- Arrays.deepToString()用來打印多維數組
- Arrays.fill(x, y)用y填充數組x中的所有元素,基本數據類型填充值,引用類型填充同樣的引用
- Arrays.sort(Object[], Comparator)排序要求參數數組對應的元素實現Comparable接口參數二如果設置爲Collections.reverseOrder()就表示反序,對String進行排序默認是按A-Z-a-z進行排序的,如果要忽略大小寫可以傳入第二個參數String.CASE_INSENSITIVE_ORDER
- Arrays.binarySearch()二分查找法,要求參數數組必須是有序的,不然會造成有值但是找不到的情況,內部就是利用compareTo就行判斷的
- 多維數組聲明,粗糙數組(每一個維度的元素數量不同 )
int[][] array = new int[2][] //這樣聲明一個粗糙數組,一維有兩個元素(都是null) int[][] array = new int[2][4] //這樣聲明數組一維兩個元素(每個元素是一個長度爲4的數組),這樣聲明的話二維的長度都相同了 array[0].length = array[1].length = 4
- 數組的類型由其創建時確定,裏面只能存儲該類型及其子類
Animal[] animals = new Bird[2]; animals[0] = new Bird(); animals[1] = new GHost(); //error ArrayStoreException
- T[].clone()可以淺拷貝一個數組
- 容器進階 一共4中容器 List、Set、Queue、Map,HashTable、Stack、Vector屬於廢棄類,不應該使用
- Collection.nCopies()可以創建一個List裏面每個Item都一樣,這個List不能set不能add因爲其沒重寫AbstractList對應的方法,可以將其傳入別的容器的構造器或者傳入addAll方法。
- Collections.fill(),將List中的所有元素都替換成指定元素,如果裏面沒元素則不進行任何操作
- Collection.toArray()、Collection.toArray(T[])前者返回一個Object數組,後者返回一個與傳入數組類型一致的數組,如果類型會拋出異常,傳入的數組length如果小於size則返回size大小的數組,否則將T[]中空餘部分填充爲null
- Collections.max()、Collections.min()要求也是必須實現Comparable接口或者傳入一個Comparator
- Arrays.asList() 創建了一個ArrayList(其內部類)內部維護的數組就是外部數組任何會引起底層數據結構的尺寸修改都不允許所以沒有重寫AbstractList的增刪方法比如(add、remove、clear、retainAll)調用這些方法會拋出UnSupportOperationException,但是可以使用該方法生成一個List然後作爲Collection的構造器或者addAll方法或Collections.addAll方法參數傳入,這樣就沒限制了,List.subList返回一個SubList內部維護的數組就是外部List的數組
- Collections.unmodifiableList()用於創建一個不可更改的List,也就是隻讀的,調用其他方法將拋出UnSupportOperationException
- Set 爲了維護set元素的唯一性,會通過各個元素的equal來確保唯一
- HashSet 最快的查找速度,判斷重複先判斷該元素的hashCode是否存在,如不存在,直接插入,如果存在調用equals返回true表示已經存在,否則不存在執行插入
- TreeSet 內部原生必須實現Comparable以用來排序(判斷重複不使用equals,使用的是compareTo),比較int時不能簡單的使用i1-i2,比如i1爲21億,i2爲-1億,i2-i1溢出了返回負值,就導致結果不準確,一般當equal返回了true,compareTo就返回0,否則返回非0,新增方法,comparator()、first()、end()、subSet()、headSet()、tailSet()
- LinkedHashSet 內部鏈表實現,維護順序
- Map的key也是通過equal來判斷是否相等的當然散列Map與HashSet一樣先根據hashCode判斷相等再有equals判斷,總之key與Set中的要求是一致的
- 可選操作 Collections中的所有讀、寫操作都是可選的,子類(繼承自AbstractXXX的類)可以選擇不實現,這樣就使用AbstractXXX的默認實現,即拋出一個UnSupportOperationExceptions
- ListIterator 初始遊標位於0最大位於size(),假設遊標位於1,remove刪除元素1,add在元素1-2之間插入,set設置元素1的值,其中remove、set必須緊跟next,使用Iterator和ListIterator注意在迭代時不要去操作原來的Collection/List,不然如果導致內外ModCount不同就會拋出異常
- Collections.rotate() 所有元素向後移動N位,最後的移動到第一位
- Collections.swap(List) 速度比較快
- 通過Collections.synchronizedXXX()來轉化爲線程安全的容器
- Collections.disjoint(v1,v2) 如果v1和v2沒有任何相同元素返回true
- 注意點
- 寫代碼的時候必須要先考慮能不能使用已有的代碼寫代碼的時候必須要先考慮能不能使用已有的代碼
- 聲明一個內部類引用必須聲明成Outer.Inner(在main方法中創建一個非靜態內部類使用outer.new Inner(),賦值給Outer.Inner注意不能直接賦值給Inner)
- 創建鏈表的時候要先創建一個空Header,即內容和next都爲null的Header
- char都是正數,如果賦值爲-1,實際上表示的是65535,因爲char是無符號的
- 文件
- Path getPath獲取的是相對路徑也就是短路徑,getAbsolute獲取的絕對路徑也就是長路徑
- Read、Write canRead、canWrite返回是否可以讀寫
- renameTo可以用來修改文件名也可以用來移動文件比如剪切
- mkdir創建一層路徑,如果目標路徑的parent路徑不存在則失敗,mkdirs創建n層路徑,如果parent路徑不存在則創建,調用了這兩個方法那麼該File對象就代表了一個路徑
- 流
- 4個基類(抽象類) InputStream、OutputStream、Reader、Writer,前兩個操作的是字節,後兩個操作的是字符
- 4個裝飾流基類 FilterInputStream、FilterOutputStream、FilterReader、FilterWriter
- InputStream、OutputStream轉化爲Reader、Writer通過InputStreamReader和OutputStreamWriter轉化
- 默認情況下FileOutputStream寫入一個已經存在的文件會先把文件清空然後再寫入,可以通過給構造器傳入第二個參數表明是否採用append方式
- 路徑問題 比如要讀取com.hfw.test.A可以使用./src/com/hfw/test/A或者src/com/hfw/test/A
- DataInputStream、DataOutputStream讀取\寫入byte、long等基本數據類型,可以將DataOutputStream寫入的文件通過DataInputStream復原,基本可以使用RandomAccessFile(操作對象也是字節)來代替這兩個類
File file = new File("data/data.txt"); if (file.exists()) { System.out.println(file.delete() ? "刪除成功" : "刪除失敗"); } System.out.println(file.getParentFile().mkdir() ? "創建父路徑成功" : "創建父路徑失敗"); System.out.println(file.createNewFile() ? "創建文件成功" : "創建文件失敗"); DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file))); dos.writeBoolean(true); dos.writeInt(24); dos.writeUTF("1995.12.15"); dos.close(); DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(file))); System.out.println(dis.readBoolean() ? "男" : "女"); System.out.println("age is " + dis.readInt()); System.out.println("birthday is " + dis.readUTF()); dis.close();
- BufferedReader 能夠readLine()並且能提高讀取能力,BufferedWriter 能夠加快寫速度,注意文件不存在時不能寫,需要先調用createNewFile創建一個文件,寫完文件記得flush一下,close流有flush的功能
- 文件輸出,只要保證目標文件的父路徑存在就行了,如果不存在會拋出FileNotFindException,不需要createNewFile
- 重定向 標準輸入輸出,比如將其輸出到文件中可以使用System.setOut
- 調用命令,使用ProcessBuilder.start()
- nio, 主要分爲Channel、ByteBuffer,我沒從緩存區中存數據或者是取數據,ByteBuffer.wrap() 可以根據參數創建一個緩衝器,ByteBuffer.allocate用於創建指定大小的緩衝器。
FileChannel fc1 = new FileInputStream(getFilePath(CopyFileWithNio.class)).getChannel(); FileChannel fc2 = new FileOutputStream("data/CopyFileWithNio.java").getChannel(); fc1.transferTo(0, fc1.size(), fc2);
- ByteBuffer內部主要是position、limit、capacity、mark,方法 flip: limit = position,position = 0 一般用於準備從緩衝區讀取已經寫入的內容、clear: limit = capacity,position = 0、
rewind: position = 0、mark: mark = position、hasRemaining,position與limit之間是否有元素、remaining:返回limit - position,mark主要用來跟reset配合,reset可以把position置爲mark,position在調用無參的get還有單參數的put時position會發生變化,調用帶索引的get與put不會改變position - 文件鎖通過fc.tryLock()、fc.lock()獲得文件鎖,某些操作系統的文件一旦被某一個進行獲得了文件鎖其他進程就不能寫該文件,也有一些操作系統文件被獲取的文件鎖,其他進程照樣可以寫該文件(只是獲取不到鎖)這樣會導致多進程寫文件,最終導致文件損壞(比如Mac),所以在寫文件時最好都獲取一下鎖,如果鎖獲取不到表明有其他進程正在寫該文件。
- 對象序列化,Serializable在開發藝術之旅中已經有了,Externalizable可以做到部分序列化,要求寫入Object和讀取Object都由自己控制,並且在反序列化的時候會先調用默認構造器然後再調用readExternal,transient關鍵字修飾的字段可以阻止序列化。Serializable類也可以加上readObject和writeObject方法用與自己決定保存哪些對象又復原哪些對象注意這兩方法必須是private的,defaultReadObject / defaultWriteObject可以讀/寫所有非transient字段,transient字段必須在這兩個方法中明確指定,此外對象序列化也能夠實現對象的深拷貝可以字節數組輸入、字節數組輸出流做到,靜態字段不能序列化,如果需要序列化需要自己手動保存字段,然後再讀出
class Data implements Serializable { public transient String name; public int age; public Data() { System.out.println("Default constructor"); } public Data(String name, int age) { this.name = name; this.age = age; } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { // 讀取所有非transient字段,defaultReadObject必須是第一條讀取 System.out.println("ReadObject"); ois.defaultReadObject(); name = (String) ois.readObject(); } private void writeObject(ObjectOutputStream oos) throws IOException { // 寫入所有非transient字段,defaultReadObject必須是第一條寫入 System.out.println("WriteObject"); oos.defaultWriteObject(); oos.writeObject(name); } }
- java中也有Preferences可以使用Preferences.userNodeForPackage獲取,裏面的內容會進行持久化不隨着退出java進程而消失,Android裏面不行進程退出保存的數據就沒了
- 枚舉
- Enum.valueOf可以根據字符串獲取枚舉對象
- Enum實現了comparable接口,可以調用compareTo
- getDeclaringClass 獲取是那個枚舉類對象
- 當枚舉類中沒有其他屬性和方法可以省略枚舉最後的一分號
- switch使用枚舉其實使用的是枚舉的ordinal屬性進行比對的
- Class.getEnumConstants 可以獲取該類中的所有枚舉對象
- 從反編譯結果可以看出,編譯器會自動創建一個繼承於Enum的子類,不允許被繼承因爲是final的,編譯器會自動加上values和valueOf兩個方法
- EnumSet簡單來說就是通過EnumSet.noneOf創建空Set或者通過of創建非空數組或者allof創建一個全枚舉的Set其他跟普通的Set沒什麼兩樣,原理就是裏面保存了一個long數組如果枚舉數量小於64舉例某個枚舉對象A其ordinal爲38則long從右到左的第38個bit位爲1,其他位是0
- EnumMap速度也很快,內部就是一個Object數組,初始化時就創建了一個與枚舉對象等長度的數組,然後以ordinal爲下標進行put,get,
- Enum類中可以定義抽象方法,其所有枚舉實例都得實現該方法,相當於創建了一個匿名內部類
public final class com.hfw.test.Enum1 extends java.lang.Enum<com.hfw.test.Enum1> { public static final com.hfw.test.Enum1 AAAA; public static final com.hfw.test.Enum1 C; public static final com.hfw.test.Enum1 D; public static final com.hfw.test.Enum1 E; public static com.hfw.test.Enum1[] values(); public static com.hfw.test.Enum1 valueOf(java.lang.String); static {}; } enum TestE { AA{ @Override void command() { } @Override void secondCommand() { super.secondCommand(); } }; abstract void command(); void secondCommand() { } }
- 註解 元註解專門用來註解其他註解,使用註解必須給出所有沒有默認值的鍵 = 值,value除外,可以直接給出值
-
@Target 表明什麼地方可以使用該註解,定義在ElementType,多個可以用逗號分割,全部區域則可以直接省略@Target
1. CONSTRUCTOR:用於描述構造器
2. FIELD:用於描述域
3. LOCAL_VARIABLE:用於描述局部變量
4. METHOD:用於描述方法
5. PACKAGE:用於描述包
6. PARAMETER:用於描述參數
7. TYPE:用於描述類、接口(包括註解類型) 或enum聲明 -
@Retention表示需要在什麼級別保存該註解信息,定義在RetentionPolicy
1. SOURCE: 註解將被編譯器忽略
2. CLASS: 註解在class文件中可用,但會被VM拋棄
3. RUNTIME: 註解在運行時也保留,所以可以通過反射讀取註解的內容 -
@Documented 將此註解包括在javadoc中
-
@Inherited 運行子類繼承父類的註解
-
註解中所允許的類型包括基本數據類型、String、Enum、Class、Annotation、這幾個的數組,不能使用包裝類型
// 基本註解,Test裏面沒元素,屬於標記註解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @interface Test { } // 使用時必須要指定id,desc由於有默認值可以不指定@Test(id = 4, desc="hfw") @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @interface Test { int id(); String desc() default "no description"; // 定義默認值,如果沒有默認值那麼在使用該註解時必須提供該值,並且默認值不能爲null,註解時提供的值也不能爲null } Method[] methods = AnnotationOne.class.getDeclaredMethods(); for (Method method : methods) { Test anno = method.getAnnotation(Test.class); if (anno != null) { System.out.println("methodName = " + method.getName() + " id = " + anno.id() + " desc = " + anno.desc()); } } ```
- 並行與併發 併發一個時間片執行任務A,下個時間片執行任務B。併發同一時間片同時執行任務A和任務B
- 線程
- 一旦線程開啓了在其run方法沒執行完前垃圾回收器是不能回收的
- Thread.yield 表明當然線程暫時不需要CPU可以讓給其他線程,不保證一定會讓出CPU,只是建議可以把CPU讓給其他相同優先級的線程,不會讓出鎖(sleep方法也一樣不會讓出鎖),wait方法會讓出鎖(與notify和notifyAll一樣只能在同步方法和同步代碼塊中調用,不然運行時會拋出異常)
- ExecutorService 不用了要記得shutdown不然會等到裏面的所有線程都死了纔會關閉,構造器同時接受一個ThreadFactory對象,可以用來設置優先級,Daemon,名稱等
- SingleThreadExecutor 等於FixedThreadExecutor(1),裏面加入的所有任務會按照順序執行(感覺裏面維護了一個隊列)
- ExecutorService可以調用submit將一個Callable對象傳入會返回一個Future對象對其調用get()會阻塞獲取運行結果,isdone用於獲取是否已經完成,android中想不到那裏有用,主線程也不能傻傻等着
- 父線程無法捕獲子線程拋出的異常,子線程拋出了異常沒捕獲不會影響到父線程(!!!Android是個例外如果子線程拋出了一個未捕獲的RuntimeException會導致程序Crash,因爲這個了異常處理器,發現任何線程拋出異常都會強行終止app)
- 可以對線程對象調用thread.setUncaughtExceptionHandler,當該線程跑出來一個未捕獲的異常時會回調該方法,也可以調用Thread類的靜態方法Thread.setDefaultUncaughtExceptionHandler,當thread沒有調用setUncaughtExceptionHandler時會調用默認的DefaultUncaughtExceptionHandler,如果設置了就不調用,就近原則(有具體的調用具體,沒有具體的調用默認的)
- volatile確保不會不進行任何編譯器優化
- 後臺線程在還有非後臺線程在運行的情況下可以一直運行,當沒有非後臺線程在運行了會自動退出,只要在調用Thread.start之前調用setDaemon,一個後臺線程啓動的所有線程都是後臺線程,並且在非後臺線程全都停止運行了以後會立刻關閉所有的後臺線程,就算後臺線程有finally語句也不會得到執行
- 實現Runnable與繼承Thread的區別,前者還可以繼承其他類,後者不能再繼承了
- t1.join()表示當前線程需要等待t1線程執行完後才能繼續運行,也可以傳入一個timeout,表示最多等待多時毫秒
- 當線程thread1進行sleep,可以調用thread1.interrupt進行打斷,但是當thread1線程該異常被捕獲後thread1.isInterrupt會變成false
- 自增自減操作不是原子的,包括兩條語句先把變量+1/-1然後再賦值給變量
- wait、notify、notifyAll使用方法
- 三個方法都必須在同步代碼塊/方法中調用
- 如果要喚醒處於wait的線程首先必須獲得wait方法所鎖住的對象鎖,然後調用該對象的notify方法(調用該方法不會立即釋放鎖,而會等到所在的同步代碼塊執行完後纔會釋放鎖)
- notify只會隨機喚醒一個處於wait的線程,notify會喚醒所有處於等待的線程(並且線程間公平競爭鎖)
- synchronized、volatile
- 如果一個非靜態方法被聲明爲synchronized那麼對於一個對象來說如果一個線程調用了該方法,那麼在該方法返回前,這個對象的其他的synchronized方法將不能被調用(獲得鎖的那個方法可以調用其他synchronized方法),相當於非靜態方法加同步鎖,鎖的是對象
TestSynchronized t = new TestSynchronized(); new Thread(() -> t.A()).start(); //導致其他線程不能調用t.C try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } t.C(); //阻塞,獲取不到鎖 class TestSynchronized { public synchronized void A() { while (true) { // 什麼都不幹 B(); //進入這裏表示當前線程已經獲得了鎖,所以可以調用B() } } public synchronized void B() { System.out.println("B"); } public synchronized void C() { System.out.println("C"); } }
- 如果一個靜態方法被聲明爲synchronized那麼,當一個線程調用該方法,在該方法沒返回之前,其他線程無論是通過對象調用該類中的synchronized方法還是通過類名調用,都會被阻塞,靜態syncronized方法其實鎖的是類的字節碼對象
- 可以顯示的創建一個鎖。以用來替換synchronized關鍵字(可以調用tryLock來嘗試獲得鎖,如果獲取不到鎖可以先去幹什麼事)Lock lock = new ReentrantLock();
public int add() { lock.lock(); try { ++age; ++age; ++age; return age; } finally { lock.unlock(); } }
- 原子性 原子操作表示一旦操作開始那麼在操作結束之前,是不會進行上下文切換的,讀取、寫入除long、double以外的基本數據類型都可以視爲是原子操作,long、double因爲JVM可以將其當做兩個32位操作來執行所以不是原子操作,如果long、double被定義爲volatile那麼也是原子操作,因爲JVM不能將其拆成兩個32位操作了
- 可見性 關鍵字volatile提供了可見性(爲了解決多線程的易變性)以及禁止指令重排序,如果一個域不聲明爲volatile,在每個線程就會都存在一個該域的副本一個線程改了該域只是改了其副本的內容,在其寫回主存前其它線程可能感覺不到該域已經發生了改變,通過volatile關鍵字修飾,修改了該域會立即寫回主存,這樣就不會導致其他線程不可見,如果一個域完全由同步方法與語句保護,就不需要設置成volatile,因爲同步也會導致向主存中刷新。注:成員變量不屬於任何一個線程因此主線程也存在成員變量的一個備份,所以以下代碼有兩個問題1. getValue是個原子操作但是evenincrement不是一個原子操作所以可能只自增了一次就被getValue獲取到了導致出現奇數2.因爲i沒有被volatile修飾所以可見性也會是問題,子線程更改了i主線程可能看不到。解決辦法是將getValue變爲同步方法i變爲volatile,其中如果不加volatile雖然可見性問題依然存在,但是程序不會出現奇數。因爲調用getValue如果發現拿不到鎖也就是evenincrement沒有執行完會阻塞
public class AtomicityTest implements Runnable { private int i = 0; public int getValue() { return i; } private synchronized void evenIncrement() { i++; i++; } public void run() { while (true) evenIncrement(); } public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); AtomicityTest at = new AtomicityTest(); exec.execute(at); while (true) { // getValue的時候可能evenIncrement處於中間態導致只++了一次,解決方法是getValue也加上同步,在evenIncrement方法沒有執行好,調用getValue將阻塞 int val = at.getValue(); if (val % 2 != 0) { System.out.println(val); System.exit(0); } } } }
- synchronized代碼塊需要一個對象充當鎖(一般是正在調用該方法的對象this,如果需要同步多個對象的同一個方法那麼可以創建一個Object對象作爲成員變量,或者直接使用類的字節碼),只有當一個線程擁有該對象的鎖才能執行同步代碼塊
- 如果一個非靜態方法被聲明爲synchronized那麼對於一個對象來說如果一個線程調用了該方法,那麼在該方法返回前,這個對象的其他的synchronized方法將不能被調用(獲得鎖的那個方法可以調用其他synchronized方法),相當於非靜態方法加同步鎖,鎖的是對象
- 線程的狀態
- 新建(new) 線程被創建時,這個狀態只會維持很短的一段時間。此時線程已經擁有獲得CPU時間的資格了,之後調度器將把這個線程轉化爲就緒狀態或者阻塞狀態
- 就緒(runnable) 在這種狀態下只要調度器把時間片分給該線程,該線程就能運行,包括現在獲得時間片正在運行的線程
- 阻塞 線程可以運行,但是由於某個條件阻止其運行,當線程處於該狀態,調度器將忽略該線程,直到線程重新進入就緒狀態,纔有可能執行操作
- 死亡 當run方法執行完後線程就屬於死亡狀態該狀態的線程是不能被調度的,是不會得到CPU時間的
- 進入阻塞狀態的幾大原因
- 調用Thread.sleep(),任務在指定時間內不會得到運行
- 調用wait(),直到線程得到了notify、notifyAll、signal、signalAll的消息線程纔會進入就緒狀態
- 線程在等待某個輸入/輸出完成
- 任務試圖調用某個同步代碼塊或者同步方法,而又得不到鎖,會進行阻塞
- 關閉線程 1. stop不推薦 2. thread.interrupt(不能關閉處於IO阻塞(可以通過關閉阻塞的流)、同步代碼塊阻塞,可以關閉Thread.sleep)
- 可以使用javap -c 類名來反編譯.class文件
- 爲什麼要重寫hashCode,當重寫了equals也就是想用內容判斷是否相等,當A.equals(B)時,但是它們的hashCode不同,這樣會導致在使用散列容器中被認爲兩個對象不相等