各位軟體工程師,女士先生們大家好!我們來談談面試問題。關於您需要準備什麼以及您需要了解什麼。這是從頭開始重複或研究這些要點的絕佳理由。 我收集了相當廣泛的有關 OOP、Java 語法、Java 中的異常、集合和多線程的常見問題,為了方便起見,我將其分為幾個部分。 重要的:我們只會討論 Java 版本 8 之前的版本。這裡不會考慮 9、10、11、12、13 的所有創新。歡迎任何關於如何改進答案的想法/評論。祝閱讀愉快,走吧!
Java面試:OOP問題
1.Java有什麼特點?
答:-
物件導向程式設計的概念:
- 面向對象;
- 遺產;
- 封裝;
- 多態性;
- 抽象。
-
跨平台: Java程式無需任何修改就可以在任何平台上運行。您唯一需要的是已安裝的 JVM(java 虛擬機器)。
-
高效能: JIT(Just In Time編譯器)允許高效能。JIT 將字節碼轉換為機器碼,然後 JVM 開始執行。
- 多執行緒:稱為 的執行緒
Thread
。JVM 建立一個名為 的執行緒main thread
。程式設計師可以透過繼承 Thread 類別或實作介面來建立多個執行緒Runnable
。
2.什麼是繼承?
繼承意味著一個類別可以繼承(“擴展”)另一個類別。這樣您就可以重複使用您繼承的類別中的程式碼。現有類別稱為superclass
,正在建立的類別稱為subclass
。他們還說parent
和child
。
public class Animal {
private int age;
}
public class Dog extends Animal {
}
其中Animal
是parent
,並且Dog
- child
。
3.什麼是封裝?
這個問題在 Java 開發人員面試中經常出現。封裝是使用存取修飾符、getter 和 setter 來隱藏實作。這樣做是為了在開發人員認為有必要的地方關閉外部使用的存取。生活中一個容易理解的例子是汽車。我們無法直接了解引擎的運作情況。對我們來說,工作就是將鑰匙插入點火開關並啟動引擎。幕後將發生什麼流程與我們無關。此外,我們對這項活動的干擾可能會導致不可預測的情況,因此我們可能會損壞汽車並傷害自己。程式設計中也發生同樣的事情。維基百科上有很好的描述。JavaRush上也有一篇關於封裝的文章。4.什麼是多態性?
多態性是程式能夠以相同的介面相同地使用對象,而無需了解該對象的特定類型。正如他們所說,一個介面 - 多種實現。透過多態性,您可以根據不同類型的物件的共同行為來組合和使用它們。例如,我們有一個 Animal 類,它有兩個後代 - Dog 和 Cat。通用 Animal 類別有一個共同的行為 - 發出聲音。當我們需要將 Animal 類別的所有後代放在一起並執行「發出聲音」方法時,我們可以使用多態性的可能性。它將如下所示: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 類,它有兩個變數 nectarCapacity 和 maxNectarCapacity:public class Bee {
/**
* Current nectar capacity
*/
private double nectarCapacity;
/**
* Maximal nectar that can take bee.
*/
private double maxNectarCapacity = 20.0;
...
}
9. 什麼是存取修飾符?
存取修飾符是一種允許您自訂對類別、方法和變數的存取的工具。有以下修飾符,依存取權限增加的順序排列:private
- 用於方法、欄位和建構函式。訪問級別只是聲明它的類別。package-private(default)
- 可用於課程。僅在聲明了類別、方法、變數、建構函式的特定套件中存取。protected
package-private
—對於那些從帶有修飾符的類別繼承的類,具有與 + 相同的存取權protected
。public
- 也用於課程。整個應用程式的完全存取權。
10.什麼是重寫方法?
當子類別想要更改父類別的行為時,就會發生方法重寫。如果你想要執行父方法中的內容,你可以在子方法中使用像 super.methodName() 這樣的構造,它將完成父方法的工作,然後才加入邏輯。需滿足的要求:- 方法簽名必須相同;
- 傳回值應該是相同的。
11.什麼是方法簽名?
方法簽名是方法名稱和方法接受的參數的集合。方法簽名是重載方法時方法的唯一識別碼。12.什麼是方法重載?
方法重載是多態性的屬性,透過更改方法簽名,您可以為相同的操作建立不同的方法:- 相同的方法名稱;
- 不同的論點;
- 可能有不同的返回類型。
add()
內容ArrayList
,並將根據傳入參數以不同的方式執行加法:
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;
- 類別不是繼承它們(擴展),而是實現它們(實作)。此外,您可以實現任意數量的介面。
- 實作介面的類別必須提供該介面所具有的所有方法的實作。
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
。在添加預設方法之前它也不存在。順便說一句,您也可以在JavaRush上閱讀有關此內容的內容。
15. 那麼如何繼承兩個相同的預設方法呢?
根據之前關於預設方法是什麼的答案,您可以問另一個問題。如果你可以在接口中實現方法,那麼理論上你可以用相同的方法實現兩個接口,如何做到這一點?有兩個不同的介面具有相同的方法:interface A {
default void foo() {
System.out.println("Foo A");
}
}
interface B {
default void foo() {
System.out.println("Foo B");
}
}
並且有一個類別實現了這兩個介面。foo()
為了避免不確定性並編譯程式碼,我們需要重寫類別中的方法,並且我們可以簡單地呼叫其中任何介面的C
方法-或。但究竟如何選擇具體的介面方法呢?有一個這樣的結構: foo()
A
B
А
В
A.super.foo()
public class C implements A, B {
@Override
public void foo() {
A.super.foo();
}
}
或者:
public class C implements A, B {
@Override
public void foo() {
B.super.foo();
}
}
因此,foo()
類別方法將使用介面中的C
預設方法或介面中的方法。 foo()
A
foo()
B
16.什麼是抽象方法和類別?
Java 有一個保留字abstract
,用來表示抽象類別和方法。首先,一些定義。abstract
抽象方法是在抽象類別中使用關鍵字創建的沒有實現的方法。也就是說,這是一個像是介面中的方法,只是增加了一個關鍵字,例如:
public abstract void foo();
抽象類別是一個也有abstract
這個字的類別:
public abstract class A {
}
抽象類別有幾個特點:
- 不能在其基礎上創建物件;
- 它可以有抽象方法;
- 它可能沒有抽象方法。
17. String、String Builder 和 String Buffer 有什麼不同?
這些值String
儲存在常數字串池中。一旦創建了一行,它將出現在該池中。並且將無法刪除它。例如:
String name = "book";
...變數將引用字串池常數字串池 如果將變數名稱設為不同的值,您將得到以下結果:
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];
從這段錄音可以得到幾個結論:
- 創建數組時,它的類型是已知的。如果類型已知,那麼數組的每個單元的大小就很清楚了。
- 數組的大小是已知的。
如何以 O(1) 的速度存取 ArrayList 中的物件?
這個問題緊接著上一個問題。確實,當我們使用數組並且其中有基元時,我們在創建該類型時提前知道該類型的大小是多少。但是,如果有一個如圖所示的方案: 我們想要建立一個包含 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 的主要創新之一。 自動裝箱是從原始類型自動轉換為適當的包裝類別的過程。 自動拆箱- 與自動裝箱完全相反 - 將包裝類別轉換為基元。但如果存在包裝器值null
,則在解包期間將引發異常NullPointerException
。
匹配原語 - 包裝器
原始 | 類包裝器 |
---|---|
布林值 | 布林值 |
整數 | 整數 |
位元組 | 位元組 |
字元 | 特點 |
漂浮 | 漂浮 |
長的 | 長的 |
短的 | 短的 |
雙倍的 | 雙倍的 |
自動打包發生:
-
當包裝類別的引用為原語分配時:
Java 5 之前:
//manual packaging or how it was BEFORE Java 5. public void boxingBeforeJava5() { Boolean booleanBox = new Boolean(true); Integer intBox = new Integer(3); // and so on to other types } после Java 5: //automatic packaging or how it became in Java 5. public void boxingJava5() { Boolean booleanBox = true; Integer intBox = 3; // and so on to 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); // for comparison it was necessary to do this: integerBox1.intValue() > integerBox2.intValue() //в 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
可用於變數、方法和類別。
- 最終變數不能重新分配給另一個物件。
- 最終類別是不孕的))它不能有繼承人。
- 最終方法不能在祖先上被重寫。
最終變數
;Java 為我們提供了兩種建立變數並為其賦值的方法:- 您可以聲明一個變數並稍後對其進行初始化。
- 您可以聲明一個變數並立即分配它。
public class FinalExample {
//final static variable, which is immediately initialized:
final static String FINAL_EXAMPLE_NAME = "I'm likely final one";
//final is a variable that is not initialized, but will only work if
//initialize this 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);
// final field FinalExample.FINAL_EXAMPLE_NAME cannot be assigned
// FinalExample.FINAL_EXAMPLE_NAME = "Not you're not!";
// final field Config.creationTime cannot be assigned
// finalExample.creationTime = 1L;
}
}
Final 變數可以被視為常數嗎?
由於我們無法為最終變數分配新值,因此這些似乎是常數變數。但這只是乍看之下。如果變數引用的資料型別是immutable
,那麼是的,它是一個常數。但是,如果資料類型mutable
是可變的,則使用方法和變數就可以更改變數所引用的物件的值final
,在這種情況下,它不能稱為常數。因此,此範例表明某些最終變數確實是常數,但有些則不是,並且它們是可以更改的。
public class FinalExample {
//immutable final variables:
final static String FINAL_EXAMPLE_NAME = "I'm likely final one";
final static Integer FINAL_EXAMPLE_COUNT = 10;
// mutable filter 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) {
// This is how you can
final int minAgeForDriveCar = 18;
// or you can do it this way, in the foreach loop:
for (final String arg : args) {
System.out.println(arg);
}
}
}
我們可以final
在擴充循環中使用該關鍵字for
,因為循環迭代完成後,for
每次都會建立一個新變數。但這些都不適用於普通的 for 循環,因此下面的程式碼將引發編譯時錯誤。
// final local changed j 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 here
class WantsToInheritFinalClass extends FinalExample {
}
最終方法
當一個方法被標記為final時,它被稱為final方法(邏輯上的,對吧?)。Final 方法不能在後代類別中被重寫。順便說一句,Object 類別中的方法 - wait() 和 notification() - 是最終方法,因此我們沒有機會重寫它們。public class FinalExample {
public final String generateAddress() {
return "Some address";
}
}
class ChildOfFinalExample extends FinalExample {
// compile error here
@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());
// update the name field, so this is a mutable object
obj.setAddress("Updated address");
System.out.println(obj.getAddress());
}
}
不可變的
不可變物件是指在創建物件後其狀態和變數不能更改的物件。為什麼 HashMap 沒有一個很好的按鍵,對吧?)例如 String、Integer、Double 等等。例子:// 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;
}
//remove the setter
public static void main(String[] args) {
ImmutableExample obj = new ImmutableExample("old address");
System.out.println(obj.getAddress());
// Therefore, do not change this field in any way, so this is an immutable object
// obj.setName("new address");
// System.out.println(obj.getName());
}
}
24.如何寫不可變類別?
弄清楚什麼是可變物件和不可變物件後,下一個問題就很自然了──怎麼寫?要編寫一個不可變的不可變類,您需要遵循簡單的步驟:- 使課程成為最終的。
- 將所有欄位設為私有並只為它們建立 getter。當然,不需要設定器。
- 將所有可變欄位設為最終值,以便該值只能設定一次。
- 透過建構子初始化所有字段,執行深複製(即複製物件本身、其變數、變數的變數等)
- 在 getter 中克隆可變變數物件以僅傳回值的副本,而不是對實際物件的參考。
/**
* An example of creating an immutable object.
*/
public final class FinalClassExample {
private final int age;
private final String name;
private final HashMap<String, String> addresses;
public int getAge() {
return age;
}
public String getName() {
return name;
}
/**
* Clone the object before returning it.
*/
public HashMap<String, String> getAddresses() {
return (HashMap<String, String>) addresses.clone();
}
/**
* In the constructor, deep copy the mutable objects.
*/
public FinalClassExample(int age, String name, HashMap<String, String> addresses) {
System.out.println("Performing a deep copy in the constructor");
this.age = age;
this.name = name;
HashMap<String, String> temporaryMap = new HashMap<>();
String key;
Iterator<String> iterator = addresses.keySet().iterator();
while (iterator.hasNext()) {
key = iterator.next();
temporaryMap.put(key, addresses.get(key));
}
this.addresses = temporaryMap;
}
}
GO TO FULL VERSION