面試心得與總結(一)
找工作一定要做好Homework,認準準備筆試和麵試,認真研究你投簡歷的公司
如果你能給應聘公司提出建議,指出他們公司產品的不足,可以改進的地方,那應聘成功的機率就回提高
之前有朋友分析了某個公司的Android客戶端App,寫了一份郵件傳送給了那個公司,然後有了一次面試機會,那家公司沒有在招聘,竟然成功了
心態很重要!
心態很重要!
心態很重要!
找工作之前,有一點你必須清楚,就是找工作是一件看緣分的事情,不是你很牛逼,你就一定能進你想進的公司,都是有一個概率在那。如果你基礎好,專案經驗足,同時準備充分,那麼你拿到offer的概率就會比較高;相反,如果你準備不充分,基礎也不好,那麼你拿到offer的概率就會比較低,但是你可以多投幾家公司,這樣拿到offer的機率就要大一點,因為你總有運氣好的時候。所以,不要懼怕面試,剛開始失敗了沒什麼的,多投多嘗試,面多了你就自然能成麵霸了。得失心也不要太重,最後每個人都會有offer的。
還有一個對待工作的心態,有些人可能覺得自己沒有動力去找一個好工作。其實你需要明白一件事情,你讀了十幾二十年的書,為的是什麼,最後不就是為了找到一個好工作麼。現在到了關鍵時刻,你為何不努力一把呢,為什麼不給自己一個好的未來呢,去一個自己不滿意的公司工作,你甘心嗎?
想清楚這一點,我相信大多數人都會有一股幹勁了,因為LZ剛剛準備開始找實習的時候,BAT這種公司想都不敢想,覺得能進個二線公司就很不錯了,後來發現自己不逼自己一把,你真不知道自己有多大能耐,所以請對找工作保持積極與十二分的熱情,也請認真對待每一次筆試面試。
J2SE基礎
一、八種基本資料型別的大小,以及他們的封裝類?
Java提供了一組基本資料型別,包括 boolean, byte, char, short, int, long, float, double.
同時,java也提供了這些型別的封裝類,分別為 Boolean, Byte, Character, Short, Integer, Long, Float, Double
為什麼Java會這麼做?在java中使用基本型別來儲存語言支援的基本資料型別,這裡沒有采用物件,而是使用了傳統的面向過程語言所採用的基本類在型,主要是從效能方面來考慮的:因為即使最簡單的數學計算,使用物件來處理也會引起一些開銷,而這些開銷對於數學計算本來是毫無必要的。但是在java中,泛型類包括預定義的集合,使用的引數都是物件型別,無法直接使用這些基本資料型別,所以java又提供了這些基本型別的包裝器。
區別:1、基本資料型別只能按值傳遞,而封裝類按引用傳遞,2、基本型別在堆疊中建立;而對於物件型別,物件在堆中建立,物件的引用在堆疊中建立。基本型別由於在堆疊中,效率會比較高,但是可能會存在記憶體洩漏的問題。
二、Switch能否用string做引數?
在 Java 7之前,switch 只能支援 byte、short、char、int或者其對應的封裝類以及 Enum 型別。在 Java 7中,String支援被加上了。
三、equals與==的區別?
“==”比較的是值【變數(棧)記憶體中存放的物件的(堆)記憶體地址】
equal用於比較兩個物件的值是否相同【不是比地址】
【特別注意】Object類中的equals方法和“==”是一樣的,沒有區別,而String類,Integer類等等一些類,是重寫了equals方法,才使得equals和“==不同”,所以,當自己建立類時,自動繼承了Object的equals方法,要想實現不同的等於比較,必須重寫equals方法。”==”比”equal”執行速度快,因為”==”只是比較引用.
四、 Object有哪些公用方法?
Object o = new Object();
/**
* 比較當前物件和是否等於另一個物件,指向的物件是否相同
*/
System.out.println(o.equals(new Object()));
/**
* 返回hashCode
*/
System.out.println(o.hashCode());
/**
* 返回包名+類名+Integer.toHexString(hashCode())
*/
System.out.println(o.toString());
/**
* 返回class物件
*/
System.out.println(o.getClass());
try {
/**
* 執行緒等待,Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.
*/
o.wait();
o.wait(1000);
o.wait(1000,1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/**
* notify()和notifyAll()都是Object物件用於通知處在等待該物件的執行緒的方法。兩者的最大區別在於:
* notifyAll使所有原來在該物件上等待被notify的執行緒統統退出wait的狀態,變成等待該物件上的鎖,一旦該物件被解鎖,他們就會去競爭。
* notify則文明得多他只是選擇一個wait狀態執行緒進行通知,並使它獲得該物件上的鎖,但不驚動其他同樣在等待被該物件notify的執行緒們,當第一個執行緒執行完畢以後釋放物件上的鎖此時如果該物件沒有再次使用notify語句,則即便該物件已經空閒,其他wait狀態等待的執行緒由於沒有得到該物件的通知,繼續處在wait狀態,直到這個物件發出一個notify或notifyAll,它們等待的是被notify或notifyAll,而不是鎖。
*/
o.notify();
o.notifyAll();
五、Java的四種引用,強弱軟虛,用到的場景。
強引用
最普遍的一種引用方式,如String s = “abc”,變數s就是字串“abc”的強引用,只要強引用存在,則垃圾回收器就不會回收這個物件。
軟引用(SoftReference)
用於描述還有用但非必須的物件,如果記憶體足夠,不回收,如果記憶體不足,則回收。一般用於實現記憶體敏感的快取記憶體,軟引用可以和引用佇列ReferenceQueue聯合使用,如果軟引用的物件被垃圾回收,JVM就會把這個軟引用加入到與之關聯的引用佇列中。
弱引用(WeakReference)
弱引用和軟引用大致相同,弱引用與軟引用的區別在於:只具有弱引用的物件擁有更短暫的生命週期。在垃圾回收器執行緒掃描它所管轄的記憶體區域的過程中,一旦發現了只具有弱引用的物件,不管當前記憶體空間足夠與否,都會回收它的記憶體。
虛引用(PhantomReference)
就是形同虛設,與其他幾種引用都不同,虛引用並不會決定物件的生命週期。如果一個物件僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。 虛引用主要用來跟蹤物件被垃圾回收器回收的活動。
虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用佇列 (ReferenceQueue)聯合使用。當垃圾回收器準備回收一個物件時,如果發現它還有虛引用,就會在回收物件的記憶體之前,把這個虛引用加入到與之關聯的引用佇列中
六、Hashcode的作用
hashCode方法的主要作用是為了配合基於雜湊的集合一起正常執行,這樣的雜湊集合包括HashSet、HashMap以及HashTable
七、ArrayList、LinkedList、Vector的區別
這三者都是實現了List介面,都擁有List接口裡面定義的方法,並且同時擁有Collection介面的方法;
ArrayList:採用的是陣列的方式進行儲存資料的,查詢和修改速度快,但是增加和刪除速度慢;執行緒是不同步的
LinkedList:採用的是連結串列的方式進行儲存資料的,查詢和修改速度慢,但是增加和刪除速度快;執行緒是不同步的
Vector:也採用的是陣列的方式進行儲存的,Vector在java1.0以前用,但是ArrayList是在java1.2版本後使用的,執行緒是同步的,效率相比ArrayList來說慢一點;同時Vector查詢資料有迭代器,有列舉,有get(int index),有indexOf(int index)四種方式,而ArrayList卻沒有列舉
八、 String、StringBuffer與StringBuilder的區別
1、可變與不可變
String類中使用字元陣列儲存字串,如下就是,因為有“final”修飾符,所以可以知道string物件是不可變的。
private final char value[];
StringBuilder與StringBuffer都繼承自AbstractStringBuilder類,在AbstractStringBuilder中也是使用字元陣列儲存字串,如下就是,可知這兩種物件都是可變的。
char[] value;
2、是否多執行緒安全
String中的物件是不可變的,也就可以理解為常量,顯然執行緒安全。
AbstractStringBuilder是StringBuilder與StringBuffer的公共父類,定義了一些字串的基本操作,如expandCapacity、append、insert、indexOf等公共方法
StringBuffer對方法加了同步鎖或者對呼叫的方法加了同步鎖,所以是執行緒安全的
StringBuilder並沒有對方法進行加同步鎖,所以是非執行緒安全的
九、Map、Set、List、Queue、Stack的特點與用法
Map
Map是鍵值對,鍵Key是唯一不能重複的,一個鍵對應一個值,值可以重複。
TreeMap可以保證順序,HashMap不保證順序,即為無序的。
Map中可以將Key和Value單獨抽取出來,其中KeySet()方法可以將所有的keys抽取正一個Set。而Values()方法可以將map中所有的values抽取成一個集合。
Set
不包含重複元素的集合,set中最多包含一個null元素
只能用Lterator實現單項遍歷,Set中沒有同步方法
List
有序的可重複集合。
可以在任意位置增加刪除元素。
用Iterator實現單向遍歷,也可用ListIterator實現雙向遍歷
Queue
Queue遵從先進先出原則。
使用時儘量避免add()和remove()方法,而是使用offer()來新增元素,使用poll()來移除元素,它的優點是可以通過返回值來判斷是否成功。
LinkedList實現了Queue介面。
Queue通常不允許插入null元素
Stack
Stack遵從後進先出原則。
Stack繼承自Vector。
它通過五個操作對類Vector進行擴充套件,允許將向量視為堆疊,它提供了通常的push和pop操作,以及取堆疊頂點的peek()方法、測試堆疊是否為空的empty方法等
如果涉及堆疊,佇列等操作,建議使用List
對於快速插入和刪除元素的,建議使用LinkedList
如果需要快速隨機訪問元素的,建議使用ArrayList
HashMap和Hashtable都實現了Map介面,但決定用哪一個之前先要弄清楚它們之間的分別。主要的區別有:執行緒安全性,同步(synchronization),以及速度。
十、 HashMap和HashTable的區別。
1.HashMap幾乎可以等價於Hashtable,除了HashMap是非synchronized的,並可以接受null(HashMap可以接受為null的鍵值(key)和值(value),而Hashtable則不行)。
2.HashMap是非synchronized,而Hashtable是synchronized,這意味著Hashtable是執行緒安全的,多個執行緒可以共享一個Hashtable;而如果沒有正確的同步的話,多個執行緒是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的擴充套件性更好。
3.另一個區別是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以當有其它執行緒改變了HashMap的結構(增加或者移除元素),將會丟擲ConcurrentModificationException,但迭代器本身的remove()方法移除元素則不會丟擲ConcurrentModificationException異常。但這並不是一個一定發生的行為,要看JVM。這條同樣也是Enumeration和Iterator的區別。
4.由於Hashtable是執行緒安全的也是synchronized,所以在單執行緒環境下它比HashMap要慢。如果你不需要同步,只需要單一執行緒,那麼使用HashMap效能要好過Hashtable。
5.HashMap不能保證隨著時間的推移Map中的元素次序是不變的
十一、HashMap和ConcurrentHashMap的區別,HashMap的底層原始碼。
Hashmap本質是陣列加連結串列。根據key取得hash值,然後計算出陣列下標,如果多個key對應到同一個下標,就用連結串列串起來,新插入的在前面。
ConcurrentHashMap:在hashMap的基礎上,ConcurrentHashMap將資料分為多個segment(類似hashtable),預設16個(concurrency level),然後在每一個分段上都用鎖進行保護,從而讓鎖的粒度更精細一些,併發效能更好
十二、TreeMap、HashMap、LindedHashMap的區別
1.HashMap裡面存入的鍵值對在取出的時候是隨機的,也是我們最常用的一個Map.它根據鍵的HashCode值儲存資料,根據鍵可以直接獲取它的值,具有很快的訪問速度。在Map 中插入、刪除和定位元素,HashMap 是最好的選擇。
2.TreeMap取出來的是排序後的鍵值對。但如果您要按自然順序或自定義順序遍歷鍵,那麼TreeMap會更好。
3. LinkedHashMap 是HashMap的一個子類,如果需要輸出的順序和輸入的相同,那麼用LinkedHashMap可以實現. (應用場景:購物車等需要順序的)
十三、Collection包結構,與Collections的區別。
Collection是集合類的上級介面,子介面主要有Set 和List、Map。
Collections是針對集合類的一個幫助類,提供了操作集合的工具方法:一系列靜態方法實現對各種集合的搜尋、排序、執行緒安全化等操作。
十四、try catch finally,try裡有return,finally還執行麼?
1、不管有木有出現異常,finally塊中程式碼都會執行;
2、當try和catch中有return時,finally仍然會執行;
3、finally是在return後面的表示式運算後執行的(此時並沒有返回運算後的值,而是先把要返回的值儲存起來,管finally中的程式碼怎麼樣,返回的值都不會改變,任然是之前儲存的值),所以函式返回值是在finally執行前確定的;
4、finally中最好不要包含return,否則程式會提前退出,返回值不是try或catch中儲存的返回值。
情況1:try{} catch(){}finally{} return;
顯然程式按順序執行。
情況2:try{ return; }catch(){} finally{} return;
程式執行try塊中return之前(包括return語句中的表示式運算)程式碼;
再執行finally塊,最後執行try中return;
finally塊之後的語句return,因為程式在try中已經return所以不再執行。
情況3:try{ } catch(){return;} finally{} return;
程式先執行try,如果遇到異常執行catch塊,
有異常:則執行catch中return之前(包括return語句中的表示式運算)程式碼,再執行finally語句中全部程式碼,
最後執行catch塊中return. finally之後也就是4處的程式碼不再執行。
無異常:執行完try再finally再return.
情況4:try{ return; }catch(){} finally{return;}
程式執行try塊中return之前(包括return語句中的表示式運算)程式碼;
再執行finally塊,因為finally塊中有return所以提前退出。
情況5:try{} catch(){return;}finally{return;}
程式執行catch塊中return之前(包括return語句中的表示式運算)程式碼;
再執行finally塊,因為finally塊中有return所以提前退出。
情況6:try{ return;}catch(){return;} finally{return;}
程式執行try塊中return之前(包括return語句中的表示式運算)程式碼;
有異常:執行catch塊中return之前(包括return語句中的表示式運算)程式碼;
則再執行finally塊,因為finally塊中有return所以提前退出。
無異常:則再執行finally塊,因為finally塊中有return所以提前退出。
最終結論:任何執行try 或者catch中的return語句之前,都會先執行finally語句,如果finally存在的話。
如果finally中有return語句,那麼程式就return了,所以finally中的return是一定會被return的,
編譯器把finally中的return實現為一個warning。
十五、Excption與Error包結構。OOM你遇到過哪些情況,SOF你遇到過哪些情況
(一)Throwable
Throwable 類是 Java 語言中所有錯誤或異常的超類。只有當物件是此類或其子類之一的例項時,才能通過 Java 虛擬機器或者 Java throw 語句丟擲,才可以是 catch 子句中的引數型別。
Throwable 類及其子類有兩個構造方法,一個不帶引數,另一個帶有 String 引數,此引數可用於生成詳細訊息。
Throwable 包含了其執行緒建立時執行緒執行堆疊的快照。它還包含了給出有關錯誤更多資訊的訊息字串。
Java將可丟擲(Throwable)的結構分為三種類型:
錯誤(Error)
執行時異常(RuntimeException)
被檢查的異常(Checked Exception)
1.Error
Error 是 Throwable 的子類,用於指示合理的應用程式不應該試圖捕獲的嚴重問題。大多數這樣的錯誤都是異常條件。
和RuntimeException一樣, 編譯器也不會檢查Error。
當資源不足、約束失敗、或是其它程式無法繼續執行的條件發生時,就產生錯誤,程式本身無法修復這些錯誤的。
2.Exception
Exception 類及其子類是 Throwable 的一種形式,它指出了合理的應用程式想要捕獲的條件。
對於可以恢復的條件使用被檢查異常(Exception的子類中除了RuntimeException之外的其它子類),對於程式錯誤使用執行時異常。
① ClassNotFoundException
當應用程式試圖使用以下方法通過字串名載入類時:
Class 類中的 forName 方法。
ClassLoader 類中的 findSystemClass 方法。
ClassLoader 類中的 loadClass 方法。
但是沒有找到具有指定名稱的類的定義,丟擲該異常。
② CloneNotSupportedException
當呼叫
Object 類中的 clone 方法複製物件,但該物件的類無法實現 Cloneable 介面時,丟擲該異常。重寫 clone 方法的應用程式也可能丟擲此異常,指示不能或不應複製一個物件。
③ IOException
當發生某種 I/O 異常時,丟擲此異常。此類是失敗或中斷的 I/O 操作生成的異常的通用類。
-EOFException
當輸入過程中意外到達檔案或流的末尾時,丟擲此異常。
此異常主要被資料輸入流用來表明到達流的末尾。
注意:其他許多輸入操作返回一個特殊值表示到達流的末尾,而不是丟擲異常。
-FileNotFoundException
當試圖開啟指定路徑名錶示的檔案失敗時,丟擲此異常。
在不存在具有指定路徑名的檔案時,此異常將由 FileInputStream、FileOutputStream 和 RandomAccessFile 構造方法丟擲。如果該檔案存在,但是由於某些原因不可訪問,比如試圖開啟一個只讀檔案進行寫入,則此時這些構造方法仍然會丟擲該異常。
-MalformedURLException
丟擲這一異常指示出現了錯誤的 URL。或者在規範字串中找不到任何合法協議,或者無法解析字串。
-UnknownHostException
指示主機 IP 地址無法確定而丟擲的異常。
④ RuntimeException
是那些可能在 Java 虛擬機器正常執行期間丟擲的異常的超類。可能在執行方法期間丟擲但未被捕獲的 RuntimeException 的任何子類都無需在 throws 子句中進行宣告。
Java編譯器不會檢查它。當程式中可能出現這類異常時,還是會編譯通過。
雖然Java編譯器不會檢查執行時異常,但是我們也可以通過throws進行宣告丟擲,也可以通過try-catch對它進行捕獲處理。
-ArithmeticException
當出現異常的運算條件時,丟擲此異常。例如,一個整數“除以零”時,丟擲此類的一個例項。
-ClassCastException
當試圖將物件強制轉換為不是例項的子類時,丟擲該異常。
例如:Object x = new Integer(0);
-LllegalArgumentException
丟擲的異常表明向方法傳遞了一個不合法或不正確的引數。
-IllegalStateException
在非法或不適當的時間呼叫方法時產生的訊號。換句話說,即 Java 環境或 Java 應用程式沒有處於請求操作所要求的適當狀態下。
-IndexOutOfBoundsException
指示某排序索引(例如對陣列、字串或向量的排序)超出範圍時丟擲。
應用程式可以為這個類建立子類,以指示類似的異常。
-NoSuchElementException
由 Enumeration 的 nextElement 方法丟擲,表明列舉中沒有更多的元素。
-NullPointerException
當應用程式試圖在需要物件的地方使用 null 時,丟擲該異常。這種情況包括:
呼叫 null 物件的例項方法。
訪問或修改 null 物件的欄位。
將 null 作為一個數組,獲得其長度。
將 null 作為一個數組,訪問或修改其時間片。
將 null 作為 Throwable 值丟擲。
應用程式應該丟擲該類的例項,指示其他對 null 物件的非法使用。
(二) SOF (堆疊溢位 StackOverflow)
StackOverflowError 的定義:
當應用程式遞迴太深而發生堆疊溢位時,丟擲該錯誤。
因為棧一般預設為1-2m,一旦出現死迴圈或者是大量的遞迴呼叫,在不斷的壓棧過程中,造成棧容量超過1m而導致溢位。
棧溢位的原因:
1.大量迴圈或死迴圈
2.全域性變數是否過多
3.陣列、List、map資料過大
(三)OOM
1.資源物件沒關閉造成的記憶體洩露,try catch finally中將資源回收放到finally語句可以有效避免OOM。資源性物件比如:
1-1,Cursor
1-2,呼叫registerReceiver後未呼叫unregisterReceiver()
1-3,未關閉InputStream/OutputStream
1-4,Bitmap使用後未呼叫recycle()
2.作用域不一樣,導致物件不能被垃圾回收器回收,比如:
2-1,非靜態內部類會隱式地持有外部類的引用,
2-2,Context洩露,概括一下,避免Context相關的記憶體洩露,記住以下事情:
1、 不要保留對Context-Activity長時間的引用(對Activity的引用的時候,必須確保擁有和Activity一樣的生命週期)
2、嘗試使用Context-Application來替代Context-Activity
3、如果你不想控制內部類的生命週期,應避免在Activity中使用非靜態的內部類,而應該使用靜態的內部類,並在其中建立一個對Activity的弱引用。這種情況的解決辦法是使用一個靜態的內部類,其中擁有對外部類的weakReference。
2-3,Thread 引用其他物件也容易出現物件洩露。
2-4,onReceive方法裡執行了太多的操作
3.記憶體壓力過大
3-1,圖片資源載入過多,超過記憶體使用空間,例如Bitmap 的使用
3-2,重複建立view,listview應該使用convertview和viewholder
十六、Java面向物件的三個特徵與含義。
三大特徵:封裝、繼承、多型
封裝:
首先,屬性可用來描述同一類事物的特徵, 行為可描述一類事物可做的操作,封裝就是要把屬於同一類事物的共性(包括屬性與行為)歸到一個類中,以方便使用.比如人這個東東,可用下面的方式封裝:
人{
年齡(屬性一)
身高(屬性二)
性別(屬性三)
做事(行為之一)
走路(行為之二)
說話(行為之三)
}
繼承:
由於封裝,使得有共同特徵的一類事物的所有描述資訊都被歸於一類之中,但我們知道,這並不是萬能的,有些事物有共性,但還存在區別,比如教師,簡單封裝起來如下:
教師{
年齡(屬性一)
身高(屬性二)
性別(屬性三)
做事(行為之一)
走路(行為之二)
說話(行為之三)
教書(行為之四)
}
上面對”教師”的封裝,與對”人”的封裝基本上差不多,只是多了一個特徵行為:教書,
教師有與人一樣的共性, 但我們不能說”人教書”,也就是不能把教書封裝到”人”之中去,教書是教師的特徵行為之一. 為了省事地封裝教師(程式碼的複用,這只是繼承存在的原因之一), 可以讓教師去繼承人,如:
教師 extends 人{
教書(行為之三)
}
這樣,我們就不用重新定義那些已經被”人”這一個類所封裝的那些屬性與行為了,而只需要使用繼承的方式,在人的基礎上拓展教師專有的行為,即”教書”即可把教師描述出來;這樣的結果, 即是教師也同時擁有”人”之中所封裝的一切屬性與行為, 還擁有自己的特徵行為”教書”.
多型:
多型的概念發展出來,是以封裝和繼承為基礎的(其實我覺得抽象也應該算是面向物件的大特徵之一,要封裝,抽象是必須的)
簡單的理解一下多型,比如:
人這個類,封裝了很多人類共有的特性,
教師是人的子類,繼承了人的屬性與行為,當然教師有自己的特徵行為,比如教書授課;
學生是人的子類,繼承了人的屬性與行為,當然學生有自己的特徵行為,比如學習做作業;
現在,當我們需要去描述教師與學生各自的行為的時候, 我們可以分開來說”教師在授課”, “學生做作業”, 但如果我們要站在抽象的角度, 也就是從教師與學生的父類”人”的角度, 來同時描述他們各自的行為時,我們怎麼描述?”人在授課”?”人在做作業”?這是不是怪怪的很不合適?不合適的問題就在於, 對於行為主體,我們使用了抽象層次的東東”人”,而對於行為本身, 我們卻使用了具體的東東”授課”與”教書”. 怎麼解決呢? 那就需要解決抽象與具體的矛盾問題.
既然是站在抽象在角度來描述,那我們把行為抽象一下,不就能同時描述了嗎?比如”人在做事”(教師授課與學生做作業都可以說成人在做事),這樣就解決了抽象層次與具體層次之間的矛盾.
到了這一步, 我們可以把兩個描述: “教師在做事”, “學生在做事” 兩者統一為”人在做事”,
然後, 我們可以在”教師”的”做事”行為中去呼叫教師自己的特徵行為”授課”,在”學生”的”做事”行為中去呼叫學生自己的特徵行為”做作業”,
所以,當呼叫”人”去”做事”的時候,如果這個人是教師,那他所做的事實際上就是”教書”,
如果這個人是學生,那他所做的事實際上就是”做作業”.
也就是說在這裡”人”是多型的, 在不同的形態時,特徵行為是不一樣的, 這裡的”人”, 同時有兩種形態,一種是教師形態,一種是學生形態,所對應的特徵行為分別是”授課”與”做作業”.
完成上述的描述過程, 其實就是多型機制的體現.
多型, 就是站在抽象的層面上去實施一個統一的行為,到個體(具體)的層面上時, 這個統一的行為會因為個體(具體)的形態特徵而實施自己的特徵行為.
多型比起封裝與繼承來說要複雜很多, 上面的描述很簡單, 不用去死摳多型兩個字,其實只要明白: 能站在抽象的角度去描述一件事, 而針對這件抽象的事, 對於每個個體(具體)又能找到其自身的行為去執行, 這就是多型.
十七、Override和Overload的含義去區別。
override是方法的重寫,通常發生在子類與父類之中,指的是子類中定義了一個與父類返回值型別,引數型別完全相同的方法
overload是方法的過載,通常在同一個類中,定義了一堆方法名相同,但返回值可能不同,引數也可能不同的方法
對於過載而言:
1、引數型別、個數、順序至少有一個不相同。
2、不能過載只有返回值不同的方法名。
3、存在於父類和子類、同類中。
方法的重寫(Overriding)和過載(Overloading)是Java多型性的不同表現。
重寫(Overriding)是父類與子類之間多型性的一種表現,而過載(Overloading)是一個類中多型性的一種表現。
十八、Interface與abstract類的區別
abstract類不能建立的例項物件。含有abstract方法的類必須定義為abstract class,abstract class類中的方法不必是抽象的。
abstract class類中定義抽象方法必須在具體(Concrete)子類中實現
如果的子類沒有實現抽象父類中的所有抽象方法,那麼子類也必須定義為abstract型別。
介面(interface)可以說成是抽象類的一種特例,介面中的所有方法都必須是抽象的。介面中的方法定義預設為public abstract型別,介面中的成員變數型別預設為
public static final。
下面比較一下兩者的語法區別:
1.抽象類可以有構造方法,介面中不能有構造方法。
2.抽象類中可以有普通成員變數,介面中沒有普通成員變數
3.抽象類中可以包含非抽象的普通方法,介面中的所有方法必須都是抽象的,不能有非抽象的普通方法。
4.抽象類中的抽象方法的訪問型別可以是public,protected,但介面中的抽象方法只能是public型別的,並且預設即為public abstract型別。
5.抽象類中可以包含靜態方法,介面中不能包含靜態方法
6.抽象類和介面中都可以包含靜態成員變數,抽象類中的靜態成員變數的訪問型別可以任意,但介面中定義的變數只能是public static final型別,並且預設即為一個類可以實現多個介面,但只能繼承一個抽象類。
十九、Static class 與non static class的區別。
在java中我們可以有靜態例項變數、靜態方法、靜態塊。類也可以是靜態的。
java允許我們在一個類裡面定義靜態類。比如內部類(nested class)。把nested class封閉起來的類叫外部類。在java中,我們不能用static修飾頂級類(top level class)。只有內部類可以為static。
靜態內部類和非靜態內部類之間到底有什麼不同呢?下面是兩者間主要的不同。
(1)內部靜態類不需要有指向外部類的引用。但非靜態內部類需要持有對外部類的引用。
(2)非靜態內部類能夠訪問外部類的靜態和非靜態成員。靜態類不能訪問外部類的非靜態成員。他只能訪問外部類的靜態成員。
(3)一個非靜態內部類不能脫離外部類實體被建立,一個非靜態內部類可以訪問外部類的資料和方法,因為他就在外部類裡面。
根據Oracle官方的說法:
Nested classes are divided into two categories: static and non-static. Nested classes that are declared static are called static nested classes. Non-static nested classes are called inner classes.
從字面上看,一個被稱為靜態巢狀類,一個被稱為內部類。
從字面的角度解釋是這樣的:
什麼是巢狀?巢狀就是我跟你沒關係,自己可以完全獨立存在,但是我就想借你的殼用一下,來隱藏一下我自己(真TM猥瑣)。
什麼是內部?內部就是我是你的一部分,我瞭解你,我知道你的全部,沒有你就沒有我。(所以內部類物件是以外部類物件存在為前提的)
簡單理解就是:如果把類比喻成雞蛋,內部類為蛋黃,外部類是蛋殼。那麼靜態類相當於熟雞蛋,就算蛋殼破碎(外部類沒有例項化),蛋黃依然完好(內部類可以例項化);而非靜態類相當於生雞蛋,蛋殼破碎(無例項化),蛋黃也會跟著xx(不能例項化)。
二十、 java多型的實現原理。
多型的概念:同一操作作用於不同物件,可以有不同的解釋,有不同的執行結果,這就是多型,簡單來說就是:父類的引用指向子類物件。下面先看一段程式碼
package polymorphism;
class Dance {
public void play(){
System.out.println("Dance playing");
}
}
class Latin extends Dance {
public void play(){
System.out.println("Latin playing");
}
}
class Tango extends Dance {
public void play(){
System.out.println("Tango playing");
}
}
public class Test {
public void perform(Dance dance){
dance.play();
}
public static void main(String[] args){
new Test().perform(new Latin()); // 向上型別轉換
}
}
執行結果:Latin playing。這個時候你可能會發現perform()方法裡面並沒有引數型別的判斷,其實這就是多型的特性,它消除了型別之間的耦合關係,令我們可以把一個物件不當做它所屬的特定型別來對待,而是當做其基類的型別來對待。因為上面的Test程式碼完全可以這麼寫:
public class Test {
public void perform(Latin dance){
dance.play();
}
public void perform(Tango dance){
dance.play();
}
public static void main(String[] args){
new Test().perform(new Latin());
}
}
但是這樣你會發現,如果增加更多新的類似perform()或者自Dance匯出的新類,就會增加大量的工作,而通過比較就會知道第一處程式碼會佔優勢,這正是多型的優點所在,它改善了程式碼的組織結構和可讀性,同時保證了可擴充套件性。
二十一、實現多執行緒的兩種方法:Thread與Runable
Java中有兩種實現多執行緒的方式。一是直接繼承Thread類,二是實現Runnable介面。那麼這兩種實現多執行緒的方式在應用上有什麼區別呢?
先看第一種:
class MyThread extends Thread{
private int ticket = 100;
public void run(){
while(true){
if(ticket > 0){
System.out.println(Thread.currentThread().getName() +
" is saling ticket" + ticket--);
}else{
break;
}
}
}
}
public class ThreadDemo1{
public static void main(String[] args){
new MyThread().start();
new MyThread().start();
new MyThread().start();
new MyThread().start();
}
}
這下達到目的了嗎?
沒有達到,每個執行緒列印100張票,而不去賣共同的100張票。這種情況是怎麼造成的呢?我們需要的是,多個執行緒去處理同一個資源,一個資源只能對應一個物件,在上面的程式中,我們建立了四個MyThread物件,就等於建立了四個資源,每個資源都有100張票,每個執行緒都在獨自處理各自的資源。
第二種情況:
經過這些實驗和分析,可以總結出,要實現這個鐵路售票程式,我們只能建立一個資源物件,但要建立多個執行緒去處理同一個資源物件,並且每個執行緒上所執行的是相同的程式程式碼。
class MyThread implements Runnable{
private int tickets = 100;
public void run(){
while(true){
if(tickets > 0){
System.out.println(Thread.currentThread().getName() +
" is saling ticket " + tickets--);
}
}
}
}
public class ThreadDemo1{
public static void main(String[] args){
MyThread t = new MyThread ();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
上面的程式中,建立了四個執行緒,每個執行緒呼叫的是同一個MyThread物件中的run()方法,訪問的是同一個物件中的變數(tickets)的例項,這個程式滿足了我們的需求。在Windows上可以啟動多個記事本程式一樣,也就是多個程序使用同一個記事本程式程式碼。
可見,實現Runnable介面相對於繼承Thread類來說,有如下顯著的好處:
(1)適合多個相同程式程式碼的執行緒去處理同一資源的情況,把虛擬CPU(執行緒)同程式的程式碼,資料有效的分離,較好地體現了面向物件的設計思想。
(2)可以避免由於Java的單繼承特性帶來的侷限。我們經常碰到這樣一種情況,即當我們要將已經繼承了某一個類的子類放入多執行緒中,由於一個類不能同時有兩個父類,所以不能用繼承Thread類的方式,那麼,這個類就只能採用實現Runnable介面的方式了。
(3)有利於程式的健壯性,程式碼能夠被多個執行緒共享,程式碼與資料是獨立的。當多個執行緒的執行程式碼來自同一個類的例項時,即稱它們共享相同的程式碼。多個執行緒操作相同的資料,與它們的程式碼無關。當共享訪問相同的物件是,即它們共享相同的資料。當執行緒被構造時,需要的程式碼和資料通過一個物件作為建構函式實參傳遞進去,這個物件就是一個實現了Runnable介面的類的例項。
二十二、執行緒同步的方法:sychronized、lock、reentrantLock等。
如果你向一個變數寫值,而這個變數接下來可能會被另一個執行緒所讀取,或者你從一個變數讀值,而它的值可能是前面由另一個執行緒寫入的,此時你就必須使用同步。
一、什麼是sychronized
sychronized是Java中最基本同步互斥的手段,可以修飾程式碼塊,方法,類.
在修飾程式碼塊的時候需要一個reference物件(預設可以使用this,也可以建立一個Object物件作為鎖)作為鎖的物件.
public class MyThread implements Runnable{
int ticket = 100;
public void run() {
while(true){
synchronized (this) {//使用當前物件作為鎖物件
if(ticket>0){
System.out.println(Thread.currentThread()+", 售出第 "+ticket+"票");
ticket--;
}
}
}
}
}
/////////////////////////////////////////////////////////
public class MyThread implements Runnable{
int ticket = 100;
Object obj = new Object();
public void run() {
while(true){
synchronized (obj) {//自己建立例項作為所物件
if(ticket>0){
System.out.println(Thread.currentThread()+", 售出第 "+ticket+"票");
ticket--;
}
}
}
}
}
在修飾方法的時候預設是當前物件作為鎖的物件.
在修飾類時候預設是當前類的Class物件作為鎖的物件.
synchronized會在進入同步塊的前後分別形成monitorenter和monitorexit位元組碼指令.在執行monitorenter指令時會嘗試獲取物件的鎖,如果此沒物件沒有被鎖,或者此物件已經被當前執行緒鎖住,那麼鎖的計數器加一,每當monitorexit被鎖的物件的計數器減一.直到為0就釋放該物件的鎖.由此synchronized是可重入的,不會出現自己把自己鎖死.
二、什麼ReentrantLock
以物件的方式來操作物件鎖.相對於sychronized需要在finally中去釋放鎖
class RunIt3 implements Runnable{
private static Lock lock = new ReentrantLock();
public void run(){
try{
lock.lock();
System.out.println(Thread.currentThread().getName() + " running");
Thread.sleep(20);
System.out.println(Thread.currentThread().getName() + " finished");
}
catch (InterruptedException e){
System.out.println(Thread.currentThread().getName() + " interrupted");
}finally{
lock.unlock();
}
}
}
三、synchronized和ReentrantLock的區別
除了synchronized的功能,多了三個高階功能.
等待可中斷,公平鎖,繫結多個Condition.
1.等待可中斷
在持有鎖的執行緒長時間不釋放鎖的時候,等待的執行緒可以選擇放棄等待. tryLock(long timeout, TimeUnit unit)
2.公平鎖
按照申請鎖的順序來一次獲得鎖稱為公平鎖.synchronized的是非公平鎖,ReentrantLock可以通過建構函式實現公平鎖. new RenentrantLock(boolean fair)
3.繫結多個Condition
通過多次newCondition可以獲得多個Condition物件,可以簡單的實現比較複雜的執行緒同步的功能.通過await(),signal();
分析理解:
在併發量比較小的情況下,使用synchronized是個不錯的選擇,但是在併發量比較高的情況下,其效能下降很嚴重,此時ReentrantLock是個不錯的方案。
1、ReentrantLock 擁有Synchronized相同的併發性和記憶體語義,此外還多了 鎖投票,定時鎖等候和中斷鎖等候
執行緒A和B都要獲取物件O的鎖定,假設A獲取了物件O鎖,B將等待A釋放對O的鎖定,
如果使用 synchronized ,如果A不釋放,B將一直等下去,不能被中斷
如果 使用ReentrantLock,如果A不釋放,可以使B在等待了足夠長的時間以後,中斷等待,而幹別的事情
ReentrantLock獲取鎖定幾種方式:
a) lock(), 如果獲取了鎖立即返回,如果別的執行緒持有鎖,當前執行緒則一直處於休眠狀態,直到獲取鎖
b) tryLock(), 如果獲取了鎖立即返回true,如果別的執行緒正持有鎖,立即返回false;
c)tryLock(long timeout,TimeUnit unit), 如果獲取了鎖定立即返回true,如果別的執行緒正持有鎖,會等待引數給定的時間,在等待的過程中,如果獲取了鎖定,就返回true,如果等待超時,返回false;
d) lockInterruptibly:如果獲取了鎖定立即返回,如果沒有獲取鎖定,當前執行緒處於休眠狀態,直到或者鎖定,或者當前執行緒被別的執行緒中斷
2、synchronized是在JVM層面上實現的,不但可以通過一些監控工具監控synchronized的鎖定,而且在程式碼執行時出現異常,JVM會自動釋放鎖定,但是使用Lock則不行,lock是通過程式碼實現的,要保證鎖定一定會被釋放,就必須將unLock()放到finally{}中
3、在資源競爭不是很激烈的情況下,Synchronized的效能要優於ReetrantLock,但是在資源競爭很激烈的情況下,Synchronized的效能會下降幾十倍,但是ReetrantLock的效能能維持常態;
synchronized:
在資源競爭不是很激烈的情況下,偶爾會有同步的情形下,synchronized是很合適的。原因在於,編譯程式通常會盡可能的進行優化synchronize,另外可讀性非常好,不管用沒用過5.0多執行緒包的程式設計師都能理解。
ReentrantLock:
ReentrantLock提供了多樣化的同步,比如有時間限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在資源競爭不激烈的情形下,效能稍微比synchronized差點點。但是當同步非常激烈的時候,synchronized的效能一下子能下降好幾十倍。而ReentrantLock確還能維持常態。
二十三、 鎖的等級:方法鎖、物件鎖、類鎖。
物件鎖(方法鎖),是針對一個物件的,它只在該物件的某個記憶體位置宣告一個標識該物件是否擁有鎖,所有它只會鎖住當前的物件,一般一個物件鎖是對一個非靜態成員變數進行synchronized修飾,或者對一個非靜態成員方法進行synchronized進行修飾,對於物件鎖,不同物件訪問同一個被synchronized修飾的方法的時候不會阻塞
類鎖是鎖住整個類,當有多個執行緒來宣告這個類的物件時候將會被阻塞,直到擁有這個類鎖的物件唄銷燬或者主動釋放了類鎖,這個時候在被阻塞的執行緒被挑選出一個佔有該類鎖,宣告該類的物件。其他執行緒繼續被阻塞住。
無論是類鎖還是物件鎖,父類和子類之間是否阻塞沒有直接關係。當對一個父類加了類鎖,子類是不會受到影響的,相反也是如此。因為synchronized關鍵字並不是方法簽名的一部分,它是對方法進行修飾的。當子類覆寫父類中的同步方法或是介面中宣告的同步方法的時候,synchronized修飾符是不會被自動繼承的,所以相應的阻塞問題不會出現。但是,當一個子類沒有覆蓋父類的方法的時候,這時候通過子類訪問方法則會產生阻塞。
插入一句:構造方法不可能是真正同步的(儘管可以在構造方法中使用同步塊)。
當同一個物件線上程1中訪問一個方法,線上程2中再去訪問另外一個加鎖方法,則同樣也會被阻塞.
對於類鎖,則會把整個類鎖住,也就說只能有一個物件擁有當前類的鎖。當一個物件擁有了類鎖之後,另外一個物件還想競爭鎖的話則會被阻塞。兩個物件A,B,如果A正在訪問一個被類鎖修飾的方法function,那麼B則不能訪問。因為類鎖只能在同一時刻被一個物件擁有。相對於物件鎖,則是不同。還是A,B兩個物件,如果A正在訪問物件鎖修飾的function,那麼這個時候B也可以同時訪問。
對於物件鎖,當一個物件擁有鎖之後,訪問一個加了物件鎖的方法,而該方法中又呼叫了該類中其他加了物件鎖的方法,那麼這個時候是不會阻塞住的。這是java通過可重入鎖機制實現的。可重入鎖指的是當一個物件擁有物件鎖之後,可以重複獲取該鎖。因為synchronized塊是可重入的,所以當你訪問一個物件鎖的方法的時候,在該方法中繼續訪問其他物件鎖方法是不會被阻塞的。
二十四、 死鎖。
/**
* 一個簡單的死鎖類
* 當DeadLock類的物件flag==1時(td1),先鎖定o1,睡眠500毫秒
* 而td1在睡眠的時候另一個flag==0的物件(td2)執行緒啟動,先鎖定o2,睡眠500毫秒
* td1睡眠結束後需要鎖定o2才能繼續執行,而此時o2已被td2鎖定;
* td2睡眠結束後需要鎖定o1才能繼續執行,而此時o1已被td1鎖定;
* td1、td2相互等待,都需要得到對方鎖定的資源才能繼續執行,從而死鎖。
*/
public class DeadLock implements Runnable {
public int flag = 1;
//靜態物件是類的所有物件共享的
private static Object o1 = new Object(), o2 = new Object();
@Override
public void run() {
System.out.println("flag=" + flag);
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("1");
}
}
}
if (flag == 0) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("0");
}
}
}
}
public static void main(String[] args) {
DeadLock td1 = new DeadLock();
DeadLock td2 = new DeadLock();
td1.flag = 1;
td2.flag = 0;
//td1,td2都處於可執行狀態,但JVM執行緒排程先執行哪個執行緒是不確定的。
//td2的run()可能在td1的run()之前執行
new Thread(td1).start();
new Thread(td2).start();
}
}
二十五、 寫出生產者消費者模式
public class ProducerConsumer {
public static void main(String[] args) {
//建立一個儲存區
ProductStack ss = new ProductStack();
//新增生產者
Producer p1 = new Producer(ss);
p1.setName("NO.1P");
Producer p2 = new Producer(ss);
p2.setName("NO.2P");
Producer p3 = new Producer(ss);
p3.setName("NO.3P");
Producer p4 = new Producer(ss);
p4.setName("NO.4P");
Producer p5 = new Producer(ss);
p5.setName("NO.5P");
//新增消費者
Consumer c1 = new Consumer(ss);
c1.setName("NO.1C");
Consumer c2 = new Consumer(ss);
c2.setName("NO.3C");
Consumer c3 = new Consumer(ss);
c3.setName("NO.3C");
Consumer c4 = new Consumer(ss);
c4.setName("NO.4C");
Consumer c5 = new Consumer(ss);
c5.setName("NO.5C");
//開啟儲存區
ss.openStack();
//各角色開始進行生產或消費活動
p1.start();
p2.start();
p3.start();
c1.start();
c2.start();
//活動100ms後停止
try {
Thread.sleep(100);
ss.closeStack();
} catch (InterruptedException e) {
e.printStackTrace();
}
//1秒後重新開始活動
try {
Thread.sleep(1000);
ss.openStack();
} catch (InterruptedException e) {
e.printStackTrace();
}
p4.start();
p5.start();
c3.start();
c4.start();
c5.start();
//活動100ms後再次停止
try {
Thread.sleep(100);
ss.closeStack();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
生產者、消費者以及產品物件
//生產者
class Producer extends Thread{
ProductStack ss;
public Producer(ProductStack ss) {
this.ss = ss;
}
/**
* 生產產品
*/
public void run() {
while (ss.isStackOpen()) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
Product pt = new Product();
System.out.println(Thread.currentThread().getName() + "生產了:第"
+ pt.getId() + "號");
ss.push(pt);
}
System.out.println(Thread.currentThread().getName() +"已停止生產!");
}
}
//消費者
class Consumer extends Thread{
ProductStack ss;
public Consumer(ProductStack ss) {
this.ss = ss;
}
/**
* 消費產品
*/
public void run() {
while (ss.isStackOpen()) {
try {
Thread.sleep(1