Java基礎知識面試題(面試題必備)

參考:感謝🙇‍ThinkWon大神
原文鏈接:https://blog.csdn.net/ThinkWon/article/details/104390612
內容自己純手打,對部分較冗長的答案做了提煉,加入了自己的🐟見。

文章目錄

1. Java概述

1.1 什麼是編程

所謂編程就是人通過編寫計算機可以認識的指令代碼,計算機運行這些代碼得到運行結果的過程就是編程。
爲了使計算機能夠理解人的意圖,人類就必須將解決問題的思路,方法和手段通過計算機可以理解的形式告訴計算機(指令代碼),計算機可以根據指令一步一步的去執行,完成某特殊的任務,這種人和計算機之間的交流就是編程。

1.2 談談你對Java的認識

Java是一門面向對象的編程語言,它不斷吸收C++語言的各種優點,還摒棄了C++語言難以理解的多繼承和指針等概念。Java語言具有功能強大和簡單易用的兩個特徵,Java語言運行程序員以優雅的思維方式進行復雜的編程。

1.3 Java1.5之後的三大版本

  1. JavaSE:Java標準版本,允許開發和部署再桌面,服務器,嵌入式中使用Java程序
  2. JavaEE:Java企業版(J2EE的前身),幫助企業開發部署可移植性,安全性的服務端應用程序。
  3. JavaME:Java微型版(移動設備和嵌入式設備,如機頂盒,手機,打印機)

1.4 JVM、JRE和JDK的關係

在這裏插入圖片描述

  1. JVM(Java Virtaul Machine)指的就是Java虛擬機,Java程序需要運行再虛擬機上,不同的平臺有不同的虛擬機,因此Java語言可以跨平臺。
  2. JRE(Java Runtime Environment)指的是Java運行環境,包括Java虛擬機和核心類庫,核心類庫主要Java.lang,包含了Java程序必不可少的系統類,如基本數據類,字符串,線程,異常處理類等
  3. JDK(Java Development Kit) Java開發工具包,是提供給Java開發人員使用的,包含了jre環境和Java開發工具如java.exe,編譯工具(javac.exe),打包工具(jar.exe)等

1.5 什麼是跨平臺性,原理是什麼?

所謂Java的跨平臺性指的是一次編譯,可以在多個系統平臺上運行。
實現原理:Java程序在虛擬機上運行,只要該系統可以安裝相應的虛擬機,該系統就能運行Java程序。

1.6 Java語言的特點

  1. 簡單易學,語法於C,C++很接近
  2. 面向對象(封裝,繼承,多態)
  3. 平臺無關性(Java虛擬機實現)
  4. 健壯性:Java是強類型語言,支持異常處理,垃圾回收機制
  5. 支持多線程網絡編程(爲網絡編程而誕生的)

1.7 什麼是字節碼,採用字節碼的好處是什麼?

字節碼:Java源代碼經過虛擬機編譯後產生的(.class)文件,它不面向任何特定的處理器,只面向虛擬機。
採用字節碼的好處:Java語言通過字節碼的方式,解決了傳統型解釋型語言執行效率的問題,又保留了解釋型語言可移植性好的特點,所以Java語言執行比較高效,而且字節碼不針對指定機器,所以Java程序實現了一次編譯,可以在多個計算機上運行。
Java程序運行經歷的階段

Java源代碼--->編譯器--->jvm可執行的Java字節碼(即虛擬指令)--->jvm--->jvm解釋器--->機器可執行的二進制機器碼--->程序

1.8 Java的主類是什麼?Java應用程序和Java小程序有什麼區別?

主類:一個Java程序可以又有多個類,但是只能有一個主類,它指的是含有main()方法的類。
Java應用程序與Java小程序有什麼區別
Java應用程序的主類不一定是public修飾,含有main()方法的類,是程序的入口。
Java小程序指的的主類是繼承系統類JApplet和Applet的類,必須是public修飾,小程序沒有main方法,主要是嵌入到瀏覽器的頁面中,調用init(),或run()來啓動,例如網頁嵌入小遊戲。

1.9 Java和C++有什麼區別?

相同點

  1. 都是面嚮對象語言,都支持封裝,繼承,多態
  2. 都是強類型語言

不同點

  1. Java相比C++沒有指針的概念,增加了GC避免了內存泄漏問題,更加安全。
  2. Java類只支持單繼承,不支持多繼承,而C++支持多繼承,雖然Java類不支多繼承,而接口支持多繼承。
  3. Java有異常捕獲機制用於捕獲異常,增加了程序的容錯能力,而C++沒有。
  4. 內存分配:Java使用JVM對內存進行掃描,將長期未使用的空間進行回收再利用,使得資源得到充分利用,內存管理交給了JVM管理維護,避免了因內存泄漏等問題導致系統崩潰。C++使用new和delete來分配和釋放內存,需要程序員自己來維護。

1.10 Oracle JDK 和 OpenJDK 的對比

  1. 穩定性:Oracle JDK 的穩定性要優於 OpenJDK 。
  2. 更新週期:Oracle JDK 每三年發佈一次,OpenJDK 沒三個月發佈一次。
  3. 開源:OpenJDK 完全開源,Oracle JDK並不是完全開源。

2. Java基礎語法

2.1 Java數據類型

2.1.1 Java有哪些數據類型

  1. 數據類型:Java是強類型的語言,對每一種數據都定義了具體的數據類型,在內存中分配了不同大小的空間。
  2. Java有8大類基本數據類型
    數值:整數:int、byte、short、long ;小數:float、double、
    字符:char
    布爾:boolean
    在這裏插入圖片描述
    注意:Java中boolea 所佔的字節數並沒有給出精確的定義,Java虛擬機給出的是4個字節,存儲類型爲int型,而boolea型數組存儲的是每個1個字節。
  3. Java有三大引用數據類型
    (class)
    接口(interface)
    數組([])

2.1.2 switch(expr)語句,expr有那些類型

JDK5之前,expr只能是int,byte,short,char。
JDK5開始,expr可以支持枚舉類型,enum
JDK7開始,expr可以支持String,Integer

2.1.3 2*8最有效率的方法

左移:2 <<< 3 相當於2*2^3
右移:2 >>> 3 相當於2/(2^3)

2.1.4 Math.round(expr)函數,Math.round(11.5)和Math.round(-11.5)分別等於多少

Math.round:表示四捨五入函數,它的原理時參數+0.5,再向下取整。

Math.round(11.5) ---> 11.5 + 0.5 = 12 -->向下取整 = 12
Math.round(-11.5) ---> -11.5 + 0.5 = -11 -->向下取整 = -11

補充兩個函數:

Math.ceil():向上取整函數
Math.floor():向下取整函數

2.1.4 float i = 3.4;是否正確?

在這裏插入圖片描述
可見無法將雙精度的3.4直接轉成float,因爲這可能會丟失精度,double(8個字節)—> float(4個字節)
解決方法

  float i = 3.4F;
  float a = (float)3.4;

2.1.5 short s1 = 1,s1 = s1+1與s1+=1,哪一個可以編譯通過?

在這裏插入圖片描述
可見s1 = s1 +1;右邊s1+1會自動轉成高精度的int,此時顯然不能轉成低精度的short類型,故編譯不能通過。
s1 += 1,會自動進行隱式的強制類型轉換,s1 = (short)(s1+1);因此可以編譯通過。

2.2 Java編碼

Java語言採用的時Unicode編碼標準,Unicode編碼爲每個字符定義了一個唯一的數值,因此在任何的平臺上都可以放心使用。

2.3 Java註釋

單行註釋:
//
多行註釋:
/**/
文檔註釋:
/**/
注意:多行註釋和文檔註釋不能嵌套使用。

2.4 訪問修飾符

protected、private、public、默認
在這裏插入圖片描述

2.4 運算符&和&&的區別

&運算符兩種用法:按位與
按位與:有0的0,全1爲1,例如:0&0 = 0,1&0 = 0,0&1=0,1&1=1
&&運算符:邏輯與或又稱短路與運算,也是兩邊都爲真(1),運算結果才爲真(1),但是邏輯與&&會引起短路(左邊表達式爲false,停止判斷右邊的表達式這是和按位與兩者最大的區別)

        int a = 1, b = 2,c = 3;
        if(a > 4 && (b = 3)>5){
            System.out.println("&&");
        }
        System.out.println(b); //2

        if(a > 4 & (b = 3)>5){
            System.out.println("&");
        }
        System.out.println(b);  //3

2.4 關鍵字

2.4.1 java中有沒有goto

Java中有goto,但是被當作保留字了,所謂保留字指的是已經被Java定義下來的字了,不能被我們當作變量來使用。goto會降低程序的可讀性,Java使用continue+break就可以做到。

2.4.2 final修飾類,方法,屬性

final修飾類,這個類則不能被繼承
final修飾方法,這個類則不能被重寫(覆蓋)
在這裏插入圖片描述
final修飾屬性,這個屬性時是不變的,也是不能改變的,變量常用大寫字母表示。

2.4.3 final,finally,finalize()之間的區別

  1. final可以修飾類,方法,屬性,修飾類時類不能被繼承,修飾方法時方法不能被重寫,修飾屬性時,該屬性不能被重新賦值。
  2. finally通常與trty-catch代碼塊配合使用,在處理異常時,通常將必須要執行的代碼放到finally中,表示是否出現異常都必須執行該部分代碼,常用是否資源。
  3. finalize():Object類的一個方法,Object類是所有類的父類,當我們使用System.gc()時,垃圾回收器就會去調用finalize()進行垃圾對象的回收。

2.4.4 this關鍵字

this關鍵字指的是當前對象的指針,或代表將來創建的對象,4個使用場景:

  1. 普通方法中使用,可以用來獲取成員變量。
  2. 在普通方法中使用,可以用來調用其它方法,this.可以被省略。
  3. 可以在構造器使用this來區分局部變量和成員變量。
  4. 可以在構造器中調用其它構造器,但必須寫在第一行。

注意this關鍵字不可以出現了static修飾的方法中,因爲static修飾的變量和方法開始都和類在堆的方法區中被初始化,而這個區域中而對象還沒被創建,當然對象也不會被創建這在這個地方。

2.4.5 super關鍵字

super關鍵字指向自己父對象的一個指針,這個父對象是離自己最近的一個父對象,2個使用場景

  1. 普通方法中,super可以調用父類的成員變量和方法,用於與子類區分
  2. 在子類構造器中可以使用super調用父類構造器,而且也必須寫在第一行。

注意:子類的構造器必須調用父類的構造器,沒有父類的構造器就會報錯。另外,在構造器中,this和super不能同時出現在第一行,換句話說,只能使用其一。
至於爲什麼super和this只能放構造器的第一行,主要將對象間的屬性關係削弱,簡單的理解這麼規定就是Java官方提醒我們,這樣寫容易造成可讀性變差,維護起來比較難。(瞭解)

2.4.6 super和this的區別

  1. super指向父類的引用,可以調用父類的構造器,方法和變量
  2. this指向當前對象的引用,可以調用當前對象的構造器,方法和變量
  3. 在構造器中,this和super只能出現其一,並且都只能出現在第一行。
  4. this在本類的構造器中調用本類的其它構造器,而super在子類中調用父類的構造器
  5. this和super都指向對象,因此都不能出現在靜態區中,包括static修飾的方法,變量,static塊中。
  6. 從本質出發,this是一個指向當前對象的引用,有當前對象的地址值,可以直接輸出this,而super只是一個關鍵字,它只可以調用父類的方法和變量,並不能輸出super關鍵字。

2.4.7 static關鍵字

static修飾的方法或變量,可以不用創建對象,使用類直接調用,這樣就可以做到不依賴於具體的對象實現某一功能。
static關鍵字還有一個比較關鍵性的作用:用靜態代碼快來優化程序性能。static代碼塊可以放在類的任何位置中,一個類中也可以有多個靜態代碼塊,在類被初次加載後,會按照靜態代碼快的順序來依次指向下來,並且每個靜態代碼塊只會被執行一次,常用來初始化一些操作。

2.4.8 static關鍵字的獨特之處

  1. 被static修飾的變量和方法是獨立於該類的任何對象的,也就是說,不屬於任何一個具體實例對象,而是被類的實例所共享的,換句話說,類的任何對象都可以調用它,並改變它。
  2. 類第一次被加載後,靜態屬性/方法/代碼塊就被加載到了方法區(存儲靜態代碼和類信息),可以先初始化,還可以後面對其賦值。
  3. 靜態變量在類加載的時候分配空間,以後創建類不會再分配了,但可以在(方法區)任意賦值。
  4. 靜態只能訪問靜態,非靜態可以訪問非靜態,也可以訪問靜態,用於方法和屬性之間共享數據。

2.4.9 static應用場景

因爲static是被類的實例對象所共享,所以如果某個成員變量是被所有對象所共享,那麼它就應該設置爲靜態變量
比較常見的應用場景:

  1. 修飾成員變量
  2. 修飾成員方法
  3. 靜態代碼塊
  4. 靜態內部類
  5. 靜態導包

2.5 流程控制語句

2.5.1 continue,break,return的區別

continue:遇到continue跳出當前循環,繼續下一次循環。
break:遇到break跳出當前循環,不繼續下一次循環。
return:用於方法的終止,結束當前方法,不會往下執行。

2.5 Java如何跳出多層循環

跳出多層循環可以在循環的外部定義一個標記,當循環執行到要跳出的條件時,使用break語句指定跳出的標記

    public static void main(String[] args) {
        ok:
        for (int i = 0; i < 100; i++) {
            for (int j = 0; j < 100; j++) {
                i += j;
                if(j == 5){
                    break ok;
                }
                System.out.println(i);

            }
        }
        System.out.println("over----------");
    }

需要注意的時,標記ok:必須定義在循環語句的上方

3. 面向對象

3.1 什麼是面向對象和麪向過程有什麼區別

3.1.1 面向過程

面向過程時具體化的,流程化的,解決問題你要一步一步的去分析,然後一步一步的實現。
優點:性能要由於面向對象,因爲面向對象的類的調用之間需要實例化,開銷很大。面向過程的應用場景有單片機,嵌入式開發,性能是最重要的因素。
缺點:不易維護,不易擴展。

3.1.2 面向對象

面向對象是模型化的,你只需要抽取出一個類,這個類可能是官方爲我們實現好的,裏面有你需要用的方法和解決問題的手段,不需要我們一步一步去實現,你只需要會用就可以了。
優點:易維護,易擴展。由於面向對象有封裝,繼承,多態的特性,可以設計出低耦合的系統,使得程序更加靈活,易於維護。
缺點:性能會比面向過程低。

3.1.3 區別

上面的優缺點就是他們主要區別,另外面向對象的底層實現還是面向過程,只不過將面向過程封裝起來,便以我們使用的面向對象了。

3.2 面向對象的三大特性

3.2.1 封裝

封裝就是把一個對象的屬性私有化,然後提供一些外部可以訪問到屬性的方法,如果不想外部訪問,就可以不提供,但是如果一個類都沒有外部可訪問的方法和屬性,那也就沒什麼意義了。

3.2.2 繼承

Java只支持單繼承,繼承是指在已存在基礎類上建立新類的技術,這個新類可以定義新的方法和屬性,可以重寫父類的方法,也可以使用父類繼承下來非private的方法和屬性,通過繼承我們可以更方面的複用以前的代碼。

3.3.3 多態

Java多態其實就是父類只能使用子類的非靜態成員方法,而如果想用子類所有的屬性,就強轉成子類就好了。
多態存在的三個前提:

1. 要有繼承關係
2. 子類要重寫父類的方法
3. 父類引用指向子類對象

3.3 類與接口

3.3.1 抽象類和接口

抽象:所謂抽象是對類對象的共同特徵抽取出來構造類的過程,包括數據抽象和行爲抽象,抽象只關注對象有哪些行爲和屬性,並不關注這些行爲的細節。
抽象類和接口對比:抽象類是用來捕捉子類的通用特性,接口時抽象方法的集合,接口的方法都是抽象方法。從設計層面上看,抽象類是對類的抽象,是一種模板設計,而接口是對行爲的抽象,是一種行爲規範。
抽象類和接口異同點
相同點:

  • 接口和抽象類都不能被實例化
  • 接口和抽象類都位於繼承的頂端,用於被其它類實現或繼承
  • 都包含抽象方法,子類必須實現它們

不同點:

參數 抽象類 接口
聲明 abstract interface
類中方法 可以是抽象方法可以不是,如果是抽象方法子類必須實現 都是抽象方法
類的變量 abstract類中可以不用初始化 interface必須初始化
實現 extends 如果子類不是抽象類的話,需要提供父類所有抽象方法的實現 implements 需要提供接口類所有抽象方法的實現
構造器 可以包含構造器 不可以包含構造器
訪問修飾符 可以任意 接口默認是public,而且不能定義爲private或protected
多繼承 不支持多繼承,只能繼承一個抽象類 接口可以多實現

背🐖: Java8接口提供了默認方法和靜態方法,以此來減少抽象類和接口之間的差異性,現在我們已經可以爲接口提供默認的實現方法,並且不再強制子類來實現它。
接口和抽象類的選取上遵循的原則:

  • 代碼複用性高,又要爲子類提供通用功能的,優先使用抽象類
  • 如果需要用到多繼承考慮使用接口,功能也更強大。
  • 制定一套行爲規範,子類必須去實現它,則考慮使用接口。

3.3.2 普通類和抽象類有什麼區別

  • 普通類不能包含抽象方法,抽象類可以包含抽象方法,而且子類繼承它就必須實現它的抽象方法
  • 抽象類不能被實例化,普通類可以

3.3.3 抽象類能不能用final修飾

不能,因爲final修飾的類是不能被繼承的,而抽象類的設計就是爲了能被繼承的。

3.3.4 創建對象使用什麼關鍵字?對象實例和對象引用有什麼區別?

創建對象使用new,將創建好的實例對象放在堆內存中,而對象的引用(地址)則放到棧內存中。可以有多個引用指向同一個實例對象,一個引用只能指向一個或0個實例對象。

3.3.5 Java創建對象有幾種方式?

四種。分別是

1. new 關鍵字,最常用
2. 反射,調用java.lang.Class或者java.lang.reflect.Constructor的newInstance()方法
3. 調用對象的clone()方法
4. 反序列化,調用java.io.ObjectInputStream的readObject()方法創建對象

3.4 變量與方法

3.4.1 成員變量和局部變量有什麼區別?

變量:在程序執行過程中,某個範圍內它的值可以發生改變的量,本質上講,變量其實是內存中的一塊小區域。
成員變量:方法外部,類內部定義的變量
局部變量:方法內部

成員變量和局部變量區別

參數 成員變量 局部變量
作用域 整個類中 方法或語句塊裏{}
存儲位置 隨對象的存在而存在,放到堆內存中 存儲在堆內存中,隨方法的調用或語句塊的執行存在,執行結束局部變量內存也就是自動釋放
生命週期 隨對象的存在而存在 方法的調用或語句塊的執行存在,執行結束局部變量內存也就是自動釋放
初始值 有初始值 沒有初始值

變量使用原則:遵循就近原則,先尋找局部比較近的變量,沒有再往遠找。

3.4.2 Java中定義一個沒有使用的無參構造的作用

作用體現在我們如果用到了繼承,子類的構造函數會默認先調用父類的構造函數執行,使用語句super(),當然父類中如果只定義了有參構造,而子類也沒有去調用它,則直接編譯不通過。

3.4.3 在調用子類構造方法之前會先調用父類沒有參數的構造方法,其目的是?

這是繼承的一種規則,目的是爲了先初始化的父類,以便子類可以使用父類的一些方法和屬性進而來幫助初始化子類。

3.4.4 一個類的構造方法作用是什麼?如果沒有程序還能正常運行嗎?

主要完成類對對象的初始化工作,如果沒有的話可以運行,會默認加上一個無參構造。

3.4.5 構造方法有那些特性

  1. 沒有返回值
  2. 方法名必須和類名一致
  3. 創建對象時自動調用,無需手動調用

3.4.6 靜態變量和實例(普通)變量有什麼區別?

靜態變量:不屬於任何一個實例對象,但共享於所有實例對象,屬於類,在內存中只會有一份,類加載後JVM會爲靜態變量分配一塊內存區域。
實例變量:屬於實例對象,每次創建對象,都會爲每個對象分配變量內存空間,實例變量屬於實例對象,在內存中,有多少個對象就有多少個實例變量。

3.4.7 靜態方法和實例(普通)方法的區別

外部方法調用上:可以以類名.靜態方法名調用靜態方法,也可以使用對象.靜態方法名調用靜態方法,而普通方法只能以對象.普通方法,所以調用靜態方法可以不用創建對象。
內部方法調用上:在類中,靜態方法內部不能調用非靜態的方法/變量,普通方法可以調用靜態/非靜態 的方法/變量。

3.4.8 爲什麼靜態方法調用非靜態成員是非法的?

這還要從代碼被加載進內存說起,靜態方法和類信息隨着類加載器的加載首先被加載到了堆內存的方法區中,注意這個時候還沒有對象的存在。而非靜態成員是屬於對象的,如果沒有對象是不能被調用的,因此不能再靜態方法中使用非靜態成員,必須使用對象調用纔可以使用。

3.4.9 什麼是返回值,返回值的作用是什麼?

返回值是指我們獲取某個方法體中代碼執行後產生的結果,前提是該方法可以產生結果。作用是接收結果,用於其它的操作。

3.5 內部類

3.5.1 什麼是內部類

在Java中,內部類就是定義在一個類中,和定義變量的方式一樣,內部類本身就是一個類的屬性。

3.5.2 內部類的分類

內部類可以分爲:
成員內部類
局部內部類
匿名內部類
靜態內部類

3.5.3 成員內部類

成員位置上的非靜態內部類,就是成員內部類

public class TestInnerClass {

    public static int radius = 1;
    public  String name  = "circle";

    public static void main(String[] args) {
        TestInnerClass tc = new TestInnerClass();  //先創建外部類
        TestInnerClass.Inner inner =  tc.new Inner(); //再創建內部類
        inner.visit();
    }

     class Inner{
        public void visit(){
            System.out.println("visit outer static variable :" + radius);
            System.out.println("visit outer variable :" + name);
        }
    }
}

成員內部類內部可以訪問外部類的靜態和非靜態變量/方法,需要主要的是成員內部類的對象創建方法,需要先創建外部對象再創建內部類對象。

3.5.4 局部內部類

定義在方法內部的類

public class Outer {

    private  int out_a = 1;
    private static int STATIC_b = 2;

    public void testFunctionClass(){
        int inner_c =3;
        class Inner {
            private void fun(){
                System.out.println(out_a);
                System.out.println(STATIC_b);
                System.out.println(inner_c);
            }
        }
        Inner  inner = new Inner();
        inner.fun();
    }
    public static void testStaticFunctionClass(){
        int d =3;
        class Inner {
            private void fun(){
                // System.out.println(out_a); 編譯錯誤,定義在靜態方法中的局部類不可以訪問外部類的實例變量
                System.out.println(STATIC_b);
                System.out.println(d);
            }
        }
        Inner  inner = new Inner();
        inner.fun();
    }
}

局部內部類在方法內部,只能訪問外部類的靜態變量和方法,創建方式在方法內部new 類名

3.5.5 匿名內部類

匿名內部類就是沒有名字的內部類,在實際開發中用的比較多。

public class TestAnonymous {

    public void test(){
        new Service() {
            @Override
            public void method() {
                System.out.println("匿名內部類");
            }
        }.method();
    }
}
//匿名內部類必須繼承或實現某一接口
interface Service{
    void method();
}

匿名內部類有一下特點:

  1. 匿名內部類必須繼承一個抽象或實現某一接口
  2. 匿名內部類中不能定義任何靜態方法/成員
  3. 當所在的方法形參被匿名內部類使用時,必須聲明爲final
  4. 匿名內部類不能是抽象的,它必須實現繼承的類或接口的所有抽象方法。

創建方式

new 抽象類/接口{
 匿名內部類實現
}

3.5.6 靜態內部類

public class TestStaticInnerClass {

    public static int radius = 1;
	public  String name  = "circle";
	
    public static void main(String[] args) {
        TestStaticInnerClass.StaicInner inner = new StaicInner();
        TestStaticInnerClass.StaicInner inner2 = new TestStaticInnerClass.StaicInner();

        inner.visit();
    }

    public static class StaicInner{
        public void visit(){
      	  //System.out.println(name);  報錯
            System.out.println("visit outer variable :" + radius);
        }
    }
}

可見靜態內部類,只能訪問外部類的靜態成員/方法,創建靜態內部類的方法
外部類.靜態內部類 類名 = new 外部類.靜態內部類()
外部類.靜態內部類 類名 = new 靜態內部類()

3.5.7 內部類的優點

  1. 內部類對象可以訪問到外部類對象內容,包括私有數據
  2. 內部類不爲同一包中的其它類所見,起到了很好的封裝性
  3. 匿名內部類可以很方面的定義回調

3.5.8 內部類使用場景

  1. 當某個類除了它的外部類,不被其它類所使用時,可以考慮內部類
  2. 適當使用內部類,可以增加程序的靈活性和可擴展性
  3. 匿名內部類安卓中比較常用,如事件監聽器

3.5.9 局部內部類和匿名內部類訪問局部變量時,爲什麼要將局部變量定義爲final

看例子:
在這裏插入圖片描述
我們如果沒有對a進行修改,是可以正常輸出a的,但是對a進行修改後,就出現了報錯。
在這裏插入圖片描述
報錯原因其實是生命週期不一致造成的,outMethod方法內的局部變量,a存儲在棧中(如果是引用類型則對象存儲在堆中),當程序執行到 inner.innerMethod();後a被銷燬,但是內部類對它的引用還在,因此引用一個不存在的變量就會報錯,而如果使用final修飾了,局部變量就會被加載到方法區的常量池中,不會被銷燬。

3.5.10 內部類運行結果

public class Outer {
    private int age = 12;

    class Inner {
        private int age = 13;
        public void print() {
            int age = 14;
            System.out.println("局部變量:" + age);  //14
            System.out.println("內部類變量:" + this.age);  //13
            System.out.println("外部類變量:" + Outer.this.age);  //12
         }
    }

    public static void main(String[] args) {
        Outer.Inner in = new Outer().new Inner();
        in.print();
    }

}

3.7 重寫與重載

3.7.1構造器能否內重寫

構造器是不能被繼承的,因此,更不能被重寫。

3.7.2 重寫與重載的區別,重載的方法能否根據返回值進行區分(重要)

重載:使用在類中,方法與方法之間的重載指的是方法名相同,參數不同(參數類型,個數,順序),與返回值無關,所以不能依據返回值來判斷方法是否重載。
重寫:出現在父子類之間,方法名,參數列表必須相同,子類的返回值必須<=父類的返回值類型(繼承樹),子類的訪問修飾符必須>=父類,(private default protected public),如果父類的方法訪問修飾符是private,則子類就不是重寫了。

3.8 對象相等判斷

3.8.1 ==與equals的區別是什麼?

  1. == :作用是判斷兩個對象的地址值是否相等,即判斷兩個對象是否是同一個對象(基本數據類型是比較值,引用數據類型是比較地址)
  2. equals:作用是比較兩個對象是否相等,有兩種情況:
    • 情況一:如果此比較類型重寫了Object的equals方法,則比較兩個對象的內容是否相同。
    • 情況二:如果比較類型沒有重寫Object的equals方法,則仍是按照 == 進行比較
String a = new String("ab");
        String b = new String("ab");

        String c = "ab";
        String d = "ab";

        if(a == b){   //false
            System.out.println("a == b");
        }
        if(c == d){   //true
            System.out.println("c == d");
        }
        if(a.equals(b)){ //true
            System.out.println("a EQ b");
        }
        if(42 == 42.0){ //true
            System.out.println("42 == 42.0");
        }

說明:

  • String的equals方法是被重寫過的,Object類的equals也是比較 == ,經過String的重寫後只比較對象的內容是否相等,相等返回true,否則返回false.
  • 當創建String對象時,虛擬機會在常量池中尋找已經存在的值和要創建對象的值是否相同,如果能找到,就把引用賦值它,如果沒有就重新創建一個對象放到常量池中。

關於String創建對象的獨特之處參考:https://www.cnblogs.com/Young111/p/11273851.html

3.8.2 hashCode與equals(重要)?

注意此類問題容易扯到HashMap的底層原理
equals:方法是Object的方法,作用是比較兩個對象是否是同一個對象
hashCode:方法也是Object的方法,返回值是返回一個哈希碼,這個哈希碼的作用是確定該對象在哈希表中的索引位置。

hashCode與equals之間的關係
兩個對象,A,B 則A.equals(B) 返回true,現在看主要看它是否重寫equals,如果沒有重寫,此時它們的hashCode一定相等,否則可能不等(可以通過重寫hashcode可以讓其相等)。
兩個對象的hashCode相等,兩個對象不一定相等。
兩個對象的hashCode不相等,equals一定不等。

面試官可能又會深挖你一個問題?爲什麼重寫equals的方法時必須也要重寫hashCode方法呢?

  1. 可以提前校驗兩個對象是否相等,避免每個都調用equals方法,影響效率,好處再很多地方都有體現,如hashmap。
  2. Java api規範要求重寫hashcode,重寫之後可以保證兩個對象的hashcode的值相同。

3.8.3 對象相等與指向它們的引用相等有和區別?

對象相等指的是內存中的內容是否相等,即對比對象是不是同一個。而引用相等指的是比較它們內存中的地址地址是否相等。

3.7 函數參數傳值

3.7.1 爲什麼Java只有值傳遞?

值傳遞的重要特徵是在內存中拷貝了一份副本內容進行傳遞,因此對於Java,基本數據類型傳值傳的是拷貝完副本給形參,由於傳遞的是副本,所以不會引起原數據的改變。而引用類型傳的是在內存中拷貝的一個內存地址過去,此時實際參數指向的地址和原對象的引用指向了同一個對象,它們的改變是會互相影響的。

3.7.2 當一個對象被當作參數傳遞到一個方法後,此方法可改變這個對象的屬性,並可返回變化後的結果,那麼這裏到底是值傳遞還是引用傳遞

仍然是值傳遞,上面已經說的很清楚了,在Java中,判斷是不是值傳遞只要看傳遞過去的值是否在內存中拷貝了一份副本內容進行傳遞,如果是就是值傳遞,顯然,對象的引用傳遞也在內存中copy了一個副本地址進行傳遞,副本改變屬性值,是會影響到原對象的屬性值。

3.7.3 引用傳值和值傳遞有什麼區別

值傳遞:值傳遞的重要特徵是在內存中拷貝了一份副本內容進行傳遞,因此對於Java,基本數據類型傳值傳的是拷貝完副本給形參,由於傳遞的是副本,所以不會引起原數據的改變。
引用傳遞:引用類型傳的是在內存中拷貝的一個內存地址過去,此時實際參數指向的地址和原對象的引用指向了同一個對象,它們的改變是會互相影響的。

3.8 Java包

3.8.1 JDK中常用的包有哪些

  • java.lang:系統基礎類
  • java.io:與輸入輸出有關的包,常用於文件操作
  • java.net:網絡編程相關
  • java.util:系統輔助類,特別是集合類
  • java.sql:操作數據庫庫相關的

3.8.2 import java和javax有什麼區別

剛開始的時候 JavaAPI 所必需的包是 java 開頭的包,javax 當時只是擴展 API 包來說使用。然而隨着時間的推移,javax 逐漸的擴展成爲 Java API 的組成部分。但是,將擴展從 javax 包移動到 java 包將是太麻煩了,最終會破壞一堆現有的代碼。因此,最終決定 javax 包將成爲標準API的一部分。

3.9 Java IO流

3.9.1 Java IO有哪幾種

  • 按照流的流向:輸入流/輸出流
  • 按照操作單元:字節流/字符流
  • 按照流的角色:節點流/處理流

3.9.2 BIO,NIO,AIO有什麼區別?

  • BIO:Block IO 同步阻塞IO,就是我們平時使用的傳統IO,特點是簡單使用方面,併發處理能力低。
  • NIO:Non IO 同步非阻塞IO,客戶端與服務的通過(channal)通道通訊,實現多路複用。
  • AIO:Asynchronous IO ,是NIO的升級版本,實現了異步的非堵塞IO,,異步IO的操作基於事件和回調機制。

3.9.3 File有哪些常用的方法

  • exists:判斷文件路徑是否存在
  • createFile:創建文件
  • createDirtory:創建文件夾
  • delete():刪除一個文件或目錄
  • copy():複製文件
  • size():查看文件個數
  • read():讀取文件
  • write():寫入文件

3.10 反射

3.10.1 什麼是Java的反射

反射就是在程序的運行中,將類的各個組成部分封裝成對象,對於任意的對象,我們可以調用的它的方法和屬性,這種獲取動態信息以及動態調用對象的功能稱之爲反射。

補充兩個小知識點:
靜態編譯:編譯時確定類型,綁定對象
動態編譯:運行時確定類型,綁定對象,反射用到。

3.10.2 Java的反射的優缺點

優點:可以在運行過程中操作這些對象,可以解耦代碼,提高代碼靈活度和可擴展性。
缺點:性能會降低,反射相當於一系列解釋的操作,通知jvm要做的是,性能比之間的Java代碼要慢

3.10.3 獲取Class的三種方式

1. Class.forName(“類的權限路徑”)
2. 通過對象.getClass
3. 通過類名.class

4. 常用的API

4.1 String相關

4.1.1 字符型常量和字符串常量的區別,什麼是字符串常量池

形式上:字符常量定義用的是單引號,字符串常量用的是雙引號
含義上:字符常量相當於一個整形的ascii值,可以參與表達式運算,字符串常量代表一個內存地址
佔內存大小:字符常量只佔一個字節,而字符串常量佔若干個字節,至少有一個結束的字符 \n

字符串常量池
字符串常量池位於堆內存中,專門用於存儲字符串常量,可以提高內存使用效率,避免多塊內存空間存儲相同的字符串,在創建字符串時,jvm會先在常量池中尋找有沒有存在的字符串常量,如果找到,就將引用賦值過去,如果沒找到則實例化一個字符串放進常量池,並返回引用。

4.1.2 String有哪些常用的方法

  • indexOf:返回字符串中字符的索引位置
  • equals:比較兩個字符串是否內容相等
  • trim:刪除字符串兩端的空白
  • replace:字符串的替換,替換後返回,原來值沒有變化
  • split:以某一條件分割字符串,返回字符串數組
  • length():返回字符串長度
  • charAt:返回指定索引的字符
  • toLowerCase/toUpperCase:將字符串轉成小寫/大寫字符
  • substring:截取字符串

4.1.3 String是基本數據類型嗎

不是基本數據類型,基本數據類型有8類,分別是int、short、long、byte、double、float、char、booean;剩下的都是引用類型,jdk 1.5引入的枚舉也是引用類型。

4.1.4 String爲什麼是不可變的?真的是不可變嗎

不可變!
String內部維護這一個final修飾的字符數組

/** The value is used for character storage. */
private final char value[];

肯定不可變!此時可能面試官會甩出一道題

  String str = "Hello";
  System.out.println(str);  //Word
  //System.out.println(str.hashCode()); 69609650
  str = str + " Word" ;
  System.out.println(str);   //Hello Word
  //System.out.println(str.hashCode()); 387817944

然後說這不是變了嗎?
其實這並不是String變了,而是str指向的引用變了,開始時在在常量池中創建了"Hello"對象並將引用返回給str,str = str + " Word" ;後str的引用值被str + " Word"所覆蓋,換句話說,只是引用地址發生改變了。開始時str指向的"Hello"並沒有發生變化,所以說String還是不變的!

4.1.5 是否可以繼承String

String 類被final修飾,不可以被繼承

4.1.6 String str = “i” 與String str = new String(“i”);

這兩種創建對象並不一樣,String str = "i"對象只會被創建一次,而String str = new String(“i”);對象創建了兩次。內存的分配位置也不同,String str = "i"創建的對象會被JVM放到常量池中,而String str = new String(“i”)常量池和堆內存中各有一個

4.1.7 String str = new String(“i”);創建了幾次對象

兩次對象,一個是常量池中的i,一個是堆內存中的i

4.1.8 String有沒有length(),數組有沒有

數組中有length屬性,而String沒有,但是String有length()方法,注意JavaScript中獲取字符串長度有length

4.1.9 在是用HashMap時,用Strig做key有什麼好處

HashMap內部的實現時通過key來確定value的值,當我們的key爲String時,因爲String是不變的,所以它的hashcode只需要算一次就被緩存下來了,這相比其它對象要快。

4.1.10 String的StringBuff和StringBuilder有什麼區別

可變性:String內部由一個private final char value[]維護着,所以它是不可變的。StringBuff和StringBuilder都繼承了AbstractStringBuilder類,AbstractStringBuilder類也是用字符數組保存字符串的,但是它是 char[] value,即字符串是可變的。
線程安全性:String對象是不可變的,也可以理解爲常量,因此是線程安全的。AbstractStringBuilder是StringBuilder和StringBuff的公共父類,定義了一些公共的方法,如insert,append,indexOf,expandCapacity等方法,但是StringBuffer對其加了同步鎖,所以是線程安全的,而StringBuilder並沒有對方法加同步鎖,所以是非線程安全的。
性能:每一次對String操作時,都會使它指向一塊新的內存空間,生成一個新的String對象,StringBuffer每一次對StringBuffer對象操作,不會生成新的對象並改變引用。相同情況下,使用StringBuilder會比StringBuffer提高性能10%-15%,但是要冒着線程不安全的風險

4.1.11 實現字符串反轉

方法有很多,介紹3個

  1. 逆序遍歷
String str = "liuzeyu";
for (int i = str.length()-1; i >= 0; i--) {
        System.out.println(str.charAt(i));
}
  1. StringBuider或StringBuff的reverse方法
    使用StringBuider和StringBuff的reverse方法即可
  2. 藉助棧先進後出的思想
String str = "liuzeyu";
            Stack<Character> stack = new Stack();
            for (int i = 0; i < str.length(); i++) {
                    stack.push(str.charAt(i));
            }
            while (stack.size() != 0){
                  System.out.println(stack.pop());
            }

4.2 包裝類相關

4.2.1 自動拆箱和自動裝箱

裝箱:將基本類型封裝成引用類型
拆箱:將引用類型(包裝類型)轉換成基本類型

4.2.2 int與integer有什麼區別

int是基本類型,屬於Java 中8大基本類型的一種。
integer屬於引用類型的包裝類,jdk 5之後引入了自動拆箱和自動裝箱
int的包裝類是integer
常見的基本類型對應的包裝類

基本類型 int char boolean byte double long short float
包裝類 Ingeter Character Boolean Byte Double Long Short Float

4.2.3 integer a = 127與integer b = 127相等嗎

會相等,因此當整形的值位於 -128~127之間,Integer可以自動拆箱和裝箱成功,原理時直接引用常量池中的Integer對象,不會去重寫new,但是如果不在這個返回內,兩對象就是不同對象。

    Integer a1 = 128;
    Integer b1 = 128;
    System.out.println(a1 == b1);  //false

    Integer c1 = 127;
    Integer d1 = 127;
    System.out.println(c1 == d1);  //true

Intger的創建對象原理和String的類似:

   Integer a = new Integer(3);
   Integer b = 3;
   int c = 3;
   System.out.println( a == b); //false
   System.out.println( c == b); //true
   System.out.println( c == a); //true
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章