1.Comparable和Comparator接口是干什么的?列出它們的區(qū)別。
Java提供了只包含一個compareTo()方法的Comparable接口。這個方法可以個給兩個對象排序。具體來說,它返回負數,0,正數來表明輸入對象小于,等于,大于已經存在的對象。
Java提供了包含compare()和equals()兩個方法的Comparator接口。compare()方法用來給兩個輸入參數排序,返回負數,0,正數表明第一個參數是小于,等于,大于第二個參數。equals()方法需要一個對象作為參數,它用來決定輸入參數是否和comparator相等。只有當輸入參數也是一個comparator并且輸入參數和當前comparator的排序結果是相同的時候,這個方法才返回true。
2.下面的代碼片段中,行A和行B所標識的代碼有什么區(qū)別呢?
public class ConstantFolding { static final int number1 = 5; static final int number2 = 6; static int number3 = 5; static int number4= 6; public static void main(String[ ] args) { int PRoduct1 = number1 * number2; //line A int product2 = number3 * number4; //line B } }
在行A的代碼中,product的值是在編譯期計算的,行B則是在運行時計算的。如果你使用Java反編譯器(例如,jd-gui)來反編譯ConstantFolding.class文件的話,那么你就會從下面的結果里得到答案。
public class ConstantFolding{ static final int number1 = 5; static final int number2 = 6; static int number3 = 5; static int number4 = 6; public static void main(String[ ] args) { int product1 = 30; int product2 = number3 * number4; }}
常量折疊是一種Java編譯器使用的優(yōu)化技術。由于final變量的值不會改變,因此就可以對它們優(yōu)化。Java反編譯器和javap命令都是查看編譯后的代碼(例如,字節(jié)碼)的利器。
3.你能想出除了代碼優(yōu)化外,在什么情況下,查看編譯過的代碼是很有幫助的?
Java里的泛型是在編譯時構造的,可以通過查看編譯后的class文件來理解泛型,也可以通過查看它來解決泛型相關的問題。
4.下面哪些是發(fā)生在編譯時,運行時,或者兩者都有?
public class { public static void evaluate(String param1); // method #1 public static void evaluate(int param1); // method #2}如果編譯器要編譯下面的語句的話:
evaluate(“My Test Argument passed to param1”);它會根據傳入的參數是字符串常量,生成調用#1方法的字節(jié)碼
public class A { public int compute(int input) { //method #3 return 3 * input; } } public class B extends A { @Override public int compute(int input) { //method #4 return 4 * input; } }子類B中的compute(..)方法重寫了父類的compute(..)方法。如果編譯器遇到下面的代碼:
public int evaluate(A reference, int arg2) { int result = reference.compute(arg2);}編譯器是沒法知道傳入的參數reference的類型是A還是B。因此,只能夠在運行時,根據賦給輸入變量“reference”的對象的類型(例如,A或者B的實例)來決定調用方法#3還是方法#4.
List<String> myList = new ArrayList<String>(10);編譯后成為了:
List myList = new ArrayList(10);
public class B extends A { @Override public int compute(int input){ //method #4 return 4 * input; } }@Override是一個簡單的編譯時注解,它可以用來捕獲類似于在子類中把toString()寫成tostring()這樣的錯誤。在Java 5中,用戶自定義的注解可以用注解處理工具(Anotation Process Tool ——APT)在編譯時進行處理。到了Java 6,這個功能已經是編譯器的一部分了。
public class MyTest{ @Test public void testEmptyness( ){ org.junit.Assert.assertTrue(getList( ).isEmpty( )); } private List getList( ){ //implemenation goes here }}@Test是JUnit框架用來在運行時通過反射來決定調用測試類的哪個(些)方法的注解。
@Test (timeout=100)public void testTimeout( ) { while(true); //infinite loop}如果運行時間超過100ms的話,上面的測試用例就會失敗。
@Test (expected=IndexOutOfBoundsException.class)public void testOutOfBounds( ) { new ArrayList<Object>( ).get(1);}如果上面的代碼在運行時沒有拋出IndexOutOfBoundsException或者拋出的是其他的異常的話,那么這個用例就會失敗。用戶自定義的注解可以在運行時通過Java反射API里新增的AnnotatedElement和”Annotation”元素接口來處理。
例如:NullPointerException,ArrayIndexOutOfBoundsException,等等
代理或者組合 – 發(fā)生在運行時,因為它更加具有動態(tài)性和靈活性。
5.你能夠通過實例來區(qū)別編譯期繼承和運行時繼承,以及指出Java支持哪種嗎?
“繼承”表示動作和屬性從一個對象傳遞到另外一個對象的場景。Java語言本身只支持編譯期繼承,它是通過“extends”關鍵字來產生子類的方式實現的,如下所示:
public class Parent { public String saySomething( ) { return “Parent is called”; }} public class Child extends Parent { @Override public String saySomething( ) { return super.saySomething( ) + “, Child is called”; }}
“Child”類的saySomething()方法的調用會返回“Parent is called,Child is Called”,因為,子類的調用繼承了父類的“Parenet is called”。關鍵字“super”是用來調用“Parent”類的方法。運行時繼承表示在運行時構建父/子類關系。Java語言本身不支持運行時繼承,但是有一種替代的方案叫做“代理”或者“組合”,它表示在運行時組件一個層次對象的子類。這樣可以模擬運行時繼承的實現。在Java里,代理的典型實現方式如下:
public class Parent { public String saySomething( ) { return “Parent is called”; }} public class Child { public String saySomething( ) { return new Parent( ).saySomething( ) + “, Child is called”; }}
子類代理了父類的調用。組合可以按照下面的方式來實現:
public class Child { private Parent parent = null; public Child( ){ this.parent = new Parent( ); } public String saySomething( ) { return this.parent.saySomething( ) + “, Child is called”; }}
6.Java中的volatile 變量是什么?
volatile是一個特殊的修飾符,只有成員變量才能使用它。在Java并發(fā)程序缺少同步類的情況下,多線程對成員變量的操作對其它線程是透明的。volatile變量可以保證下一個讀取操作會在前一個寫操作之后發(fā)生。
7.什么是FutureTask?
在Java并發(fā)程序中FutureTask表示一個可以取消的異步運算。它有啟動和取消運算、查詢運算是否完成和取回運算結果等方法。只有當運算完成的時候結果才能取回,如果運算尚未完成get方法將會阻塞。一個FutureTask對象可以對調用了Callable和Runnable的對象進行包裝,由于FutureTask也是調用了Runnable接口所以它可以提交給Executor來執(zhí)行。
8.Java中interrupted 和 isInterrupted方法的區(qū)別?
interrupted() 和 isInterrupted()的主要區(qū)別是前者會將中斷狀態(tài)清除而后者不會。Java多線程的中斷機制是用內部標識來實現的,調用Thread.interrupt()來中斷一個線程就會設置中斷標識為true。當中斷線程調用靜態(tài)方法Thread.interrupted()來檢查中斷狀態(tài)時,中斷狀態(tài)會被清零。而非靜態(tài)方法isInterrupted()用來查詢其它線程的中斷狀態(tài)且不會改變中斷狀態(tài)標識。簡單的說就是任何拋出InterruptedException異常的方法都會將中斷狀態(tài)清零。無論如何,一個線程的中斷狀態(tài)有有可能被其它線程調用中斷來改變。
9.如果你提交任務時,線程池隊列已滿。會時發(fā)會生什么?
這個問題問得很狡猾,許多程序員會認為該任務會阻塞直到線程池隊列有空位。事實上如果一個任務不能被調度執(zhí)行那么ThreadPoolExecutor’s submit()方法將會拋出一個RejectedExecutionException異常。
10.Java線程池中submit() 和 execute()方法有什么區(qū)別?
兩個方法都可以向線程池提交任務,execute()方法的返回類型是void,它定義在Executor接口中, 而submit()方法可以返回持有計算結果的Future對象,它定義在ExecutorService接口中,它擴展了Executor接口,其它線程池類像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有這些方法。
11.volatile 變量和 atomic 變量有什么不同?
首先,volatile 變量和 atomic 變量看起來很像,但功能卻不一樣。Volatile變量可以確保先行關系,即寫操作會發(fā)生在后續(xù)的讀操作之前, 但它并不能保證原子性。例如用volatile修飾count變量那么 count++ 操作就不是原子性的。而AtomicInteger類提供的atomic方法可以讓這種操作具有原子性如getAndIncrement()方法會原子性的進行增量操作把當前值加一,其它數據類型和引用變量也可以進行相似操作。
12.如果同步塊內的線程拋出異常會發(fā)生什么?
這個問題坑了很多Java程序員,若你能想到鎖是否釋放這條線索來回答還有點希望答對。無論你的同步塊是正常還是異常退出的,里面的線程都會釋放鎖,所以對比鎖接口我更喜歡同步塊,因為它不用我花費精力去釋放鎖,該功能可以在finally block里釋放鎖實現。
我是天王蓋地虎的分割線新聞熱點
疑難解答