介绍

使用幻像类型是一种非常简单的技术,可用于提高代码的编译时安全性。 有许多潜在的用例具有不同的复杂性级别,但是即使幻像类型的使用非常轻巧,也可以显着提高编译时的安全性。 幻像类型只是带有未使用类型参数的参数化类型。 例如:

public class MyPhantomType<T> {public String sayHello() {return 'hello';}// other methods/fields that never refer to T
}

该示例类的类型参数为T,但实际上从未在代码中使用。 乍一看,这似乎没有什么用,但事实并非如此! 幻像类型的所有对象实例都带有类型信息,因此该技术可用于“标记”带有一些可在编译时检查的额外信息的值。 当然,我们可以在不使用泛型的情况下编写代码来随时逃避键入操作,但是应不惜一切代价避免这样做。 某些语言(例如Scala)完全不允许删除类型参数,因此使用Scala时,您将始终必须完全保留类型信息。

示例用例和实现

幻像类型最简单,最有用的用例之一是数据库ID。 如果我们有一个典型的三层(数据,服务,Web)Java Web应用程序,则可以通过在架构的“端点”以外的所有地方用幻像类型替换原始id来获得很多编译时安全性。 因此,数据层会将原始ID放入数据库查询中,而Web层可能会从外部资源(例如HTTP参数)获取原始ID,但是否则,我们始终会处理幻像类型。 在此示例中,我假设数据库ID类型始终为64位长数字。 首先,我们需要将由所有“实体类”实现的标记器接口,该接口应受幻像类型id机制支持:

public interface Entity {Long getId();
}

这个标记接口的唯一目的是将我们的幻像型id限制为一组标记的类,并提供将在实现中使用的getId方法。 实际的幻像类型是单个id值的不可变容器。 type参数表示id的“目标类型”,这使得可以以编译时安全的方式在不同实体的id值之间进行区分。 我喜欢将此类称为Ref(参考的简写),但这只是个人选择。

@Value
@RequiredArgsConstructor(AccessLevel.PRIVATE)
public final class Ref<T extends Entity> implements Serializable {public final long id;  public static <T extends Entity> Ref<T> of(T value) {return new Ref<T>(value.getId());}public static <T extends Entity> Ref<T> of(long id, Class<T> clazz) {return new Ref<T>(id);}@Overridepublic String toString() {return String.valueOf(id);}}

此示例类使用Project Lombok中的@Value和@RequiredArgsConstructor批注。 如果您不使用Lombok,请手动添加构造函数,getter,equals和hashCode实现(或在下面查找完整的实现)。 注意类型参数T永远不会在任何地方使用。 这也意味着您在运行时无法知道Ref的类型,但这通常不是必需的。

使用示例实现

现在,我们将在可能的情况下将原始ID替换为Refs。 例如,我们可以有一个将用户添加到组中的服务级别方法:

void addUserToGroup(long userId, long groupId);
// without parameter names
void addUserToGroup(long, long);// VSvoid addUserToGroup(Ref<User> userRef, Ref<Group> groupRef);
// without parameter names
void addUserToGroup(Ref<User>, Ref<Group>);

现在,当我们要调用此方法时,将始终需要Ref对象而不是原始的long值。 在此示例中,有两种获取参考值的方法。

  1. 如果您有实际对象的实例,请调用Ref.of(object)。 这是除Web以外的其他层中最常见的方法
  2. 如果您有原始ID,并且知道目标类型,请调用Ref.of(id,TargetType.class)。 如果原始ID来自外部,则通常在Web层中需要这样做

为了从Ref提取原始ID值,您可以阅读该字段或使用getter。 通常仅在构建数据库查询之前才需要这样做。

总结思想

为了了解裁判的好处,请尝试考虑以下情况:

  • 如果您在采用不同类型ID的方法调用中更改参数顺序,会发生什么情况? (例如我们的addUserToGroup)
  • 如果更改数据库ID的类型(例如Integer-> Long或Long-> UUID)会发生什么?
  • 如果您经常具有与id相同类型的方法参数,但它们不是id,那么您将有多大可能出现运行时错误? 例如,如果您有整数ID,并且在同一方法中混合了ID和某种列表索引

在所有这些情况下,使用Refs都可以确保在代码不正确的地方出现编译时错误。 在典型的代码库中,这是不费吹灰之力的巨大胜利。 编译时的安全性降低了重构的成本和难度,这使得维护代码库变得非常容易和安全。

数据库ID只是幻像类型的简单示例。 其他典型的用例包括某种状态机(例如,Order <InProcess>,Order <Completed>与仅Order对象),以及某种类型的值单元信息(例如,LongNumber <Weight>,LongNumber <Temperature>与longs) 。

Ref <T>实现(无Lombok)

public final class Ref<T extends Entity> implements Serializable {public final long id;public static <T extends Entity> Ref<T> of(T value) {return new Ref<T>(value.getId());}public static <T extends Entity> Ref<T> of(long id, Class<T> clazz) {return new Ref<T>(id);}@Overridepublic String toString() {return String.valueOf(id);}private Ref(long id) {this.id = id;}public long getId() {return this.id;}@Overridepublic int hashCode() {return (int) (id ^ (id >>> 32));}@Overridepublic boolean equals(Object o) {if (this == o)return true;if (o == null || o.getClass() != this.getClass())return false;Ref<?> other = (Ref<?>) o;return other.id == this.id;}
}

参考: Gekkio的技术博客博客中的JCG合作伙伴 Joonas Javanainen提出了幻像类型 , 提高了编译时安全性 。

翻译自: https://www.javacodegeeks.com/2013/02/increased-compile-time-safety-with-phantom-types.html

幻像类型提高了编译时的安全性相关推荐

  1. c#编译时提高兼容性_幻像类型提高了编译时的安全性

    c#编译时提高兼容性 介绍 使用幻像类型是一种非常简单的技术,可用于提高代码的编译时安全性. 有很多潜在的用例,其复杂性程度各不相同,但是即使幻像类型的轻量级使用也可以显着提高编译时的安全性. 幻像类 ...

  2. 佩伯尔幻像_幻像类型提高了编译时的安全性

    佩伯尔幻像 介绍 使用幻像类型是一种非常简单的技术,可用于提高代码的编译时安全性. 有很多潜在的用例具有不同的复杂性级别,但是即使幻像类型的轻量级使用也可以显着提高编译时的安全性. 幻像类型只是带有未 ...

  3. 编译时类型和运行时类型到底是什么?

    最近在看多态,里面有一句话困扰了我很久,就是 编译时类型和运行时类型 ps:多态定义:把子类对象直接赋给父类的引用时,当运行时调用该引用类型的方法时,其方法行为总是表现出子类的行为特征,而不是父类的行 ...

  4. iOS进阶--提高XCode编译速度、Xcode卡顿解决方案

    提升编译链接的速度主要有以下三个方式: 1. 提高XCode编译时使用的线程数 defaults write com.apple.Xcode PBXNumberOfParallelBuildSubta ...

  5. 编译时类型 和运行时类型的 区别(1)

    class T{ void f(int x){ System.out.println("int in T:" +x); } void f(double x){ System.out ...

  6. C++工作笔记-编译时类型检查与运行时类型检查

    转载链接如下: https://blog.csdn.net/u013298353/article/details/17676959 编译时 编译时顾名思义就是正在编译的时候.那啥叫编译呢?就是编译器帮 ...

  7. java 运行 对象_java实例对象的编译时类型和运行时类型

    为什么要区分编译时类型和运行时类型? 看这样一句代码:Person p=new Women()(Women类继承自Person类)那么,假如p的属性修饰符为public 访问属性时得到的是Person ...

  8. WPF 在编译时 显示 CS0426 类型“xxx”中不存在类型名“xxx”

    WPF 程序在编译时显示 CS0426 类型"xxx"中不存在类型名"xxx": 具体原因: 1. 命名空间和 类名称相同造成的. 一般报错,报在XAML 中的 ...

  9. java 类没有返回类型,返回类型的Java方法编译时没有return语句

    问题 问题1: 为什么以下代码在没有return语句的情况下编译? public int a() { while(true); } 注意:如果我在一段时间后添加返回,那么我得到aUnreachable ...

最新文章

  1. 鱼佬:从数据竞赛到工作!
  2. 用户金字塔模型详解及在实际运营工作中的意义
  3. 什么是机器人的五点校正法_Epson机器人原点校准命令及方法(详细解释指令)
  4. xhr如何发送post请求_js实现ajax的post请求步骤
  5. FineReport连接mysql8.0.16
  6. 【洛谷P2743】【poj 1743】[USACO5.1]乐曲主题Musical Themes
  7. ddr3服务器内存条维修,详解服务器中内存故障的优质解决方案
  8. (27)python-多维数组
  9. GPS之家论坛最新地图下载精选 汇集论坛精华 不断更新中(2013.3.30)
  10. 面向对象七大设计原则
  11. 上传文件计算机传输的,电脑文件怎么传输到iPad
  12. CentOS装docker
  13. 作为一个渗透测试学习者必知必读的好书推荐
  14. just for save
  15. 详解旨在提升EVM底层性能的兼容公链Monad
  16. bi比较好的公司,bi商业智能软件排名
  17. 数据库编程基本练习题
  18. air游戏接入小米支付sdk
  19. java队名_求JAVA团队名称
  20. oracle12 expdb,12c导出导入用expdp

热门文章

  1. java填充线缺口,在geom_freqpoly线下填充区域的最简单方法是什么?
  2. qt弹簧教程_弹簧启动执行器教程
  3. intellij远程调试_IntelliJ中的远程调试Wildfly应用程序
  4. java关闭窗口函数_2016年将是Java终于拥有窗口函数的那一年!
  5. 新增操作 失败后重试_可重试的操作
  6. eap aka_使用API​​密钥(aka身份验证令牌)部署到Maven Central
  7. observable_在Completablefuture和Observable之间转换
  8. saml2_向SAML响应中添加自定义声明–(如何为WSO2 Identity Server编写自定义声明处理程序)...
  9. JDK 13:VM.events已添加到jcmd
  10. java中重载 参数顺序_Java方法中的参数太多,第4部分:重载