【面試題系列】——Java基礎
本文主要包括Java基礎及面向物件相關面試題。
目錄
1,Java科普
1.1 為什麼安裝包要分JDK和JRE?
JRE主要包含JVM,用於執行Java程式。
JDK包含了JRE,除此之外,包含了比如像javac等程式開發需要用到的工具。
先來說說什麼是JDK,JRE。
JDK:Java Development Kit Java開發工具包【開發Java程式用】
JRE:Java Runtiome Environment Java執行環境【執行Java程式用】
【注:下載的JDK包是包含了JDK和JRE的,JDK和JRE是邏輯上的區分,兩者在JDK下載的包中都有】
JDK與JRE的關係:以Java程式碼執行為例,編寫好Java程式碼之後,通過javac將java原始檔編譯成class位元組碼檔案,然後通過java命令,執行位元組碼檔案。那麼執行位元組碼的環境就是JRE。(JRE的核心就是JVM)
瞭解完JDK和JRE是什麼之後,再聊聊為什麼開發者當時要把一個安裝包分成兩部分呢?
平常使用的軟體都是一鍵安裝的,但JDK需要安裝兩次。JDK的發明者不會這麼無聊,故意給開發者增加麻煩。
【我想】:這應該跟生產環境的部署問題有關,關於生產環境部署JDK還是JRE一直飽受爭議,具體情況根據專案而定。
出於對效能的考慮,儘可能的使伺服器輕,能少裝一個軟體就少裝一個,這樣生產環境部署JRE就OK了。【又省了資源】
除了這個方面之外,還有一種可能。JDK的開發也有可能是分團隊的,JDK和JRE可能是交由不同團隊開發,JDK和JRE的耦合也可能因此而減小,從而加快JDK的迭代版本。(畢竟現在JDK一年更新兩次)
1.2 為什麼Java語言是跨平臺的?
JVM有兩個功能
- 將class位元組碼轉換為機器碼
- 相容不同的作業系統
跨平臺和Java 虛擬機器有關。
JVM有兩個主要的功能:
- 適配不同的作業系統的指令集(相容不同的作業系統)
- 翻譯位元組碼檔案為機器碼執行
(Oracle官網上下載JDK,不同作業系統的JDK是不一樣的,對應不同的虛擬機器)
1.3 為什麼安裝完JDK後要設定環境變數?
在解決這個問題之前,先來了解一些環境變數是幹嘛的:
環境變數是在作業系統中一個具有特定名字的物件,它包含了一個或者多個應用程式所將使用到的資訊。例如Windows和DOS作業系統中的path環境變數,當要求系統執行一個程式而沒有告訴它程式所在的完整路徑時,系統除了在當前目錄下面尋找此程式外,還應到path中指定的路徑去找。使用者通過設定環境變數,來更好的執行程序。
簡單來說,執行一個程式(命令),作業系統會從當前目錄尋找,或者從環境變數中尋找。
換句話說,如果在java,javac的目錄下執行這個兩個命令,是沒有問題的。但是如果更換了目錄,系統在當前目錄找不到,就會去環境變數中尋找。所以設定環境變數的根本目的是在電腦的任何一個資料夾下都可以編譯執行Java程式。
1.4 Java和C++區別
題外話,Java和C++有什麼區別?(據說有些面試官老愛幹這種事)
- C++支援多繼承,Java支援單繼承
- Java有垃圾回收機制
- Java不支援指標,更加安全
2,資料型別&變數
2.1 boolean佔幾個位元組?
表示變數:4個位元組(轉換為int儲存)
表示byte陣列:1個位元組
2.2 為什麼long可以自動轉換為float?
float,double採用指數方式儲存,能表示的數比long更大
System.out.println(Long.MAX_VALUE);
System.out.println(Float.MAX_VALUE);
System.out.println(Double.MAX_VALUE);
System.out.println(Float.MAX_VALUE>Long.MAX_VALUE);
9223372036854775807
3.4028235E38
1.7976931348623157E308
true
float和double使用指數表示,取值範圍在
-3.4*10^38 - 3.4*10^38
順便看看float和double的精度:7位和16位。
2.3 包裝型別和基本資料型別的區別
初始值:包裝型別的初始值為null,基本資料型別的初始值基本上是0,char是'u000'
儲存方式:包裝型別存在堆裡,基本資料型別存在棧中
2.4 String,StringBuffer,StringBuilder的區別
- 可變性
String底層使用fina修飾的陣列實現,是不可變的,StringBuffer和StringBuilder是可變的
- 安全性
String不可變,自然執行緒安全,StringBuffer使用synchronized同步鎖實現執行緒安全,StringBuilder是執行緒不安全的
-
可變性
String是不可變的,StringBuffer和StringBuilder是可變的
String string1 = "111"; string1 = "222"; System.out.println(string1); string1指向222時,建立了一個新的物件並指向它
String 底層使用final修飾的陣列實現,故是不變的
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { private final char value[];
-
安全性
StringBuffer和String是執行緒安全的,,StringBuilder是執行緒不安全的。
String 中的物件是不可變的,也就可以理解為常量,執行緒安全。
StringBuffer 對方法加了synchronized同步鎖所以是執行緒安全的。
-
對於三者使用的總結
- 操作少量的資料: 適用String
- 單執行緒操作字串緩衝區下操作大量資料: 適用StringBuilder
- 多執行緒操作字串緩衝區下操作大量資料: 適用StringBuffer
2.5 String s = "Hello";s = s + "world!";這兩行程式碼執行後,原始的String物件中的內容到底變了沒有?
沒有,只是指向了另外一個物件。
因為String被設計成不可變(immutable)類(final修飾),所以它的所有物件都是不可變物件。
在這段程式碼中,s原先指向一個String物件,內容是 "Hello",然後對s進行了+操作
這時,s不指向原來那個物件了,而指向了另一個 String物件,內容為"Hello world!"
原來那個物件還存在於記憶體之中,只是s這個引用變數不再指向它了。
2.6 String str =“i”;和 String str = new String("i");有區別嗎?
前者會被JVM分配到常量池中,常量池中沒有重複的元素,如果再次建立一個字串變數等於i的話,就會直接指向常量池中的i。
後者是new了一個物件,堆中是允許物件重複的。
前者會被JVM分配到常量池中,常量池中沒有重複的元素。
String str1 =“i”;
String str2 =“i”;
str2不會重新建立一個常量,而是指向str1。
String str1 = new String("i");
String str2 = new String("i");
str1會在堆記憶體中建立物件
str2還是會再次建立一個新的物件
2.7 如何將字串反轉?
使用StringBuilder或者StringBuffer的reverse()方法。
StringBuffer str1 = new StringBuffer("12345");
StringBuffer str2 = str1.reverse();
System.out.println(str2);
54321
2.8 Java有幾種變數?
-
類變數
獨立於方法之外,必須用static修飾
-
例項變數
獨立於方法之外,不用static修飾
-
區域性變數
方法中的變數
2.9 引用資料型別包含哪幾種?
- 類
- 介面
- 陣列
3,運算子
3.1 ==和equals的區別
equals用於比較內容是否相同(源自Object類,通常被重寫)
==對於基本資料型別比較值相同,對於引用資料型別比較記憶體地址是否相同(是否是同一個物件)
知乎上非常形象的一張圖:
3.2 &和&&區別(|和||同理)
& : 兩邊都為true時才為true
|:兩邊有一個為false即為false
短路邏輯運算子
&&:左邊為fasle直接返回flase(不計算右邊)
||:左邊為true直接返回true(不計算右邊)
就是為了簡化計算量
^表示異或,相同為false,不同為true
4,面向物件
4.1 什麼是面向物件?
如果摒棄軟體開發的範疇,這是一種通過明確社會分工而提高效率的方法。
在軟體開發的範圍內,就是通過抽象出系統功能而實現最大化程式碼複用的開發模式。
4.2 封裝相關
-
什麼是封裝
通過隱藏實現,暴露介面,一來實現程式碼解耦,二來通過訪問修飾符保證資料安全。
4.3 繼承相關
-
繼承的作用?
實現程式碼複用
-
繼承的規則?
- 子類繼承父類非private的屬性和方法
- 子類可以擴充套件自己的屬性和方法
-
構造器是否會被繼承?
-
構造器不會被繼承,但子類物件初始化時會呼叫父類無參構造器
【為什麼。子類和父類有最基本的依賴關係,比如說資料依賴】
-
當父類顯式寫了有參構造器,且沒有無參構造器。子類繼承父類的時候必須顯式的呼叫父類的有參構造器。呼叫的方式可以使用super(a,b)來呼叫。
-
-
子類父類的初始化順序
原則:靜態優於非靜態,父類優於子類 - 父類靜態變數,靜態語句塊 - 子類靜態變數,靜態語句塊 - 父類非靜態程式碼塊,構造器 - 子類非靜態程式碼塊,構造器
4.4 多型相關
-
什麼是多型?
一類事物的多種表現形態。(比如手機有各種各樣的品牌,但都屬於手機這一類事物)
-
如何體現多型?
方法過載:針對本類的不同方法而言,方法名相同,引數不同(個數,順序)【返回型別隨意】
方法重寫:針對繼承而言,除了方法體可以自定義外,其他必須與父類保持一致(方法名,返回型別,引數)
-
向上轉型&向下轉型
up:子類轉換為父類,目的是訪問父類的公共方法,實現程式碼的複用和簡潔(比如100個類把公共方法寫在父類中,就不需要每個類都寫一遍了)
down:父類轉換為子類,據說是為了呼叫子類的擴充套件方法。(為啥不直接new一個物件,已提交知乎問答)
4.5 關鍵詞static
-
修飾變數
稱為靜態變數,類變數,全域性變數
可直接通過類名.變數名訪問
-
修飾方法
稱為靜態方法,類方法
可通過類名.方法名直接訪問
- 非static方法可以訪問static方法,static方法不能訪問非static方法
4.6 關鍵詞final
- 修飾類不能被繼承
- 修飾方法不能被重寫
- 修飾變數則變數變為常量
4.7 介面和抽象類的區別?
-
繼承與實現
類只能單繼承,但可以實現多介面
-
方法是否能實現
抽象類不僅可以做方法宣告,也可以做方法實現。(介面只能做方法宣告)
-
修飾符
介面的變數都預設採用final修飾,方法採用public修飾。
抽象類可自定義。
4.8 為什麼重寫equals必須重寫hashCode?
equals和hashCode位於Object類中,所有的類都會繼承Object類。
equals通常被用來比較物件的內容是否相同,hashCode是用來返回物件Hash值的一種方法。
如果不重寫hashCode會導致,equals相同但hashCode不相同,equals不相同但hashCode相同。
重寫的目的一來為了避免hash衝突,二來提高物件訪問速度。