Java基礎知識干貨1傳送門->http://www.lxweimin.com/p/78fdfacf1868
內部類
-
常規內部類
常規內部類的組成
- 只有內部類可以聲明為 private,常規類只可以被聲明為 public和protected
- 內部類可以訪問外部類私有域的成員變量
- 內部類不允許有靜態方法和變量
- 靜態內部類
- 靜態內部類不允許訪問外圍類對象
- 靜態內部類可以有靜態方法和靜態變量
- 聲明在接口中的內部類自動成為static和public類
Java異常層次
超類和子類的異常
- 如果在子類中覆蓋了超類的一個方法,子類方法中聲明的受查異常不能比超類中聲明的異常更通用(應該拋出更具體的異常)
- 如果超類方法沒有拋出任何受查異常,子類也不能拋出
建議同時捕獲多個異常
catch(FileNotFoundException | UnkownHostException e) {
}
可以在catch字句中再拋出一個異常,這樣做的目的是改變異常的類型
下列代碼表明執行sevlet的代碼可能不想知道發生錯誤的具體類型,但是希望明確知道servlet是否有問題。
try {
//access the database
}
catch(SQLException e) {
//這樣做可以讓用戶拋出多個異常,而又不會丟失原始異常的細節
Throwable se = new ServletException("db error");
se.init(e);
throw se;
}
泛型的聲明及調用
- 泛型類: 位于類名的后面
public class Pair<T> {
...
}
- 泛型方法: 位于修飾符的后面,返回類型的前面
public static <T> T getMiddle(T...a) {
...
}
在調用的泛型方法的時候,可以指定泛型具體參數,也可以不指定泛型具體參數.
- 在不指定泛型的情況下,泛型變量的類型為該方法的幾種類型的同一個父類的最小級,直到Object.
//Integer和Float最小父類是Number
Number num = Test.getMiddle(1,1.2);
//Integer和String最下父類是Object
Object o = Test.getMiddle(1,"aaa");
- 在指定泛型的情況下,會發生編譯錯誤
int a = Test.<Integer>getMiddle(1,1.2);
泛型類型變量的限定
下例將T限制為實現了Comparable接口的類。
一個類型變量或通配符可以有多個限定
T extends Comparable & Serializable
public static <T extends Comparable> T min(T[] a) {
...
}
運行時類型查詢只適用于原始數據類型
由于類型擦出機制的存在,虛擬機中的對象總有一個特定的非泛型類型,因此所有的類型查詢只產生原始類型
if(a instanceof Pair<String>) //error
if(a instanceof) Pair<T>) //error
getClass()方法總是返回原始類型
Pair<String> a1 = ...;
Pair<Emploee> a2 = ...;
if(a1.getClass() == a2.getClass()) //true
不能創建參數化類型的數組
Pair<String>[] table = new Pair<String>[10]; //error
因為類型擦除機制使得table能夠存放任意類型的對象,所以必須使用 ArrayList 對象。
泛型類中的靜態方法和靜態變量不可以使用泛型類所聲明的泛型類型參數
public class Singleton<T> {
private static T singleInstance; //error
public static T getSingleInstance() { //error
...
}
}
因為泛型類中的泛型參數的實例化是在定義對象的時候制定的,而靜態變量和靜態方法不需要對象來調用,所以是錯誤的。
注意擦除后的沖突
public class Pair<T> {
public boolean equals(T ob) {
return first.equals(ob);
}
}
類型擦除后,類中存在兩個 boolean equals(Object o) 方法,引起沖突,補救措施->rename
要想支持擦除的轉化,就需要強行限制一個類或類型變量不能同時成為兩個接口類型的子類,且這兩個接口是同一接口的不同參數化
class Emploee implments Comparable<Emploee> {...}
class Manager extends Emploee implments Comparable<Manager> {...} //error
隊列(queue)
隊尾添加元素,頭部刪除元素,并且可以查找隊列中元素的個數
規則: 先進先出(FIFO)
實現方式: 循環數組,鏈表
兩種實現方式的比較: 循環數組比鏈表更高效,但容量也有限,刪除(或插入)中間元素時效率很差(因為需要一個一個進行移位操作)
public interface Queue<E> {
void add(E e);
E remove();
int size();
}
Collection接口
在Java類庫中,集合類的基本接口是Collection接口
public interface Collection<E> {
boolean add(E e);
int size();
boolean isEmpty();
boolean contains(Object e);
Iterator<E> iterator();
boolean remove(Object e);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
void clear();
boolean equals(Object o);
int hasCode();
}
迭代器
Iterator接口包含4個方法
public interface Iterator<E> {
E next();
boolean hasNext();
//刪除上次訪問對象
void remove();
default void forEachRemaining(Consumer<? super E> action);
}
通過反復調用next方法,可以逐個訪問集合中的每個元素,如果到達了集合的末尾,next()方法將拋出 NoSuchElementException. 因此需在調next()方法之前調用hasNext();
如果想要查看集合中的所有元素,就請求一個迭代器
Collection<String> collection = new ArrayList<>();
Iterator<String> iterator = collection.iterator();
while(iterator.hasNext()) {
...
}
ListIterator可以實現雙向遍歷
集合框架中的接口
List是一個有序集合,元素會增加到容器的特定位置。
List的訪問方式:
- 使用迭代器訪問(必須順序訪問)
- 使用整數索引來訪問
Java具體集合
鏈表
Vector: Vector之所以沒在圖中體現是因為這是Java集合中的遺留類,但是Vector所有方法都是同步的,我們平時使用單線程應用時,應該優先選擇使用ArrayList.
ArrayList: 使用數組實現,從中間插入/刪除元素很困難,但是按索引訪問效率高。
LinkedList: 按索引訪問效率很差,需要一個一個向下面遍歷知道找到要訪問的元素。
使用ListIterator向LinkedList中添加元素:
Java語言中的所有鏈表都是雙向鏈表,鏈表是一個有序集合,每個對象的位置十分重要,由于迭代器是描述集合中位置的,只有通過迭代器對有序集合添加元素才更有意義,但Iterator接口中沒有add(),其子接口ListIterator中包含add().
ListIterator<String> iter = staff.listIterator();
//add()在迭代器位置之前添加一個元素
iter.next();
iter.add("tom");
如果多次調用add(),將依次把各元素添加至當前迭代器位置之前。
散列集
散列表不在意元素的順序,但其可以實現快速的查找所需要的對象。
在Java中,散列表使用鏈表數組實現,要想要查找表中對象的位置,就要先計算他的散列碼,然后與桶(bucket)的總數取余。
桶滿的情況下,需要將新對象逐個與鏈表中的對象進行比較,查看這個對象是否已經存在(Java SE8中,桶滿是會從鏈表變為平衡二叉樹).
如果想控制散列表的運行性能,就要指定一個初始的桶數,通常將桶數設置在預計元素的75%~150%。
當然,并不是總能預先知道需要存的元素的個數,如果散列表太滿,就需要進行再散列(rehashed),即創建一個桶數更多的表,并將所有元素插入到新表中,然后丟棄原來的表,何時丟棄由裝填因子(load factor)決定,默認為0.75
樹集
TreeSet類(使用紅黑樹實現)與散列集十分類似,不過它比散列集有所改進,它是一個有序集合,可以以任意的順序將元素插入到集合中,在對集合進行遍歷時,每個值將自動的按照排序后的順序出現。
隊列
雙端隊列: Java SE6引入了Deque接口,并由ArrayDeque和LinkedList類實現,這兩個類都提供雙端隊列,雙端隊列即可以在頭部和尾部同時添加或刪除元素。
優先級隊列(priority queue): 可以按照任意的順序插入,卻總是按照排序的順序進行檢索(使用堆(heap)來實現)
映射
Java類庫為映射提供了兩個通用的實現:HashMap和TreeMap,這兩個類都實現了Map接口
- HashMap: 使用散列表實現,可以快速的查找鍵/值
- LinkedHashMap: 迭代遍歷時,按插入次序遍歷,迭代時也可以按照LRU(最近最少使用算法,長時間未使用的在前)次序迭代,迭代訪問比HashMap快,因為它使用鏈表維護內部次序。
- WeakHashMap: 允許釋放映射所指的對象
- ConcurrentHashMap: 線程安全的Map
- IdentityHashMap: 使用==代替equals()對鍵進行比較
線程安全的容器(簡記為“喂,SHE”)
Vector: 效率低,但是比ArrayList多了個同步化機制,在web應用中特別是前臺頁面,往往效率優先,故不常使用。
Stack:
hashtable: 比hashMap多了個線程安全
enumeration:
枚舉類型(不能被繼承)
枚舉是一組含有有限個具名值的集合,使用關鍵字enum聲明枚舉類型
enum shrubbery {GROUND,CRAWLING,HANGING}
除了不能繼承一個enum外,基本上可以將enum看做一個常規的類,甚至可以有main()方法
public enum EnumTest{
WOLF("This is a wolf"),
TIGET("This is a tiger"),
SNAKE("This is a snake");
//必須在實例之后定義方法和變量
private String desc;
public String getDesc() {
return desc;
}
//構造函數聲明為private
private EnumTest(String desc) {
this.desc = desc;
}
}
遍歷
for(EnumTest item : EnumTest.values()) {
System.out.println(item + item.getDesc());
}
枚舉中的values():
我們原本聲明的枚舉類Explore
enum Explore{
HERE,THERE
}
經過編譯后變為
final class Explore extends java.lang.Enun{
public static final Explore HERE;
public static final Explore THERE;
//自動添加
public static final Explorep[] values();
//自動添加
public static final Explore valueOf(java.lang.String);
static{};
}
任務的定義
線程可以驅動任務,而任務的實現必須實現Runnable接口并編寫run()方法。
任務的run()方法通常會有某種形式的循環,使得任務一直運行下去直到不再需要。
在run()方法中對靜態方法Thread.yield()的調用是對線程調度器的一種建議,建議它切換至另一個任務執行。
Thread類
將Runnable對象轉變為工作任務的傳統方式是把它提交給一個Thread構造器。
public class BasicThread {
public static void main(String[] args) {
Thread t = new Thread(new LiftOff());
t.start();
}
}
使用Executor
Executor在客戶端和任務執行之間提供了一個間接層,與客戶端直接執行任務不同,這個中介對象將執行任務。Executor允許你管理異步任務的執行,而無需顯式的管理線程的聲明周期
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new LiftOff());
//防止新任務被提交給exec
exec.shutdown();
- newCachedThreadPool(): 運行時為每個線程分派一個線程。
- newFixedThreadPool(): 一次性執行代價高昂的線程分配。
- newSingleThreadExecutor(): 一次只運行一個線程,多個需要排隊.
從任務中產生返回值
Runnable是執行工作中的獨立任務,不產生任何返回值,如果希望任務完成時能夠返回一個值,那么可以實現Callable接口。
public TaskWithResult implements Callable<String> {
private int id;
public TaskWithResult(int id) {
this.id = id;
}
public String call() {
return "result is " + id;
}
}
調用
ExecutorService exec = Executors.newCachedThreadPool();
ArrayList<Future<String>> array = new ArrayList<>();
for(int i = 0;i < 5;i++) {
//必須通過submit()來調用,調用之后返回Future對象
array.add(exec.submit());
}
for(Future<String> fu : array) {
//使用get()獲取結果,獲取結果前可以使用isDone()來檢查是否完成
System.out.println(fu.get());
}
休眠
try {
TimeUnit.MILLSECONDS.sleep(100);
} catch (InterruptedException e) {
System.err.println("interrupted");
}
優先級
public void run() {
//在run()方法開頭設置優先級
Thread.currentThread().setPriority(10);
}
一般調整優先級時,只是用MAX_PRIORITY,NORM_PRIORITY,MIN_PRIORITY這三個級別.
后臺線程(daemon)
后臺線程不屬于程序中不可或缺的一部分,當所有的非后臺程序全部執行完畢時,程序也就終止了,同時會殺死進程中的所有后臺線程,反過來說,只要有任何非后臺線程在運行,程序就不會終止。
Thread t = new Thread(new LiftOff());
t.setDaemon(true);
t.start();
可以通過調用isDaemon()來判斷一個線程是否是一個后臺線程,如果是一個后臺線程,那么它創建的所有后臺線程也都是后臺線程。
通過編寫定制的ThreadFactory()可以定制由Executor創建的線程的屬性.
public class DaemonThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
}
調用
ExecutorService exec = Exectors.newCachedThreadPool(new DaemonThreadFactory());
另外,后臺線程在不執行finally語句的情況下就會終止run()方法。