草庐IT

Java 8 Map 默认实现细节

coder 2024-03-06 原文

我查看了新的 Java 8 Map 方法(如 getOrDefault)的默认实现,发现有些奇怪。例如考虑 getOrDefault 方法。具体实现如下。

default V getOrDefault(Object key, V defaultValue) {
    V v;
    return ((v = get(key)) != null) || containsKey(key) ? v : defaultValue;
}

现在,这里“奇怪”的事情是 ((v = get(key)) != null 中的“使用赋值结果”模式。据我所知,不鼓励使用这种特定模式, 因为它相当妨碍可读性。IMO 更简洁的版本将类似于

default V getOrDefault(Object key, V defaultValue) {
    V v = get(key);
    return v != null || containsKey(key) ? v : defaultValue;
}

我的问题是,除了编码标准/习惯之外,是否有任何特别的理由使用前者而不是后者模式。特别是,我想知道这两个版本是否在跟踪和性能方面是等效的?

我唯一能想到的是编译器可能例如确定 containsKey 通常可以更快地进行评估,因此首先对其进行评估,但据我所知,短路必须保留执行顺序(至少 C 是这种情况)。

编辑:按照@ruakh 的建议,这是两个字节码(由javap -c 生成)

  public V getOrDefault(java.lang.Object, V);
    Code:
       0: aload_0
       1: aload_1
       2: invokeinterface #1,  2            // InterfaceMethod get:(Ljava/lang/Object;)Ljava/lang/Object;
       7: dup                               // <-- difference here
       8: astore_3
       9: ifnonnull     22
      12: aload_0
      13: aload_1
      14: invokeinterface #2,  2            // InterfaceMethod containsKey:(Ljava/lang/Object;)Z
      19: ifeq          26
      22: aload_3
      23: goto          27
      26: aload_2
      27: areturn

  public V getOrDefault(java.lang.Object, V);
    Code:
       0: aload_0
       1: aload_1
       2: invokeinterface #1,  2            // InterfaceMethod get:(Ljava/lang/Object;)Ljava/lang/Object;
       7: astore_3
       8: aload_3                           // <-- difference here
       9: ifnonnull     22
      12: aload_0
      13: aload_1
      14: invokeinterface #2,  2            // InterfaceMethod containsKey:(Ljava/lang/Object;)Z
      19: ifeq          26
      22: aload_3
      23: goto          27
      26: aload_2
      27: areturn

我不得不承认,即使经过多年的 Java 编码,我仍然不知道如何解释 Java 字节码。有人能解释一下这里的区别吗?

最佳答案

这只是一个样式问题。有些人喜欢尽可能紧凑的代码, 而其他人则更喜欢更长但更简单的代码。好像有些开发商 从事 Java 核心库的工作属于前一组。

就效率而言,两种变体是相同的。


让我们看看编译器实际上对这两个变体做了什么:

public class ExampleMap<K, V> extends HashMap<K, V> {

    V getOrDefault1(Object key, V defaultValue) {
        V v;
        return ((v = get(key)) != null) || containsKey(key) ? v : defaultValue;
    }

    V getOrDefault2(Object key, V defaultValue) {
        V v = get(key);
        return v != null || containsKey(key) ? v : defaultValue;
    }
}

现在让我们使用 javap -c ExampleMap 转储生成的字节码:

Compiled from "ExampleMap.java"
public class ExampleMap<K, V> extends java.util.HashMap<K, V> {
  public ExampleMap();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/util/HashMap."<init>":()V
       4: return

  V getOrDefault1(java.lang.Object, V);
    Code:
       0: aload_0
       1: aload_1
       2: invokevirtual #2                  // Method get:(Ljava/lang/Object;)Ljava/lang/Object;
       5: dup
       6: astore_3
       7: ifnonnull     18
      10: aload_0
      11: aload_1
      12: invokevirtual #3                  // Method containsKey:(Ljava/lang/Object;)Z
      15: ifeq          22
      18: aload_3
      19: goto          23
      22: aload_2
      23: areturn

  V getOrDefault2(java.lang.Object, V);
    Code:
       0: aload_0
       1: aload_1
       2: invokevirtual #2                  // Method get:(Ljava/lang/Object;)Ljava/lang/Object;
       5: astore_3
       6: aload_3
       7: ifnonnull     18
      10: aload_0
      11: aload_1
      12: invokevirtual #3                  // Method containsKey:(Ljava/lang/Object;)Z
      15: ifeq          22
      18: aload_3
      19: goto          23
      22: aload_2
      23: areturn
}

如您所见,代码大部分相同。唯一的小区别是线条 两种方法的5和6。一个只是复制堆栈的顶部值 (记住,Java 字节码采用基于堆栈的机器模型),而另一个 从实例变量加载(相同的)值。

当即时编译器根据这个字节生成真正的机器代码时 代码,它将执行各种优化,比如决定哪些值 写回 RAM 并将其保存在 CPU 寄存器中。我认为这是安全的 假设在这些优化发生之后,没有区别 留下任何东西。

关于Java 8 Map 默认实现细节,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44151328/

有关Java 8 Map 默认实现细节的更多相关文章

  1. ruby - 默认情况下使选项为 false - 2

    这是在Ruby中设置默认值的常用方法:classQuietByDefaultdefinitialize(opts={})@verbose=opts[:verbose]endend这是一个容易落入的陷阱:classVerboseNoMatterWhatdefinitialize(opts={})@verbose=opts[:verbose]||trueendend正确的做法是:classVerboseByDefaultdefinitialize(opts={})@verbose=opts.include?(:verbose)?opts[:verbose]:trueendend编写Verb

  2. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  3. ruby-on-rails - date_field_tag,如何设置默认日期? [ rails 上的 ruby ] - 2

    我想设置一个默认日期,例如实际日期,我该如何设置?还有如何在组合框中设置默认值顺便问一下,date_field_tag和date_field之间有什么区别? 最佳答案 试试这个:将默认日期作为第二个参数传递。youcorrectlysetthedefaultvalueofcomboboxasshowninyourquestion. 关于ruby-on-rails-date_field_tag,如何设置默认日期?[rails上的ruby],我们在StackOverflow上找到一个类似的问

  4. ruby-on-rails - 在默认方法参数中使用 .reverse_merge 或 .merge - 2

    两者都可以defsetup(options={})options.reverse_merge:size=>25,:velocity=>10end和defsetup(options={}){:size=>25,:velocity=>10}.merge(options)end在方法的参数中分配默认值。问题是:哪个更好?您更愿意使用哪一个?在性能、代码可读性或其他方面有什么不同吗?编辑:我无意中添加了bang(!)...并不是要询问nobang方法与bang方法之间的区别 最佳答案 我倾向于使用reverse_merge方法:option

  5. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  6. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  7. java - 我的模型类或其他类中应该有逻辑吗 - 2

    我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

  8. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  9. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  10. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

随机推荐