猿问

编译时的泛型重载

是否可以设计一种在编译时调用不同方法重载的方法?


可以说,我有这个小班:


@RequiredArgsConstructor

public 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();

//or

new 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。


杨__羊羊
浏览 154回答 2
2回答

慕盖茨4494581

诀窍是什么?不要这样做。提供静态工厂方法:class BaseValidator<T> {&nbsp; static ValidationString getValidation(String newValue) {&nbsp; &nbsp; return new ValidationString(newValue);&nbsp; }&nbsp; static ValidationInteger getValidation(Integer newValue) {&nbsp; &nbsp; return new ValidationInteger(newValue);&nbsp; }}class ValidationString extends BaseValidator<String> { ... }class ValidationInteger extends BaseValidator<Integer> { ... }尽管我认为这很奇怪:您指的是基类中的子类。这种循环依赖使代码难以使用,尤其是在重构时,但也可能在初始化时。相反,我建议创建一个实用程序类来包含工厂方法:class Validators {&nbsp; private Validators() {}&nbsp; static ValidationString getValidation(String newValue) {&nbsp; &nbsp; return new ValidationString(newValue);&nbsp; }&nbsp; static ValidationInteger getValidation(Integer newValue) {&nbsp; &nbsp; return new ValidationInteger(newValue);&nbsp; }}没有这样的循环。关于泛型,需要意识到的一件非常重要的事情是,它只不过是使显式强制转换为隐式(然后检查所有这些隐式强制转换是否是类型安全的)。换句话说,这: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> {&nbsp; void validate(T b);}class BaseValidator<T> {&nbsp; BaseValidator<T> validate(Validator<T> v) {&nbsp; &nbsp; v.validate(this.value);&nbsp; }}并像这样调用,演示如何链接方法调用以应用多个验证:new BaseValidator<>("")&nbsp; &nbsp; .validate(s -> !s.isEmpty())&nbsp; &nbsp; .validate(s -> s.matches("pattern"))&nbsp; &nbsp; ...new BaseValidator<>(123)&nbsp; &nbsp; .validate(v -> v >= 0)&nbsp; &nbsp; ...

慕田峪9158850

我们决定添加更多的类步骤。您可以采用通用方式或使用显式类型的方式(在此示例中,String)。我们对所有更新方法的要求(我们有许多数据库对象......)有点复杂。我们只想要一个更新方法(对于每个数据库对象),它...忽略字段,即为空。忽略等于“旧”值的字段。验证未被忽略的字段。仅在没有发生验证问题时保存。用许多 if 块来做到这一点是可能的,但不是真正可读的。并且复制粘贴失败的可能性很高。我们的代码如下所示:private void update(@NonNull final User.UpdateFinalStep params) {&nbsp; &nbsp; UpdateWizard.update(dbUserService.get(params.getId())&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .field(params.getStatus())&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .withGetter(DbUser::getAccountStatus)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .withSetter(DbUser::setAccountStatus)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .finishField()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .field(Optional.ofNullable(params.getUsername())&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;.map(String::toLowerCase)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;.orElse(null))&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .withGetter(DbUser::getUsername)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .withSetter(DbUser::setUsername)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .beginValidationOfField(FieldName.USERNAME)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .notEmptyAndMatchPattern(USERNAME_PATTERN, () -> this.checkUniqueUsername(params.getUsername(), params.getId()))&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .endValidation()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .field(params.getLastName())&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .withGetter(DbUser::getLastname)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .withSetter(DbUser::setLastname)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .beginValidationOfField(FieldName.USER_LASTNAME)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .notEmptyAndMatchPattern(LAST_NAME_PATTERN)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .endValidation()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .field(params.getFirstName())&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .withGetter(DbUser::getFirstname)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .withSetter(DbUser::setFirstname)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .beginValidationOfField(FieldName.USER_FIRSTNAME)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .notEmptyAndMatchPattern(FIRST_NAME_PATTERN)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .endValidation()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .save(dbUserService::save);}这是非常易读的,并且允许以非常简单的方式添加新字段。使用泛型,我们不会给“愚蠢的开发者”犯错的机会。如图所示,accountStatus 和 username 指向不同的类。最后,我们可以非常流畅地使用更新方法:userService.startUpdate() &nbsp;&nbsp;&nbsp;.withId(currentUserId) &nbsp;&nbsp;&nbsp;.setStatus(AccountStatus.INACTIVE) &nbsp;&nbsp;&nbsp;.finallyUpdate();
随时随地看视频慕课网APP

相关分类

Java
我要回答