目錄
精選牛客網鄙視題&詳解(一)
1. 經典問題:繼承關係中程序執行的順序問題
- 初始化父類中的靜態成員變量和靜態代碼塊 ;
- 初始化子類中的靜態成員變量和靜態代碼塊 ;
- 初始化父類的普通成員變量和代碼塊,再執行父類的構造方法
- 初始化子類的普通成員變量和代碼塊,再執行子類的構造方法;
// 答案:YXYZ
class X{
Y y=new Y();
public X(){
System.out.print("X");
}
}
class Y{
public Y(){
System.out.print("Y");
}
}
public class Z extends X{
Y y=new Y();
public Z(){
System.out.print("Z");
}
public static void main(String[] args) {
new Z();
}
}
2. Java Object類的基本方法
-
getClass():
返回正在運行的class 類,一般當我們在多線程的時候想看那個線程正在執行的時候可以查看public final native Class<?> getClass();
-
hashCode():
可以想象數組的索引,表示Hash碼值,重寫了equals方法一般都要重寫hashCode方法。這個方法在一些具有哈希功能的Collection中用到。public native int hashCode();
-
equals():
一般equals和==
是不一樣的,但是在Object中兩者是一樣的
從equal可以看出我們比較的是對象的引用,他其實與==
沒有差別, 但是爲什麼我們認爲他是比較的內容?因爲我們重寫了他public boolean equals(Object obj) { return (this == obj); } //重寫,比較的是內容 public boolean equals(Object obj) { if (obj instanceof Boolean) { return value == ((Boolean)obj).booleanValue(); } return false; }
-
clone():
賦值一份引用,實現對象的淺複製,只有實現了Cloneable接口才可以調用該方法主要是JAVA裏除了8種基本類型傳參數是值傳遞,其他的類對象傳參數都是引用傳遞,我們有時候不希望在方法裏講參數改變,這是就需要在類中複寫clone方法。
protected native Object clone() throws CloneNotSupportedException;
-
toString():
返回該對象的字符串表示,一般情況下我們都會重寫它public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
-
notify():
他的作用就是喚醒等待的線程public final native void notify();
-
notifyAll()
他的作用就是喚醒所有等待的線程 -
wait()
他的作用就是讓線程上鎖,處於等待狀態 -
finalize():
他的作用主要就是當jvm進行垃圾回收之前會執行,用於釋放資源,在釋放資源之前她會先檢查一下該對象的狀態是否可達,一般情況下不要調用,因爲會拋異常,如果已經釋放資源,你依舊調用這個時候就會拋出異常
3. 類加載時靜態代碼塊執行的順序
-
類加載是並不是靜態塊最先初始化,而是靜態域!
-
而靜態域中包含:靜態變量、靜態塊和靜態方法,而他們的初始化順序取決於然們的位置,從上到下依次進行初始化
-
構造代碼塊({}內的部分)在每一次創建對象時執行,始終在構造方法前執行
-
構造方法在新建對象時調用( 就是new的時候 )
public class B { public static B t1 = new B(); public static B t2 = new B(); { System.out.println("構造塊"); } static { System.out.println("靜態塊"); } public static void main(String[] args) { B t = new B(); } }
- 分析:由題目可知該類中既有靜態變量,又有靜態塊,還有靜態方法,
所以首先初始化靜態變量t1(這時忽略其他帶有static的部分)打印"構造塊",再初始化靜態變量t2(這時還是忽略其他帶有static的部分)打印"構造塊",再然後靜態塊打印"靜態塊",最後執行main打印"構造塊"
- 分析:由題目可知該類中既有靜態變量,又有靜態塊,還有靜態方法,
4. Java反射機制
Java反射機制主要提供了以下功能:
-
在運行時判斷任意一個對象所屬的類;
-
在運行時構造任意一個類的對象;
-
在運行時判斷任意一個類所具有的成員變量和方法;
-
在運行時調用任意一個對象的方法;生成動態代理。
解析:JAVA反射機制概念:
JAVA反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。
反射是通過一個名爲Class的特殊類,用Class.forName(“className”);得到類的字節碼對象,然後用newInstance()方法在虛擬機內部構造這個對象(針對無參構造函數)。
也就是說反射機制讓我們可以先拿到java類對應的字節碼對象,然後動態的進行任何可能的操作,
使用反射的主要作用是方便程序的擴展。
5. try{} catch{} finally()的使用
-
鄙視題1
try { system.out.println("try"); throw new Exception(); } catch { system.out.println("catch"); } finally { system.out.println("finally"); } // 執行結果 try,catch,finally
-
鄙視題2
public int add(int a,int b) { try { return a+b; } catch (Exception e) { System.out.println("catch語句塊");//不會執行 } finally{ System.out.println("finally語句塊"); } //因爲try中有return,所以finally之後的都不會執行 System.out.println("我不會出現的!");//不會執行 return 0;//不會執行 } // 執行結果 finally語句塊 /* 1、finally塊一定會執行,無論是否try…catch。 2、finally前有return,會先執行return語句,並保存下來,再執行finally塊,最後return。 3、finally前有return、finally塊中也有return,先執行前面的return,保存下來,再執行finally的return,覆蓋之前的結果,並返回。 */
-
鄙視題3
public static void main(String[] args) { try { int i = 100 / 0; System.out.println(i);//後面的語句不會執行 } catch (Exception e) { System.out.println(1); throw new RuntimeException();//只會執行finally中的,其他的不會執行 } finally { System.out.println(2222); } //不會執行 System.out.println(3); } // 執行結果 1 2222 Exception in thread "main" java.lang.RuntimeException /* 需要理解Try…catch…finally與直接throw的區別: - try catch是直接處理,處理完成之後程序繼續往下執行. - throw則是將異常拋給它的上一級處理,程序便不往下執行了。 - 本題的catch語句塊裏面,打印完1之後,又拋出了一個RuntimeException,程序並沒有處理它,而是直接拋出,因此執行完finally語句塊之後,程序終止了 */
-
鄙視題4
public class TryCatchDemo { public int calculate() { int x = 1; try { System.out.println("A"); return ++x; } catch (Exception e) { //System.out.println("D"); } finally { System.out.println("B"); ++x; } // 因爲try中有return,所以finally之後的都不會執行 System.out.println("C");// 不會執行 return x;// 不會執行 } public static void main(String[] args) { TryCatchDemo tryCatchDemo=new TryCatchDemo(); int y=tryCatchDemo.calculate(); System.out.println(y); } } // 執行結果 A B 2 /* 解析: 如果 try 語句裏有 return,那麼代碼的行爲如下: A,有返回值:就把返回值保存到局部變量中 (執行”return ++x”,x=2,保存在局部變量) B,執行 jsr 指令跳到 finally 語句裏執行 (準備執行finally{…}代碼塊) C,執行完 finally 語句後,返回之前保存在局部變量表裏的值 (雖然執行”++x”後x=3,但是返回保存在局部變量中的x=2) */
throws:寫在方法聲明之後,表示方法可能拋出異常,調用者需要處理這個異常。
throw:寫在方法體中,表示方法一定會拋出一個異常,要麼try…catch處理,要麼throws拋出。
6. ArrayList list = new ArrayList(20);中的list擴充幾次?
當第一次插入元素時才分配10(默認)個對象空間。之後擴容會按照1.5倍增長。
初始化的三種方式:
- public ArrayList(),默認的新構造器,將會以默認的大小來初始化內部的數組(默認大小爲不同jdk版本不同:jdk1.7、jdk1.8中默認初始數組容量爲0,而jdk1.6中默認爲10)
- public ArrayList(Collection<?extends E> c),用一個ICollection對象來構造,並將該集合的元素添加到ArrayList
- public ArrayList(int initialCapacity),用指定的大小來初始化內部的數組
7. Java中的新生代、老年代、永久代和各種GC
JVM中的堆,一般分爲三大部分:新生代、老年代、永久代
一、 新生代
- 主要是用來存放新生的對象。一般佔據堆的1/3空間。由於頻繁創建對象,所以新生代會頻繁觸發MinorGC進行垃圾回收。
- 新生代又分爲 Eden區、ServivorFrom、ServivorTo三個區(默認的:Edem : from : to = 8 : 1 : 1 )
- Eden區:Java新對象的出生地(如果新創建的對象佔用內存很大,則直接分配到老年代)。當Eden區內存不夠的時候就會觸發MinorGC,對新生代區進行一次垃圾回收。
- ServivorTo:保留了一次MinorGC過程中的倖存者。
- ServivorFrom:上一次GC的倖存者,作爲這一次GC的被掃描者。
- 當JVM無法爲新建對象分配內存空間的時候(Eden滿了),Minor GC被觸發。因此新生代空間佔用率越高,Minor GC越頻繁。
- MinorGC的過程:採用複製算法
首先,把Eden和ServivorFrom區域中存活的對象複製到ServicorTo區域(如果有對象的年齡以及達到了老年的標準,一般是15,則賦值到老年代區)
同時把這些對象的年齡+1(如果ServicorTo不夠位置了就放到老年區)
然後,清空Eden和ServicorFrom中的對象;最後,ServicorTo和ServicorFrom互換,原ServicorTo成爲下一次GC時的ServicorFrom區。
二、 老年代
- 老年代的對象比較穩定,所以MajorGC不會頻繁執行。
- 在進行MajorGC前一般都先進行了一次MinorGC,使得有新生代的對象晉身入老年代,導致空間不夠用時才觸發。當無法找到足夠大的連續空間分配給新創建的較大對象時也會提前觸發一次MajorGC進行垃圾回收騰出空間。
- MajorGC採用標記—清除算法:
首先掃描一次所有老年代,標記出存活的對象,然後回收沒有標記的對象。MajorGC的耗時比較長,因爲要掃描再回收。MajorGC會產生內存碎片,爲了減少內存損耗,我們一般需要進行合併或者標記出來方便下次直接分配。當老年代也滿了裝不下的時候,就會拋出OOM(Out of Memory)異常。
三、 永久代
- 指內存的永久保存區域,主要存放Class和Meta(元數據)的信息。
- Class在被加載的時候被放入永久區域。它和和存放實例的區域不同,GC不會在主程序運行期對永久區域進行清理。所以這也導致了永久代的區域會隨着加載的Class的增多而脹滿,最終拋出OOM異常。
- 在Java8中,永久代已經被移除,被一個稱爲“元數據區”(元空間)的區域所取代。
- 元空間的本質和永久代類似,都是對JVM規範中方法區的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用本地內存。因此,默認情況下,元空間的大小僅受本地內存限制。類的元數據放入 native memory, 字符串池和類的靜態變量放入java堆中. 這樣可以加載多少類的元數據就不再由MaxPermSize控制, 而由系統的實際可用空間來控制。
- Major GC和Full GC區別
- Full GC:收集young gen、old gen、perm gen
- Major GC:有時又叫old gc,只收集old gen
8. 可以把任何一種數據類型的變量賦給Object類型的變量。
引用類型都直接或間接繼承自Object
基本類型 在賦值時都會 先自動裝箱成其相對應包裝類型,然後賦值。所以是正確的。
補充:java的裝箱與拆箱
-
定義:裝箱就是將基本數據類型轉化爲包裝類型,那麼拆箱就是將包裝類型轉化爲基本數據類型。
Java中基礎數據類型與它們的包裝類進行運算時,編譯器會自動幫我們進行轉換,轉換過程對程序員是透明的,這就是裝箱和拆箱,裝箱和拆箱可以讓我們的代碼更簡潔易懂
原始類型 所佔位數 包裝類型 byte 1字節,8位 Byte short 2字節,16位 Short int 4字節,32位 Integer long 8字節,64位 Long float 4字節,32位 Float double 8字節,64位 Double char 2字節,16位 Character boolean 長度不明確 Boolean 當表格中左邊列出的基礎類型與它們的包裝類有如下幾種情況時,編譯器會自動幫我們進行裝箱或拆箱.
- 進行 = 賦值操作(裝箱或拆箱)
- 進行+,-,*,/混合運算 (拆箱)
- 進行>,<,==比較運算(拆箱)
- 調用equals進行比較(裝箱)
- ArrayList,HashMap等集合類 添加基礎類型數據時(裝箱)
-
示例
/* 裝箱: 因爲10是屬於基本數據類型的,原則上它是不能直接賦值給一個對象Integer的,但當javac發現在一個需要Integer的上下文裏出現了int類型的值,就會通過Integer.valueOf()自動把這個值裝箱爲Integer,自動將基本數據類型轉化爲對應的封裝類型。 */ Integer a = 10; /* 拆箱:故名思議就是將對象重新轉化爲基本數據類型 自動拆箱有個很典型的用法就是在進行運算的時候:因爲對象時不能直接進行運算的,而是要轉化爲基本數據類型後才能進行加減乘除 */ int b = a; int i = new Integer(2);//這是拆箱
-
鄙視題
int num1 = 297; Integer num2 = 297; System.out.println("num1==num2: " + (num1 == num2)); Integer num3 = 97; Integer num4 = 97; System.out.println("num3==num4: " + (num3 == num4)); num3 = 297; num4 = 297; System.out.println("num3==num4: " + (num3 == num4)); // 結果 num1 == num2: true num3 == num4: true num3 == num4: false /* 分析:考察自動裝箱拆箱緩存問題 這是因爲JVM會自動維護八種基本類型的常量池,int常量池中初始化-128~127的範圍,所以當爲Integer i=127時,在自動裝箱過程中是取自常量池中的數值,而當Integer i>128時,i不在常量池範圍內,所以在自動裝箱過程中需new i,所以地址不一樣。 */
以下時實現自動裝箱過程所調用的方法:
//boolean原生類型自動裝箱成Boolean public static Boolean valueOf(boolean b) { return (b ? TRUE : FALSE); } //byte原生類型自動裝箱成Byte public static Byte valueOf(byte b) { final int offset = 128; return ByteCache.cache[(int)b + offset]; } //byte原生類型自動裝箱成Byte public static Short valueOf(short s) { final int offset = 128; int sAsInt = s; if (sAsInt >= -128 && sAsInt <= 127) { // must cache return ShortCache.cache[sAsInt + offset]; } return new Short(s); } //char原生類型自動裝箱成Character public static Character valueOf(char c) { if (c <= 127) { // must cache return CharacterCache.cache[(int)c]; } return new Character(c); } //int原生類型自動裝箱成Integer public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } //int原生類型自動裝箱成Long public static Long valueOf(long l) { final int offset = 128; if (l >= -128 && l <= 127) { // will cache return LongCache.cache[(int)l + offset]; } return new Long(l); } //double原生類型自動裝箱成Double public static Double valueOf(double d) { return new Double(d); } //float原生類型自動裝箱成Float public static Float valueOf(float f) { return new Float(f); } /* 通過分析源碼發現,只有double和float的自動裝箱代碼沒有使用緩存,每次都是new 新的對象,其它的6種基本類型都使用了緩存策略。使用緩存策略是因爲,緩存的這些對象都是經常使用到的(如字符、-128至127之間的數字),防止每次自動裝箱都創建一此對象的實例。而double、float是浮點型的,沒有經常使用到的數據,緩存效果沒有其它幾種類型使用效率高。且裝箱調用的都是valueOf方法。 而拆箱跟自動裝箱的方向相反,將Integer及Double這樣的引用類型的對象重新簡化爲基本類型的數據。 */
-
總結:
自動裝箱和拆箱是由編譯器來完成的,編譯器會在編譯期根據語法決定是否進行裝箱和拆箱動作,裝箱過程是通過調用包裝器的valueOf方法實現的,而拆箱過程是通過調用包裝器的xxxvalue方法實現的(xxx代表對應的基本數據類型)。
9. jre 判斷程序是否執行結束的標準是()
// 程序可以理解爲進程 所有的前臺線程執行完畢
-
後臺線程:指爲其他線程提供服務的線程,也稱爲守護線程。JVM的垃圾回收線程就是一個後臺線程。
-
前臺線程:是指接受後臺線程服務的線程,其實前臺後臺線程是聯繫在一起,就像傀儡和幕後操縱者一樣的關係。傀儡是前臺線程、幕後操縱者是後臺線程。由前臺線程創建的線程默認也是前臺線程。可以通過isDaemon()和setDaemon()方法來判斷和設置一個線程是否爲後臺線程。
-
主線程也即是指 main()函數,是一個前臺線程,使用new Thread方式創建的線程默認都是前臺線程。
-
前臺線程和後臺線程的區別和聯繫:
1、後臺線程不會阻止進程的終止。屬於某個進程的所有前臺線程都終止後,該進程就會被終止。所有剩餘的後臺線程都會停止。
2、可以在任何時候將前臺線程修改爲後臺線程。
3、不管是前臺線程還是後臺線程,如果線程內出現了異常,都會導致進程的終止。
10. Java的集合
- 鄙視題1
下面哪些接口直接繼承自Collection接口()
答案:List、Map
- 鄙視題2
對 Map 的用法,正確的有:
A.new java.util.Map().put("key" , "value") ;
B.new java.util.SortedMap().put("key" , "value") ;
C.new java.util.HashMap().put( null , null ) ;
D.new java.util.TreeMap().put( 0 , null ) ;
// 正確答案: C D
/*
解析:
Map和SortedMap是接口,不能直接new對象,A,B錯誤
HashMap 允許null-null鍵值對 C正確
TreeMap 允許value值爲null,不允許key值爲null,D是value爲null,key不爲null,正確
*/
- ArrayList是線程不安全的,在多線程的情況下不要使用。
- 如果一定在多線程使用List的,您可以使用Vector,因爲Vector和ArrayList基本一致,區別在於Vector中的絕大部分方法都使用了同步關鍵字修飾,這樣在多線程的情況下不會出現併發錯誤哦,還有就是它們的擴容方案不同,ArrayList是通過原始容量*3/2+1,而Vector是允許設置默認的增長長度,Vector的默認擴容方式爲原來的2倍。 切記Vector是ArrayList的多線程的一個替代品。
- LinkedList
- LinkedList的鏈式線性表的特點爲: 適合於在鏈表中間需要頻繁進行插入和刪除操作。
- LinkedList的鏈式線性表的缺點爲: 隨機訪問速度較慢。查找一個元素需要從頭開始一個一個的找。速度你懂的。