CodeGym /Java 博客 /随机的 /探索 Java 开发人员职位面试中的问题和答案。第7部分
John Squirrels
第 41 级
San Francisco

探索 Java 开发人员职位面试中的问题和答案。第7部分

已在 随机的 群组中发布
嘿大家!编程充满陷阱。几乎没有一个话题不会让你绊倒并绊倒脚趾。对于初学者来说尤其如此。拯救脚趾的唯一方法就是学习。特别是,您需要深入研究最基本的主题。今天,我们将继续回顾 Java 开发人员面试中最常见的问题。这些面试问题很好地涵盖了基本主题。请注意,该列表还包括一些不那么标准的问题,让您能够以不同的方式处理常见问题。 探索 Java 开发人员职位面试中的问题和答案。 第 7 - 1 部分

62.什么是字符串池,为什么需要它?

Java 程序可用的内存的一部分称为堆(我们稍后会讨论),堆的一部分称为字符串。它用于存储字符串值。换句话说,当您创建字符串时,例如使用双引号,如下所示:
String str = "Hello world";
JVM检查字符串池是否已经有指定的值。如果是,则为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关键字创建对象时,也可以在字符串池中添加(或获取对)值。为此,我们使用 String 类的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三次,这告诉我们所有三个变量都引用内存中的同一字符串。

63.字符串池使用了哪些GoF设计模式?

在字符串池中,GoF设计模式是享元模式。如果您在这里注意到另一种设计模式,请在评论中分享。这里我们就来说说享元设计模式。它是一种结构设计模式,其中在程序中的不同位置将自身表示为唯一实例的对象实际上并不唯一。享元通过存储对象的共享状态而不是在每个对象中存储相同的数据来节省内存。要理解要点,请考虑这个基本示例。假设我们有一个Employee接口:
public interface Employee {
   void work();
}
它有一些实现,例如Lawyer类:
public class Lawyer implements Employee {

   public Lawyer() {
       System.out.println("A lawyer was hired.");
   }

   @Override
   public void work() {
       System.out.println("Settling legal issues...");
   }
}
还有一个会计类:
public class Accountant implements Employee {

   public Accountant() {
       System.out.println("An accountant was hired.");
   }

   @Override
   public void work() {
       System.out.println("Keeping accounting records...");
   }
}
这些方法完全是任意的——对于这个例子,我们只需要看到它们正在被执行。构造函数也是如此。控制台输出告诉我们何时创建新对象。我们还有一个人力资源部门,其任务是返回所请求的员工。如果该员工尚未在职,则会雇用他们,并且人力资源部门会退回新员工:
public class HumanResourcesDepartment {
   private Map<String, Employee> currentEmployees = new HashMap<>();

   public Employee getEmployee(String type) throws Exception {
       Employee result;
       if (currentEmployees.containsKey(type)) {
           result = currentEmployees.get(type);
       } else {
           switch (type) {
               case "Accountant":
                   result = new Accountant();
                   currentEmployees.put(type, result);
                   break;
               case "Lawyer":
                   result = new Lawyer();
                   currentEmployees.put(type, result);
                   break;
               default:
                   throw new Exception("This employee is not on the staff!");
           }
       }
       return result;
   }
}
所以,逻辑很简单:如果想要的对象存在,则返回它;否则返回。如果没有,则创建它,将其放入存储中(类似于缓存),然后返回它。现在让我们看看它是如何工作的:
public static void main(String[] args) throws Exception {
   HumanResourcesDepartment humanResourcesDepartment = new HumanResourcesDepartment();
   Employee empl1 = humanResourcesDepartment.getEmployee("Lawyer");
   empl1.work();
   Employee empl2 = humanResourcesDepartment.getEmployee("Accountant");
   empl2.work();
   Employee empl3 = humanResourcesDepartment.getEmployee("Lawyer");
   empl1.work();
   Employee empl4 = humanResourcesDepartment.getEmployee("Accountant");
   empl2.work();
   Employee empl5 = humanResourcesDepartment.getEmployee("Lawyer");
   empl1.work();
   Employee empl6 = humanResourcesDepartment.getEmployee("Accountant");
   empl2.work();
   Employee empl7 = humanResourcesDepartment.getEmployee("Lawyer");
   empl1.work();
   Employee empl8 = humanResourcesDepartment.getEmployee("Accountant");
   empl2.work();
   Employee empl9 = humanResourcesDepartment.getEmployee("Lawyer");
   empl1.work();
   Employee empl10 = humanResourcesDepartment.getEmployee("Accountant");
   empl2.work();
}
这是我们将在控制台中看到的内容:
聘请了一名律师。解决法律问题...聘请了一名会计师。保存会计记录...解决法律问题...保存会计记录...解决法律问题...保存会计记录...解决法律问题...保存会计记录...解决法律问题...保存会计记录…
正如您所看到的,我们只创建了两个对象并多次重复使用它们。该示例非常简单,但它演示了这种设计模式如何节省我们的资源。您可能已经注意到,这种模式的逻辑与保险池的逻辑非常相似。 探索 Java 开发人员职位面试中的问题和答案。 第 7 - 2 部分

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”)。第二种是重载变体,有一个附加的限制参数。 limit是结果数组允许的最大大小。换句话说,一旦字符串被分割成允许的最大数量的子字符串,分割就会停止,最后一个元素将包含可能未分割的字符串中的任何“剩余部分”。例子:
String str = "Hello, world it's Amigo!";
String[] arr = str.split(" ", 2);
for (String s : arr) {
  System.out.println(s);
}
控制台输出:
你好,世界,我是阿米戈!
正如我们所看到的,如果不是limit = 2,数组的最后一个元素可能会被分成三个子字符串。

65. 为什么字符数组比字符串更适合存储密码?

存储密码时首选数组而不是字符串有几个原因:

1. 字符串池和字符串不变性。

当使用数组(char[])时,我们可以在使用完数据后显式删除数据。我们还可以根据需要覆盖数组,甚至在垃圾收集之前就从系统中消除密码(将几个单元格更改为无效值就足够了)。相比之下,String是一个不可变的类。这意味着,如果我们想要更改String对象的值,我们将得到一个新的,但旧的将保留在字符串池中。如果我们想删除包含密码的字符串,我们将面临一项复杂的任务,因为我们需要垃圾收集器从字符串池中删除该值,但该字符串可能会在那里保留很长时间。也就是说,在安全存储数据方面,String不如char数组。

2. 如果我们将String值输出到控制台(或日志),那么我们得到:

String password = "password";
System.out.println("Password - " + password);
控制台输出:
密码-密码
如果您碰巧将数组打印到控制台:
char[] arr = new char[]{'p','a','s','s','w','o','r','d'};
System.out.println("Password - " + arr);
控制台将显示难以理解的乱码:
密码 - [C@7f31245a
事实上,这并不是胡言乱语。以下是如何理解您所看到的内容: [C是类名 -字符数组, @是分隔符,然后 7f31245a是十六进制哈希码。

3. 官方 Java 加密体系结构 (JCA) 参考指南明确提到将密码存储在char[]而不是String中:

“在java.lang.String类型的对象中收集和存储密码似乎是合乎逻辑的。但是,这里有一个警告:String类型的对象是不可变的,即没有定义允许您更改(覆盖)的方法或者在使用后将String的内容清零。此功能使String对象不适合存储安全敏感信息,例如用户密码。您应该始终在 char 数组中收集和存储安全敏感信息。 探索 Java 开发人员职位面试中的问题和答案。 第 7 - 3 部分

枚举

66.简述Java中的Enum

Enum是 enumeration 的缩写,它是一组由公共类型联合起来的字符串常量。我们使用enum关键字声明一个。以下是enum的示例:某些校园中允许的角色:
public enum Role {
   STUDENT,
   TEACHER,
   DIRECTOR,
   SECURITY_GUARD
}
大写字母的单词是枚举常量。它们以简化的方式声明,无需new运算符。使用枚举使生活变得更加轻松,因为它们有助于避免名称中的错误和混淆(因为列表定义了唯一的有效值)。对我来说,它们在开关 结构中非常方便。

67. Enum 可以实现接口(使用implements关键字)吗?

是的。毕竟,枚举应该不仅仅代表被动集(例如校园中的角色)。在 Java 中,它们可以表示更复杂的对象,因此您可能需要向它们添加额外的功能。这还允许您通过在需要实现的接口类型的地方 替换枚举值来利用多态性。

68. Enum 可以扩展类(使用 extends 关键字)吗?

不,不能,因为枚举是默认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 "Selected role - " + super.toString();
   }
}
这就是我今天的全部内容。直到下一部分!
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION