- 相關推薦
Java面試題問答之編譯時與運行時
在開發(fā)和設計的時候,我們需要考慮編譯時,運行時以及構建時這三個概念。下面是百分網(wǎng)小編為大家搜索整理的關于常見的Java問題與解答,歡迎參考閱讀,希望對大家有所幫助!想了解更多相關信息請持續(xù)關注我們應屆畢業(yè)生考試網(wǎng)!
Q.下面的代碼片段中,行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.在行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é)碼)的利器。
Q.你能想出除了代碼優(yōu)化外,在什么情況下,查看編譯過的代碼是很有幫助的?
A.Java里的泛型是在編譯時構造的,可以通過查看編譯后的class文件來理解泛型,也可以通過查看它來解決泛型相關的問題。
Q.下面哪些是發(fā)生在編譯時,運行時,或者兩者都有?
A.
方法重載:這個是發(fā)生在編譯時的。方法重載也被稱為編譯時多態(tài),因為編譯器可以根據(jù)參數(shù)的類型來選擇使用哪個方法。
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”);
它會根據(jù)傳入的參數(shù)是字符串常量,生成調用#1方法的字節(jié)碼
方法覆蓋:這個是在運行時發(fā)生的。方法覆蓋被稱為運行時多態(tài),因為在編譯期編譯器不知道并且沒法知道該去調用哪個方法。JVM會在代碼運行的時候做出決定。
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);
}
編譯器是沒法知道傳入的參數(shù)reference的類型是A還是B。因此,只能夠在運行時,根據(jù)賦給輸入變量“reference”的對象的類型(例如,A或者B的實例)來決定調用方法#3還是方法#4.
泛型(又稱類型檢驗):這個是發(fā)生在編譯期的。編譯器負責檢查程序中類型的正確性,然后把使用了泛型的代碼翻譯或者重寫成可以執(zhí)行在當前JVM上的非泛型代碼。這個技術被稱為“類型擦除“。換句話來說,編譯器會擦除所有在尖括號里的類型信息,來保證和版本1.4.0或者更早版本的JRE的兼容性。
List
編譯后成為了:
List myList = new ArrayList(10);
注解(Annotation):你可以使用運行時或者編譯時的注解。
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 ( ).get(1);
}
如果上面的代碼在運行時沒有拋出IndexOutOfBoundsException或者拋出的是其他的異常的話,那么這個用例就會失敗。用戶自定義的注解可以在運行時通過Java反射API里新增的AnnotatedElement和”Annotation”元素接口來處理。
異常(Exception):你可以使用運行時異常或者編譯時異常。
運行時異常(RuntimeException)也稱作未檢測的異常(unchecked exception),這表示這種異常不需要編譯器來檢測。RuntimeException是所有可以在運行時拋出的異常的父類。一個方法除要捕獲異常外,如果它執(zhí)行的時候可能會拋出RuntimeException的子類,那么它就不需要用throw語句來聲明拋出的異常。
例如:NullPointerException,ArrayIndexOutOfBoundsException,等等
受檢查異常(checked exception)都是編譯器在編譯時進行校驗的,通過throws語句或者try{}cathch{} 語句塊來處理檢測異常。編譯器會分析哪些異常會在執(zhí)行一個方法或者構造函數(shù)的時候拋出。
面向切面的編程(Aspect Oriented Programming-AOP):切面可以在編譯時,運行時或,加載時或者運行時織入。(PS:這點需要了解Spring等框架才明白哦)
編譯期:編譯期織入是最簡單的方式。如果你擁有應用的代碼,你可以使用AOP編譯器(例如,ajc – AspectJ編譯器)對源碼進行編譯,然后輸出織入完成的class文件。AOP編譯的過程包含了waver的調用。切面的形式可以是源碼的形式也可以是二進制的形式。如果切面需要針對受影響的類進行編譯,那么你就需要在編譯期織入了。
編譯后:這種方式有時候也被稱為二進制織入,它被用來織入已有的class文件和jar文件。和編譯時織入方式相同,用來織入的切面可以是源碼也可以是二進制的形式,并且它們自己也可以被織入切面。
裝載期:這種織入是一種二進制織入,它被延遲到JVM加載class文件和定義類的時候。為了支持這種織入方式,需要顯式地由運行時環(huán)境或者通過一種“織入代理(weaving agent)“來提供一個或者多個“織入類加載器(weaving class loader)”。
運行時:對已經加載到JVM里的類進行織入
繼承 – 發(fā)生在編譯時,因為它是靜態(tài)的
代理或者組合 – 發(fā)生在運行時,因為它更加具有動態(tài)性和靈活性。
Q.你有沒有聽說過“組合優(yōu)于繼承”這樣的說法呢?如果聽說過的話,那么你是怎么理解的呢?
A.繼承是一種多態(tài)工具,而不是一種代碼復用工具。有些開發(fā)者喜歡用繼承的方式來實現(xiàn)代碼復用,即使是在沒有多態(tài)關系的情況下。是否使用繼承的規(guī)則是繼承只能用在類之間有“父子”關系的情況下。
不要僅僅為了代碼復用而繼承。當你使用組合來實現(xiàn)代碼復用的時候,是不會產生繼承關系的。過度使用繼承(通過“extends”關鍵字)的話,如果修改了父類,會損壞所有的子類。這是因為子類和父類的緊耦合關系是在編譯期產生的。
不要僅僅為了多態(tài)而繼承。如果你的類之間沒有繼承關系,并且你想要實現(xiàn)多態(tài),那么你可以通過接口和組合的方式來實現(xiàn),這樣不僅可以實現(xiàn)代碼重用,同時也可以實現(xiàn)運行時的靈活性。
這就是為什么四人幫(Gang of Four)的設計模式里更傾向于使用組合而不是繼承的原因。面試者會在你的答案里著重關注這幾個詞語——“耦合”,“靜態(tài)還是動態(tài)”,以及“發(fā)生在編譯期還是運行時”。運行時的靈活性可以通過組合來實現(xiàn),因為類可以在運行時動態(tài)地根據(jù)一個結果有條件或者無條件地進行組合。但是繼承卻是靜態(tài)的。
Q.你能夠通過實例來區(qū)別編譯期繼承和運行時繼承,以及指出Java支持哪種嗎?
A.“繼承”表示動作和屬性從一個對象傳遞到另外一個對象的場景。Java語言本身只支持編譯期繼承,它是通過“extends”關鍵字來產生子類的方式實現(xiàn)的,如下所示:
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語言本身不支持運行時繼承,但是有一種替代的方案叫做“代理”或者“組合”,它表示在運行時組件一個層次對象的子類。這樣可以模擬運行時繼承的實現(xiàn)。在Java里,代理的典型實現(xiàn)方式如下:
public class Parent {
public String saySomething( ) {
return “Parent is called”;
}
}
public class Child {
public String saySomething( ) {
return new Parent( ).saySomething( ) + “, Child is called”;
}
}
子類代理了父類的調用。組合可以按照下面的方式來實現(xiàn):
public class Child {
private Parent parent = null;
public Child( ){
this.parent = new Parent( );
}
public String saySomething( ) {
return this.parent.saySomething( ) + “, Child is called”;
}
}
【Java面試題問答之編譯時與運行時】相關文章:
Java 問答面試題09-03
Java面試題(精選)06-30
Java原理面試題07-13
Java基礎面試題集錦09-02
Java基礎語法面試題10-28
經常被遺忘的Java面試題09-30
Java開發(fā)崗位面試題匯總06-30
最新2016年Java精選面試題10-07
高級Java多線程面試題及回答06-08
華為Java程序員面試題10-18