34

String在 Java 中是不可变的。从广义上讲,以下代码段是“错误的”。

String s = "hello world!";

s.toUpperCase(); // "wrong"!!

System.out.println(s); // still "hello world!"!!!

尽管这是“错误的”,但代码还是可以编译和运行,这可能会让许多初学者感到困惑,他们必须要么被告知错误是什么,要么通过查阅文档自己找出答案。

阅读文档是理解 API 的重要组成部分,但我想知道这是否可以通过额外的编译时检查来补充。特别是,我想知道是否可以使用 Java 的注释框架来强制某些方法返回的值不被忽略。然后 API 设计者/库作者将在他们的方法中使用此注释来记录不应忽略哪些返回值。

一旦 API 补充了这个注解(或者可能是另一种机制),那么每当用户编写上述代码时,它就不会编译(或者会发出严厉的警告)。

那么这可以做到吗,你将如何做这样的事情?


附录:动机

显然,在一般情况下,Java应该允许忽略方法的返回值。List.add总是 true)、 (前一个值)之类的方法的返回值System.setProperty可能在大多数情况下都可以安全地忽略。

但是,也有许多方法的返回值不应忽略。这样做几乎总是一个程序员错误,或者不是正确使用 API。其中包括:

  • 不可变类型(例如String,BigInteger等)上的方法返回操作的结果,而不是改变调用它的实例。
  • 其返回值是其行为的关键部分且不应被忽略的方法,但人们有时还是会这样做(例如InputStream.read(byte[])返回读取的字节数,不应假定为数组的整个长度)

目前我们可以编写忽略这些返回值的代码,并让它们在没有警告的情况下编译和运行。静态分析检查器/错误查找器/样式执行器/等几乎可以肯定地将这些标记为可能的代码异味,但如果这可以由 API 本身(可能通过注释)强制执行,这似乎是合适/理想的。

一个类几乎不可能确保它总是“正确”使用,但是它可以做一些事情来帮助指导客户正确使用(参见:Effective Java 2nd Edition,Item 58: Use checked exceptions for recovery conditions and编程错误的运行时异常第 62 条:记录每个方法抛出的所有异常)。有一个注释可以强制客户端不忽略某些方法的返回值,并让编译器在编译时以错误或警告的形式强制执行,似乎符合这个想法。


附录 2:片段

以下是一个初步尝试,简洁地说明了我想要实现的目标:

@interface Undiscardable { }
//attachable to methods to indicate that its
//return value must not be discarded

public class UndiscardableTest {
     public static @Undiscardable int f() {
             return 42;
     }

     public static void main(String[] args) {
             f(); // what do I have to do so this generates
                  // compilation warning/error?

             System.out.println(f()); // this one would be fine!
     }
}

上面的代码编译并运行良好(见 ideone.com)。我怎样才能让它不是这样?如何分配我想要的语义@Undiscardable

4

7 回答 7

12

您还可以查看 jsr305。它定义了一个@CheckReturnValue注释:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.annotation.meta.When;

@Documented
@Target( { ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE,
        ElementType.PACKAGE })
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckReturnValue {
    When when() default When.ALWAYS;
}

它与 findbugs 兼容,并在有人忘记处理返回值时生成警告。

Guavas Splitter 使用它: http ://code.google.com/p/guava-libraries/source/browse/guava/src/com/google/common/base/Splitter.java

我必须说我喜欢可以指导静态代码分析的注释。

于 2012-03-08T19:59:21.177 回答
8

我不确定可行性——尤其是在可移植的方式中——但请看一下Adrian Kuhn的 Java (GitHub 代码中的罗马数字。他使用注释处理Sun 的私有 API通过访问源代码进行一些替换来将罗马数字文字添加到 Java中。 javac

也许您可以使用类似的方法:

  • 在源代码中查找对您的注释方法的调用
  • 检查结果是否已分配(IMO 不容易)
  • 如果不是,则生成编译器警告

不要错过 Adrian 帖子中的以下资源:

你也许也喜欢

参考

相关问题

于 2010-09-01T02:39:07.933 回答
2

简而言之:您希望有一个@Deprecated类似的注释,它可以帮助编译器/IDE 在调用该方法而不分配其结果时发出警告/错误?如果不修改 Java 源代码和编译器,就无法实现这一点。必须对特定方法进行注释,并且编译器必须知道它们。在不修改源代码和/或编译器的情况下,您最多可以创建一种 IDE 插件/设置,它可以识别这些情况并相应地生成错误/警告。


更新:您可以围绕它编写一个框架/插件,以相应地检查调用的方法和错误。您只想在运行时使用注释。您可以通过使用 注释注释来做到这一点。然后,您可以使用来确定此注释是否可用。这是一个启动示例,这样的框架如何完成这项工作:@Retention (RetentionPolicy.RUNTIME)Method#getAnnotation()

package com.example;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public class Test {

    public static void main(String[] args) throws Exception {
        if (Test.class.getMethod("f", new Class[0]).getAnnotation(Undiscardable.class) != null) {
            System.err.println("You should not discard the return value of f()!");
        } else {
            f();
        }

        System.out.println(f());
    }

    public static @Undiscardable int f() {
        return 42;
    }
}

@Retention(RetentionPolicy.RUNTIME)
@interface Undiscardable {}

尽管如此,要让编译器完成这项工作,您还需要做更多的工作。

于 2010-09-01T00:28:38.997 回答
2

@CheckResult在 Android 上,如果未使用返回值,您可以使用它来显示警告。

public class ImmutableObject {

    public final int value;

    public ImmutableObject(int value) {
        this.value = value;
    }

    @CheckResult
    public ImmutableObject addOne() {
        return new ImmutableObject(value + 1);
    }
}

这将发出警告:

ImmutableObject obj = new ImmutableObj();
obj.addOne();  // Warning here
ImmutableObject obj2 = obj.addOne();  // No warning

如果使用 RxJava,你也可以使用@CheckReturnValue.

于 2018-09-24T17:03:38.353 回答
1

免责声明:实际上,我有同样的问题,但还没有一个完整的解决方案。但:

我有一个想法如何以一种干净的方式完成,我想在这里发布,同时尝试完成它:

  1. 可以在调用特定方法后使用 AspectJ 调用代码。例如

    @AfterReturning(切入点=“调用(int Foo.m(int))”,返回=“x”)
    公共无效 doSomething(int x){ ... }
    可用于。返回值 x 被传递给您的方法。

  2. 然后,您的方法可以观察返回值的引用计数。如果返回值为 Garbadge Collected,则它已被丢弃,您可以发出警告,请参阅 http://java.dzone.com/articles/letting-garbage-collector-do-c

当然,我更喜欢注释和编译时支持,因为上面可能只适用于测试环境,可能不适用于生产(由于其性能影响)。

如果这可行,有什么评论吗?

于 2013-06-29T20:01:05.617 回答
1

您不需要定义注释。您可以在调用方法时定义规则:

  1. 该方法具有 void 返回类型;
  2. 该方法的结果用作另一个方法调用的参数;或者
  3. 该方法的结果被分配给一个变量。

您可以实现执行此规则的处理器或执行执行此规则的 Checkstyle。

于 2010-09-01T01:02:14.610 回答
0

你有一个问题,问题是人们可能会错误地忘记使用方法的返回。通过使用注释,您告诉库编写者他们必须负责提醒调用者不要丢弃某些方法的结果。

虽然这似乎是个好主意,但我认为不是。我们是否希望通过向用户通知他们的不良做法来混淆代码?有很多产品可以查看代码并在您做错(或不受欢迎)的事情时告诉您,例如 Lint、Sonar 甚至 JavaDocs(在较小程度上)。

如果您不同意库作者所说的话,我们现在是否应该使用@SuppressWarnings("return-discarded")。

虽然这可能有助于作为学习帮助,但我的观点更多的是关注点分离,而不是帮助新手程序员。类中的代码(和注释)应该与类的功能相关,而不是规定何时以及如何使用它的方法的策略。

于 2016-09-26T13:24:38.823 回答