JavaRush /Java 博客 /Random-ZH /Java 开发人员访谈问答分析。第7部分

Java 开发人员访谈问答分析。第7部分

已在 Random-ZH 群组中发布
嘿大家!编程充满陷阱。事实上,没有什么话题是你不会遇到困难和坎坷的。对于初学者来说尤其如此。减少这种情况的唯一方法就是学习。特别是,这适用于对最基本主题的详细分析。今天我继续分析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