穹顶灯打不出阴暗面_Java 8的阴暗面

穹顶灯打不出阴暗面

这篇文章最初是在jooq.org上发布的,这是一个专门系列的一部分,该系列着重于Java 8的所有方面,包括如何利用lambda表达式,扩展方法和其他重要内容。 您可以在GitHub上找到源代码

到目前为止,我们已经展示了这个主要新版本中令人激动的部分 。 但是也有一些警告。 其中很多。 那件事

  • ……令人困惑
  • … 错了
  • …被省略(目前)
  • …被省略(很长时间)

Java主要版本始终有两个方面。 从好的方面来说,我们获得了许多新功能,大多数人会认为这已经过期了 。 其他语言,平台早在Java 5之前就有泛型。其他语言,平台早在Java 8之前就有lambda。但是现在,我们终于有了这些功能。 在通常的古怪Java方式中。

Lambda表达式的引入非常优美。 从向后兼容的角度来看,能够将每个匿名SAM实例编写为lambda表达式的想法非常引人注目。 那么,什么黑暗面到Java 8?

超载变得更糟

重载,泛型和vararg不是朋友。 我们已经在上一篇文章以及这个Stack Overflow问题中 对此进行了解释 。 在您的奇怪应用程序中,这些可能并非每天都有问题,但对于API设计人员和维护人员而言,这是非常重要的问题。

使用lambda表达式,事情变得“更糟”。 因此,您认为您可以提供一些便利的API,以重载现有的run()方法(该方法接受Callable并接受新的Supplier类型:

static <T> T run(Callable<T> c) throws Exception {
    return c.call();
}
 
static <T> T run(Supplier<T> s) throws Exception {
    return s.get();

看起来非常有用的Java 7代码现在是Java 8的一大难题。 因为您不能只使用lambda参数简单地调用这些方法:

public static void main(String[] args)
throws Exception {
    run(() -> null);
    //  ^^^^^^^^^^ ambiguous method call
}

倒霉。 您将不得不采用以下任一“经典”解决方案:

run((Callable<Object>) (() -> null));
run(new Callable<Object>() {
    @Override
    public Object call() throws Exception {
        return null;
    }
});

因此,尽管总有变通办法,但这些变通办法总是“糟透了”。 即使从向后兼容的角度来看事情不会中断,这也真是令人沮丧。

并非所有关键字都支持默认方法

默认方法是一个不错的补充。 有人可能声称Java最终具有特质 。 其他人显然与该术语脱离了关系,例如Br​​ian Goetz:

向Java添加默认方法的主要目标是“接口演变”,而不是“穷人的特征”。

如lambda-dev邮件列表中所示。

事实是,默认方法与Java中的其他任何东西相比,都有很多正交和不规则的特征。 这里有一些批评:

他们不能成为最终的

鉴于默认方法也可以用作API中的便捷方法:

public interface NoTrait {
 
    // Run the Runnable exactly once
    default final void run(Runnable r) {
        //  ^^^^^ modifier final not allowed
        run(r, 1);
    }
 
    // Run the Runnable "times" times
    default void run(Runnable r, int times) {
        for (int i = 0; i < times; i++)
            r.run();
    }
}

不幸的是,以上操作是不可能的,因此第一个重载的便捷方法可能会在子类型中被覆盖,即使这对API设计人员而言毫无意义。

无法使其同步

mm! 用语言难以实现吗?

public interface NoTrait {
    default synchronized void noSynchronized() {
        //  ^^^^^^^^^^^^ modifier synchronized
        //  not allowed
        System.out.println("noSynchronized");
    }
}

是的, synchronized很少使用,就像决赛。 但是,当您拥有该用例时,为什么不仅仅允许它呢? 是什么使接口方法主体如此特别?

默认关键字

这可能是所有功能中最怪异和最不规则的。 default关键字本身。 让我们比较一下接口和抽象类:

// Interfaces are always abstract
public /* abstract */ interface NoTrait {
 
    // Abstract methods have no bodies
    // The abstract keyword is optional
    /* abstract */ void run1();
 
    // Concrete methods have bodies
    // The default keyword is mandatory
    default void run2() {}
}
 
// Classes can optionally be abstract
public abstract class NoInterface {
 
    // Abstract methods have no bodies
    // The abstract keyword is mandatory
    abstract void run1();
 
    // Concrete methods have bodies
    // The default keyword mustn't be used
    void run2() {}
}

如果从头开始重新设计该语言,则可能不需要任何abstractdefault关键字。 两者都是不必要的。 存在或不存在主体的事实足以使编译器评估方法是否抽象。 即,情况应该如何:

public interface NoTrait {
    void run1();
    void run2() {}
}
 
public abstract class NoInterface {
    void run1();
    void run2() {}
}

上面的内容将更加精简和规范。 遗憾的是,EG从未真正讨论过default的用途。 好吧,这是经过辩论的,但是EG从来不想接受这种选择。 我已经尝试过运气,下面的响应

我不认为#3是一种选择,因为与方法主体的接口一开始是不自然的。 至少指定“默认”关键字为读者提供了某种语言来说明该语言允许使用方法主体的原因。 就个人而言,我希望接口将保持纯合同形式(不执行),但是我不知道有更好的选择来发展接口。

同样,这是EG的明确承诺,即不承诺Java中的“特征”。 默认方法是实现1-2个其他功能的纯粹必要手段。 他们从一开始就没有精心设计。

其他修饰符

幸运的是,在项目后期,使用了static修饰符使其成为规格。 因此现在可以在接口中指定静态方法。 但是由于某种原因,这些方法不需要(也不允许!) default关键字,它必须是EG完全随机决定的,就像您显然无法在接口中定义static final方法一样。

虽然可见性修饰符已在lambda-dev邮件列表中进行了讨论 ,但超出了此发行版的范围。 也许,我们可以在将来的版本中获得它们。

实际上很少执行默认方法

有些方法可能在接口上使用了明智的默认实现,有人可能会猜测。 直观地,集合接口(例如ListSet会将它们放在其equals()hashCode()方法上,因为这些方法的协定在接口上定义良好。 它还使用listIterator()AbstractList实现,对于大多数定制列表而言,这是合理的默认实现。

如果对这些API进行改造,以使使用Java 8轻松实现自定义集合,那将是非常不错的。例如,我可以使我的所有业务对象都实现List ,而不会浪费AbstractList上的单个基类继承。

但是,可能有一个与向后兼容性相关的令人信服的原因阻止了Oracle的Java 8团队实现这些默认方法。 谁给我们发送了省略它的原因,将获得免费的jOOQ标签 :-)

不是在这里发明的-心态

在lambda-dev EG邮件列表中也多次批评了这一点。 而且在撰写本博客系列文章时 ,我只能确认新的功能接口让人很难记住。 由于这些原因,他们感到困惑:

一些原始类型比其他更平等

与所有其他类型相比, intlongdouble基本类型是首选的,因为它们在java.util.function包以及整个Streams API中都具有功能接口。 boolean是二等公民,因为它仍然把它做成包在一个形式BooleanSupplierPredicate ,或者更糟: IntPredicate

所有其他原始类型在该区域中实际上并不存在。 即,没有针对byteshortfloatchar特殊类型。 尽管满足最后期限的争论无疑是有效的,但这种古怪的现状将使新手学习这种语言更加困难。

这些类型不只是称为函数

坦白地说。 所有这些类型都只是“功能”。 没有人真正关心ConsumerPredicateUnaryOperator等之间的隐式差异。

实际上,当您寻找具有非void返回值和两个参数的类型时,您可能会调用什么呢? Function2 ? 好吧,你错了。 它称为BiFunction

这是一个决策树,用于了解您要查找的类型如何被调用:

  • 您的函数返回void吗? 叫做Consumer
  • 您的函数返回boolean吗? 这叫做Predicate
  • 您的函数返回intlongdouble吗? 叫做XXToIntYYXXToLongYYXXToDoubleYY东西
  • 您的函数没有参数吗? 叫做Supplier
  • 您的函数是否接受单个intlongdouble参数? 它被称为IntXXLongXXDoubleXX东西
  • 您的函数是否接受两个参数? 叫做BiXX
  • 您的函数是否接受两个相同类型的参数? 叫做BinaryOperator
  • 您的函数返回的类型是否与作为单个参数的类型相同? 叫做UnaryOperator
  • 您的函数是否带有两个参数,第一个是引用类型,第二个是原始类型? 它称为ObjXXConsumer (只有使用该配置的使用者存在)
  • 否则:称为Function

好主啊! 最近,我们当然应该去Oracle Education检查Oracle Certified Java Programmer课程的价格是否急剧上涨了……幸运的是,有了Lambda表达式,我们几乎不必记住所有这些类型!


翻译自: https://jaxenter.com/the-dark-side-of-java-8-107735.html

穹顶灯打不出阴暗面

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值