【问题标题】:Java generic type bounded to classes with specific annotationJava 泛型类型绑定到具有特定注释的类
【发布时间】:2019-11-06 07:15:14
【问题描述】:

在java中可以将泛型参数绑定到实现特定接口的类,因此可以进行以下操作

interface MyInterface {}

class MyClassA implements MyInterface {}

class MyBoundedClassA<T extends MyInterface>

现在,如果我想将参数绑定到带有特定注释的类的接口,例如:

interface @MyAnnotation {}

@MyAnnotation
class MyClassB {}

class MyBoundedClassB<T extends MyAnnotation> // NOT possible

有可能在 Java 中实现这样的行为吗?

---- 编辑

应要求添加真实世界的示例。稍微修改域以使示例更易于理解。

有众所周知的用于序列化对象的杰克逊库。该库不支持字符串以外的映射键序列化,因此无法开箱即用地进行以下操作

class TimeRange {
  LocalDateTime startDate;
  LocalDateTime endDate;

}

class SportsmenActivities {
  private Map<String, <TimeRange, List<Activity>>  sportActivities;
}

在本例中,外部地图的键是“sportsmanCode”,如“andy”、“mike”、“john”。内部地图包含给定运动员在给定时期内进行的活动。

假设安迪慢跑了一天,而不是条目:

new SportsmanActivities().get("andy").put(TimeRange.of('2012-12-01,'2012-12-02'), List.with(new JoggingActivity)) // did some pseudo code here for readablity

现在正如所说,Jackson 不会开箱即用地序列化它,所以我编写了通用模块,它允许序列化这种复杂的地图。

要使用它,你需要做的是像这样注释你的“关键”类:

@KeySerializable
class TimeRange {
  @MyMapKey
  LocalDateTime startDate;
  @MyMapKey
  LocalDateTime endDate;

}

您可以猜到,带有@MyMapKey 注释的字段将用于生成 MapKey。

现在我有一个 jackson 类的实现,它动态序列化所有作为“文本映射键”传递的东西,并用 @KeySerializable 注释。签名如下

    class MyMapKeySerializer<T> extends JsonSerializer<T> {
      serialize (T keyToSerialize) { 
      // do magic 
      }

   }

这行得通,但我想限制 T 只接受带有 @KeySerializable 注释的类,因为只有这样的类才有意义。理想情况下,这将是这样的:

   class MyMapKeySerializer<T annotatedWith @KeySerializable> extends JsonSerializer<T> {
      serialize (T keyToSerialize) { 
      // do magic 
      }

   } 

【问题讨论】:

  • 注解是“元数据”,可以从注解的元素中查询。它们不被视为类/对象,这意味着它们不能与泛型一起使用。那你为什么需要这个机械师?我确信您的问题有其他解决方案。
  • @n247s:我不认为你是对的。注释是符合合同 Class extends Annotation> 并且每个注释都由这样的类表示。我需要这个机制来限制可以作为参数传递的类的数量。我想保证作为 T 传递的类使用特定注释进行注释(将任何其他类作为 T 传递在 MyBoundedClassB 的上下文中没有意义)
  • @walkeros 注解本身就是类的实例,但是“这个类/方法/变量有那个注解”不是类型系统的一部分。
  • @walkeros 可能,但不是。
  • Checker Framework 这样的工具插入编译器,以类似于您要求的方式限制通用实例化。 For exampleclass MyList&lt;T extends @NonNull Object&gt; {...}。我不知道这是否满足您的用例。您能否让您的示例更加真实,并解释您为什么需要该功能,以便我们了解其他解决方案是否可行?

标签: java generics annotations


【解决方案1】:

如果您的目标是断言只接受带注释的类,那么您几乎没有解决方法:

  1. 编写一个在编译时执行断言的注释处理器(查看@NonNull 等如何工作)。这是一项有趣的工作,但并非微不足道,因为编译/类型系统对于许多经验丰富的 Java 开发人员来说是全新的。
  2. 使用某种形式的 AOP(AspectJ、Spring AOP 等)通过装饰器“建议”所有带注释的方法,装饰器的职责是断言参数具有相同的注释。
  3. 在运行时使用parameter.getClass().isAnnotationPresent(MyAnnotation.class) 显式检查

【讨论】:

  • (+1) - 3 - parameter.getClass().isAnnotationPresent(MyAnnotation.class) 实际上是我正在做的,它有效,但它在运行时。 2 - 不可能,因为我在类级别而不是方法级别上有注释,1 - 这似乎很有趣。我会对此进行探索,但我想知道是否已经有类似的 java 内置机制。
  • 您会惊讶于 AspectJ 切入点的复杂程度。我确信它可以做到。不过,将 AspectJ 引入构建是对项目的重大改变。 eclipse.org/aspectj/doc/next/progguide/language-joinPoints.html
【解决方案2】:

Checker Framework 之类的工具插入编译器,以类似于您要求的方式限制泛型实例化。它是作为注解处理器实现的,它提供了正确使用的编译时保证。

For example,你可以写class MyList&lt;T extends @NonNull Object&gt; {...}

Checker 框架使您能够build your own checker,它强制执行您喜欢的关于@KeySerializable 的任何规则。在您的情况下,规则可能非常简单,您只需定义几个类型限定符并使用Subtyping Checker——至少一开始是这样。

请注意,要使 Checker Framework 使用 @KeySerializable 注释,该注释必须是类型注释而不是声明注释。

【讨论】:

  • 对 Checker Framework 的功能印象深刻。正是我想要的。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-13
  • 2018-09-30
  • 1970-01-01
  • 2014-10-06
  • 1970-01-01
  • 2017-08-23
相关资源
最近更新 更多