大家好,女士們,先生們,軟件工程師們!說說面試題吧。關於您需要準備什麼以及您需要了解什麼。這是第一次回顧或研究這些要點的好時機。 最後,我收集了相當廣泛的關於 OOP、Java 語法、Java 異常、集合和多線程的常見問題集,為了方便起見,我將把它們分成幾個部分。很難一次涵蓋所有內容,但我希望這些材料能為那些準備以程序員身份找到第一份工作的人提供良好的基礎。為了最好地理解和保留,我建議也通過其他來源進行梳理。您可以通過從幾個不同的角度來處理一個概念,從而更深入地掌握它。 重要的:我們只會討論版本 8 之前的 Java。這裡不會考慮版本 9、10、11、12 和 13 中出現的所有創新。歡迎任何關於如何改進答案的想法/意見。享受你的閱讀。我們走吧!
Java面試:關於OOP的問題
1、Java有什麼特點?
回答:-
面向對象的概念:
- 面向對象
- 遺產
- 封裝
- 多態性
- 抽象
-
跨平台: Java 程序可以在任何平台上運行而無需任何更改。當然,這需要安裝 JVM(Java 虛擬機)。
-
高性能:即時 (JIT) 編譯器使高性能成為可能。JIT 編譯器將字節碼轉換為機器碼,然後 JVM 開始執行。
- 多線程: JVM 創建一個稱為
main thread
. 程序員可以通過從 Thread 類派生或實現Runnable
接口來創建多個線程。
2、什麼是繼承?
繼承意味著一個類可以繼承另一個類(使用extends關鍵字)。這意味著您可以重用您繼承的類中的代碼。現有類稱為 ,superclass
新創建的類是subclass
。人們還說使用術語 parent 和child
。
public class Animal {
private int age;
}
public class Dog extends Animal {
}
Animal
和在 哪裡。 parent
_Dog
child
3.什麼是封裝?
這個問題在 Java 開發人員職位的面試中經常被問到。封裝通過使用訪問修飾符、getter 和 setter 來隱藏實現。這樣做是為了防止在開發人員認為有必要的地方進行外部訪問。現實生活中的一個簡單例子就是汽車。我們無法直接訪問引擎的運行情況。我們所需要做的就是將鑰匙插入點火開關並啟動發動機。引擎蓋下發生的過程與我們無關。此外,如果我們要干預發動機的活動,可能會導致不可預測的情況,可能會損壞汽車並造成人身傷害。編程中也會發生完全相同的事情。這在維基百科上有很好的描述. CodeGym上也有一篇關於封裝的文章。4.什麼是多態?
多態性是程序以相同方式處理具有相同接口的對象的能力,而無需有關對象特定類型的信息。俗話說,“一個接口——許多實現”。使用多態性,您可以根據共享行為組合和使用不同類型的對象。例如,我們有一個 Animal 類,它有兩個後代:Dog 和 Cat。通用 Animal 類具有所有人共享的行為,即發出聲音的能力。當我們需要收集所有繼承 Animal 類的東西並執行“make sound”方法時,我們使用多態能力。這是它的樣子:
List<Animal> animals = Arrays.asList(new Cat(), new Dog(), new Cat());
animals.forEach(animal -> animal.makeSound());
換句話說,多態性是有幫助的。這也適用於多態(重載)方法。如何使用多態
Java語法面試題
5. 什麼是 Java 中的構造函數?
構造器具有以下特點:- 創建新對象時,程序使用適當的構造函數來創建它。
- 構造函數就像一個方法。它的顯著特點是沒有返回值(包括void),而且它的名字和類的名字一樣。
- 如果沒有顯式創建構造函數,則會自動創建一個空的構造函數。
- 可以覆蓋構造函數。
- 如果您聲明了一個帶參數的構造函數,但又需要一個不帶參數的構造函數,那麼您必須單獨創建它,因為它不會自動創建。
6.哪兩個類不繼承Object?
不要被技巧問題所迷惑——沒有這樣的課程。所有類都直接或通過祖先繼承 Object 類!7. 什麼是局部變量?
這是 Java 開發人員的另一個熱門面試問題。局部變量是在方法內部定義的變量,只要方法正在執行就存在。一旦執行結束,局部變量就不復存在。這是一個在 main() 方法中使用名為 helloMessage 的局部變量的程序:
public static void main(String[] args) {
String helloMessage;
helloMessage = "Hello, World!";
System.out.println(helloMessage);
}
8. 什麼是實例變量?
實例變量是在類內部聲明的變量。只要一個對象存在,它就存在。例如,我們有一個 Bee 類,它有兩個實例變量——nectarLoad 和 maxNectarLoad:
public class Bee {
/**
* Current nectar load
*/
private double nectarLoad;
/**
* Maximum nectar that can the bee can collect.
*/
private double maxNectarLoad = 20.0;
...
}
9. 什麼是訪問修飾符?
訪問修飾符是一種用於自定義對類、方法和變量的訪問的機制。存在以下修飾符,按訪問權限遞增的順序列出:private
— 此訪問修飾符用於方法、字段和構造函數。訪問僅限於聲明它們的類。package-private (default)
— 這是類的默認訪問級別。訪問僅限於聲明類、方法、變量或構造函數的特定包。protected
package-private
— 此訪問修飾符提供與為繼承具有修飾符的類的類添加訪問權限相同的訪問級別protected
。public
— 此訪問級別也用於類。此訪問級別意味著在整個應用程序中具有完全訪問權限。
10. 什麼是方法覆蓋?
當子類想要更改其父類的行為時,我們會覆蓋方法。如果我們還需要執行父方法中的操作,我們可以在子方法中使用 super.methodName() ,它將執行父方法。之後我們可以添加額外的邏輯。必須遵守的要求:- 方法簽名必須相同
- 返回值必須相同
11. 什麼是方法簽名?
方法簽名是方法名稱和方法採用的參數的組合。方法簽名是重載方法時方法的唯一標識符。12.什麼是方法重載?
方法重載是多態性的一個特性,我們可以通過更改方法簽名來創建執行相同操作的多個方法:- 同名
- 不同的論點
- 可以有不同的返回類型
ArrayList
類的add()
方法可以被重載,允許我們根據輸入參數以不同的方式添加:
add(Object o)
— 此方法只是添加一個對象add(int index, Object o)
— 此方法在特定索引處添加一個對象add(Collection<Object> c)
— 此方法添加對象列表add(int index, Collection<Object> c)
— 此方法添加從特定索引開始的對象列表。
13.什麼是接口?
Java 不支持多重繼承。為了克服這個限制,接口被添加到我們熟悉和喜愛的形式中;)很長一段時間以來,接口只有方法而沒有任何實現。在這個答案的上下文中,讓我們談談它們。例如:
public interface Animal {
void makeSound();
void eat();
void sleep();
}
一些細節如下:
- 接口中的所有方法都是公共的和抽象的
- 所有變量都是 public static final
- 類不繼承接口(即我們不使用 extends 關鍵字)。相反,類實現它們(即我們使用 implements 關鍵字)。此外,您可以根據需要實現任意數量的接口。
- 實現接口的類必須提供接口中所有方法的實現。
public class Cat implements Animal {
public void makeSound() {
// Method implementation
}
public void eat() {
// Implementation
}
public void sleep() {
// Implementation
}
}
14. 什麼是接口中的默認方法?
現在讓我們談談默認方法。他們有什麼用?他們是為了誰?添加這些方法是為了“雙手”服務。我在說什麼?好吧,一方面,需要添加新功能:lambdas 和 Stream API。另一方面,有必要保留 Java 著名的特性——向後兼容性。為此,接口需要一些新的現成解決方案。這就是默認方法來找我們的方式。默認方法是接口中已實現的方法,用default
關鍵字標記。stream()
例如,接口中眾所周知的方法Collection
。相信我,這個界面並不像看起來那麼簡單。或者還有同樣著名的forEach()
方法Iterable
界面。在添加默認方法之前它也不存在。順便說一句,您還可以在此處的CodeGym 上閱讀有關它的信息。
15. 那麼如何繼承兩個相同的默認方法呢?
先前關於什麼是默認方法的回答引出了另一個問題。如果可以在接口中實現方法,那麼理論上可以用同一個方法實現兩個接口。我們該怎麼做?下面是兩個不同的接口,使用相同的方法:
interface A {
default void foo() {
System.out.println("Foo A");
}
}
interface B {
default void foo() {
System.out.println("Foo B");
}
}
我們有一個實現這兩個接口的類。但是我們如何在接口 A 或 B 中選擇特定的方法呢?以下特殊構造允許這樣做A.super.foo()
::
public class C implements A, B {
public void fooA() {
A.super.foo();
}
public void fooB() {
B.super.foo();
}
}
這樣,fooA()
方法將使用接口foo()
的默認方法A
,而fooB()
方法將使用接口foo()
的方法B
。
16.什麼是抽象方法和類?
在 Java 中,abstract
是一個保留字。它用於表示抽像類和方法。首先,我們需要定義。abstract
抽象方法是在抽像類中沒有實現的情況下使用關鍵字聲明的方法。也就是說,這是一個接口中的方法,但添加了一個關鍵字,例如:
public abstract void foo();
抽像類是一個也標有abstract
關鍵字的類:
public abstract class A {
}
抽像類有幾個特點:
- 您不能創建抽像類的對象
- 它可以有抽象方法
- 它也可能沒有抽象方法
17.String、StringBuilder、StringBuffer有什麼區別?
String
值存儲在常量字符串池中。一旦創建了一個字符串,它就會出現在這個池中。而且你不能刪除它。例如:
String name = "book";
該變量將指向常量字符串池 將 name 變量設置為不同的值,我們有:
name = "pen";
常量字符串池如下所示: 換句話說,兩個值都保留在那裡。 字符串緩衝區:
String
值存儲在堆棧中。如果更改了值,則新值將替換舊值。String Buffer
是同步的,因此是線程安全的。- 由於線程安全,其性能較差。
StringBuffer name = “book”;
name 變量的值一變,棧中的值就變了: StringBuilder和 完全一樣StringBuffer
,只是它不是線程安全的。結果,它明顯快於StringBuffer
.
18. 抽像類和接口有什麼區別?
抽像類:- 抽像類有一個默認的構造函數。每次創建抽像類的後代時都會調用它。
- 它們可以包括抽象方法和非抽象方法。通常,抽像類不必具有抽象方法。
- 繼承抽像類的類必須只實現抽象方法。
- 抽像類可以有實例變量(見問題 #5)。
- 接口沒有構造函數,無法初始化。
- 只能添加抽象方法(默認方法除外)。
- 實現接口的類必須實現所有方法(默認方法除外)。
- 接口只能有常量。
19. 為什麼訪問數組中的元素是 O(1)?
這個問題是在我上次採訪中被問到的。後來才知道,這道題的目的是看一個人是怎麼想的。顯然,這些知識幾乎沒有實用價值。僅僅知道它就足夠了。首先,我們需要澄清 O(1) 是“恆定時間”算法的時間複雜度的表示法。換句話說,這個名稱表示最快的執行時間。要回答這個問題,我們需要考慮一下我們對數組的了解。要創建一個int
數組,我們必須編寫以下內容:
int[] intArray = new int[100];
從這個語法中可以得出幾個結論:
- 聲明數組時,其類型是已知的。如果類型已知,則數組中每個單元格的大小也是已知的。
- 整個數組的大小是已知的。
那麼在訪問 ArrayList 中的對象時我們如何達到 O(1) 呢?
這個問題緊接在上一個問題之後。事實是,在處理包含基元的數組時,我們提前(在創建時)知道元素類型的大小。但是如果我們有這種繼承層次並且 我們想為類型 A 的元素創建一個集合併添加不同的實現(B、C 和 D),我們該怎麼辦:
List<A> list = new ArrayList();
list.add(new B());
list.add(new C());
list.add(new D());
list.add(new B());
在這種情況下,我們如何計算每個單元格的大小呢?畢竟,每個對像都是不同的,可能具有不同的附加字段。該怎麼辦?在這裡提出問題的方式是為了讓您感到困惑。我們知道集合併不直接存儲對象。它只存儲對對象的引用。並且所有引用都具有相同的大小,這是已知的。因此,我們在這裡計算地址的方式與上一個問題相同。
21. 自動裝箱和拆箱
歷史背景:自動裝箱和拆箱是 JDK 5 中的一些主要創新。 自動裝箱是從原始類型自動轉換為相應包裝類的過程。 拆箱與自動裝箱完全相反。它是將包裝類轉換為原語的過程。但是如果 wrapper 的值為,那麼在拆箱時會拋出null
a 。NullPointerException
基元及其相應的包裝器
原始 | 包裝類 |
---|---|
布爾值 | 布爾值 |
整數 | 整數 |
字節 | 字節 |
字符 | 特點 |
漂浮 | 漂浮 |
長的 | 長的 |
短的 | 短的 |
雙倍的 | 雙倍的 |
// 自動裝箱發生:
-
將原語分配給包裝類的引用時:
在 Java 5 之前:
// Manual boxing (the way it was BEFORE Java 5). public void boxingBeforeJava5() { Boolean booleanBox = new Boolean(true); Integer intBox = new Integer(3); // And so on for other types } After Java 5: // Automatic boxing (the way it became in Java 5). public void boxingJava5() { Boolean booleanBox = true; Integer intBox = 3; // And so on for other types }
-
當原語作為參數傳遞給需要包裝器的方法時:
public void exampleOfAutoboxing() { long age = 3; setAge(age); } public void setAge(Long age) { this.age = age; }
// 拆箱發生:
-
當我們將包裝類的實例分配給原始變量時:
// BEFORE Java 5: int intValue = new Integer(4).intValue(); double doubleValue = new Double(2.3).doubleValue(); char c = new Character((char) 3).charValue(); boolean b = Boolean.TRUE.booleanValue(); // And after JDK 5: int intValue = new Integer(4); double doubleValue = new Double(2.3); char c = new Character((char) 3); boolean b = Boolean.TRUE;
-
在算術運算過程中。這些操作僅適用於原始類型,因此需要對原始類型進行拆箱。
// BEFORE Java 5: Integer integerBox1 = new Integer(1); Integer integerBox2 = new Integer(2); // A comparison used to require this: integerBox1.intValue() > integerBox2.intValue() // In Java 5 integerBox1 > integerBox2
-
將包裝類的實例傳遞給採用相應原語的方法時:
public void exampleOfAutoboxing() { Long age = new Long(3); setAge(age); } public void setAge(long age) { this.age = age; }
22.什麼是final關鍵字,用在什麼地方?
關鍵字final
可用於變量、方法和類。
- final 變量的值在初始化後不能更改。
- 最後一堂課是不育的 :) 它不能有孩子。
- final 方法不能被後代覆蓋。
最終變量
Java 為我們提供了兩種聲明變量並為其賦值的方法:- 您可以聲明一個變量並稍後對其進行初始化。
- 您可以聲明一個變量並立即賦值。
public class FinalExample {
// A static final variable that is immediately initialized:
final static String FINAL_EXAMPLE_NAME = "I'm likely the final one";
// A final variable that is not initialized, but will only work if you
// initialize it in the constructor:
final long creationTime;
public FinalExample() {
this.creationTime = System.currentTimeMillis();
}
public static void main(String[] args) {
FinalExample finalExample = new FinalExample();
System.out.println(finalExample.creationTime);
// The final FinalExample.FINAL_EXAMPLE_NAME field cannot be accessed
// FinalExample.FINAL_EXAMPLE_NAME = "Not you're not!";
// The final Config.creationTime field cannot be accessed
// finalExample.creationTime = 1L;
}
}
最終變量可以被視為常量嗎?
由於我們不能為 final 變量賦新值,因此這些似乎是常量變量。但乍一看:如果變量的數據類型是immutable
,那麼,是的,它是一個常量。但是如果數據類型是mutable
,即可變的,那麼就可以使用方法和變量來改變變量引用的對象的值final
。因此,它不能稱為常數。下面的例子表明,一些最終變量是真正的常量,而另一些則不是,因為它們可以改變。
public class FinalExample {
// Immutable final variables
final static String FINAL_EXAMPLE_NAME = "I'm likely the final one";
final static Integer FINAL_EXAMPLE_COUNT = 10;
// Mutable final variables
final List<String> addresses = new ArrayList();
final StringBuilder finalStringBuilder = new StringBuilder("Constant?");
}
本地最終變量
當final
在方法中創建變量時,它被稱為變量local final
:
public class FinalExample {
public static void main(String[] args) {
// You can do this
final int minAgeForDriveCar = 18;
// Or you can do this, in a for-each loop:
for (final String arg : args) {
System.out.println(arg);
}
}
}
我們可以在增強的 for 循環中使用 final 關鍵字,因為在每次循環迭代後都會創建一個新變量。請記住,這不適用於普通的 for 循環,因此我們會遇到編譯時錯誤。
// The final local j variable cannot be assigned
for (final int i = 0; i < args.length; i ++) {
System.out.println(args[i]);
}
最後一堂課
final
聲明為不能擴展的類。更簡單地說,沒有其他類可以繼承它。final
JDK 中類的一個很好的例子是 String。創建不可變類的第一步是將其標記為final
,從而防止它被擴展:
public final class FinalExample {
}
// Compilation error!
class WantsToInheritFinalClass extends FinalExample {
}
最終方法
當一個方法被標記為 final 時,它被稱為 final 方法(有道理,對吧?)。不能在子類中重寫 final 方法。順便說一句,Object 類的 wait() 和 notify() 方法是最終的,所以我們沒有能力覆蓋它們。
public class FinalExample {
public final String generateAddress() {
return "Some address";
}
}
class ChildOfFinalExample extends FinalExample {
// Compilation error!
@Override
public String generateAddress() {
return "My OWN Address";
}
}
在 Java 中如何以及在何處使用 final
- 使用final關鍵字定義一些類級別的常量;
- 為您不想更改的對象創建最終變量。例如,我們可以用於記錄目的的特定於對象的屬性。
- 如果您不想擴展某個類,則將其標記為最終類。
- 如果您需要創建一個不可變類,則需要將其設為 final。
- 如果您希望方法的實現在其後代中不發生變化,則將方法標記為
final
. 這對於確保實現不會改變非常重要。
23.什麼是可變類型和不可變類型?
可變的
可變對像是其狀態和變量在創建後可以更改的對象。可變類的示例包括 StringBuilder 和 StringBuffer。例子:
public class MutableExample {
private String address;
public MutableExample(String address) {
this.address = address;
}
public String getAddress() {
return address;
}
// This setter can change the name field
public void setAddress(String address) {
this.address = address;
}
public static void main(String[] args) {
MutableExample obj = new MutableExample("First address");
System.out.println(obj.getAddress());
// We are updating the name field, so this is a mutable object
obj.setAddress("Updated address");
System.out.println(obj.getAddress());
}
}
不變的
不可變對像是在創建對像後其狀態和變量無法更改的對象。HashMap 的一個很好的鍵,你不覺得嗎?:) 例如,String、Integer、Double 等。例子:
// We'll make this class final so no one can change it
public final class ImmutableExample {
private String address;
ImmutableExample(String address) {
this.address = address;
}
public String getAddress() {
return address;
}
// We remove the setter
public static void main(String[] args) {
ImmutableExample obj = new ImmutableExample("Old address");
System.out.println(obj.getAddress());
// There is no way to change this field, so it is an immutable object
// obj.setName("new address");
// System.out.println(obj.getName());
}
}
在下一部分中,我們將考慮有關集合的問題和答案。 我在 GitHub 上的個人資料 Java Core 的前 50 個工作面試問題和答案。第2部分
GO TO FULL VERSION