JavaRush /Java Blog /Random-TW /Java 開發者訪談問答分析。第7部分

Java 開發者訪談問答分析。第7部分

在 Random-TW 群組發布
嘿大家!程式設計充滿陷阱。幾乎沒有一個話題不會讓你絆倒和坎坷。對於初學者來說尤其如此。減少這種情況的唯一方法就是學習。特別是,這適用於對最基本主題的詳細分析。今天我繼續分析Java 開發者訪談中的250 多個問題,這些問題很好地涵蓋了基本主題。我想指出的是,該清單還包含一些非標準問題,可讓您從不同的角度看待常見主題。Java 開發者訪談問答分析。 第 7-1 部分

62. 什麼是字串池以及為什麼需要它?

在Java的記憶體中(堆,我們後面會講到)有一個區域-字串池,或者說字串池。它旨在存儲字串值。換句話說,當您建立某個字串時,例如透過雙引號:
String str = "Hello world";
檢查字串池是否具有給定值。如果是,則為str變數指派對池中該值的參考。如果沒有,將在池中建立一個新值,並將對其的引用指派給str變數。讓我們來看一個例子:
String firstStr = "Hello world";
String secondStr = "Hello world";
System.out.println(firstStr == secondStr);
true 將顯示在螢幕上。我們記得==比較引用——這意味著這兩個引用引用字串池中的相同值。這樣做是為了不在記憶體中產生許多相同的String類型的對象,因為我們記得,String是一個不可變的類,如果我們有很多對同一個值的引用,那沒有什麼問題。更改一個位置的值會導致同時更改多個其他連結的情況不再可能。 但儘管如此,如果我們使用new創建一個字串:
String str = new String("Hello world");
將在記憶體中建立一個單獨的物件來儲存該字串值(並且字串池中是否已經有這樣的值並不重要)。作為確認:
String firstStr = new String("Hello world");
String secondStr = "Hello world";
String thirdStr = new String("Hello world");
System.out.println(firstStr == secondStr);
System.out.println(firstStr == thirdStr);
我們將得到兩個false 值,這意味著我們這裡有三個不同的值被引用。實際上,這就是為什麼建議僅使用雙引號來建立字串的原因。但是,您可以在使用new建立物件時將值新增(或取得參考)到字串池中。為此,我們使用字串類別方法 - intern()。此方法強制在字串池中建立一個值,或取得指向該值的連結(如果該值已儲存在該值中)。這是一個例子:
String firstStr = new String("Hello world").intern();
String secondStr = "Hello world";
String thirdStr = new String("Hello world").intern();
System.out.println(firstStr == secondStr);
System.out.println(firstStr == thirdStr);
System.out.println(secondStr == thirdStr);
結果,我們將在控制台中收到三個true值,這意味著所有三個變數都引用同一個字串。Java 開發者訪談問答分析。 第 7 - 2 部分

63. 字串池中使用了哪些GOF模式?

GOF模式在字串池中清晰可見- 享元( flyweight),也稱為定居者(settler)。如果您在這裡看到其他模板,請在評論中分享。好吧,我們來談談輕量級模板。輕量級是一種結構設計模式,其中在程式中的不同位置將自己呈現為唯一實例的物件實際上並非如此。輕量級透過在物件之間共享共享狀態來節省內存,而不是在每個物件中儲存相同的資料。為了理解本質,我們來看一個最簡單的例子。假設我們有一個員工介面:
public interface Employee {
   void work();
}
還有一些實現,例如律師:
public class Lawyer implements Employee {

   public Lawyer() {
       System.out.println("Юрист взят в штат.");
   }

   @Override
   public void work() {
       System.out.println("Решение юридических вопросов...");
   }
}
還有會計師:
public class Accountant implements Employee{

   public Accountant() {
       System.out.println("Бухгалтер взят в штат.");
   }

   @Override
   public void work() {
       System.out.println("Ведение бухгалтерского отчёта....");
   }
}
這些方法是有條件的:我們只需要看到它們被執行。同樣的情況也適用於構造函數。由於控制台輸出,我們將看到何時建立新物件。我們還有一個員工部門,其任務是發出所請求的員工,如果他不在,則僱用他並根據請求發出:
public class StaffDepartment {
   private Map<String, Employee> currentEmployees = new HashMap<>();

   public Employee receiveEmployee(String type) throws Exception {
       Employee result;
       if (currentEmployees.containsKey(type)) {
           result = currentEmployees.get(type);
       } else {
           switch (type) {
               case "Бухгалтер":
                   result = new Accountant();
                   currentEmployees.put(type, result);
                   break;
               case "Юрист":
                   result = new Lawyer();
                   currentEmployees.put(type, result);
                   break;
               default:
                   throw new Exception("Данный сотрудник в штате не предусмотрен!");
           }
       }
       return result;
   }
}
也就是說,邏輯很簡單:如果存在給定單元,則返回它;如果沒有,則創建它,將其放入儲存(類似於快取)中,然後將其返回。現在讓我們看看它是如何運作的:
public static void main(String[] args) throws Exception {
   StaffDepartment staffDepartment = new StaffDepartment();
   Employee empl1  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl2  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
   Employee empl3  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl4  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
   Employee empl5  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl6  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
   Employee empl7  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl8  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
   Employee empl9  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl10  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
}
相應地,在控制台中將會有一個輸出:
律師已被聘請。解決法律問題...已聘請會計師。維護會計報告...解決法律問題...維護會計報告....解決法律問題...維護會計報告....解決法律問題...維護會計報告....解決法律問題.. .維護會計報告...
正如您所看到的,只創建了兩個對象,並且它們被重複使用了很多次。這個例子很簡單,但是它清楚地演示了使用這個模板如何可以節省我們的資源。嗯,正如您所注意到的,這種模式的邏輯與保險池的邏輯非常相似。您可以在本文中閱讀有關GOF模式類型的更多資訊。Java 開發者訪談問答分析。 第 7 - 3 部分

64. 如何將字串分割成多個部分?請提供相應程式碼範例

顯然,這題是關於split的方法。String類別有此方法的兩種變體:
String split(String regex);
String split(String regex);
regex是行分隔符號 - 將字串分割成字串陣列的一些正規表示式,例如:
String str = "Hello, world it's Amigo!";
String[] arr = str.split("\\s");
for (String s : arr) {
  System.out.println(s);
}
控制台將輸出以下內容:
你好,世界,我是阿米戈!
也就是說,我們的字串值被分成一個字串數組,分隔符號是一個空格(為了分隔,我們可以使用非空格正規表示式“\\s”和字串表達式“”)。第二個重載方法有一個附加參數 - limitlimit — 結果陣列的最大允許值。也就是說,當字串已經分割成最大允許數量的子字串時,將不再進行進一步的分割,並且最後一個元素將具有可能未分割的字串的「剩餘部分」。例子:
String str = "Hello, world it's Amigo!";
String[] arr = str.split(" ", 2);
for (String s : arr) {
  System.out.println(s);
}
控制台輸出:
你好,世界,我是阿米戈!
正如我們所看到的,如果沒有limit = 2約束,則陣列的最後一個元素可能會被分成三個子字串。Java 開發者訪談問答分析。 第 7 - 4 部分

65. 為什麼字元陣列比字串更適合儲存密碼?

儲存密碼時喜歡陣列而不是字串有幾個原因: 1. 字串池和字串不變性。 當使用陣列(char[])時,我們可以在使用完資料後明確刪除資料。此外,我們可以根據需要重寫數組,並且有效密碼不會出現在系統中的任何位置,即使在垃圾回收之前也是如此(將幾個單元格更改為無效值就足夠了)。同時,String是一個不可變的類別。也就是說,如果我們想改變它的值,我們將得到一個新的,而舊的將保留在字串池中。如果我們想要刪除密碼的String值,這可能是一項非常困難的任務,因為我們需要垃圾收集器從String 池中刪除該值,並且該String值很可能會在那裡保留一段時間。很長一段時間。也就是說,在這種情況下,String在資料儲存安全性方面 不如char數組。2. 如果字串值意外輸出到控制台(或日誌),則該值本身將被顯示:
String password = "password";
System.out.println("Пароль - " + password);
控制台輸出:
密碼
同時,如果你不小心向控制台輸出了一個陣列:
char[] arr = new char[]{'p','a','s','s','w','o','r','d'};
System.out.println("Пароль - " + arr);
控制台中會出現難以理解的官話:
密碼 - [C@7f31245a
其實不是官話,而是: [C是類別名,是一個char數組, @是分隔符,後面的 7f31245a是一個十六進位的 hashcode。 3. 官方文件 Java Cryptography Architecture Guide 明確提到將密碼儲存在char[]而不是String: 「在 java.lang.String類型的物件中收集和儲存密碼似乎是合乎邏輯的。但是,這裡有一個警告:String物件是不可變的,也就是沒有定義任何方法來允許String物件的內容在使用後被修改(覆寫)或清空。此功能使得String物件不適合儲存使用者密碼等敏感資訊。相反,您應該始終收集敏感的安全資訊並將其存儲在字元數組中。”Java 開發者訪談問答分析。 第 7 - 5 部分

列舉

66.簡述Java中的Enum

Enum是一種枚舉,是一組由公用型別聯合起來的字串常數。透過關鍵字-enum聲明。這是一個列舉範例-某所學校的有效角色:
public enum Role {
   STUDENT,
   TEACHER,
   DIRECTOR,
   SECURITY_GUARD
}
用大寫字母書寫的單字與以簡化方式聲明的枚舉常數相同,無需使用new運算子。使用枚舉極大地簡化了生活,因為它們有助於避免命名中的錯誤和混亂(因為只能有特定的值列表)。就我個人而言,我覺得它們在Switch 的邏輯設計中使用起來非常方便。

67. Enum 可以實作介面嗎?

是的。畢竟,枚舉必須不僅僅代表被動集合(例如角色)。在 Java 中,它們可以表示具有某些功能的更複雜的對象,因此您可能需要為它們添加額外的功能。這還允許您透過在需要實現的介面類型的地方替換枚舉值來使用多態性的功能。

68. Enum 可以擴充類別嗎?

不,不能,因為枚舉是泛型類別Enum <T>的預設子類,其中T表示泛型枚舉類型。它只不過是 Java 語言所有枚舉類型的公共基底類別。列舉到類別的轉換是由Java編譯器在編譯時完成的。這個擴充沒有在程式碼中明確指出,但總是不可見地存在。

69. 是否可以建立沒有物件實例的 Enum?

對我來說,這個問題有點奇怪,或者說我沒有完全理解。我有兩種解釋: 1. 是否可以有一個沒有值的枚舉- 是的,當然它會像一個空類 - 無意義:
public enum Role {
}
並調用:
var s = Role.values();
System.out.println(s);
我們將在控制台收到:
[Lflyweight.Role;@9f70c54
角色值的空數組) 2. 是否可以在沒有new運算符的情況下創建枚舉- 是的,當然。正如我上面所說,您不需要對枚舉值(枚舉)使用new運算符,因為這些是靜態值。

70. 我們可以重寫 Enum 的 toString() 方法嗎?

是的,當然,您可以重寫toString()方法來定義在呼叫toString方法時顯示枚舉的特定方式(將枚舉轉換為常規字串時,例如,用於輸出到控制台或日誌)。
public enum Role {
   STUDENT,
   TEACHER,
   DIRECTOR,
   SECURITY_GUARD;

   @Override
   public String toString() {
       return "Выбрана роль - " + super.toString();
   }
}
這就是今天的全部內容,直到下一部分!Java 開發者訪談問答分析。 第 7 - 6 部分
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION