为了账号安全,请及时绑定邮箱和手机立即绑定

编译时的泛型重载

编译时的泛型重载

杨__羊羊 2022-06-30 11:41:53
是否可以设计一种在编译时调用不同方法重载的方法?可以说,我有这个小班:@RequiredArgsConstructorpublic class BaseValidator<T> {    private final T newValue;}现在,我需要返回不同对象的方法(取决于T)。像这样:private StringValidator getValidator() {    return new ValidationString(newValue);}private IntegerValidator getValidator() {    return new Validation(newValue);}最后,我想要一个非常流畅的调用层次结构,看起来像这样:new BaseValidator("string")    .getValidator() // which returns now at compile-time a StringValidator    .checkIsNotEmpty();//ornew BaseValidator(43)    .getValidator() // which returns now a IntegerValidator    .checkIsBiggerThan(42);在我的“真实”案例中(我有一种非常具体的方法来更新对象和每个对象的很多条件,并且复制和粘贴问题的可能性非常高。所以向导强制所有开发人员实施精确这边走。) : 理想图像我尝试了不同的方法。验证器中的复杂泛型,或使用泛型。我的最后一个方法看起来像这样。public <C> C getValidator() {    return (C) getValidation(newValue);}private ValidationString getValidation(String newValue) {    return new StringValidator(newValue);}private ValidationInteger getValidation(Integer newValue) {    return new IntegerValidation(newValue);}诀窍是什么?//编辑:我希望它在编译时而不是instanceof在运行时使用 -checks。
查看完整描述

2 回答

?
慕盖茨4494581

TA贡献1850条经验 获得超11个赞

诀窍是什么?


不要这样做。


提供静态工厂方法:


class BaseValidator<T> {

  static ValidationString getValidation(String newValue) {

    return new ValidationString(newValue);

  }


  static ValidationInteger getValidation(Integer newValue) {

    return new ValidationInteger(newValue);

  }

}


class ValidationString extends BaseValidator<String> { ... }

class ValidationInteger extends BaseValidator<Integer> { ... }

尽管我认为这很奇怪:您指的是基类中的子类。这种循环依赖使代码难以使用,尤其是在重构时,但也可能在初始化时。


相反,我建议创建一个实用程序类来包含工厂方法:


class Validators {

  private Validators() {}


  static ValidationString getValidation(String newValue) {

    return new ValidationString(newValue);

  }


  static ValidationInteger getValidation(Integer newValue) {

    return new ValidationInteger(newValue);

  }

}

没有这样的循环。


关于泛型,需要意识到的一件非常重要的事情是,它只不过是使显式强制转换为隐式(然后检查所有这些隐式强制转换是否是类型安全的)。


换句话说,这:


List<String> list = new ArrayList<>();

list.add("foo");

System.out.println(list.get(0).length());

只是一种更好的写作方式:


List list = new ArrayList();

list.add((String) "foo");

System.out.println(((String) list.get(0)).length());

虽然<String>看起来它是类型的一部分,但它基本上只是对编译器的一条指令,用于喷射大量强制转换。


具有不同类型参数的泛型类都具有相同的方法。这是您的方法中的具体困难:您不能BaseValidator<String>.getValidator()用checkIsNotEmpty方法(仅)BaseValidator<Integer>.getValidator()返回某些东西,而用方法(仅)返回某些东西checkIsGreaterThan。


好吧,说你不能,这并不完全正确。在您尝试涉及方法范围的类型变量 ( <C> C getValidator()) 时,您可以编写:


new BaseValidator<>("string").<StringValidator>getValidator().checkIsNotEmpty()

(假设上面StringValidator有checkIsNotEmpty方法)


但:


让我们不要拐弯抹角:它是丑陋的。

比丑陋更糟糕的是,它不是类型安全的。你同样可以写:


新 BaseValidator<>("string").getValidator().checkIsGreaterThan(42)


这是荒谬的,但编译器允许。问题是在调用站点选择了返回类型:您要么必须返回 null (并NullPointerException在您尝试调用以下方法时得到 a );或返回一些非空值并冒险 a ClassCastException。无论哪种方式:不好。


但是,您可以做的是使通用验证器成为方法调用的参数。例如:


interface Validator<T> {

  void validate(T b);

}


class BaseValidator<T> {

  BaseValidator<T> validate(Validator<T> v) {

    v.validate(this.value);

  }

}

并像这样调用,演示如何链接方法调用以应用多个验证:


new BaseValidator<>("")

    .validate(s -> !s.isEmpty())

    .validate(s -> s.matches("pattern"))

    ...


new BaseValidator<>(123)

    .validate(v -> v >= 0)

    ...


查看完整回答
反对 回复 2022-06-30
?
慕田峪9158850

TA贡献1794条经验 获得超8个赞

我们决定添加更多的类步骤。您可以采用通用方式或使用显式类型的方式(在此示例中,String)。我们对所有更新方法的要求(我们有许多数据库对象......)有点复杂。我们只想要一个更新方法(对于每个数据库对象),它...

  1. 忽略字段,即为空。

  2. 忽略等于“旧”值的字段。

  3. 验证未被忽略的字段。

  4. 仅在没有发生验证问题时保存。

用许多 if 块来做到这一点是可能的,但不是真正可读的。并且复制粘贴失败的可能性很高。

我们的代码如下所示:

private void update(@NonNull final User.UpdateFinalStep params) {


    UpdateWizard.update(dbUserService.get(params.getId())


            .field(params.getStatus())

            .withGetter(DbUser::getAccountStatus)

            .withSetter(DbUser::setAccountStatus)

            .finishField()


            .field(Optional.ofNullable(params.getUsername())

               .map(String::toLowerCase)

               .orElse(null))

            .withGetter(DbUser::getUsername)

            .withSetter(DbUser::setUsername)

            .beginValidationOfField(FieldName.USERNAME)

            .notEmptyAndMatchPattern(USERNAME_PATTERN, () -> this.checkUniqueUsername(params.getUsername(), params.getId()))

            .endValidation()


            .field(params.getLastName())

            .withGetter(DbUser::getLastname)

            .withSetter(DbUser::setLastname)

            .beginValidationOfField(FieldName.USER_LASTNAME)

            .notEmptyAndMatchPattern(LAST_NAME_PATTERN)

            .endValidation()


            .field(params.getFirstName())

            .withGetter(DbUser::getFirstname)

            .withSetter(DbUser::setFirstname)

            .beginValidationOfField(FieldName.USER_FIRSTNAME)

            .notEmptyAndMatchPattern(FIRST_NAME_PATTERN)

            .endValidation()


            .save(dbUserService::save);

}

这是非常易读的,并且允许以非常简单的方式添加新字段。使用泛型,我们不会给“愚蠢的开发者”犯错的机会。


如图所示,accountStatus 和 username 指向不同的类。

//img1.sycdn.imooc.com//62bd1bb10001285711100425.jpg

最后,我们可以非常流畅地使用更新方法:

userService.startUpdate()
   .withId(currentUserId)
   .setStatus(AccountStatus.INACTIVE)
   .finallyUpdate();


查看完整回答
反对 回复 2022-06-30
  • 2 回答
  • 0 关注
  • 153 浏览

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号