Android面試之Java 基礎篇

一隻有交流障礙的醜程式猿發表於2018-03-11

本文是Android面試題整理中的一篇,結合右下角目錄食用更佳,包括:

  • Java設計思想
  • 抽象類和介面
  • 類和方法
  • 內部類
  • 錯誤和異常
  • 關鍵字和運算子
  • 基本型別和常用類
  • 編碼
  • 其他未分類等

Java設計思想


0. OOP是什麼

物件導向程式設計(Object Oriented Programming)

1. JDK和JRE

  1. JDK:java development kit:java開發工具包,是開發人員所需要安裝的環境(包含JRE)
  2. JRE:java runtime environment:java執行環境,java程式執行所需要安裝的環境

2. 物件導向的特徵有哪些

物件導向的特徵有:抽象封裝繼承多型

  1. 抽象:抽象是將一類物件的共同特徵總結出來構造類的過程,包括資料抽象和行為抽象兩方面。抽象只關注物件有哪些屬性和行為,並不關注這些行為的細節是什麼。
  2. 封裝:隱藏物件的實現細節,僅對外公開介面,是針對一個物件來說的
  3. 多型:多型性是指允許不同子型別的物件對同一訊息作出不同的響應。簡單的說就是用同樣的物件引用呼叫同樣的方法但是做了不同的事情
  4. 繼承:繼承是從已有類得到繼承資訊建立新類的過程。提供繼承資訊的類被稱為父類(超類、基類);得到繼承資訊的類被稱為子類(派生類)

3. java是值傳遞還是引用傳遞

java是值傳遞。可以理解為傳入的是一個引用的副本,指向統一地址。當值改變時,原引用和副本指向地址中的值都變了;當副本指向的地址改變,指向新值時,原引用指向的地址沒有改變,原值也沒有改變。

抽象類和介面


0. 介面的意義

  1. 規範
  2. 擴充套件
  3. 回掉
  4. java是單繼承的

1. 抽象類的意義

  1. 為其他子類提供一個公共的型別
  2. 封裝子類中重複定義的內容
  3. 定義抽象方法,子類可以有不同的實現

2. 抽象類和介面有什麼不同

  1. 單繼承:java中只可以繼承一個類,但是可以實現多個介面
  2. 成員變數:介面的成員變數都是public static final 的,抽象類可以有各種型別
  3. 方法:抽象類中可以有方法的具體實現,介面中方法都是抽象的
  4. 擴充套件://jdk 7 : 只能宣告全域性常量(public static final)和抽象方法(public abstract) void method1(); // jdk 8 : 宣告靜態方法 和 預設方法 public static void method2(){ System.out.println("method2"); } default void method3(){ System.out.println("method3"); method4(); } //jdk 9 : 宣告私有方法 private void method4(){ System.out.println("私有方法"); }}

3. 介面是否可繼承(extends)介面?抽象類是否可實現(implements)介面?抽象類是否可繼承具體類(concrete class)?

  1. 介面可以繼承介面,而且支援多重繼承
  2. 抽象類可以實現(implements)介面
  3. 抽象類是否可繼承具體類,也可以繼承抽象類

4. Java識別符號命名規範

0. 規範(強制)

  1. 數字、字母、下劃線、$(java中內部類編譯後會生成包含$的類名) 組成
  2. 不能以數字開頭
  3. 不能和關鍵字或保留關鍵字相同

1. 推薦的命名方式(非強制)

  1. 方法:java中通常用小駝峰命名法
  2. 常量:通常用大寫字母,不同單詞間用“_”分隔開,如MOBILE_NUM
  3. 類名:大駝峰命名法

類和方法


0. 一個".java"原始檔中是否可以包含多個類(不是內部類)?有什麼限制?

一個".java"檔案內可以有多個類,但只能有一個類是公開的

1. 構造器(constructor)是否可被重寫(override)

構造器不能被繼承,因此不能被重寫,但可以被過載

2. 靜態變數和成員變數的區別

  1. 靜態變數屬於類,被多個例項共享,成員變數屬於例項
  2. 靜態變數儲存在方法區,成員變數在堆
  3. 靜態變數在類載入時候存在,成員變數在例項載入之後存在
  4. 靜態方法可以直接使用靜態變數,不能直接使用成員變數

3. Object 中定義了哪些方法

clone/toString/wait/notify/notifyAll/equals/hashcode/finalize/getClass

4. Cloneable 實現原理

  1. Cloneable是一個介面,沒有具體方法
  2. clone方法是Object類中方法,會檢查當前例項是否實現Cloneable介面,沒有實現則丟擲異常,實現了就呼叫native方法進行clone(clone進行的是淺拷貝),原始碼如下
protected Object clone() throws CloneNotSupportedException {
        if (!(this instanceof Cloneable)) {
            throw new CloneNotSupportedException("Class " + getClass().getName() +
                                                 " doesn't implement Cloneable");
        }
        return internalClone();
    }
複製程式碼

5. 兩個物件值相同(x.equals(y) == true),但卻可有不同的hash code,這句話對不對?

  1. 不對。
  2. java 規定,值相同,hashCode一定要相同;hashCode相同,值可能不同
  3. 如果值相同,hashCode不同,就會造成Hashset、HashMap等藉助hashCode實現的資料結構出現錯亂,相同的值或者key可能出現多次

6. 如何實現物件的克隆

  1. 通過實現Cloneable介面實現clone:這裡要注意深拷貝和淺拷貝問題,如果該類內部變數是引用型別的,並且內部變數類沒有實現Cloneable介面,那麼克隆出來的該變數是淺拷貝的(只是複製了引用,兩個引用指向統一例項)
  2. 通過實現Serializable介面,通過物件的序列化和反序列化實現克隆。
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class MyUtil {

private MyUtil() {
throw new AssertionError();
}

@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T obj) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bout);
oos.writeObject(obj);

ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bin);
return (T) ois.readObject();

// 說明:呼叫ByteArrayInputStream或ByteArrayOutputStream物件的close方法沒有任何意義
// 這兩個基於記憶體的流只要垃圾回收器清理物件就能夠釋放資源,這一點不同於對外部資源(如檔案流)的釋放
}
}
複製程式碼

7. 談一談”==“與”equals()"的區別

  1. == :對於基本型別,比較的是他們的值;對於引用型別,比較的是引用的值,也就是物件例項的地址
  2. equals()方法是Object類中的方法,預設實現是public boolean equals(Object obj) {return (this == obj);};我們可以重寫該方法達到我們的目的,例如String重寫了該方法要求每個字元都相等。

8. 類中方法呼叫順序

指出下面程式的執行結果

class A {

static {
System.out.print("1");
}

public A() {
System.out.print("2");
}
}

class B extends A{

static {
System.out.print("a");
}

public B() {
System.out.print("b");
}
}

public class Hello {

public static void main(String[] args) {
A ab = new B();
ab = new B();
}

}
複製程式碼

執行結果:1a2b2b。 建立物件時構造器的呼叫順序是: 父類靜態初始化塊 -> 子類靜態初始化塊 -> 父類初始化塊 ->呼叫了父類構造器 -> 子類初始化塊 -> 呼叫子類的構造器

9. 過載(Overload)和重寫(Override)的區別

重寫(Override)和過載(Overload)其實並無聯絡,可能是因為名稱相似,容易引起混淆 > 重寫發生在執行時,過載發生在編譯期

重寫(Override)

重寫是針對父類和子類來說的,是在子類中重寫父類的方法。

  1. 要求方法名,引數個數和型別必須相同
  2. 返回的資料型別必須與父類相同或者是其子類
  3. 訪問修飾符的限制一定要大於父類中該方法的訪問修飾符(public>protected>default>private)
  4. 重寫方法一定不能丟擲新的檢查異常或者比被重寫方法申明更加寬泛的檢查型異常

過載(Overload)

過載是針對一個類說的,是Java中多型性的一種表現

  1. 要求方法名相同
  2. 必須有不同的引數列表
  3. 可以有不同的返回型別
  4. 可以有不同的修飾符
  5. 可以丟擲不同的異常。
擴充套件:華為的面試題中曾經問過這樣一個問題 - "為什麼不能根據返回型別來區分過載"
答:因為呼叫時不能指定型別資訊,編譯器不知道你要呼叫哪個函式。例如:
float max(int a, int b);
int max(int a, int b);
當呼叫max(1, 2);時無法確定呼叫的是哪個。
參考:https://www.zhihu.com/question/21455159/answer/59874307
複製程式碼

10. 闡述靜態變數和例項變數的區別。

  1. 靜態變數(static 修飾的變數)屬於類,被所有類的例項共享,沒有例項時也可通過類直接訪問
  2. 例項變數:必須通過例項來訪問

11. 是否可以從一個靜態(static)方法內部發出對非靜態(non-static)方法的呼叫?

不可以,靜態方法只能訪問靜態成員,因為非靜態方法的呼叫要先建立物件,在呼叫靜態方法時可能物件並沒有被初始化

12. 抽象的(abstract)方法是否可同時是靜態的(static),是否可同時是本地方法(native),是否可同時被synchronized修飾?

  1. 抽象方法不能是靜態的:靜態方法不能被子類重寫,抽象方法必須被子類重寫,衝突;
  2. 抽象方法不能是native的:本地方法是由原生程式碼(如C程式碼)實現的方法,而抽象方法是沒有實現的,也是矛盾的
  3. 抽象方法不能用sychronized:synchronized和方法的實現細節有關,抽象方法不涉及實現細節,因此也是相互矛盾的

13. Super與this表示什麼

Super表示當前類的父類物件;This表示當前類的物件

14. hashcode()和equals()的關係

  1. equals 相等,hashcode一定相等
  2. hashcode相等,equals不一定相等

內部類


1. 內部類的作用

  1. 內部類可以很好的實現隱藏
  2. 內部類擁有外圍類的所有元素的訪問許可權
  3. 可以間接實現多重繼承
  4. 可以避免修改介面而實現同一個類中兩種同名方法的呼叫

2. 靜態巢狀類(Static Nested Class,或者叫靜態內部類)和內部類(Inner Class)的不同

  1. 內部類持有外部類的引用(this),靜態內部類不持有
  2. 因為持有外部類的引用,所以new時需要先有外部類的例項,再用外部類例項new內部類例項,舉例:new Outer().new Inner();
  3. 擴充套件:在Android中,因為內部類持用外部類引用,所以容易造成記憶體洩漏,一般推薦使用靜態內部類

3. Anonymous Inner Class(匿名內部類)是否可以繼承其它類?是否可以實現介面?

可以繼承其他類,也可以實現介面

解析:
btn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view){

}
})

這裡new的就是一個匿名內部類,這個匿名內部類實現了View.OnClickListener介面。所以匿名內部類本身一定會繼承或實現一個且僅一個類或者介面。
複製程式碼

4. 內部類(非靜態內部類)可以引用它的包含類(外部類)的成員嗎?有沒有什麼限制?

  1. 一個內部類物件可以訪問建立它的外部類物件的成員,包括私有成員。
  2. 應用區域性變數,區域性變數前要加final修飾

5. 內部類訪問區域性變數的時候,為什麼要加final

  1. 內部類和區域性變數生命週期不同(方法結束後區域性變數的生命週期就結束了,而內部類只要有引用就不結束,內部類的生命週期>=區域性變數)
  2. Java為了解決這一問題,會在編譯時在內部類的構造方法裡邊,將區域性變數作為引數傳入內部類
  3. 這就造成了區域性變數如果改變,內部類不知情的場景,所以要加final,保證引用不可改變

擴充套件:在java8中,可以不使用final關鍵字,但是如果我們改變區域性變數的引用,編譯會發生錯誤,從而保證了區域性變數的引用不變。

6. 為什麼內部類會持有外部類的引用?持有的引用是this?還是其它?

內部類雖然和外部類寫在同一個檔案中, 但是編譯完成後, 還是生成各自的class檔案,內部類通過this訪問外部類的成員。

  1. 編譯器自動為內部類新增一個成員變數, 這個成員變數的型別和外部類的型別相同, 這個成員變數就是指向外部類物件
  2. 編譯器自動為內部類的構造方法新增一個引數, 引數的型別是外部類的型別, 在構造方法內部使用這個引數為內部類中新增的成員變數賦值;
  3. 在呼叫內部類的建構函式初始化內部類物件時,會預設傳入外部類的引用。

錯誤和異常


1. java中的異常

  1. 基類是Throwable,Error和Exception繼承自Throwable
  2. Error通常是系統丟擲來的,也可以catch到,但一般不可恢復,開發是也不做處理
  3. Exception分為受檢查異常和不受檢查異常,受檢查異常會在編譯時強制要求我們try/catch

2. throw 和 throws

  1. throw:丟擲異常
  2. throws:在方法宣告處使用,表示此方法可能丟擲的異常,呼叫此方法處需要處理這些異常。

3. Error和Exception有什麼區別?

  1. Error是系統丟擲的,不能在執行時捕獲,比如記憶體溢位
  2. Exception 是需要我們捕捉並處理的異常,如型別轉換錯誤等,我們可以通過捕捉異常,使程式發生異常時仍可正常執行

4. 執行時異常與受檢異常有何異同?

  1. checked exception:這種異常,JAVA編譯器強制要求我們必需對出現的這些異常進行try/catch或者繼續上拋
  2. runtime exception:出現執行時異常後,系統會把異常一直往上層拋,一直遇到處理程式碼。如果沒有處理塊,到最上層,如果是多執行緒就由Thread.run()丟擲,如果是單執行緒就被main()丟擲。丟擲之後,如果是執行緒,這個執行緒也就退出了。如果是主程式丟擲的異常,那麼這整個程式也就退出了

5. 列出一些你常見的執行時異常

NullPointerException (空指標異常) ClassCastException (類轉換異常) IndexOutOfBoundsException (下標越界異常) IllegalArgumentException (非法引數異常)

6. Exception繼承相關考題

題目1:
類ExampleA繼承Exception,類ExampleB繼承ExampleA。有如下程式碼片斷,請問執行此段程式碼的輸出是什麼?
try {
throw new ExampleB("b")
} catch(ExampleA e){
System.out.println("ExampleA");
} catch(Exception e){
System.out.println("Exception");
}

解析:ExampleA。(根據里氏代換原則[能使用父型別的地方一定能使用子型別],抓取ExampleA型別異常的catch塊能夠抓住try塊中丟擲的ExampleB型別的異常)

題目2:
class Annoyance extends Exception {}
class Sneeze extends Annoyance {}

class Human {

public static void main(String[] args)
throws Exception {
try {
try {
throw new Sneeze();
}
catch ( Annoyance a ) {
System.out.println("Caught Annoyance");
throw a;
}
}
catch ( Sneeze s ) {
System.out.println("Caught Sneeze");
return ;
}
finally {
System.out.println("Hello World!");
}
}
}
解析:輸出Caught AnnoyanceCaught SneezeHello World!
複製程式碼

關鍵字和運算子


1. &和&&的區別;|和||的區別?

  1. &有兩種用法:(1)按位與;(2)邏輯與,我們這裡說的是邏輯與。
  2. 與運算要求左右兩端的布林值都是true整個表示式的值才是true
  3. &&運算子是短路邏輯與運算,如果&&左邊的表示式的值是false,右邊的表示式會被直接短路掉,不會進行運算
  4. &左右兩邊的表示式都會計算,我們常用&&,比如if(username != null &&!username.equals("hahaha")){}

2, transient關鍵字

如果用transient宣告一個例項變數,當物件儲存時,它的值不需要維持。換句話來說就是,用transient關鍵字標記的成員變數不參與序列化過程

3. 修飾符的區別

修飾符一共有四個:private、protected、public和default(也有人管預設叫friendly)

  1. private:私有的,除自己外任何類不能使用
  2. protected:同包可以使用,其他包子類可以使用
  3. public:任何類可以使用
  4. default:同包可以使用,其他包不能使用
修飾符 當前類 同 包 子 類 其他包
public
protected ×
default × ×
private × × ×

4. Java有沒有goto?

goto 和 const 是Java中的保留字,在目前版本的Java中沒有使用。

5. 在Java中,如何跳出當前的多重巢狀迴圈

在最外層迴圈前加一個標記如A,然後用break A;可以跳出多重迴圈 (應該避免使用帶標籤的break和continue,因為它不會讓你的程式變得更優雅)。

6. switch 是否能作用在byte 上,是否能作用在long 上,是否能作用在String上?

  1. 在Java 5以前,switch(expr)中,expr只能是byte、short、char、int
  2. Java 5開始,Java中引入了列舉型別,expr也可以是enum型別
  3. 從Java 7開始,expr還可以是字串(String)
  4. long型別不支援

7. static

  1. 可以修飾內部類(靜態內部類)
  2. 可以修飾成員變數,該變數屬於類,被所有例項共享
  3. 可以修飾方法,該方法屬於類,被所有例項共享
  4. 可以修飾程式碼塊(靜態程式碼塊),該程式碼塊在第一次被載入時被呼叫

8. Java語言如何進行異常處理,關鍵字:throws、throw、try、catch、finally分別如何使用?

  1. Java通過物件導向的方法進行異常處理,把各種不同的異常進行分類在Java中,每個異常都是一個物件,它是Throwable類或其子類的例項。當一個方法出現異常後便丟擲一個異常物件,該物件中包含有異常資訊,呼叫這個物件的方法可以捕獲到這個異常並可以對其進行處理。
  2. Java的異常處理是通過5個關鍵詞來實現的:try、catch、throw、throws和finally。一般情況下是用try來執行一段程式,如果系統會丟擲(throw)一個異常物件,可以通過它的型別來捕獲(catch)它,或通過總是執行程式碼塊(finally)來處理;try用來指定一塊預防所有異常的程式;catch子句緊跟在try塊後面,用來指定你想要捕獲的異常的型別;throw語句用來明確地丟擲一個異常;throws用來宣告一個方法可能丟擲的各種異常(當然宣告異常時允許無病呻吟);finally為確保一段程式碼不管發生什麼異常狀況都要被執行;t
  3. try語句可以巢狀,每當遇到一個try語句,異常的結構就會被放入異常棧中,直到所有的try語句都完成。如果下一級的try語句沒有對某種異常進行處理,異常棧就會執行出棧操作,直到遇到有處理這種異常的try語句或者最終將異常拋給JVM。

9. 闡述final、finally、finalize的區別。

這是三個不同的概念,只是因為長得較像而被出成了一道題

final

final是一個修飾符,用來修飾類,變數,方法

  1. final修飾的類不能被繼承
  2. final修飾的方法不能被重寫
  3. final修飾的成員變數是不可變的,如果成員變數是基本資料型別,初始化之後成員變數的值不能被改變,如果成員變數是引用型別,那麼它只能指向初始化時指向的那個物件,不能再指向別的物件,但是物件當中的內容是允許改變的

finally

finally與try,catch一起搭配使用,不論是否catch到異常,finally中的內容都會執行

finalize

finalize是Object類中的方法,垃圾回收器在垃圾回收時會呼叫該方法,我們可以在子類中重寫該方法來做一些清理工作

10. finally 語句一定會執行嗎

在極特殊的情況下可能不執行

  1. 呼叫了System.exit()方法
  2. JVM崩潰了

基本型別和常用類


0. int和Integer有什麼區別?

  1. int是基本型別,Integer是int的包裝型別
  2. 包裝型別可以有一些自己的方法,引入包裝型別可以使java更好的物件導向
  3. 每個基本型別都有其包裝類:
  • 原始型別: boolean,char,byte,short,int,long,float,double
  • 包裝型別:Boolean,Character,Byte,Short,Integer,Long,Float,Double
擴充套件1:
java5中引入了自動拆裝箱功能,例如在比較時可以自動拆裝箱
class AutoUnboxingTest {

    public static void main(String[] args) {
        Integer a = new Integer(3);
        Integer b = 3;                  // 將3自動裝箱成Integer型別
        int c = 3;
        System.out.println(a == b);     // false 兩個引用沒有引用同一物件
        System.out.println(a == c);     // true a自動拆箱成int型別再和c比較
    }
}
複製程式碼
擴充套件2:
一道和裝箱有關的面試題
public class Test03 {

    public static void main(String[] args) {
        Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150;

        System.out.println(f1 == f2); //true
        System.out.println(f3 == f4); //false
    }
}

分析:自動裝箱時,使用的時Integer的valueof方法,當int在-128到127之間時,並不會new一個新的物件,而是直接使用常量池中的Integer
具體分析: 

public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
    
IntegerCache是Integer的內部類,其程式碼如下所示:
/**
     * Cache to support the object identity semantics of autoboxing for values between
     * -128 and 127 (inclusive) as required by JLS.
     *
     * The cache is initialized on first usage.  The size of the cache
     * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
     * During VM initialization, java.lang.Integer.IntegerCache.high property
     * may be set and saved in the private system properties in the
     * sun.misc.VM class.
     */

    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }
    簡單的說,如果整型字面量的值在-128到127之間,那麼不會new新的Integer物件,而是直接引用常量池中的Integer物件,
    所以上面的面試題中f1==f2的結果是true,而f3==f4的結果是false複製程式碼

1. float f=3.4;是否正確?

答:不正確。3.4是雙精度數,將雙精度型(double)賦值給浮點型(float)屬於下轉型(down-casting,也稱為窄化)會造成精度損失,因此需要強制型別轉換float f =(float)3.4; 或者寫成float f =3.4F;。

2. short s1 = 1; s1 = s1 + 1;有錯嗎?short s1 = 1; s1 += 1;有錯嗎?

  1. 對於short s1 = 1; s1 = s1 + 1;由於1是int型別,因此s1+1運算結果也是int 型,需要強制轉換型別才能賦值給short型。
  2. short s1 = 1; s1 += 1;可以正確編譯,因為s1+= 1;相當於s1 = (short)(s1 + 1);其中有隱含的強制型別轉換。

3. Java中char 型變數中能不能存貯一箇中文漢字,為什麼?

Java中 char型變數用來儲存Unicode編碼的字元,unicode編碼字符集中包含了漢字,所以char型別可以儲存漢字 char型別佔兩個位元組

4. 陣列有沒有length()方法?String有沒有length()方法?

陣列有length屬性,String有length()方法

5. String是基本資料型別嘛

  1. 不是。
  2. java中8個基本型別為:byte、short、char、int、float、long、double、boolean。
  3. java中除基本型別外,都是引用型別(列舉是java5以後引入的特殊引用型別)
  4. String型別比較特殊,不可變。但它不是基本型別

6. 是否可以繼承String類

String 類是final的,不能被繼承

7. String和StringBuilder、StringBuffer的區別

String 是隻讀字串,StringBuilder和StringBuffer可以改變,StringBuilder效率高,執行緒不安全,StringBuffer執行緒安全。 在拼接String時,使用+編譯器會幫我們進行優化,使用StringBuilder進行拼接,這時+和StringBuilder沒有多大區別。但當迴圈中使用+時,我們應該顯示的使用StringBuilder,以防止多次呼叫new StringBuilder,造成不必要的效能浪費。

迴圈中使用+舉例:
String str = "hello,world!";
        String result = "";

        for (int i = 0; i < loopCount; i++) {
            result += str;
        }
這個時候編譯器會優化成
String str = "hello,world!";
        String result = "";

        for (int i = 0; i < loopCount; i++) {
            result = new StringBuilder(result).append(str).toString();
        }
多次new StringBuilder造成了效能浪費。
複製程式碼
擴充套件例題
class StringEqualTest {

    public static void main(String[] args) {
        String s1 = "Programming";
        String s2 = new String("Programming");
        String s3 = "Program";
        String s4 = "ming";
        String s5 = "Program" + "ming";
        String s6 = s3 + s4;
        System.out.println(s1 == s2); // false
        System.out.println(s1 == s5); //true
        System.out.println(s1 == s6); //false
        System.out.println(s1 == s6.intern()); //true
        System.out.println(s2 == s2.intern()); //false
    }
}

解析:1. String是引用型別,這裡 == 比較的是引用是否相同,即是否指向相同的地址
     2. 在new String物件時,會產生一個新的物件,並不會使用常量池中的字串
     3. intern會在常量池中尋找該字串(如果沒有責新建),並返回他的地址
複製程式碼

8. String s = new String("xyz");建立了幾個字串物件?

兩個物件,一個是靜態區的"xyz";一個是用new建立在堆上的物件。

9. String 和基本資料型別之間的轉換

  1. String 轉基本資料型別:呼叫基本資料型別對應包裝類的parseXXX(String)或valueOf(String)方法
  2. 基本資料型別轉String:基本資料型別+“”;String.valueof(12)

10. 實現字串的反轉

  1. 方法有很多,可以用StringBuffer/StringBuilder的reverse方法,這裡reverse是通過位移實現的
  2. 再舉例一種遞迴方法:
public String reverse(String originString){
if(originString == null || originString.length <= 1)
return originString;
return reverse(originString.subString(1)) + originString.charAt(0);
}

複製程式碼

11. String 為什麼要設計成不可變的

1. 安全性

  1. 執行緒安全,不可變天生執行緒安全
  2. String常被用作HashMap的key,如果可變會引有安全問題,如兩個key相同
  3. String常被用作資料庫或介面的引數,可變的話也會有安全問題

2. 效率

  1. 通過字串池可以節省很多空間
  2. 每個String對應一個hashcode,再次使用的話不用重新計算

編碼


0. 講一下Java的編碼方式

為什麼需要編碼

計算機儲存資訊的最小單元是一個位元組即8bit,所以能表示的範圍是0~255,這個範圍無法儲存所有的字元,所以需要一個新的資料結構char來表示這些字元,從char到byte需要編碼。

常見的編碼方式有以下幾種:

  1. ASCII:總共有 128 個,用一個位元組的低 7 位表示,031 是控制字元如換行回車刪除等;32126 是列印字元,可以通過鍵盤輸入並且能夠顯示出來。
  2. GBK:碼範圍是 8140~FEFE(去掉 XX7F)總共有 23940 個碼位,它能表示 21003 個漢字,它的編碼是和 GB2312 相容的,也就是說用 GB2312 編碼的漢字可以用 GBK 來解碼,並且不會有亂碼。
  3. UTF-16:UTF-16 具體定義了 Unicode 字元在計算機中存取方法。UTF-16 用兩個位元組來表示 Unicode 轉化格式,這個是定長的表示方法,不論什麼字元都可以用兩個位元組表示,兩個位元組是 16 個 bit,所以叫 UTF-16。UTF-16 表示字元非常方便,每兩個位元組表示一個字元,這個在字串操作時就大大簡化了操作,這也是 Java 以 UTF-16 作為記憶體的字元儲存格式的一個很重要的原因。
  4. UTF-8:統一採用兩個位元組表示一個字元,雖然在表示上非常簡單方便,但是也有其缺點,有很大一部分字元用一個位元組就可以表示的現在要兩個位元組表示,儲存空間放大了一倍,在現在的網路頻寬還非常有限的今天,這樣會增大網路傳輸的流量,而且也沒必要。而 UTF-8 採用了一種變長技術,每個編碼區域有不同的字碼長度。不同型別的字元可以是由 1~6 個位元組組成。

Java中需要編碼的地方一般都在字元到位元組的轉換上,這個一般包括磁碟IO和網路IO。

Reader 類是 Java 的 I/O 中讀字元的父類,而 InputStream 類是讀位元組的父類,InputStreamReader 類就是關聯位元組到字元的橋樑,它負責在 I/O 過程中處理讀取位元組到字元的轉換,而具體位元組到字元的解碼實現它由 StreamDecoder 去實現,在 StreamDecoder 解碼過程中必須由使用者指定 Charset 編碼格式。

1. Unicode與UTF-8的關係

Unicode是字符集 UTF-8是一種編碼方式,達到了對資料流壓縮的目的

其他未分類


什麼時候用斷言(assert)

  1. 斷言在軟體開發中是一種常用的除錯方式
  2. 斷言檢查通常在開發和測試時開啟,釋出時關閉
  3. 斷言是一個包含布林表示式的語句,在執行這個語句時假定該表示式為true;如果表示式的值為false,那麼系統會報告一個AssertionError。斷言的使用如下面的程式碼所示:assert(a > 0); // throws an AssertionError if a <= 0
  4. Android中不推薦使用斷言

Java中的四種引用及應用場景

Java中的引用有四種:強引用,弱引用,軟引用,虛引用

  1. 強引用: 通常我們使用new操作符建立一個物件時所返回的引用即為強引用
  2. 弱引用: 若一個物件只能通過弱引用到達,那麼它就會被回收(即使記憶體充足),同樣可用於圖片快取中,這時候只要Bitmap不再使用就會被回收
  3. 軟引用: 若一個物件只能通過軟引用到達,那麼這個物件在記憶體不足時會被回收,可用於圖片快取中,記憶體不足時系統會自動回收不再使用的Bitmap
  4. 虛引用: 虛引用是Java中最“弱”的引用,通過它甚至無法獲取被引用的物件,它存在的唯一作用就是當它指向的物件回收時,它本身會被加入到引用佇列中,這樣我們可以知道它指向的物件何時被銷燬

動態代理和靜態代理的區別,動態代理的使用場景

  1. 優點:動態代理與靜態代理相比較,最大的好處是介面中宣告的所有方法都被轉移到呼叫處理器一個集中的方法中處理(InvocationHandler.invoke)。這樣,在介面方法數量比較多的時候,我們可以進行靈活處理,而不需要像靜態代理那樣每一個方法進行中轉。
  2. 缺點:它始終無法擺脫僅支援 interface 代理的桎梏,因為它的設計註定了這個遺憾。

併發和並行的區別

  1. 併發:(在一個時間段內同時執行多件事)如單核cpu以時間片的方式讓多個執行緒輪循執行,在外界看來他們是同時執行的
  2. 並行:(在一個時刻同時執行多件事)多核cpu每個核執行一個App,他們是真正的同時執行

java中的流型別

  1. 位元組流:位元組流繼承於InputStream、OutputStream,以位元組方式讀取
  2. 字元流:字元流繼承於Reader、Writer,以字元方式讀取

位元組流和字元流的區別

  1. 位元組流操作的基本單元是位元組;字元流是Unicode字元
  2. 位元組流不使用緩衝區,字元流使用緩衝區
  3. 位元組流通常用於處理二進位制資料,實際上它可以處理任意型別的資料,但它不支援直接寫入或讀取Unicode碼元;字元流通常處理文字資料,它支援寫入及讀取Unicode碼元。

IO和NIO區別

  1. IO面向流,NIO面向緩衝區
  2. IO是阻塞的,NIO是非阻塞的
  3. Java NIO的選擇器允許一個單獨的執行緒來監視多個輸入通道,你可以註冊多個通道使用一個選擇器,然後使用一個單獨的執行緒來“選擇”通道:這些通道里已經有可以處理的輸入,或者選擇已準備寫入的通道。這種選擇機制,使得一個單獨的執行緒很容易來管理多個通道

大檔案的複製

利用NewIO的FileChanel

如何在Java中建立Immutable物件

  1. 私有化成員變數
  2. 通過構造方法初始化成員變數
  3. 只提供get方法,不提供set方法
  4. get方法返回的是一個拷貝副本

舉例說明同步和非同步

  1. java 中同步常指多執行緒時資料的同步,在多執行緒中如果資料不是執行緒安全的,那麼有可能會出現很多錯誤
  2. 非同步多指方法呼叫時,是否等待方法呼叫完成才繼續向下執行,例如通常網路請求就是非同步的。

Java中如何實現序列化,有什麼意義。

  1. 序列化是將物件的狀態資訊轉換為可以儲存或傳輸的形式的過程。
  2. java將物件序列化成了位元組資訊
  3. java通過實現Serializable介面實現序列化

for-each與常規for迴圈的效率對比

  1. for-each 使程式碼更加簡潔優雅
  2. for-each 實際上是通過迭代器(Iterator)實現的

java8 有哪些新特性

支援Lambda 表示式,方法引用,增加了新的時間工具類等

你在專案中哪些地方用到了XML

XML的主要作用有兩個方面:資料交換和資訊配置。在做資料交換時,XML將資料用標籤組裝成起來,然後壓縮打包加密後通過網路傳送給接收者,接收解密與解壓縮後再從XML檔案中還原相關資訊進行處理,XML曾經是異構系統間交換資料的事實標準,但此項功能幾乎已經被JSON(JavaScript Object Notation)取而代之。當然,目前很多軟體仍然使用XML來儲存配置資訊,我們在很多專案中通常也會將作為配置資訊的硬程式碼寫在XML檔案中,Java的很多框架也是這麼做的,而且這些框架都選擇了dom4j作為處理XML的工具,因為Sun公司的官方API實在不怎麼好用

什麼是DAO模式

  1. DAO 是 Data Access Object 的縮寫
  2. 位於業務邏輯和持久化資料之間
  3. 實現對持久化資料的訪問。

簡述一下你瞭解的設計模式

所謂設計模式,就是一套被反覆使用的程式碼設計經驗的總結(情境中一個問題經過證實的一個解決方案)。使用設計模式是為了可重用程式碼、讓程式碼更容易被他人理解、保證程式碼可靠性。設計模式使人們可以更加簡單方便的複用成功的設計和體系結構。將已證實的技術表述成設計模式也會使新系統開發者更加容易理解其設計思路。 在GoF的《Design Patterns: Elements of Reusable Object-Oriented Software》中給出了三類(建立型[對類的例項化過程的抽象化]、結構型[描述如何將類或物件結合在一起形成更大的結構]、行為型[對在不同的物件之間劃分責任和演算法的抽象化])共23種設計模式,包括:Abstract Factory(抽象工廠模式),Builder(建造者模式),Factory Method(工廠方法模式),Prototype(原始模型模式),Singleton(單例模式);Facade(門面模式),Adapter(介面卡模式),Bridge(橋樑模式),Composite(合成模式),Decorator(裝飾模式),Flyweight(享元模式),Proxy(代理模式);Command(命令模式),Interpreter(直譯器模式),Visitor(訪問者模式),Iterator(迭代子模式),Mediator(調停者模式),Memento(備忘錄模式),Observer(觀察者模式),State(狀態模式),Strategy(策略模式),Template Method(模板方法模式), Chain Of Responsibility(責任鏈模式)。 面試被問到關於設計模式的知識時,可以揀最常用的作答,例如:

  • 工廠模式:工廠類可以根據條件生成不同的子類例項,這些子類有一個公共的抽象父類並且實現了相同的方法,但是這些方法針對不同的資料進行了不同的操作(多型方法)。當得到子類的例項後,開發人員可以呼叫基類中的方法而不必考慮到底返回的是哪一個子類的例項。
  • 代理模式:給一個物件提供一個代理物件,並由代理物件控制原物件的引用。實際開發中,按照使用目的的不同,代理可以分為:遠端代理、虛擬代理、保護代理、Cache代理、防火牆代理、同步化代理、智慧引用代理。
  • 介面卡模式:把一個類的介面變換成客戶端所期待的另一種介面,從而使原本因介面不匹配而無法在一起使用的類能夠一起工作。
  • 模板方法模式:提供一個抽象類,將部分邏輯以具體方法或構造器的形式實現,然後宣告一些抽象方法來迫使子類實現剩餘的邏輯。不同的子類可以以不同的方式實現這些抽象方法(多型實現),從而實現不同的業務邏輯。 除此之外,還可以講講上面提到的門面模式、橋樑模式、單例模式、裝潢模式(Collections工具類和I/O系統中都使用裝潢模式)等,反正基本原則就是揀自己最熟悉的、用得最多的作答,以免言多必失。

Comparable和Comparator介面是幹什麼的

  1. 他們都是介面
  2. Comparable是比較本例項和其他例項的大小
  3. Comparator是比較器,用來輸入兩個例項後比較兩個例項大小

URI和URL

URI 是統一資源識別符號,而URL 是統一資源定位符

XML文件定義有幾種形式?它們之間有何本質區別?解析XML文件有哪幾種方式?

  1. XML文件定義分為DTD和Schema兩種形式,二者都是對XML語法的約束
  2. 其本質區別在於Schema本身也是一個XML檔案,可以被XML解析器解析,而且可以為XML承載的資料定義型別,約束能力較之DTD更強大
  3. 對XML的解析主要有DOM(文件物件模型,Document Object Model)、SAX(Simple API for XML)和StAX(Java 6中引入的新的解析XML的方式,Streaming API for XML),其中DOM處理大型檔案時其效能下降的非常厲害,這個問題是由DOM樹結構佔用的記憶體較多造成的,而且DOM解析方式必須在解析檔案之前把整個文件裝入記憶體,適合對XML的隨機訪問(典型的用空間換取時間的策略);SAX是事件驅動型的XML解析方式,它順序讀取XML檔案,不需要一次全部裝載整個檔案。當遇到像檔案開頭,文件結束,或者標籤開頭與標籤結束時,它會觸發一個事件,使用者通過事件回撥程式碼來處理XML檔案,適合對XML的順序訪問;顧名思義,StAX把重點放在流上,實際上StAX與其他解析方式的本質區別就在於應用程式能夠把XML作為一個事件流來處理。將XML作為一組事件來處理的想法並不新穎(SAX就是這樣做的),但不同之處在於StAX允許應用程式程式碼把這些事件逐個拉出來,而不用提供在解析器方便時從解析器中接收事件的處理程式。

Math.round(11.5) 等於多少?Math.round(-11.5)等於多少?

Math.round(11.5)的返回值是12,Math.round(-11.5)的返回值是-11. Math.round()得到的值大於等於原來的值。

用最有效率的方法計算2乘以8?

2 << 3(左移n位相當於乘以2的n次方,右移n位相當於除以2的n次方)。

什麼是UML

UML(Unified Modeling Language)是統一建模語言。為軟體開發提供模型化和視覺化支援,方便溝通交流。

18. UML中有哪些常用的圖

UML定義了多種圖形化的符號來描述軟體系統部分或全部的靜態結構和動態結構,包括:用例圖(use case diagram)、類圖(class diagram)、時序圖(sequence diagram)、協作圖(collaboration diagram)、狀態圖(statechart diagram)、活動圖(activity diagram)、構件圖(component diagram)、部署圖(deployment diagram)等。在這些圖形化符號中,有三種圖最為重要,分別是:用例圖(用來捕獲需求,描述系統的功能,通過該圖可以迅速的瞭解系統的功能模組及其關係)、類圖(描述類以及類與類之間的關係,通過該圖可以快速瞭解系統)、時序圖(描述執行特定任務時物件之間的互動關係以及執行順序,通過該圖可以瞭解物件能接收的訊息也就是說物件能夠向外界提供的服務)。 用例圖:

Android面試之Java 基礎篇
類圖:
Android面試之Java 基礎篇
時序圖:
Android面試之Java 基礎篇

寫一個方法,輸入一個檔名和一個字串,統計這個字串在這個檔案中出現的次數。

import java.io.BufferedReader;
import java.io.FileReader;

public class MyUtil{

private MyUtil(){
throw new AssertError;
}

public static int countWordInFile(String filename,String word){
int counter = 0;
try(FileReader fr = new FileReader(filename){
try(BufferReader br = new BufferReader(fr)){
String line = null;
while((line = br.readLine()) != null){
int index = -1;
while(line.length() >= word.length() && (index = line.indexof(word) >= 0){
counter ++;
line = line.subString(index+word.length());
})
}


}catch(Exception e){
Log.e(e);
}

} catch(Exception e){
Log.e(e);
}
)
}

}
複製程式碼

如何用Java程式碼列出一個目錄下所有的檔案?

import java.io.File;

class Test12 {

public static void main(String[] args) {
File f = new File("/Users/Hao/Downloads");
for(File temp : f.listFiles()) {
if(temp.isFile()) {
System.out.println(temp.getName());
}
}
}
}



如果需要對資料夾繼續展開,程式碼如下所示:

import java.io.File;

class Test12 {

public static void main(String[] args) {
showDirectory(new File("/Users/Hao/Downloads"));
}

public static void showDirectory(File f) {
_walkDirectory(f, 0);
}

private static void _walkDirectory(File f, int level) {
if(f.isDirectory()) {
for(File temp : f.listFiles()) {
_walkDirectory(temp, level + 1);
}
}
else {
for(int i = 0; i < level - 1; i++) {
System.out.print("\t");
}
System.out.println(f.getName());
}
}
}



在Java 7中可以使用NIO.2的API來做同樣的事情,程式碼如下所示:

class ShowFileTest {

public static void main(String[] args) throws IOException {
Path initPath = Paths.get("/Users/Hao/Downloads");
Files.walkFileTree(initPath, new SimpleFileVisitor<Path>() {

@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
System.out.println(file.getFileName().toString());
return FileVisitResult.CONTINUE;
}

});
}
}
複製程式碼

日期和時間

1. 獲取當前年月日,時分秒
Calendar cal = Calendar.getInstance();
System.out.println(cal.get(Calendar.YEAR));
System.out.println(cal.get(Calendar.MONTH));    // 0 - 11
System.out.println(cal.get(Calendar.DATE));
System.out.println(cal.get(Calendar.HOUR_OF_DAY));
System.out.println(cal.get(Calendar.MINUTE));
System.out.println(cal.get(Calendar.SECOND));

2. 取得從1970年1月1日0時0分0秒到現在的毫秒數
Calendar.getInstance().getTimeInMillis();
System.currentTimeMillis();
Clock.systemDefaultZone().millis(); // Java 8

3. 如何取得某月的最後一天
Calendar time = Calendar.getInstance();
time.getActualMaximum(Calendar.DAY_OF_MONTH);

4. 如何格式化日期
利用java.text.DataFormat 的子類(如SimpleDateFormat類)中的format(Date)方法可將日期格式化。Java 8中可以用java.time.format.DateTimeFormatter來格式化時間日期,程式碼如下所示
public static void main(String[] args) {
SimpleDateFormat oldFormatter = new SimpleDateFormat("yyyy/MM/dd");
Date date1 = new Date();
System.out.println(oldFormatter.format(date1));

// Java 8
DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
LocalDate date2 = LocalDate.now();
System.out.println(date2.format(newFormatter));
}

複製程式碼

列印昨天的當前時刻

Calender calender = Calender.getInstance();
calender.add(Calender.DATE,-1);
calender.getTime();


在Java 8中,可以用下面的程式碼實現相同的功能。

import java.time.LocalDateTime;

class YesterdayCurrent {

public static void main(String[] args) {
LocalDateTime today = LocalDateTime.now();
LocalDateTime yesterday = today.minusDays(1);

System.out.println(yesterday);
}
}

複製程式碼

用java寫一個單例

public class Single{
private static Single  instance = new Single();

private Single(){

}
public static Single getInstance(){
return instance;
}
}
複製程式碼

用java寫一個氣泡排序

public <T extends Comparable<T>> void sort(T[] list){

boolean swap = true;
T temp;
for(int i = list.length - 1 ; i> 0 && swap; i--){
swap = false;
for (int j = 0;j<i-1;j++){
if (list[j].compareTo(list[j+1]) > 0) {
temp = list[j];
list[j] = list[j+1];
list[j+1] = temp;
swap = true;
}
}
}
複製程式碼

}

用Java寫一個折半查詢

public static <T> int binarySearch(T[] x, T key, Comparator<T> comp) {
int low = 0;
int high = x.length - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
int cmp = comp.compare(x[mid], key);
if (cmp < 0) {
low= mid + 1;
}
else if (cmp > 0) {
high= mid - 1;
}
else {
return mid;
}
}
return -1;
}
複製程式碼

參考資料

Java面試題全集(上)

Java執行緒面試題 Top 50

相關文章