JavaRush /Java Blog /Random-TW /Java 核心。面試問題,第 2 部分
Andrey
等級 26

Java 核心。面試問題,第 2 部分

在 Random-TW 群組發布
對於第一次聽到 Java Core 這個詞的人來說,這些是語言的基礎。有了這些知識,您就可以安全地去實習/實習了。
Java 核心。 面試問題,第 2 - 1 部分
這些問題將幫助您在面試前刷新知識,或為自己學習新的東西。要獲得實用技能,請在JavaRush學習。 原始文章 其他部分連結: Java Core。面試問題,第 1 部分 Java 核心。面試問題,第 3 部分

為什麼要避免使用finalize()方法?

finalize()我們都知道垃圾收集器在釋放物件佔用的記憶體之前調用方法的說法。finalize()下面是一個範例程序,證明不能保證 方法呼叫:
public class TryCatchFinallyTest implements Runnable {

	private void testMethod() throws InterruptedException
	{
		try
		{
			System.out.println("In try block");
			throw new NullPointerException();
		}
		catch(NullPointerException npe)
		{
			System.out.println("In catch block");
		}
		finally
		{
			System.out.println("In finally block");
		}
	}

	@Override
	protected void finalize() throws Throwable {
		System.out.println("In finalize block");
		super.finalize();
	}

	@Override
	public void run() {
		try {
			testMethod();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
public class TestMain
{
	@SuppressWarnings("deprecation")
	public static void main(String[] args) {
	for(int i=1;i< =3;i++)
	{
		new Thread(new TryCatchFinallyTest()).start();
	}
	}
}
輸出: 在try區塊中 在catch區塊中 在finally區塊中 在try區塊中 在catch區塊中 在finally區塊中 在try區塊中 在catch區塊中 在finally區塊中 令人驚訝的是,該方法finalize沒有為任何線程執行。這證明了我的話。我認為原因是終結器是由單獨的垃圾收集器執行緒執行的。如果 Java 虛擬機器終止得太早,那麼垃圾收集器就沒有足夠的時間來建立和執行終結器。不使用該方法的其他原因finalize()可能是:
  1. 此方法finalize()不適用於建構函數等鏈。這意味著當您呼叫類別建構函式時,將無條件呼叫超類別建構函式。但在該方法的情況下finalize(),這種情況不會發生。finalize()必須顯式呼叫超類別方法。
  2. 該方法拋出的任何異常finalize都會被垃圾收集器執行緒忽略,並且不會進一步傳播,這意味著該事件不會記錄在您的日誌中。這很糟糕,不是嗎?
  3. finalize()如果您的類別中存在該方法,您還會受到顯著的效能損失。在《有效編程》(第二版)中,Joshua Bloch 說:
    「是的,還有一件事:使用終結器時會產生很大的效能損失。在我的機器上,創建和銷毀簡單物件的時間大約為 5.6 奈秒。
    新增終結器會將時間增加到 2400 奈秒。換句話說,使用終結器創建和刪除物件大約要慢 430 倍。”

為什麼HashMap不能用在多執行緒環境中?這會導致無限循環嗎?

我們知道HashMap這是一個非同步集合,其同步對應項是HashTableHashTable因此,當您存取集合並且在所有執行緒都可以存取集合的單一實例的多執行緒環境中時,出於顯而易見的原因,例如避免髒讀和確保資料一致性,使用它會更安全。在最壞的情況下,這種多執行緒環境將導致無限循環。對,是真的。HashMap.get()可能會導致無限循環。讓我們看看如何?如果你查看該方法的源代碼HashMap.get(Object key),它看起來像這樣:
public Object get(Object key) {
    Object k = maskNull(key);
    int hash = hash(k);
    int i = indexFor(hash, table.length);
    Entry e = table[i];
    while (true) {
        if (e == null)
            return e;
        if (e.hash == hash && eq(k, e.key))
            return e.value;
        e = e.next;
    }
}
while(true)e.next如果由於某種原因它可以指向自身,則它總是可能成為多執行緒運行時環境中無限循環的受害者。這會導致死循環,但是e.next它如何指向自身(即 to e)呢?void transfer(Entry[] newTable)這可能發生在調整大小時呼叫的方法HashMap
do {
    Entry next = e.next;
    int i = indexFor(e.hash, newCapacity);
    e.next = newTable[i];
    newTable[i] = e;
    e = next;
} while (e != null);
如果在另一個執行緒嘗試更改地圖實例 ( ) 的同時發生調整大小,則這段程式碼很容易產生無限循環HashMap。避免這種情況的唯一方法是在程式碼中使用同步,或者更好的是使用同步集合。

解釋抽象和封裝。它們是如何連接的?

簡而言之,“抽象僅顯示物件中對當前視圖重要的那些屬性。 ” 在物件導向程式設計理論中,抽象涉及定義代表抽象「參與者」的物件的能力,這些物件可以執行工作、更改和報告其狀態的變化,以及與系統中的其他物件「交互」。任何程式語言中的抽像都有多種方式。這可以從為低階語言命令定義介面的例程的建立中看出。一些抽象試圖透過完全隱藏建構它們的抽象(例如設計模式)來限製程式設計師需求的整體表示的廣度。通常,抽象可以透過兩種方式來看待: 資料抽像是一種創建複雜資料類型並僅公開有意義的操作以與資料模型互動的方法,同時向外界隱藏所有實作細節。 執行抽像是識別所有重要語句並將它們作為工作單元公開的過程。當我們建立方法來完成某些工作時,我們通常會使用此功能。 將資料和方法限制在類別內並結合執行隱藏(使用存取控制)通常稱為封裝。結果是具有特徵和行為的資料類型。封裝本質上也涉及資料隱藏和實作隱藏。 “封裝一切可以改變的東西”。這句話是眾所周知的設計原則。就此而言,在任何類別中,資料變更都可能在運行時發生,並且實作變更可能在未來版本中發生。因此,封裝適用於資料和實作。所以它們可以這樣連接:
  • 抽象主要是類別可以做什麼[想法]
  • 封裝比較多如何實現這個功能【實作】

介面和抽象類別的區別?

主要區別如下:
  • 介面不能實作任何方法,但抽象類別可以。
  • 一個類別可以實作多個接口,但只能有一個超類別(抽像或非抽象)
  • 介面不是類別層次結構的一部分。不相關的類別可以實現相同的介面。
你必須記住的是:「當你可以用「它做什麼」來完整地描述一個概念而不必指定「它是如何做的」時,那麼你應該使用介面。如果您需要包含一些實作細節,那麼您需要在抽象類別中表示您的概念。” 另外,換句話說:是否有許多類別可以「組合在一起」並用單一名詞來描述?如果是這樣,則使用該名詞的名稱建立一個抽象類,並從中繼承類別。例如,CatDog可以繼承抽象類Animal,而這個抽象基類將實現方法void Breathe()- 呼吸,因此所有動物都會以相同的方式執行該方法。哪些動詞可以應用在我的班級並且可以應用在其他班級?為每個動詞創建一個介面。例如,所有動物都可以吃東西,所以我將創建一個介面IFeedable並使其Animal實現該介面。僅足以實現一個介面Dog(能夠喜歡我),但不是全部。有人說:主要區別在於你想要在哪裡實施。建立介面時,可以將實作移至實作介面的任何類別。透過建立抽象類,您可以在一處共享所有衍生類別的實現,並避免許多不好的事情,例如重複程式碼。 HorseILikeable

StringBuffer如何節省記憶體?

該類別String被實現為不可變對象,這意味著當您最初決定將某些內容放入該對象時String,虛擬機會分配一個固定長度的數組,該數組的大小與原始值的大小完全相同。然後,這將被視為虛擬機器內的常數,如果字串的值不發生變化,這將提供顯著的效能改進。但是,如果您決定以任何方式更改字串的內容,虛擬機器實際上所做的是將原始字串的內容複製到臨時空間中,進行更改,然後將這些變更儲存到新的記憶體陣列中。因此,在初始化後更改字串的值是一項昂貴的操作。 StringBuffer另一方面,它是作為虛擬機器內部動態擴展的數組實現的,這意味著任何修改操作都可以發生在現有的記憶體單元上,並且將根據需要分配新的記憶體。但是,虛擬機器無法進行最佳化,StringBuffer因為其內容在每個實例之間被認為是不一致的。

為什麼wait和notify方法宣告在Object類別中而不是Thread類別中?

只有當您希望執行緒能夠存取共享資源並且共享資源可以是堆中的任何 java 物件時,才需要 , ,wait方法。因此,這些方法是在基類上定義的,以便每個物件都有一個允許執行緒在其監視器上等待的控制項。Java 沒有任何用於共享共享資源的特殊物件。沒有定義這樣的資料結構。因此,類別有責任能夠成為共享資源,並提供、、 等輔助方法。Java 基於 Charles Hoare 的監視器思想。在Java中,所有物件都有一個監視器。執行緒在監視器上等待,因此要執行等待,我們需要兩個參數: notifynotifyAllObjectObjectwait()notify()notifyAll()
  • 一個執行緒
  • 監視器(任何對象)。
在Java設計中,執行緒無法被精確定義;它總是執行程式碼的目前執行緒。但是,我們可以定義一個監視器(它是我們可以呼叫方法的物件wait)。這是一個很好的設計,因為如果我們可以強制任何其他執行緒在特定監視器上等待,就會導致“入侵”,從而使設計/程式設計並行程式變得困難。請記住,在 Java 中,不建議使用所有乾擾其他執行緒的操作(例如,stop())。

編寫一個程序,在 Java 中建立死鎖並修復它

在 Java 中deadlock,這是一種至少兩個執行緒在不同資源上持有一個區塊,並且都在等待另一個資源可用以完成其任務的情況。他們都無法對所持有的資源留下鎖定。 Java 核心。 面試問題,第 2 - 2 部分 範例程式:
package thread;

public class ResolveDeadLockTest {

	public static void main(String[] args) {
		ResolveDeadLockTest test = new ResolveDeadLockTest();

		final A a = test.new A();
		final B b = test.new B();

		// Thread-1
		Runnable block1 = new Runnable() {
			public void run() {
				synchronized (a) {
					try {
					// Добавляем задержку, чтобы обе нити могли начать попытки
					// блокирования ресурсов
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					// Thread-1 заняла A но также нуждается в B
					synchronized (b) {
						System.out.println("In block 1");
					}
				}
			}
		};

		// Thread-2
		Runnable block2 = new Runnable() {
			public void run() {
				synchronized (b) {
					// Thread-2 заняла B но также нуждается в A
					synchronized (a) {
						System.out.println("In block 2");
					}
				}
			}
		};

		new Thread(block1).start();
		new Thread(block2).start();
	}

	// Resource A
	private class A {
		private int i = 10;

		public int getI() {
			return i;
		}

		public void setI(int i) {
			this.i = i;
		}
	}

	// Resource B
	private class B {
		private int i = 20;

		public int getI() {
			return i;
		}

		public void setI(int i) {
			this.i = i;
		}
	}
}
由於非常明顯的原因(如上所述),運行上述程式碼將導致死鎖。現在我們需要解決這個問題。我相信任何問題的解決都在於問題本身。在我們的例子中,A 和 B 的訪問模型是主要問題。因此,為了解決這個問題,我們只需改變共享資源的存取運算子的順序即可。更改後將如下所示:
// Thread-1
Runnable block1 = new Runnable() {
	public void run() {
		synchronized (b) {
			try {
				// Добавляем задержку, чтобы обе нити могли начать попытки
				// блокирования ресурсов
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			// Thread-1 заняла B но также нуждается в А
			synchronized (a) {
				System.out.println("In block 1");
			}
		}
	}
};

// Thread-2
Runnable block2 = new Runnable() {
	public void run() {
		synchronized (b) {
			// Thread-2 заняла B но также нуждается в А
			synchronized (a) {
				System.out.println("In block 2");
			}
		}
	}
};
再次運行這個類,現在您將不會看到死鎖。我希望這可以幫助您避免僵局並在遇到僵局時擺脫僵局。

如果實作 Serialized 介面的類別包含不可序列化的元件,會發生什麼情況?如何解決這個問題?

在這種情況下,它會在執行過程中拋出NotSerializableException。要解決此問題,有一個非常簡單的解決方案 - 選取這些方塊transient。這意味著選中的欄位不會被序列化。如果您還想存儲這些字段的狀態,那麼您需要考慮引用變量,它已經實現了Serializable. 您可能還需要使用readResolve()和方法writeResolve()。我們總結一下:
  • 首先,使您的欄位不可序列化transient
  • 首先writeObject,呼叫defaultWriteObject線程來保存所有非transient字段,然後調用其餘方法來序列化不可序列化物件的各個屬性。
  • 在 中readObject,首先呼叫defaultReadObject流來讀取所有非transient字段,然後呼叫其他方法(對應於您在 中新增的方法writeObject)來反序列化您的非transient物件。

解釋Java中的transient和volatile關鍵字

“該關鍵字transient用於指示不會被序列化的欄位。” 根據 Java 語言規範: 變數可以用瞬態指示符來標記,以表示它們不是物件持久狀態的一部分。例如,您可能包含從其他字段派生的字段,最好以程式設計方式取得它們,而不是透過序列化來恢復其狀態。例如,在一個類別中,(director)和(rate)BankPayment.java等欄位可以被序列化,並且(應計利息)可以隨時計算,甚至在反序列化之後也是如此。如果我們還記得的話,Java 中的每個線程都有自己的本地內存,並在該本地內存上執行讀取/寫入操作。當所有操作完成後,它將變數的修改狀態寫入共享內存,所有線程都從這裡訪問該變數。通常,這是虛擬機器內的普通線程。但 volatile 修飾符告訴虛擬機,線程對該變數的存取必須始終與該變數自己的副本與記憶體中該變數的主副本相符。這意味著每次執行緒想要讀取變數的狀態時,它必須清除內部記憶體狀態並從主記憶體更新變數。 在無鎖演算法中最有用。您將儲存共享資料的變數標記為易失性,然後您不使用鎖來存取該變量,而一個執行緒所做的所有變更將對其他執行緒可見。或者,如果您想建立「發生後」關係以確保不重複計算,請再次確保變更即時可見。應該使用 Volatile 在多執行緒環境中安全地發布不可變物件。欄位聲明確保所有執行緒始終看到對該實例的目前可用引用。 principalrateinterestVolatilepublic volatile ImmutableObject

迭代器和列表迭代器的差別?

我們可以使用,或Iterator來迭代元素。但它只能用於迭代元素。其他差異如下所述。你可以: SetListMapListIteratorList
  1. 以相反的順序迭代。
  2. 在任何地方獲取索引。
  3. 在任何地方添加任何價值。
  4. 在目前位置設定任意值。
祝你學業順利!! 文章作者Lokesh Gupta 原文文章 Java Core。面試問題,第 1 部分 Java 核心。面試問題,第 3 部分
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION