Java开发中不要使用受检异常

2024-01-24

简介

Java是唯一(主流)实现了受检异常概念的编程语言。一开始,受检异常就是争议的焦点。在当时被视为一种创新概念(Java于1996年推出),如今却被视不良实践。

本文要讨论Java中非受检异常和受检异常的动机以及它们优缺点。与大多数关注这个主题的人不同,我希望提供一个平衡的观点,而不仅仅是对受检异常概念的批评。

我们先深入探讨Java中受检异常和非受检异常的动机。Java之父詹姆斯·高斯林对这个话题有何看法?接下来,我们要看一下Java中异常的工作原理以及受检异常存在的问题。我们还将讨论在何时应该使用哪种类型的异常。最后,我们将提供一些常见的解决方法,例如使用Lombok的@SneakyThrows注解。

Java和其他编程语言中异常的历史

在软件开发中,异常处理可以追溯到20世纪60年代LISP的引入。通过异常,我们可以解决在程序错误处理过程中可能遇到的几个问题。

异常的主要思想是将正常的控制流与错误处理分离。让我们看一个不使用异常的例子:


  

java

复制代码

public void handleBookingWithoutExceptions(String customer, String hotel) { if (isValidHotel(hotel)) { int hotelId = getHotelId(hotel); if (sendBookingToHotel(customer, hotelId)) { int bookingId = updateDatabase(customer, hotel); if (bookingId > 0) { if (sendConfirmationMail(customer, hotel, bookingId)) { logger.log(Level.INFO, "Booking confirmed"); } else { logger.log(Level.INFO, "Mail failed"); } } else { logger.log(Level.INFO, "Database couldn't be updated"); } } else { logger.log(Level.INFO, "Request to hotel failed"); } } else { logger.log(Level.INFO, "Invalid data"); } }

程序的逻辑只占据了大约5行代码,其余的代码则是用于错误处理。这样,代码不再关注主要的流程,而是被错误检查所淹没。

如果我们的编程语言没有异常机制,我们只能依赖函数的返回值。让我们使用异常来重写我们的函数:


  

java

复制代码

public void handleBookingWithExceptions(String customer, String hotel) { try { validateHotel(hotel); sendBookingToHotel(customer, getHotelId(hotel)); int bookingId = updateDatabase(customer, hotel); sendConfirmationMail(customer, hotel, bookingId); logger.log(Level.INFO, "Booking confirmed"); } catch(Exception e) { logger.log(Level.INFO, e.getMessage()); } }

采用这种方法,我们不需要检查返回值,而是将控制流转移到catch块中。这样的代码更易读。我们有两个独立的流程: 正常流程和错误处理流程。

除了可读性之外,异常还解决了"半谓词问题"(semipredicate problem)。简而言之,半谓词问题发生在表示错误(或不存在值)的返回值成为有效返回值的情况下。让我们看几个示例来说明这个问题:

示例:


  

java

复制代码

int index = "Hello World".indexOf("World"); int value = Integer.parseInt("123"); int freeSeats = getNumberOfAvailableSeatsOfFlight();

indexOf() 方法如果未找到子字符串,将返回 -1。当然,-1 绝对不可能是一个有效的索引,所以这里没有问题。然而,parseInt() 方法的所有可能返回值都是有效的整数。这意味着我们没有一个特殊的返回值来表示错误。最后一个方法 getNumberOfAvailableSeatsOfFlight() 可能会导致隐藏的问题。我们可以将 -1 定义为错误或没有可用信息的返回值。乍看起来这似乎是合理的。然而,后来可能发现负数表示等待名单上的人数。异常机制能更优雅地解决这个问题。

Java中异常的工作方式

在讨论是否使用受检异常之前,让我们简要回顾一下Java中异常的工作方式。下图显示了异常的类层次结构:

java-exception

RuntimeException继承自Exception,而Error继承自Throwable。RuntimeException和Error被称为非受检异常,意味着它们不需要由调用代码处理(即它们不需要被“检查”)。所有其他继承自Throwable(通常通过Exception)的类都是受检异常,这意味着编译器期望调用代码处理它们(即它们必须被“检查”)。

所有继承自Throwable的异常,无论是受检的还是非受检的,都可以在catch块中捕获。

最后,值得注意的是,受检异常和非受检异常的概念是Java编译器的特性。JVM本身并不知道这个区别,所有的异常都是非受检的。这就是为什么其他JVM语言不需要实现这个特性的原因。

在我们开始讨论是否使用受检异常之前,让我们简要回顾一下这两种异常类型之间的区别。

受检异常

受检异常需要被try-catch块包围,或者调用方法需要在其签名中声明异常。由于Scanner类的构造函数抛出一个FileNotFoundException异常,这是一个受检异常,所以下面的代码无法编译:


  

java

复制代码

public void readFile(String filename) { Scanner scanner = new Scanner(new File(filename)); }

我们会得到一个编译错误:


  

java

复制代码

Unhandled exception: java.io.FileNotFoundException

我们有两种选项来解决这个问题。我们可以将异常添加到方法的签名中:


  

java

复制代码

public void readFile(String filename) throws FileNotFoundException { Scanner scanner = new Scanner(new File(filename)); }

或者我们可以使用try-catch块在现场处理异常:


  

java

复制代码

public void readFile(String filename) { try { Scanner scanner = new Scanner(new File(filename)); } catch (FileNotFoundException e) { // handle exception } }

非受检异常

对于非受检异常,我们不需要做任何处理。由Integer.parseInt引发的NumberFormatException是一个运行时异常,所以下面的代码可以编译通过:


  

java

复制代码

public int readNumber(String number) { return Integer.parseInt(callEndpoint(number)); }

然而,我们仍然可以选择处理异常,因此以下代码也可以编译通过:


  

java

复制代码

public int readNumber(String number) { try { return Integer.parseInt(callEndpoint(number)); } catch (NumberFormatException e) { // handle exception return 0; } }

为什么我们要使用受检异常?

如果我们想了解受检异常背后的动机,我们需要看一下Java的历史。该语言的创建是以强调健壮性和网络功能为重点的。

最好用Java创始人詹姆斯·高斯林(James Gosling)自己的一句话来表达:“你不能无意地说,‘我不在乎。’你必须明确地说,‘我不在乎。’”这句话摘自一篇与詹姆斯·高斯林进行的有趣的采访,在采访中他详细讨论了受检异常。

在《编程之父》这本书中,詹姆斯也谈到了异常。他说:“人们往往忽略了检查返回代码。”

这再次强调了受检异常的动机。通常情况下,当错误是由于编程错误或错误的输入时,应该使用非受检异常。如果在编写代码时程序员无法做任何处理,应该使用受检异常。后一种情况的一个很好的例子是网络问题。开发人员无法解决这个问题,但程序应该适当地处理这种情况,可以是终止程序、重试操作或简单地显示错误消息。

受检异常存在的问题

了解了受检异常和非受检异常背后的动机,我们再来看看受异常在代码库中可能引入的一些问题。

受检异常不适应规模化

一个主要反对受异常的观点是代码的可扩展性和可维护性。当一个方法的异常列表发生变化时,会打破调用链中从调用方法开始一直到最终实现try-catch来处理异常的方法的所有方法调用。举个例子,假设我们调用一个名为libraryMethod()的方法,它是外部库的一部分:


  

java

复制代码

public void retrieveContent() throws IOException { libraryMethod(); }

在这里,方法libraryMethod()本身来自一个依赖项,例如,一个处理对外部系统进行REST调用的库。其实现可能如下所示:


  

java

复制代码

public void libraryMethod() throws IOException { // some code }

在将来,我们决定使用库的新版本,甚至用另一个库替换它。尽管功能相似,但新库中的方法会抛出两个异常:


  

java

复制代码

public void otherSdkCall() throws IOException, MalformedURLException { // call method from SDK }

由于有两个受检异常,我们的方法声明也需要更改:


  

java

复制代码

public void retrieveContent() throws IOException, MalformedURLException { sdkCall(); }

对于小型代码库来说,这可能不是一个大问题,但对于大型代码库来说,这将需要进行相当多的重构。当然,我们也可以直接在方法内部处理异常:


  

java

复制代码

public void retrieveContent() throws IOException { try { otherSdkCall(); } catch (MalformedURLException e) { // do something with the exception } }

使用这种方法,我们在代码库中引入了一种不一致性,因为我们立即处理了一个异常,而推迟了另一个异常的处理。

异常传播

一个与可扩展性非常相似的论点是受检异常如何在调用堆栈中传播。如果我们遵循“尽早抛出,尽晚捕获”的原则,我们需要在每个调用方法上添加一个throws子句(a):

异常传播

相反,非受检异常(b)只需要在实际发生异常的地方声明一次,并在我们希望处理异常的地方再次声明。它们会在调用堆栈中自动传播,直到达到实际处理异常的位置。

不必要的依赖关系

受检异常还会引入与非受检异常不必要的依赖关系。让我们再次看看在场景(a)中我们在三个不同的位置添加了IOException。如果methodA()、methodB()和methodC()位于不同的类中,那么所有相关类都将对异常类有一个依赖关系。如果我们使用了非受检异常,我们只需要在methodA()和methodC()中有这个依赖关系。甚至methodB()所在的类或模块都不需要知道异常的存在。

让我们用一个例子来说明这个想法。假设你从度假回家。你在酒店前台退房,乘坐公共汽车去火车站,然后换乘一次火车,在回到家乡后,你又乘坐另一辆公共汽车从车站回家。回到家后,你意识到你把手机忘在了酒店里。在你开始整理行李之前,你进入了“异常”流程,乘坐公共汽车和火车回到酒店取手机。在这种情况下,你按照之前相反的顺序做了所有的事情(就像在Java中发生异常时向上移动堆栈跟踪一样),直到你到达酒店。显然,公共汽车司机和火车操作员不需要知道“异常”,他们只需要按照他们的工作进行。只有在前台,也就是“回家”流程的起点,我们需要询问是否有人找到了手机。

糟糕的编码实践

当然,作为专业的软件开发人员,我们绝不能在良好的编码实践上选择方便。然而,当涉及到受检异常时,往往会诱使我们快速引入以下三种模式。通常的想法是以后再处理。我们都知道这样的结果。另一个常见的说法是“我想为正常流程编写代码,不想被异常打扰”。我经常见到以下三种模式。

第一种模式是捕获所有异常(catch-all exception):


  

java

复制代码

public void retrieveInteger(String endpoint) { try { URL url = new URL(endpoint); int result = Integer.parseInt(callEndpoint(endpoint)); } catch (Exception e) { // do something with the exception } }

我们只是捕获所有可能的异常,而不是单独处理不同的异常:


  

java

复制代码

public void retrieveInteger(String endpoint) { try { URL url = new URL(endpoint); int result = Integer.parseInt(callEndpoint(endpoint)); } catch (MalformedURLException e) { // do something with the exception } catch (NumberFormatException e) { // do something with the exception } }

当然,在一般情况下,这并不一定是一种糟糕的实践。如果我们只想记录异常,或者在Spring Boot的@ExceptionHandler中作为最后的安全机制,这是一种适当的做法。

第二种模式是空的catch块:


  

java

复制代码

public void myMethod() { try { URL url = new URL("malformed url"); } catch (MalformedURLException e) {} }

这种方法显然绕过了受检异常的整个概念。它完全隐藏了异常,使我们的程序在没有提供任何关于发生了什么的信息的情况下继续执行。

第三种模式是简单地打印堆栈跟踪并继续执行,就好像什么都没有发生一样:


  

java

复制代码

public void consumeAndForgetAllExceptions(){ try { // some code that can throw an exception } catch (Exception ex){ ex.printStacktrace(); } }

为了满足方法签名而添加额外的代码

有时我们可以确定除非出现编程错误,否则不会抛出异常。让我们考虑以下示例:


  

java

复制代码

public void readFromUrl(String endpoint) { try { URL url = new URL(endpoint); } catch (MalformedURLException e) { // do something with the exception } }

MalformedURLException是一个受检异常,当给定的字符串不符合有效的URL格式时,会抛出该异常。需要注意的重要事项是,如果URL格式不正确,就会抛出异常,这并不意味着URL实际上存在并且可以访问。

即使我们在之前验证了格式:


  

java

复制代码

public void readFromUrl(@ValidUrl String endpoint)

或者我们已经将其硬编码:


  

java

复制代码

public static final String endpoint = "http://www.example.com";

编译器仍然强制我们处理异常。我们需要写两行“无用”的代码,只是因为有一个受检异常。

如果我们无法编写代码来触发某个异常的抛出,就无法对其进行测试,因此测试覆盖率将会降低。

有趣的是,当我们想将字符串解析为整数时,并不强制我们处理异常:


  

java

复制代码

Integer.parseInt("123");

parseInt方法在提供的字符串不是有效整数时会抛出NumberFormatException,这是一个非受检异常。

Lambda表达式和异常

受检异常并不总是与Lambda表达式很好地配合使用。让我们来看一个例子:


  

java

复制代码

public class CheckedExceptions { public static String readFirstLine(String filename) throws FileNotFoundException { Scanner scanner = new Scanner(new File(filename)); return scanner.next(); } public void readFile() { List<String> fileNames = new ArrayList<>(); List<String> lines = fileNames.stream().map(CheckedExceptions::readFirstLine).toList(); } }

由于我们的readFirstLine()方法抛出了一个受检异常,所以会导致编译错误:

Unhandled exception: java.io.FileNotFoundException in line 8.

如果我们尝试使用try-catch块来修正代码:


  

java

复制代码

public void readFile() { List<String> fileNames = new ArrayList<>(); try { List<String> lines = fileNames.stream() .map(CheckedExceptions::readFirstLine) .toList(); } catch (FileNotFoundException e) { // handle exception } }

我们仍然会得到一个编译错误,因为我们无法在lambda内部将受检异常传播到外部。我们必须在lambda表达式内部处理异常并抛出一个运行时异常:


  

java

复制代码

public void readFile() { List<String> lines = fileNames.stream() .map(filename -> { try{ return readFirstLine(filename); } catch(FileNotFoundException e) { throw new RuntimeException("File not found", e); } }).toList(); }

不幸的是,如果静态方法引用抛出受检异常,这种方式将变得不可行。或者,我们可以让lambda表达式返回一个错误消息,然后将其添加到结果中:


  

java

复制代码

public void readFile() { List<String> lines = fileNames.stream() .map(filename -> { try{ return readFirstLine(filename); } catch(FileNotFoundException e) { return "default value"; } }).toList(); }

然而,代码看起来仍然有些杂乱。

我们可以在lambda内部传递一个非受检异常,并在调用方法中捕获它:


  

java

复制代码

public class UncheckedExceptions { public static int parseValue(String input) throws NumberFormatException { return Integer.parseInt(input); } public void readNumber() { try { List<String> values = new ArrayList<>(); List<Integers> numbers = values.stream() .map(UncheckedExceptions::parseValue) .toList(); } catch(NumberFormatException e) { // handle exception } } }

在这里,我们需要注意之前使用受检异常和使用非受检异常的例子之间的一个关键区别。对于非受检异常,流的处理将继续到下一个元素,而对于受检异常,处理将结束,并且不会处理更多的元素。显然,我们想要哪种行为取决于我们的用例。

处理受检异常的替代方法

将受检异常包装为非受检异常

我们可以通过将受检异常包装为非受检异常来避免在调用堆栈中的所有方法中添加throws子句。而不是让我们的方法抛出一个受检异常:

public void myMethod() throws IOException{}

我们可以将其包装在一个非受检异常中:


  

java

复制代码

public void myMethod(){ try { // some logic } catch(IOException e) { throw new MyUnchckedException("A problem occurred", e); } }

理想情况下,我们应用异常链。这样可以确保原始异常不会被隐藏。我们可以在第5行看到异常链的应用,原始异常作为参数传递给新的异常。这种技术在早期版本的Java中几乎适用于所有核心Java异常。

异常链是许多流行框架(如Spring或Hibernate)中常见的一种方法。这两个框架从受检异常转向非受检异常,并将不属于框架的受检异常包装在自己的运行时异常中。一个很好的例子是Spring的JDBC模板,它将所有与JDBC相关的异常转换为Spring框架的非受检异常。

Lombok @SneakyThrows

Project Lombok为我们提供了一个注解,可以消除异常链的需要。而不是在我们的方法中添加throws子句:


  

java

复制代码

public void beSneaky() throws MalformedURLException { URL url = new URL("http://test.example.org"); }

我们可以添加@SneakyThrows 注解,这样我们的代码就可以编译通过:


  

java

复制代码

@SneakyThrows public void beSneaky() { URL url = new URL("http://test.example.org"); }

然而,重要的是要理解,@SneakyThrows并不会使MalformedURLException的行为完全像运行时异常一样。我们将无法再捕获它,并且以下代码将无法编译:


  

java

复制代码

public void callSneaky() { try { beSneaky(); } catch (MalformedURLException e) { // handle exception } }

由于@SneakyThrows移除了异常,而MalformedURLException仍然被视为受检异常,因此我们将在第4行得到编译器错误:


  

java

复制代码

Exception 'java.net.MalformedURLException' is never thrown in the corresponding try block

性能

在我的研究过程中,我遇到了一些关于异常性能的讨论。在受检异常和非受检异常之间是否存在性能差异?实际上,它们之间没有性能差异。这是一个在编译时决定的特性。

然而,是否在异常中包含完整的堆栈跟踪会导致显着的性能差异:


  

java

复制代码

public class MyException extends RuntimeException { public MyException(String message, boolean includeStacktrace) { super(message, null, !includeStacktrace, includeStacktrace); } }

在这里,我们在自定义异常的构造函数中添加了一个标志。该标志指定是否要包含完整的堆栈跟踪。在抛出异常的情况下,构建堆栈跟踪会导致程序变慢。因此,如果性能至关重要,则应排除堆栈跟踪。

一些指南

如何处理软件中的异常是我们工作的一部分,它高度依赖于具体的用例。在我们结束讨论之前,这里有三个高级指南,我相信它们(几乎)总是正确的。

  • 如果不是编程错误,或者程序可以执行一些有用的恢复操作,请使用受检异常。

  • 如果是编程错误,或者程序无法进行任何恢复操作,请使用运行时异常。

  • 避免空的catch块。

结论

本文深入探讨了Java中的异常。我们讲了为什么要引入异常到语言中,何时应该使用受检异常和非受检异常。我们还讨论了受检异常的缺点以及为什么它们现在被认为是不良实践 - 尽管也有一些例外情况。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java开发中不要使用受检异常 的相关文章

  • 如何配置jackson属性命名策略?

    此代码不起作用 Configuration public class RepositoryRestMvcConfig extends RepositoryRestMvcConfiguration Bean Override public O
  • 当从搜索表单动态构建 WHERE 子句时,如何防止 SQL 注入?

    我知道在 Java 中保护 SQL 查询免受 SQL 注入的唯一真正正确的方法是使用准备好的语句 然而 这样的语句要求基本结构 选择的属性 连接的表 WHERE条件的结构 不会改变 我这里有一个 JSP 应用程序 其中包含一个带有大约十几个
  • 如何将 Java 字节数组转换为 Scala 字节数组?

    我是 Scala 新手 目前正在从事一个涉及 Java 和 Scala 模块的项目 现在我想使用 byte 类型的参数从 Java 调用 Scala 方法 Scala 方法的签名为 def foo data Array Byte Java
  • 将 XML 转换为 Java 对象 [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 函数 isAssignableFrom 在服务器启动期间返回 false

    实施oauth2系统 我在使用以下代码时遇到一些问题 import org springframework security oauth2 provider endpoint FrameworkEndpointHandlerMapping
  • 尝试将过滤器添加到 Grizzly+Jersey 应用程序时出现问题

    我有这个服务器初始化类 package magic app main import org glassfish grizzly http server HttpServer import org glassfish jersey grizz
  • 如何使用 OpenCV 检测图像帧中的对象?

    我正在使用 Raspberry Pi 开发一个漫游器 它将清扫房间并捡起掉落在地上的物体 为了检测物体 我使用了在流动站操作开始时拍摄的参考图像 以及每 10 秒单击一次的图像 新图像 为了确定图像帧是否发生变化 我在参考图像和新图像之间进
  • Spring JPA (Hibernate) Entity Manager 何时将连接返回到连接池?

    在我的 java 进程中 我使用以下 spring 配置连接到 MySql Configuration EnableTransactionManagement PropertySources PropertySource classpath
  • 如何在 Python 中小写字符串?

    有没有办法将字符串转换为小写 Kilometers kilometers See How to change a string into uppercase https stackoverflow com questions 9257094
  • java中类的命名约定 - 全部大写

    在 Java 中 当类全部大写时 如何命名它 例如 如果我想创建一个班级来选择某些人成为 VIP 我应该将类命名为 VIPSelector 还是 VipSelector Thanks 你的两个选择都有效 类的主要目标是让它们以大写字母开头
  • GAE、JPA、XG 事务、实体组过多异常

    我知道 GAE 上的 XG 交易有 5 个实体组的限制 但我认为我在一项交易中仅使用 3 个组 商品 类别 商品类别 但仍然遇到此异常 引起原因 java lang IllegalArgumentException 在单个事务中对太多实体组
  • 遍历多行字符串

    我得到一些数据 def data some useless text n even more n finally interesting text 我怎样才能得到其中 有趣的部分 所以基本上所有行都不是以 开头的 Groovy 的一种选择是
  • 如何使用 Java 11 和 JavaFX 11 运行 ControlsFX 示例应用程序

    ControlFX 网站 http fxexperience com controlsfx says 如果您想使用 ControlsFX 示例应用程序 只需 下载 ControlsFX 版本并在上运行以下命令 命令提示符 请务必将 替换为实
  • hashlib 和 urandom 哪个更随机?

    我正在和一个朋友一起开发一个项目 我们需要生成随机哈希 在我们有时间讨论之前 我们都提出了不同的方法 并且因为他们使用不同的模块 我想问你们大家什么会更好 如果有这样的事情的话 hashlib sha1 str random random
  • Python-打印字符串一定次数[重复]

    这个问题在这里已经有答案了 可能的重复 Python 多次打印 https stackoverflow com questions 6293421 python printing multiple times 我想知道如何打印 String
  • Activity 在 Android 上创建两次

    首先 我是 Android 开发新手 所以请耐心等待 我将从用户界面开始 我有一个按钮 一旦您点击它 就会启动一个活动以获取结果 public class GUIActivity extends Activity Override publ
  • 扫描图像到可读文本

    我想知道是否有一种方法可以通过编写代码来将带有文本的扫描图像转换为可读文本 那可能吗 OCRTools http www ocrtools com是我用于 net 的 对于Java 我用过Aspire http asprise com pr
  • 如何提取Python代码文件中使用的函数?

    我想创建代码文件中使用的所有函数的列表 例如 如果我们在名为 add random py 的文件中有以下代码 import numpy as np from numpy import linalg def foo print np rand
  • Android:如何获取小数点后的两位数?不想截断值

    如何获取小数点后仅两位数的双精度值 例如 如果 a 190253 80846153846 那么结果值应该像 a 190253 80 尝试 我尝试过这个 public static DecimalFormat twoDForm new Dec
  • “您的安全设置已阻止本地应用程序运行”Java 8

    我正在尝试在 Chrome 窗口中运行一个小小程序 但收到错误消息 我确实看到所有回复都告诉我将安全性更改为中级 但版本 8 中不存在该选项 到目前为止 几个小时的谷歌搜索和向同学寻求帮助没有带来任何进展 有人可以建议一下吗 Medium在

随机推荐

  • DSCA190V 57310001-PK

    DSCA190V 57310001 PK DSCA190V 57310001 PK 具有两个可编程继电器功能 并安装在坚固的 XP 外壳中 DSCA190V 57310001 PK 即可使用 只需最少的最终用户校准 DSCA190V 573
  • 深度学习(5)--Keras实战

    一 Keras基础概念 Keras是深度学习中的一个神经网络框架 是一个高级神经网络API 用Python编写 可以在TensorFlow CNTK或Theano之上运行 Keras优点 1 允许简单快速的原型设计 用户友好性 模块化和可扩
  • Android开发--自定义时频域折线绘制图

    直接上干货 1 XML
  • Kafka速度之谜:高性能的幕后秘密大揭秘

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 一 kafka高性能的原因 Page Cache ZeroCopy 零拷贝 前言 Kafka的介绍 kafka是linkedIn开源的分布式消息系统 归给Ap
  • ESP10B 锁定连接器

    ESP10B 锁定连接器 ESP10B 电机新增内容包括双极型号标准 NEMA 尺寸 17 23 和 34 的步进电机现在包括输出扭矩范围从 61 盎司英寸到 1291 盎司英寸的双极型号 该电机配有带锁定连接器的尾缆 可轻松连接 每转可步
  • 自动驾驶离不开的仿真!Carla-Autoware联合仿真全栈教程

    随着自动驾驶技术的不断发展 研发技术人员开始面对一系列复杂挑战 特别是在确保系统安全性 处理复杂交通场景以及优化算法性能等方面 这些挑战中 尤其突出的是所谓的 长尾问题 即那些在实际道路测试中难以遇到的罕见或异常驾驶情况 这些问题暴露了实车
  • 题解 | #翻转单词序列# 复习Java几个集合方法使用

    人生第一次面试gg 字节跳动 复活赛 二面 字节后端实习面试 字节飞书后端一面凉经 字节 客服平台 一面面经 已挂 菜鸟前端 避雷避雷中华财险 中厂大厂到底怎么选 樊登读书精准营销岗面经 跨行 转技术岗进大厂的好机会 来看 岗位名称 云 软
  • sychnorized积累

    sychnorized 1 对象锁 包括方法锁 默认锁对象为this 当前实例对象 和同步代码块锁 自己指定锁对象 2 类锁 指synchronize修饰静态的方法或指定锁对象为Class对象 3 加锁和释放锁的原理 现象 时机 内置锁th
  • 两个月进口猛增10倍,买近百台光刻机,难怪ASML不舍中国市场

    据统计数据显示 2023年11月和12月 中国从荷兰进口的光刻机设备同比猛增10倍 进口金额超过19亿美元 让ASML赚得盆满钵满 ASML早前表示中国客户在2023年订购的光刻机全数交付 2023年11月中国进口的光刻机达到42台 进口金
  • Cortex-M3与M4权威指南

    处理器类型 所有的ARM Cortex M 处理器是32位的精简指令集处理器 它们有 32位寄存器 32位内部数据路径 32位总线接口 除了32位数据 Cortex M处理器也可以有效地处理器8位和16位数据以及支持许多涉及64位数据的操作
  • 高精度运算合集,加减乘除,快速幂,详细代码,OJ链接

    文章目录 零 前言 一 加法 高精度加法步骤 P1601 A B 二 减法 高精度减法步骤
  • 每日变更的最佳实践

    在优维公司内部 我们采用发布单的方式进行每天的应用变更管理 这里给各位介绍优维的最佳实践 变更是需要多角色合作的 而且他是整体研发流程的一部分 在优维内部 我们坚持每日变更 打通开发环节到最终发布上线的全过程 在保证质量的前提下 尽可能提升
  • 【js学习之路】遍历数组api之 `filter `和 `map`的区别

    一 前言 数组是我们在项目中经常使用的数据类型 今天我们主要简述作用于遍历数组的api filter 和 map 的区别 二 filter和map的共同点 首先 我们主要阐述一下 filter 和 map 的共同点 api的参数都是回调函数
  • 肿瘤的转录调控:Cell子刊揭示原发性肝癌中转录因子活性的全基因组图谱|国自然热点

    转录调控的研究历史比较长 相关研究在近十年来仍一直增长 也是近年来高分文章的焦点之一 在2023年最佳国自然 中标 研究热点 转录调控中标率高达189 作为国自然热点之一的肿瘤微环境的研究在近几年也一直处于上升趋势 转录调控在肿瘤发生 发展
  • 高中数学:因式分解(初接高)

    一 乘法公式 二 十字相乘法 例题 三 增添项法 主要解决整式中含高次项的因式分解题 补充 由于数学笔记 用键盘敲实在是麻烦 这里就把我的笔记截图上来了 大家将就看 有看不清楚的地方 可评论 定回复
  • 实力认证!鼎捷软件荣膺“领军企业”和“创新产品”两大奖项

    近日 由中国科学院软件研究所 中科软科技股份有限公司联合主办的 2023中国软件技术大会 于北京成功举办 本届大会以 大模型驱动下的软件变革 为主题 数十位来自知名互联网公司和软件巨头企业的技术大咖 不同领域行业专家 畅销书作者等分享嘉宾
  • 在 Python 中实现 List 抽象

    在 Python 中 创建一个包含多个对象的 list 很常见 例如 对于一组具有相同功能的对象 比如播放声音 希望能够使用类似 my list play 的语法来触发 list 中所有对象的 play 方法 另一个例子是 当希望关闭 li
  • Eggplant—HMI自动化测试软件

    产品概述 Eggplant是英国TestPlant公司推出的创新性自动化测试工具 通过VNC或RDP通讯技术远程桌面连接被测对象 基于图像和文字识别算法进行对象定位 进而驱动和确认被测HMI设备的响应 能够实现自动化的HMI操作测试 较大提
  • SAP ERP系统是什么?SAP好用吗?

    A公司是一家传统制造企业 公司曾先后使用过数个管理软件系统 但各部门使用的软件都是单独功能 导致企业日常管理中数据流与信息流相对独立 形成了 信息孤岛 随着公司近年业务规模的快速发展以及客户数量的迅速增加 企业原有的信息系统在销售预测及生产
  • Java开发中不要使用受检异常

    简介 Java是唯一 主流 实现了受检异常概念的编程语言 一开始 受检异常就是争议的焦点 在当时被视为一种创新概念 Java于1996年推出 如今却被视不良实践 本文要讨论Java中非受检异常和受检异常的动机以及它们优缺点 与大多数关注这个