代码之家  ›  专栏  ›  技术社区  ›  DCR

为什么需要铸造

  •  0
  • DCR  · 技术社区  · 3 年前

    以下工程按预期进行:

    public boolean equals(Object otherObject) {
        if (otherObject instanceof Employee) {
            Employee toCompare = (Employee) otherObject;
            if (this.getLastName().equals(toCompare.getLastName()) && this.idNumber == toCompare.idNumber) return true;
        }
        return false;
    } 
    

    我的问题是,为什么这不起作用:

    public boolean equals(Object otherObject) {
        if (otherObject instanceof Employee) {            
            if (this.getLastName().equals(otherObject.getLastName()) && 
               this.idNumber == otherObject.idNumber) return true;
        }
        return false;
    }  
    

    他们把这个排除在编译器之外的原因是什么?或者,toCompare有什么其他Object没有的?它们都指向同一个位置,有什么区别?

    1 回复  |  直到 3 年前
        1
  •  3
  •   Unmitigated    3 年前

    您可以在变量上访问的字段和方法取决于其静态类型。 otherObject 属于类型 Object ,因此不能调用特定于的方法 Employee

    Pattern matching for instanceof 不过,这让它写起来更简单。

    if (otherObject instanceof Employee emp) {
        return this.getLastName().equals(emp.getLastName()) && 
               this.idNumber == emp.idNumber;
    }
    
        2
  •  2
  •   rzwitserloot    3 年前

    你所说的是所谓的流动型紧缩。你想要这个:

    Object foo = ...;
    if (foo instanceof String) {
      foo.toLowerCase();
    }
    

    工作,因为特别是在这个范围内 if foo 当然,是一根弦。

    这种想法存在一些根本问题:

    • 想象 foo 不是局部变量,而是字段。当然,这应该仍然有效。除了字段可以由其他线程动态更改。语言也应该如此 将此原则应用于字段,但是 将其应用于方法locals?这听起来像是你在为别人打开大门,让他们提出一个令人难以置信的问题,为什么在大火中会有区别。
    • 这意味着如果你改变 foo 内部 如果 事情变得有点奇怪。这里应该发生什么:
    Object foo = ...;
    if (foo instanceof String) {
      if (Math.random() < 0.00005) foo = 5;
      foo.toLowerCase();
    }
    

    这显然不应该奏效——这不太可能,但有可能 foo 调用时不是字符串 foo.toLowerCase() 。所以在这里不起作用?

    希望你能看到推理过度的普遍问题。当你写一些代码时,它会使调试变得异常复杂,而它并没有完成你想要的任务。它使编译器变得极其复杂。

    现在,这并不意味着添加它是个坏主意。这只是对简单得多的事情的解释:

    • JDK的工程师们在发布java1.0的时候就是这样设置的,这是可以理解的。

    即使你认为这是错误的,那也不是重点。重点很简单,可以理解的是,选择不使用流式结构。

    这就引出了下一点:

    …现在我们陷入了困境

    引入流类型 现在 破坏向后兼容性 。奇怪,但却是真的——这是因为在java中,方法参数是方法标识的一部分,因此,更改类型可以使完全相同的代码现在引用完全不同的方法。这是最新的香草java,如果你喜欢,可以试试:

    class Test {
      public static void main(String[] args) {
        Object a = "foo";
        String b = "foo";
        System.out.println(test(a));
        System.out.println(test(b));
      }
    
      public static void test(Object x) {
        System.out.println("foo(Object)");
      }
    
      public static void test(String x) {
        System.out.println("foo(String)");
      }
    }
    

    这将打印 foo(Object) 然后 foo(String) 。你可能会发现这很奇怪——在这两种情况下,传递的对象都是String类型。这是因为使用这两种方法中的哪一种是由编译器决定的,而不是在运行时。Java确实有动态类型,但它仅适用于重写方法时,这不仅要求名称相同,还要求参数类型相同。这里的情况并非如此。

    我们可以再深入研究一下为什么会设计这样的语言,但在4871年后的某个时候,我们会在这里,你会问“sooo,关于那个大爆炸”。这当然远远超出了这个问题的范围。

    无论如何,考虑到java就是这样工作的,添加流类型会破坏一切。毕竟,想象一下我在上面的代码中所做的:

    Object q = "Hello";
    if (q instanceof String) {
      test(q);
    }
    

    然后打印当前java test(Object) 而如果添加流类型,则会得到 test(String) 并且这是不向后兼容的。

    好消息

    仅仅因为其他语言是这样做的,并不意味着java必须遵循并打破这些东西,也不意味着java只需要坐在那里后悔。

    您只需以不同的方式添加功能即可。这就是模式的用武之地 极大地 比这更复杂的功能,但它可以做的很多事情之一是:

    Object foo = "x";
    if (foo instanceof String bar) {
      bar.toLowerCase();
    }
    

    这可以很好地工作,并避免了向后不兼容的问题。它还避免了对“..”的混淆。。但是如果 foo 是一个在中途更改的字段(无关;测试和变量赋值 bar 是原子的)',如果重新分配它(感觉自由,但可变 foo 属于类型 Object 和变量 酒吧 属于类型 String ,没有什么能改变这一点,所以也没有混淆)。

    它还允许解构、模式匹配等等。

    如果你想了解更多,这些事情会在openjdk邮件列表上进行详细讨论(如果你想知道所有细节,你会花几个星期的时间阅读)。valhalla-dev、amber-dev、core-lib-dev可能对此很感兴趣。这是的主页 amber-dev mailing list