三种很难学到的Java踩坑教训 - Miloš


学习Java很难。经验是最好的老师。经验教你克服错误。我从错误中吸取了教训。你可以从我的错误中吸取教训。
这是我通过艰苦的方式学到的东西,而您不必这样做。
1. Lambdas 可能会给你带来困难
Lambda通常超过 4 行代码。做的比应该做的多。更多的责任——你的工作记忆更难。更多行——多重责任 lambda。
您是否需要更改 lambda 中的变量? 你不能那样做。 为什么?如果 lambda 可以访问调用跨界变量,则可能会出现多线程问题。您不能从 lambda 内部更改调用变量。
lambda 中的快乐路径效果很好。运行时崩溃后,您会收到以下响应:

at [CLASS].lambda$null$2([CLASS].java:85)
at [CLASS]$$Lambda$64/730559617.accept(Unknown Source)

Lambda 堆栈跟踪令人痛苦。名称被混淆、难以跟踪且难以调试。
调试 lambda 的更好方法是什么? 使用中间结果。

map(elem -> {
 int result = elem.getResult();
 return result;
});

更好的方法是使用高级 IntelliJ 调试技巧。使用TAB来选择要调试的代码。将此与中间结果相结合以获得更好的体验。
“当我们在包含 lambda 的行处停止时,如果我们按 F7(步入),那么 IntelliJ 将突出显示要调试的代码段。我们可以使用 Tab 切换要调试的块,一旦我们决定,然后我们再次单击 F7。”
如何从 lambda 访问调用跨界变量? 您只能访问 final 或有效的 final 变量。您需要包装调用变量。无论是AtomicType或您自己的类型。您可以从 lambda 更改包装的变量。
如何解决堆栈跟踪问题? 使用命名函数。您现在可以找到负责任的代码、审查逻辑并更快地解决问题。使用命名函数来减少神秘的堆栈跟踪。
相同的 lambda 重复? 把它放在一个命名函数中。您将有一个参考点。每个 lambda 都有一个生成的函数,这使得跟踪变得更加困难。

lambda$yourNamedFunction
lambda$0

命名函数解决了另一个问题。命名函数会划分大lambdas,创建更小的代码块,并创建可插入的函数。
.map(this::namedFunc1).filter(this::namedFilter1).map(this::namedFunc2)

 
2. 你会遇到列表List问题
您需要使用 Lists。您需要 HashMaps 来获取类似字典的数据。您需要 TreeMap 角色。您不可能不使用集合。
你如何构建一个列表?你需要什么清单?你需要一个不可变或可变的列表吗?答案会影响代码的未来。早点选择正确的清单,否则就会受苦。
Arrays::asList创建“直写”列表。你不能用这个列表做什么?您无法更改已创建列表的大小。大小是不可变的。你可以做什么?设置元素、排序或其他不影响大小的操作。Arrays::asList小心使用,因为它的大小是不可变的,但内容不是。
new ArrayList()创建一个新的“可变”列表。
创建的列表支持哪些操作?这就是要谨慎的原因。
List::of 创建一个“不可变的”集合。大小是不变的,但是内容在某些条件下是不可变的,如果内容是原始值,即 int,则列表是不可变的。看下面的例子。

@Test
public void testListOfBuilders() {
  System.out.println("### TESTING listOF with mutable content ###");

  StringBuilder one = new StringBuilder();
  one.append(
"a");

  StringBuilder two = new StringBuilder();
  two.append(
"a");

  List<StringBuilder> asList = List.of(one, two);

  asList.get(0).append(
"123");

  System.out.println(asList.get(0).toString());
}
输出:
### TESTING listOF with mutable content ###
a123

您需要创建不可变对象并将它们放入List::of才能真正保证不可变, List::of不保证不变性。

 
3. 注释让你慢下来
你使用注解吗?你了解他们吗?你知道他们做什么吗?
自定义Logged注释 是否适用于每种方法?错误的。我已经使用 ourLogged来记录方法的参数。令我惊讶的是,它不起作用。您盲目地使用自定义注释。

@Transaction
@Method("GET")
@PathElement(
"time")
@PathElement(
"date")
@Autowired
@Secure(
"ROLE_ADMIN")
public void manage(@Qualifier('time')int time) {
...
}

这段代码有什么不愉快的地方?有很多配置摘要。你会经常遇到这种情况。配置与常规代码混合在一起。本身还不错,但很难看。
注释是有代价的。读者为阅读付出的代价。我们为了更快的开发而交易冗长。
注释是为了减少样板代码。您不需要为每个端点编写日志逻辑。您不需要配置事务,使用@Transactional. 注释通过提取代码来减少样板。
好的,我们应该回到 XML 吗?不。我正在使用基于 Spring 的应用程序,并且使用 XML 进行配置是轻而易举的。我们确实有注释,但大部分配置都是在XML 中。模式指导配置,学习曲线并不陡峭。注释很方便。注释优势在于方便,XML 优势在于冗长。
这里没有明确的赢家,因为两者都在发挥作用。直到今天,我仍然使用 XML 和注释。当您发现重复的样板文件时,最好将注释背后的逻辑移动。例如,日志记录是一个很好的注释候选者。这个故事的寓意是:不要滥用注释,不要忘记 XML