【Android面試】2023最新面試專題三:Java核心基礎(chǔ)(上)

1 Java中提供了抽象類還有接口,開發(fā)中如何去選擇呢?

這道題想考察什么?

Java是面向?qū)ο缶幊痰?,抽象是它的一大特征,而體現(xiàn)這個(gè)特征的就是抽象類與接口。抽象類與接口某些情況下都能夠互相替代,但是如果真的都能夠互相替代,那Java為何會(huì)設(shè)計(jì)出抽象與接口的概念?這就需要面試者能夠掌握兩者的區(qū)別。

考察的知識點(diǎn)

OOP(面向?qū)ο螅┚幊趟枷?,抽象與接口的區(qū)別與應(yīng)用場景;

考生應(yīng)該如何回答

抽象類的設(shè)計(jì)目的,是代碼復(fù)用;接口的設(shè)計(jì)目的,是對類的行為進(jìn)行約束。

  • 當(dāng)需要表示is-a的關(guān)系,并且需要代碼復(fù)用時(shí)用抽象類
  • 當(dāng)需要表示has-a的關(guān)系,可以使用接口

比如狗具有睡覺和吃飯方法,我們可以使用接口定義:

public interface Dog {
    public void sleep();
    public void eat();
}

但是我們發(fā)現(xiàn)如果采用接口,就需要讓每個(gè)派生類都實(shí)現(xiàn)一次sleep方法。此時(shí)為了完成對sleep方法的復(fù)用,我們可以選擇采用抽象類來定義:

public abstract class Dog {
    public void sleep(){
        //......
    }
    public abstract void eat();
}

但是如果我們訓(xùn)練,讓狗擁有一項(xiàng)技能——握手,怎么添加這個(gè)行為? 將握手方法寫入抽象類中,但是這會(huì)導(dǎo)致所有的狗都能夠握手。

握手是訓(xùn)練出來的,是對狗的一種擴(kuò)展。所以這時(shí)候我們需要單獨(dú)定義握手這種行為。這種行為是否可以采用抽象類來定義呢?

public abstract Handshake{
    abstract void doHandshake();
}

如果采用抽象類定義握手,那我們現(xiàn)在需要?jiǎng)?chuàng)建一類能夠握手的狗怎么辦?

public class HandShakeDog extends Dog //,Handshake

大家都知道在Java中不能多繼承,這是因?yàn)槎嗬^承存在二義性問題。

二義性問題:一個(gè)類如果能夠繼承多個(gè)父類,那么如果其中父類A與父類B具有相同的方法,當(dāng)調(diào)用這個(gè)方法時(shí)會(huì)調(diào)用哪個(gè)父類的方法呢?

所以此時(shí),我們就需要使用接口來定義握手行為:

public interface Handshake{
    void doHandshake();
}
public class HandShakeDog extends Dog implements Handshake

不是所有的狗都會(huì)握手,也不止狗會(huì)握手。我們可以同樣訓(xùn)練貓,讓貓也具備握手的技能,那么貓Cat類,同樣能夠?qū)崿F(xiàn)此接口,這就是"has-a"的關(guān)系。

抽象類強(qiáng)調(diào)從屬關(guān)系,接口強(qiáng)調(diào)功能,除了使用場景的不同之外,在定義中的不同則是:

定義.png

2 重載和重寫是什么意思,區(qū)別是什么? (京東)

這道題想考察什么?

Java基礎(chǔ)

考察的知識點(diǎn)

面向?qū)ο蠖鄳B(tài)的基礎(chǔ)概念

考生應(yīng)該如何回答

重寫(Override)

重寫就是重新寫的意思,當(dāng)父類中的方法對于子類來說不適用或者需要擴(kuò)展增強(qiáng)時(shí),子類可以對從父類中繼承來的方法進(jìn)行重寫。

比如Activity是Android開發(fā)中四大組件之一。在Activity中存在各種聲明周期方法:onCreate、onStart .....等等。而我們應(yīng)用中需要使用Activity來展示UI,那么我們會(huì)需要編寫自己的類繼承自Activity。

public class MainActivity extends Activity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

在上述代碼中,onCreate就是被我們重寫的Activity中定義方法。我們在onCreate增加了setContentView

的調(diào)用,完成了對超類Activity中對應(yīng)方法的修改與擴(kuò)展。

重載(Overload)

重載則是在同一個(gè)類中,允許存在多個(gè)同名方法,只要它們的參數(shù)列表不同即可。比如在Android開發(fā)中,我們會(huì)使用LayoutInflater的inflate方法加載布局,inflate方法存在多個(gè)定義,其中包括兩個(gè)參數(shù)的,與三個(gè)參數(shù)的:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    //......
}

可以看到在兩個(gè)參數(shù)的inflate方法中,會(huì)為我們調(diào)起三個(gè)參數(shù)的inflate方法,而定義第一個(gè)inflate方法的目的就是為了提供默認(rèn)的attachToRoot參數(shù)值。因?yàn)镴ava中無法定義方法參數(shù)默認(rèn)值,所以我們經(jīng)常使用重載達(dá)成此目的。


3 靜態(tài)內(nèi)部類是什么?和非靜態(tài)內(nèi)部類的區(qū)別是什么?

這道題想考察什么?

掌握static的作用與注意事項(xiàng)

考察的知識點(diǎn)

Java中關(guān)鍵字static

考生應(yīng)該如何回答

在定義內(nèi)部類時(shí),如果內(nèi)部類被static聲明,則該內(nèi)部類為靜態(tài)內(nèi)部類。

public class OuterClass{
    static class InnerClass{
        
    }
}

當(dāng)內(nèi)部類被static聲明,那么在內(nèi)部類中就無法直接使用外部類的屬性。比如編寫普通內(nèi)部類:

public class OuterClass{
    int i;
    public class InnerClass{
        public InnerClass(){
            i = 10;
        }
    }
}

此時(shí)對OuterClass.java 進(jìn)行編譯,會(huì)生成:OuterClass.class 與 OuterClass$InnerClass.class 兩個(gè)文件。對后者反編譯我們將看到:

public class OuterClass$InnerClass {
    public OuterClass$InnerClass(OuterClass var1) {
        this.this$0 = var1;
        var1.i = 10;
    }
}

可以看到,普通內(nèi)部類構(gòu)造方法中實(shí)際上會(huì)隱式的傳遞外部類實(shí)例對象給內(nèi)部類。在內(nèi)部類中使用外部類的屬性:i。實(shí)際上是通過外部類的實(shí)例對象:var1獲取的。同時(shí)如果我們需要構(gòu)建出InnerClass的實(shí)例對象,非靜態(tài)內(nèi)部類也無法脫離外部類實(shí)體被創(chuàng)建。

下面我們將InnerClass定義為static靜態(tài)內(nèi)部類:

public class OuterClass{
    int i;
    public static class InnerClass{
        public InnerClass(){
            //i = 10; 
        }
    }
}

此時(shí)無法使用外部類的普通成員屬性:i。其對應(yīng)字節(jié)碼為:

public class OuterClass$InnerClass {
    public OuterClass$InnerClass() {
       
    }
}

靜態(tài)內(nèi)部類中不再隱式的持有外部類的實(shí)例對象。但是如果我們將屬性i定義為static,那么在靜態(tài)內(nèi)部類中也是可以直接使用外部類的靜態(tài)成員屬性的,此時(shí)字節(jié)碼為:

public class OuterClass$InnerClass {
    public OuterClass$InnerClass() {
        OuterClass.i = 10;
    }
}

內(nèi)部靜態(tài)類不需要有指向外部類的引用。但非靜態(tài)內(nèi)部類需要持有對外部類的引用。但是靜態(tài)內(nèi)部類能夠直接利用new OuterClass.InnerClass() 實(shí)例化。

因此靜態(tài)內(nèi)部類與非靜態(tài)內(nèi)部類的區(qū)別有:

  1. 非靜態(tài)內(nèi)部類能夠訪問外部類的靜態(tài)和非靜態(tài)成員,靜態(tài)類只能訪問外部類的靜態(tài)成員。
  2. 非靜態(tài)內(nèi)部類不能脫離外部類被創(chuàng)建,靜態(tài)內(nèi)部類可以。

4 Java中在傳參數(shù)時(shí)是將值進(jìn)行傳遞,還是傳遞引用?

這道題想考察什么?

是否了解什么是值傳遞和引用傳遞與真實(shí)場景使用,是否熟悉什么是值傳遞和引用傳遞在工作中的表現(xiàn)是什么?

考察的知識點(diǎn)

什么是值傳遞和引用傳遞的概念,兩者對開發(fā)中編寫的代碼的影響

考生應(yīng)該如何回答

值傳遞:在方法調(diào)用時(shí),傳遞的參數(shù)是這個(gè)參數(shù)指向值的拷貝;

引用傳遞:在方法調(diào)用時(shí),傳遞引用的地址

在Java中對于參數(shù)的傳遞可以分為兩種情況:

1.基本數(shù)據(jù)類型的參數(shù)

 1 public class TransferTest {
 2     public static void main(String[] args) {
 3         int num = 1;
 4         System.out.println("changeNum()方法調(diào)用之前:num = " + num);
 5         changeNum(num);
 6         System.out.println("changeNum()方法調(diào)用之后:num = " + num);
 7     }
 8 
 9     public static void changeNum(int x) {
10         x = 2;
11     }
12 }

運(yùn)行結(jié)果:

運(yùn)行結(jié)果.png

傳遞過程的示意圖如下:

傳遞1.png

分析:num作為參數(shù)傳遞給changeNum()方法時(shí),是將內(nèi)存空間中num所指向的那個(gè)存儲(chǔ)單元中存放的值1復(fù)制了一份傳遞給了changeNum()方法中的x變量,而這個(gè)x變量也在內(nèi)存空間中分配的一個(gè)存儲(chǔ)單元。這時(shí)就把num對的值1傳遞給了x變量所指向的存儲(chǔ)單元中。此后在changeNum()方法中對x變量的一切操作都是針對于x所指向的這個(gè)存儲(chǔ)單元,與num所指向的存儲(chǔ)單元無關(guān)。

所以,在changeNum()方法被調(diào)用后,num所指向的存儲(chǔ)單元的值還是沒有發(fā)生變化,這就是所謂的“值傳遞”。

值傳遞的精髓是:傳遞的是存儲(chǔ)單元中的內(nèi)容,而不是存儲(chǔ)單元的引用。

2.引用類型的參數(shù)

 1  public class TransferTest2 {
 2     public static void main(String[] args) {
 3         Person person = new Person();
 4         System.out.println(person);
 5         change(person);
 6         System.out.println(person);
 7     }
 8 
 9     public static void change(Person p) {
10         p = new Person();
11     }
12 }
13 
14 /**
15  * Person類
16  */
17 class Person {
18 
19 }

運(yùn)行結(jié)果:

運(yùn)行結(jié)果2.png

可以看出兩次打印結(jié)果一致。即調(diào)用change()方法后,person變量并沒發(fā)生改變。

傳遞過程的示意圖如下:

傳遞.png

01.當(dāng)程序執(zhí)行到第3行 Person person = new Person()時(shí),程序在堆內(nèi)存(heap)中開辟了一塊內(nèi)存空間用來存儲(chǔ)Person類實(shí)例對象,同時(shí)在棧內(nèi)存(stack)中開辟了一個(gè)存儲(chǔ)單元來存儲(chǔ)該實(shí)例對象的引用,即上圖中person指向的存儲(chǔ)單元。

02.當(dāng)程序執(zhí)行到第5行 change(person)時(shí),person作為參數(shù)(實(shí)參)傳遞給了change()方法。這里是person將自己的存儲(chǔ)單元的內(nèi)容傳遞給了change()方法的p變量。 此后在change()方法中對p變量的一切操作都是針對于p變量所指向的存儲(chǔ)單元,與perosn所指向的存儲(chǔ)單元就沒有關(guān)系了。

因此Java的參數(shù)傳遞,不管是基本數(shù)據(jù)類型還是引用類型的參數(shù),都是按值傳遞!

5 使用equals和==進(jìn)行比較的區(qū)別

這道題想考察什么?

在開發(fā)中當(dāng)需要對引用類型和基本數(shù)據(jù)類型比較時(shí)應(yīng)該怎么做,為什么有區(qū)別。

考察的知識點(diǎn)

equals 的實(shí)現(xiàn)以及棧和堆的內(nèi)存管理。

考生應(yīng)該如何回答

==

對于不同的數(shù)據(jù)類型,使用==進(jìn)行比較的意義不同:

  • 基本數(shù)據(jù)類型(也稱原始數(shù)據(jù)類型) :byte,short,char,int,long,float,double,boolean。用==,比較的是他們的值;
  • 引用數(shù)據(jù)類型:用(==)進(jìn)行比較的時(shí)候,比較的是地址。

對于引用類型,除非是同一個(gè)new出來的對象,他們的比較的結(jié)果為true,否則為false。因?yàn)槊縩ew一次,都會(huì)重新開辟堆內(nèi)存空間,哪怕他們的值一致,但是也是在不同的地址存放。所以對于引用類型的值比較應(yīng)該使用equals方法。

equals()方法介紹

equals 方法是 Object 中定義的一個(gè)方法,源碼如下:

public boolean equals(Object obj) {
    return (this == obj);
}

可以看到實(shí)際上就是用的 == 實(shí)現(xiàn)的,所以這個(gè)原始方法意義不大,一般在類中做比較的時(shí)候,都會(huì)重寫這個(gè)方法,如String、Integer、Date等。

//Integer
public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        //比較Integer中包裝的int值
        return value == ((Integer)obj).intValue();
    }
    return false;
}
//String
public boolean equals(Object anObject) {
    if (this == anObject) { //同一個(gè)對象
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = length();
        if (n == anotherString.length()) {
            int i = 0;
            while (n-- != 0) {
                if (charAt(i) != anotherString.charAt(i))  //逐個(gè)字符比較
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

String的比較

public class StringDemo {
    public static void main(String args[]) {
        String str1 = "Hello";
        String str2 = new String("Hello");
        String str3 = str2; // 引用傳遞
        System.out.println(str1 == str2); // false
        System.out.println(str1 == str3); // false
        System.out.println(str2 == str3); // true
        System.out.println(str1.equals(str2)); // true
        System.out.println(str1.equals(str3)); // true
        System.out.println(str2.equals(str3)); // true
    }
}

棧和堆的內(nèi)存分析圖:

棧和堆的內(nèi)存分析圖.png

由此可見,equals 是比較字符串的內(nèi)容是否一樣,== 是比較字符串的堆內(nèi)存地址是否一樣。

結(jié)論

equals和==的區(qū)別,需要分情況討論:

  1. 沒有重寫 equals ,則 equals 和 == 是一樣的。
  2. 如果重寫了 equals,則需看 equals 的方法實(shí)現(xiàn)。以 String 類為例:
    1. equals 是比較字符串的內(nèi)容是否一樣;
    2. == 是比較字符串的堆內(nèi)存地址是否一樣,或者說引用的值是否相等。

文末

此面試題會(huì)持續(xù)更新?。。?!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容