`
deepinmind
  • 浏览: 451586 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
1dc14e59-7bdf-33ab-841a-02d087aed982
Java函数式编程
浏览量:41632
社区版块
存档分类
最新评论

慎用Java 8的默认方法

阅读更多

默认方法给JVM的指令集增加了一个非常不错的新特性。使用了默认方法之后,如果库中的接口增加了新的方法,实现了这个接口的用户类能够自动获得这个方法的默认实现。一旦用户想更新他的实现类的话,只需覆盖一下这个默认方法就可以了,取而代之的是一个在特定场景下更有意义的实现。更棒的是,用户可以在重写的方法里面调用接口的默认实现来增加一些额外的功能。

目前为止一切都还不错。然而,给现有的Java接口增加默认方法可能会导致代码的不兼容。看个例子就很容易能明白了。假设有一个库,它需要用户实现它的一个接口作为输入:

interface SimpleInput {
  void foo();
  void bar();
}
 
abstract class SimpleInputAdapter implements SimpleInput {
  @Override
  public void bar() {
    // some default behavior ...
  }
}
 


在Java 8以前,上述这种接口和一个对应的适配器类的组合在Java语言中是一种很常见的模式。类库的开发人员提供了一个适配器来减少库使用者的编码量。然而提供这个接口的目的其实是为了能实现某种类似多重继承的关系。

我们假设有一个用户使用了这个适配器:

class MyInput extends SimpleInputAdapter{
  @Override
  public void foo() {
    // do something ...
  }
  @Override
  public void bar() {
    super.bar();
    // do something additionally ...
  }
}
 


有了这个实现,用户可以和库进行交互了。注意这个实现是如何重写bar方法来给默认的实现增加额外的功能的。

那如果这个库迁移到Java 8的话会怎样?首先,这个库很可能会废弃掉这个适配器类并将这个功能迁移到默认方法里。最终这个接口看起来会是这样的:

interface SimpleInput {
  void foo();
  default void bar() {
    // some default behavior
  }
}}
 



有了这个新接口后,用户得更新他的代码来使用这个默认方法,而不再是适配器类了。使用新接口而非适配器类的一大好处就是,用户可以去继承一个别的类而不是这个适配器类了。我们来动手实践一下,将MyInput类改造成使用默认方法。由于现在我们可以继承别的类了,我们再额外地扩展一个第三方的基类试试。这个基类具体是做什么的在这里并不重要,我们先假设一下这么做对我们这个用例来说是有意义的。

class MyInput extends ThirdPartyBaseClass implements SimpleInput {
  @Override
  public void foo() {
    // do something ...
  }
  @Override
  public void bar() {
    SimpleInput.super.foo();
    // do something additionally ... 
  }
}
 


为了实现和原先那个类同样的功能,这里我们用到了Java 8的新语法来调用接口的默认方法。同样的,我们把myMethod的逻辑放到某个基类MyBase里面。可以捶捶肩膀放松下了。重构之后棒极了!

我们使用的这个库得到了很大的改进。然而,维护人员需要添加另一个接口来实现一些额外的功能。这个接口叫做CompexInput ,它继承了SimpleInput类,并增加了一个额外的方法。由于通常都认为默认方法是可以放心地添加的,因此维护人员重写了SimpleInput类的默认方法并添加了一些额外的动作来给用户提供一个更好的默认实现。毕竟使用适配器类的时候这个做法也十分常见:

interface ComplexInput extends SimpleInput {
  void qux();
  @Override
  default void bar() {
    SimpleInput.super.bar(); 
    // so complex, we need to do more ...
  }
} 
 


这个新特性看起来非常不错,因此ThirdPartyBaseClass类的维护人员也决定使用这个库了。为了实现这个,他将ThirdPartyBaseClass类实现了ComplexInput接口。

但这样的话对MyInput类意味着什么?由于它继承了ThirdPartyBaseClass类,因此默认实现了ComplexInput接口,这样的话调用SimpleInput的默认方法就不合法了。结果就是,用户的代码最后无法通过编译。还有就是,现在已经彻底无法调用这个方法了,因为Java把这种调用间接父类的super-super方法认为是不合法的。你只能去调用ComplexInput接口的默认方法了。然而这首先需要你在MyInput类中显式的实现一下这个接口。对于这个库的用户而言,这些改动完全是意想不到的。

很奇怪的是,Java在运行时并没有对这个进行区分。JVM的校验器允许一个编译过的类进行SimpleInput::foo方法的调用,尽管加载的这个类继承了ThirdPartyBaseClass的更新版本后隐式地实现了ComplexInput接口。要怪只能怪编译器了。(注:编译器与运行时的行为不一致)

那我们从中学到了什么?简单地说,不要在另一个接口中重写原接口的默认方法。不要用另一个默认方法来重写它,也不要某个抽象方法来重写它。总而言之,使用默认方法时应当十分谨慎。虽然它们使得Java现有的集合库的接口更容易改进了,但它允许你在类的继承结构中进行方法调用,这本质上其实是增加了复杂性。在Java 7以前,你只需遍历线性的类层次结构看一下实际调用的代码就可以了。当你觉得的确需要的时候,再去使用默认方法。

原创文章转载请注明出处:http://it.deepinmind.com

英文原文链接
1
1
分享到:
评论

相关推荐

    java代码优化编程

    11. 慎用异常:异常处理应仅用于错误处理,而不是控制程序流程。异常的创建和处理会影响性能。 12. 循环中的异常处理:避免在循环内部使用`try-catch`,将其移至循环外部,以减少异常处理的开销。 13. 设置`...

    Java开发手册笔记

    **【推荐】慎用`Object`的`clone`方法来拷贝对象** - **原因**:默认情况下,`clone`方法执行的是浅拷贝。若要实现深拷贝,需要重写`clone`方法并实现深度拷贝。 - **实践建议**:在需要深拷贝的情况下,考虑使用...

    Java String转换时为null的解决方法

    慎用方法2,对于不知道具体类型的情况下,可以使用方法3,只是转换后String的判null条件改为:if (!objStr.equals('null'))。 掌握Java String转换时为null的解决方法对我们在开发中是非常重要的,可以避免空指针...

    java代码效率优化.docx

    11. 慎用异常:异常对性能不利。抛出异常首先要创建一个新的对象。 Java 中的异常处理需要创建新的对象,使得性能下降。异常只能用于错误处理,不应该用来控制程序流程。 12. 不要在循环中使用:Try { } catch() {...

    java简答题总结pdf文档

    4. not in也要慎用 5. 在where子句中对字段使用like左侧模糊查询 6. 在where子句中对字段进行表达式操作 7. 在where子句中对字段进行函数操作 第9题:优化数据库 优化数据库可以通过以下方法: 1. 分库分表 2. ...

    Java基础系列

    - `del`:删除文件,如`del *.txt`删除所有`.txt`扩展名的文件,`del *.*`删除所有文件(慎用)。 - `exit`:退出命令提示符窗口。 - `cls`:清屏命令。 ##### 2. Java简介 - **Java概述**: - **定义**:...

    Java性能优化技巧集锦

    3. **慎用异常** - 异常处理不应作为常规控制流程的一部分,因为异常处理会带来额外的对象创建和堆栈调整开销。只在真正需要捕获错误时使用异常。 4. **避免重复初始化变量** - 避免在构造函数链中重复初始化变量...

    35 个 Java 代码性能优化总结

    7. **慎用异常** - **解释**:异常处理机制虽然强大,但其实现机制复杂且开销较大。 - **实践**:仅在处理错误或异常情况时使用异常,避免将其作为常规流程控制手段。 8. **不要在循环中使用try…catch…** - **...

    29个要点帮你完成java代码优化

    11. 慎用异常:异常处理不应作为常规流程,异常对象的创建和处理成本高,应仅用于处理错误或异常情况。 此外,还有一些其他优化技巧: 12. 使用集合类的批量操作:如`List`的`addAll()`,比逐一添加元素更高效。 ...

    怎样提高代码效率.doc

    11. 慎用异常:异常处理应仅用于错误处理,不应作为正常流程控制手段。异常的抛出和捕获会产生额外的性能开销。 12. 循环中的异常处理:尽量将`try-catch`块放在最外层,避免在循环内部使用,以减少异常处理的开销...

    jdbc基础和参考

    many-to-one:标签中对于cascade的取值delete,delete-orphan,all-delete-orphan(只用unique属性值不为true不能出现)慎用 cascade:级联属性 none:不做任何级联操作 save-update:对当前对象执行save,update, ...

    bigtable-sql基本使用1

    - "Rowcount"显示表行数,但大表时慎用,避免长时间无响应或内存溢出问题。 - SQL窗口执行查询,结果可导出为CSV、XML等格式。 ### 注意事项 7. **查询限制**:执行SQL时,尽可能加上`LIMIT`子句,防止返回大量...

    sample-interfaces

    6. **默认方法**:从Java 8开始,接口引入了默认方法,它们提供了一个默认的实现,允许在不破坏现有接口实现的情况下向接口添加新方法。 在"sample-interfaces-master"这个文件夹中,我们可以期待找到以下内容: -...

    Android getBackground().setAlpha遇到问题解决办法

    这主要是由于Android系统为了优化内存使用,会默认将同一资源加载的Drawable实例共享一个状态。 当我们在多个View中使用相同的背景资源时,如果不做特殊处理,修改其中一个View的背景透明度,会影响到其他View。这...

    MyBatis详细笔记.rar

    3. 慎用`SELECT *`,尽可能指定需要查询的列。 4. 合理利用缓存,但要注意缓存的一致性问题。 5. 对于大数据量的查询,考虑使用分页查询。 以上就是MyBatis的基本概念和核心功能,通过学习和实践,我们可以利用...

    Intellij IDEA使用总结

    - **`F1`**:帮助文档,建议慎用,以免造成界面卡顿; - **`CTRL+F4`**:关闭当前窗口。 #### 四、SVN 配置管理 1. **将 SVN 库添加到 IDEA 中**: - 进入 **Settings -> Version Control -> VCS = SVN**; - ...

Global site tag (gtag.js) - Google Analytics