目录
不要用返回Null来表示一个空值
不要将数组作为API的传入参数和返回值
考虑添加静态接口方法以提供用于对象创建的单个入口点
青睐功能性接口和Lambdas的组合优于继承
确保你将@FunctionalInterface注解添加到功能性接口
避免使用功能性接口作为参数的重载方法
避免在接口中过度使用默认方法
确保在执行之前进行API方法的参数不变量检查
不要简单地调用Optional.get()
考虑在不同的API实现类中分行调用接口
首页 Java java教程 Java 8 API 设计经验浅析

Java 8 API 设计经验浅析

Feb 23, 2017 am 10:46 AM
api java8

本文由码农网 – 小峰原创翻译,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划!

任何写Java代码的人都是API设计师!无论编码者是否与他人共享代码,代码仍然被使用:要么其他人或他们自己使用,要么两者皆有。因此,对于所有的Java开发人员来说,了解良好API设计的基础很重要。

一个好的API设计需要仔细思考和大量的经验。幸运的是,我们可以从其他更聪明的人,如Ference Mihaly——正是他的博客启发我写了这篇Java 8 API附录——那里得到学习。在设计Speedment API时,我们非常依赖于他列出的接口清单。(我建议大家不妨读一读他的指南。)

从一开始就做到这一点很重要,因为一旦API发布,就会成为使用API的人坚实的基石。正如Joshua Bloch曾经说过的:“公共API,就像钻石一样永恒久远。你有机会把它做正确的话,就应该竭尽全力去做。”

一个精心设计的API结合了两个世界的精华,既是坚实而精确的基石,又具有高度的实施灵活性,最终让API设计师和API使用者受益。

至于为什么要使用接口清单?正确地获取API(即定义Java类集合的可见部分)比编写构成API背后实际工作的实现类要困难得多。它是一个真的很少有人掌握的艺术。使用接口清单允许读者避免最明显的错误,成为更好的程序员和节省大量的时间。

强烈建议API设计者将自己置于客户端代码的角度,并从简单性,易用性和一致性方面优化这个视图——而不是考虑实际的API实现。同时,他们应该尽量隐藏尽可能多的实现细节。

不要用返回Null来表示一个空值

可以证明,不一致的null处理(导致无处不在的NullPointerException)是历史上Java应用程序错误最大的唯一来源。一些开发人员将引入null概念当作是计算机科学领域犯的最糟糕的错误之一。幸运的是,减轻Java null处理问题的第一步是在Java 8中引入了Optional类。确保将返回值为空的方法返回Optional,而不是null。

这向API使用者清楚地表明了该方法可能返回值,也可能不返回值。不要因为性能原因的诱惑使用null而不使用Optional。反正Java 8的转义分析将优化掉大多数Optional对象。避免在参数和字段中使用Optional。

你可以这样写

public Optional<String> getComment() {
    return Optional.ofNullable(comment);
}
登录后复制


而不要这样写

public String getComment() {
    return comment; // comment is nullable
}
登录后复制


不要将数组作为API的传入参数和返回值

当Java 5中引入Enum概念时,出现了一个重大的API错误。我们都知道Enum类有一个名为values()的方法,用来返回所有Enum不同值的数组。现在,因为Java框架必须确保客户端代码不能更改Enum的值(例如,通过直接写入数组),因此必须得为每次调用value()方法生成内部数组的副本。

这导致了较差的性能和较差的客户端代码可用性。如果Enum返回一个不可修改的List,该List可以重用于每个调用,那么客户端代码可以访问更好和更有用的Enum值的模型。在一般情况下,如果API要返回一组元素,考虑公开Stream。这清楚地说明了结果是只读的(与具有set()方法的List相反)。

它还允许客户端代码容易地收集另一个数据结构中的元素或在运行中对它们进行操作。此外,API可以在元素变得可用时(例如,从文件,套接字或从数据库中拉入),延迟生成元素。同样,Java 8改进的转义分析将确保在Java堆上创建实际最少的对象。

也不要使用数组作为方法的输入参数,因为——除非创建数组的保护性副本——使得有可能另一个线程在方法执行期间修改数组的内容。

你可以这样写

public Stream<String> comments() {
    return Stream.of(comments);
}
登录后复制


而不要这样写

public String[] comments() {
    return comments; // Exposes the backing array!
}
登录后复制


考虑添加静态接口方法以提供用于对象创建的单个入口点

避免允许客户端代码直接选择接口的实现类。允许客户端代码创建实现类直接创建了一个更直接的API和客户端代码的耦合。它还使得API的基本功能更强,因为现在我们必须保持所有的实现类,就像它们可以从外部观察到,而不仅仅只是提交到接口。

考虑添加静态接口方法,以允许客户端代码来创建(可能为专用的)实现接口的对象。例如,如果我们有一个接口Point有两个方法int x() 和int y() ,那么我们可以显示一个静态方法Point.of( int x,int y) ,产出接口的(隐藏)实现。

所以,如果x和y都为零,那么我们可以返回一个特殊的实现类PointOrigoImpl(没有x或y字段),否则我们返回另一个保存给定x和y值的类PointImpl。确保实现类位于另一个明显不是API一部分的另一个包中(例如,将Point接口放在com.company。product.shape中,将实现放在com.company.product.internal.shape中)。

你可以这样写

Point point = Point.of(1,2);
登录后复制


而不要这样写

Point point = new PointImpl(1,2);
登录后复制


青睐功能性接口和Lambdas的组合优于继承

出于好的原因,对于任何给定的Java类,只能有一个超类。此外,在API中展示抽象或基类应该由客户端代码继承,这是一个非常大和有问题的API 功能。避免API继承,而考虑提供静态接口方法,采用一个或多个lambda参数,并将那些给定的lambdas应用到默认的内部API实现类。

这也创造了一个更清晰的关注点分离。例如,并非继承公共API类AbstractReader和覆盖抽象的空的handleError(IOException ioe),我们最好是在Reader接口中公开静态方法或构造器,接口使用Consumer 并将其应用于内部的通用ReaderImpl。

你可以这样写

Reader reader = Reader.builder()
    .withErrorHandler(IOException::printStackTrace)
    .build();
登录后复制


而不要这样写

Reader reader = new AbstractReader() {
    @Override
    public void handleError(IOException ioe) {
        ioe. printStackTrace();
    }
};
登录后复制


确保你将@FunctionalInterface注解添加到功能性接口

使用@FunctionalInterface注解标记的接口,表示API用户可以使用lambda实现接口,并且还可以通过防止抽象方法随后被意外添加到API中来确保接口对于lambdas保持长期使用。

你可以这样写

@FunctionalInterface
public interface CircleSegmentConstructor {
    CircleSegment apply(Point cntr, Point p, double ang);
    // abstract methods cannot be added
}
登录后复制


而不要这样写

public interface CircleSegmentConstructor {
    CircleSegment apply(Point cntr, Point p, double ang);
    // abstract methods may be accidently added later
}
登录后复制


避免使用功能性接口作为参数的重载方法

如果有两个或更多的具有相同名称的函数将功能性接口作为参数,那么这可能会在客户端侧导致lambda模糊。例如,如果有两个Point方法add(Function renderer) 和add(Predicate logCondition),并且我们尝试从客户端代码调用point.add(p -> p + “ lambda”) ,那么编译器会无法确定使用哪个方法,并产生错误。相反,请根据具体用途考虑命名方法。

你可以这样写

public interface Point {
    addRenderer(Function<Point, String> renderer);
    addLogCondition(Predicate<Point> logCondition);
}
登录后复制


而不要这样写

public interface Point {
    add(Function<Point, String> renderer);
    add(Predicate<Point> logCondition);
}
登录后复制


避免在接口中过度使用默认方法

默认方法可以很容易地添加到接口,有时这是有意义的。例如,想要一个对于任何实现类都期望是相同的并且在功能上要又短又“基本”的方法,那么一个可行的候选项就是默认实现。此外,当扩展API时,出于向后兼容性的原因,提供默认接口方法有时是有意义的。

众所周知,功能性接口只包含一个抽象方法,因此当必须添加其他方法时,默认方法提供了一个安全舱口。然而,通过用不必要的实现问题来污染API接口以避免API接口演变为实现类。如果有疑问,请考虑将方法逻辑移动到单独的实用程序类和/或将其放置在实现类中。

你可以这样写

public interface Line {
    Point start();
    Point end();
    int length();
}
登录后复制


而不要这样写

public interface Line {
    Point start();
    Point end();
    default int length() {
        int deltaX = start().x() - end().x();
        int deltaY = start().y() - end().y();
    return (int) Math.sqrt(
        deltaX * deltaX + deltaY * deltaY
        );
    }
}
登录后复制


确保在执行之前进行API方法的参数不变量检查

在历史上,人们一直草率地在确保验证方法输入参数。因此,当稍后发生结果错误时,真正的原因变得模糊并隐藏在堆栈跟踪下。确保在实现类中使用参数之前检查参数的空值和任何有效的范围约束或前提条件。不要因性能原因而跳过参数检查的诱惑。

JVM能够优化掉冗余检查并产生高效的代码。好好利用Objects.requireNonNull()方法。参数检查也是实施API约定的一个重要方法。如果不想API接受null但是却做了,用户会感到困惑。

你可以这样写

public void addToSegment(Segment segment, Point point) {
    Objects.requireNonNull(segment);
    Objects.requireNonNull(point);
    segment.add(point);
}
登录后复制


而不要这样写

public void addToSegment(Segment segment, Point point) {
    segment.add(point);
}
登录后复制


不要简单地调用Optional.get()

Java 8的API设计师犯了一个错误,在他们选择名称Optional.get()的时候,其实应该被命名为Optional.getOrThrow()或类似的东西。调用get()而没有检查一个值是否与Optional.isPresent()方法同在是一个非常常见的错误,这个错误完全否定了Optional原本承诺的null消除功能。考虑在API的实现类中使用任一Optional的其他方法,如map(),flatMap()或ifPresent(),或者确保在调用get()之前调用isPresent()。

你可以这样写

Optional<String> comment = // some Optional value 
String guiText = comment
  .map(c -> "Comment: " + c)
  .orElse("");
登录后复制


而不要这样写

Optional<String> comment = // some Optional value 
String guiText = "Comment: " + comment.get();
登录后复制


考虑在不同的API实现类中分行调用接口

最后,所有API都将包含错误。当接收来自于API用户的堆栈跟踪时,如果将不同的接口分割为不同的行,相比于在单行上表达更为简洁,而且确定错误的实际原因通常更容易。此外,代码可读性将提高。

你可以这样写

Stream.of("this", "is", "secret") 
  .map(toGreek()) 
  .map(encrypt()) 
  .collect(joining(" "));
登录后复制


而不要这样写

Stream.of("this", "is", "secret").map(toGreek()).map(encrypt()).collect(joining(" "));
登录后复制

           

 以上就是Java 8 API 设计经验浅析 的内容,更多相关内容请关注PHP中文网(www.php.cn)!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1655
14
CakePHP 教程
1414
52
Laravel 教程
1307
25
PHP教程
1254
29
C# 教程
1228
24
PHP和Manticore Search开发指南:快速创建搜索API PHP和Manticore Search开发指南:快速创建搜索API Aug 07, 2023 pm 06:05 PM

PHP和ManticoreSearch开发指南:快速创建搜索API搜索是现代Web应用程序中不可或缺的功能之一。无论是电子商务网站、社交媒体平台还是新闻门户,都需要提供一个高效、准确的搜索功能来帮助用户找到他们感兴趣的内容。而ManticoreSearch作为一个性能卓越的全文搜索引擎,为我们提供了一个强大的工具来创建出色的搜索API。本文将向您介绍如何

如何在PHP项目中通过调用API接口来实现数据的爬取和处理? 如何在PHP项目中通过调用API接口来实现数据的爬取和处理? Sep 05, 2023 am 08:41 AM

如何在PHP项目中通过调用API接口来实现数据的爬取和处理?一、介绍在PHP项目中,我们经常需要爬取其他网站的数据,并对这些数据进行处理。而许多网站提供了API接口,我们可以通过调用这些接口来获取数据。本文将介绍如何使用PHP来调用API接口,实现数据的爬取和处理。二、获取API接口的URL和参数在开始之前,我们需要先获取目标API接口的URL以及所需的

Oracle API使用指南:探索数据接口技术 Oracle API使用指南:探索数据接口技术 Mar 07, 2024 am 11:12 AM

Oracle是一家全球知名的数据库管理系统提供商,其API(ApplicationProgrammingInterface,应用程序接口)是一种强大的工具,可帮助开发人员轻松地与Oracle数据库进行交互和集成。在本文中,我们将深入探讨OracleAPI的使用指南,向读者展示如何在开发过程中利用数据接口技术,同时提供具体的代码示例。1.Oracle

React API调用指南:如何与后端API进行交互和数据传输 React API调用指南:如何与后端API进行交互和数据传输 Sep 26, 2023 am 10:19 AM

ReactAPI调用指南:如何与后端API进行交互和数据传输概述:在现代的Web开发中,与后端API进行交互和数据传输是一个常见的需求。React作为一个流行的前端框架,提供了一些强大的工具和功能来简化这一过程。本文将介绍如何使用React来调用后端API,包括基本的GET和POST请求,并提供具体的代码示例。安装所需的依赖:首先,确保在项目中安装了Axi

如何处理Laravel API报错问题 如何处理Laravel API报错问题 Mar 06, 2024 pm 05:18 PM

标题:如何处理LaravelAPI报错问题,需要具体代码示例在进行Laravel开发时,经常会遇到API报错的情况。这些报错可能来自于程序代码逻辑错误、数据库查询问题或是外部API请求失败等多种原因。如何处理这些报错是一个关键的问题,本文将通过具体的代码示例来演示如何有效处理LaravelAPI报错问题。1.错误处理在Laravel

Oracle API集成策略解析:实现系统间无缝通信 Oracle API集成策略解析:实现系统间无缝通信 Mar 07, 2024 pm 10:09 PM

OracleAPI集成策略解析:实现系统间无缝通信,需要具体代码示例在当今数字化时代,企业内部系统之间需要相互通信和数据共享,而OracleAPI就是帮助实现系统间无缝通信的重要工具之一。本文将从OracleAPI的基本概念和原理入手,探讨API集成的策略,最终给出具体的代码示例帮助读者更好地理解和应用OracleAPI。一、OracleAPI基本

开发建议:如何利用ThinkPHP框架进行API开发 开发建议:如何利用ThinkPHP框架进行API开发 Nov 22, 2023 pm 05:18 PM

开发建议:如何利用ThinkPHP框架进行API开发随着互联网的不断发展,API(ApplicationProgrammingInterface)的重要性也日益凸显。API是不同应用程序之间进行通信的桥梁,它可以实现数据共享、功能调用等操作,为开发者提供了相对简单和快速的开发方式。而ThinkPHP框架作为一款优秀的PHP开发框架,具有高效、可扩展和易用

使用Python将API数据保存为CSV格式 使用Python将API数据保存为CSV格式 Aug 31, 2023 pm 09:09 PM

在数据驱动的应用程序和分析领域,API(应用程序编程接口)在从各种来源检索数据方面发挥着至关重要的作用。使用API数据时,通常需要以易于访问和操作的格式存储数据。其中一种格式是CSV(逗号分隔值),它允许有效地组织和存储表格数据。本文将探讨使用强大的编程语言Python将API数据保存为CSV格式的过程。通过遵循本指南中概述的步骤,我们将学习如何从API检索数据、提取相关信息并将其存储在CSV文件中以供进一步分析和处理。让我们深入了解使用Python进行API数据处理的世界,并释放CSV格式的潜

See all articles