首页 / JAVA / Java中的通用流利生成器
Java中的通用流利生成器
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Java中的通用流利生成器,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含9648字,纯文字阅读大概需要14分钟。
内容图文
![Java中的通用流利生成器](/upload/InfoBanner/zyjiaocheng/825/1655aa062d754977a5efac5d35c9274f.jpg)
我知道有类似的问题.我没有看到我的问题的答案.
我会用一些简化的代码呈现我想要的东西.假设我有一个复杂的对象,它的一些值是通用的:
public static class SomeObject<T, S> {
public int number;
public T singleGeneric;
public List<S> listGeneric;
public SomeObject(int number, T singleGeneric, List<S> listGeneric) {
this.number = number;
this.singleGeneric = singleGeneric;
this.listGeneric = listGeneric;
}
}
我想用流畅的Builder语法构造它.我想让它变得优雅.我希望它能像那样工作:
SomeObject<String, Integer> works = new Builder() // not generic yet!
.withNumber(4)
// and only here we get "lifted";
// since now it's set on the Integer type for the list
.withList(new ArrayList<Integer>())
// and the decision to go with String type for the single value
// is made here:
.withTyped("something")
// we've gathered all the type info along the way
.create();
没有不安全的强制转换警告,也不需要预先指定泛型类型(在顶部,构建Builder的位置).
相反,我们让类型信息明确地流入链中 – 以及withList和withTyped调用.
现在,实现它的最优雅方式是什么?
我知道最常见的技巧,例如recursive generics的使用,但我玩了一段时间并且无法弄清楚它是如何应用于这个用例的.
下面是一个平凡的详细解决方案,它在满足所有要求的意义上起作用,但代价是冗长 – 它引入了四个构建器(在继承方面无关)??,表示定义与否的T和S类型的四种可能组合.
它确实有效,但这并不是一个值得骄傲的版本,如果我们期望更多的通用参数而不仅仅是两个,那就无法维护.
public static class Builder {
private int number;
public Builder withNumber(int number) {
this.number = number;
return this;
}
public <T> TypedBuilder<T> withTyped(T t) {
return new TypedBuilder<T>()
.withNumber(this.number)
.withTyped(t);
}
public <S> TypedListBuilder<S> withList(List<S> list) {
return new TypedListBuilder<S>()
.withNumber(number)
.withList(list);
}
}
public static class TypedListBuilder<S> {
private int number;
private List<S> list;
public TypedListBuilder<S> withList(List<S> list) {
this.list = list;
return this;
}
public <T> TypedBothBuilder<T, S> withTyped(T t) {
return new TypedBothBuilder<T, S>()
.withList(list)
.withNumber(number)
.withTyped(t);
}
public TypedListBuilder<S> withNumber(int number) {
this.number = number;
return this;
}
}
public static class TypedBothBuilder<T, S> {
private int number;
private List<S> list;
private T typed;
public TypedBothBuilder<T, S> withList(List<S> list) {
this.list = list;
return this;
}
public TypedBothBuilder<T, S> withTyped(T t) {
this.typed = t;
return this;
}
public TypedBothBuilder<T, S> withNumber(int number) {
this.number = number;
return this;
}
public SomeObject<T, S> create() {
return new SomeObject<>(number, typed, list);
}
}
public static class TypedBuilder<T> {
private int number;
private T typed;
private Builder builder = new Builder();
public TypedBuilder<T> withNumber(int value) {
this.number = value;
return this;
}
public TypedBuilder<T> withTyped(T t) {
typed = t;
return this;
}
public <S> TypedBothBuilder<T, S> withList(List<S> list) {
return new TypedBothBuilder<T, S>()
.withNumber(number)
.withTyped(typed)
.withList(list);
}
}
我可以应用更聪明的技术吗?
解决方法:
好的,所以更传统的步骤制作方法就是这样的.
不幸的是,因为我们正在混合泛型和非泛型方法,我们必须重新声明许多方法.我不认为这有很好的办法.
基本思路就是:定义接口上的每个步骤,然后在私有类上实现它们.我们可以通过继承原始类型来实现通用接口.这很难看,但它确实有效.
public interface NumberStep {
NumberStep withNumber(int number);
}
public interface NeitherDoneStep extends NumberStep {
@Override NeitherDoneStep withNumber(int number);
<T> TypeDoneStep<T> withTyped(T type);
<S> ListDoneStep<S> withList(List<S> list);
}
public interface TypeDoneStep<T> extends NumberStep {
@Override TypeDoneStep<T> withNumber(int number);
TypeDoneStep<T> withTyped(T type);
<S> BothDoneStep<T, S> withList(List<S> list);
}
public interface ListDoneStep<S> extends NumberStep {
@Override ListDoneStep<S> withNumber(int number);
<T> BothDoneStep<T, S> withTyped(T type);
ListDoneStep<S> withList(List<S> list);
}
public interface BothDoneStep<T, S> extends NumberStep {
@Override BothDoneStep<T, S> withNumber(int number);
BothDoneStep<T, S> withTyped(T type);
BothDoneStep<T, S> withList(List<S> list);
SomeObject<T, S> create();
}
@SuppressWarnings({"rawtypes","unchecked"})
private static final class BuilderImpl implements NeitherDoneStep, TypeDoneStep, ListDoneStep, BothDoneStep {
private final int number;
private final Object typed;
private final List list;
private BuilderImpl(int number, Object typed, List list) {
this.number = number;
this.typed = typed;
this.list = list;
}
@Override
public BuilderImpl withNumber(int number) {
return new BuilderImpl(number, this.typed, this.list);
}
@Override
public BuilderImpl withTyped(Object typed) {
// we could return 'this' at the risk of heap pollution
return new BuilderImpl(this.number, typed, this.list);
}
@Override
public BuilderImpl withList(List list) {
// we could return 'this' at the risk of heap pollution
return new BuilderImpl(this.number, this.typed, list);
}
@Override
public SomeObject create() {
return new SomeObject(number, typed, list);
}
}
// static factory
public static NeitherDoneStep builder() {
return new BuilderImpl(0, null, null);
}
由于我们不希望人们访问丑陋的实现,因此我们将其设为私有,并让每个人都通过静态方法.
否则它的工作方式与您自己的想法非常相似:
SomeObject<String, Integer> works =
SomeObject.builder()
.withNumber(4)
.withList(new ArrayList<Integer>())
.withTyped("something")
.create();
// we could return 'this' at the risk of heap pollution
这是关于什么的?好的,所以这里一般都有问题,就像这样:
NeitherDoneStep step = SomeObject.builder();
BothDoneStep<String, Integer> both =
step.withTyped("abc")
.withList(Arrays.asList(123));
// setting 'typed' to an Integer when
// we already set it to a String
step.withTyped(123);
SomeObject<String, Integer> oops = both.create();
如果我们没有创建副本,我们现在有123个伪装成String.
(如果您只使用构建器作为一组流畅的调用,则不会发生这种情况.)
虽然我们不需要为withNumber制作副本,但我只是采取了额外的步骤并使构建器不可变.我们创建的对象比我们要多,但是没有其他好的解决方案.如果每个人都以正确的方式使用构建器,那么我们可以使它变得可变并返回它.
由于我们对新颖的通用解决方案感兴趣,因此这是一个单独的类中的构建器实现.
这里的区别在于,如果我们第二次调用它们的任何一个setter,我们就不会保留typed和list的类型.这本身并不是一个缺点,我想它只是不同.这意味着我们可以这样做:
SomeObject<Long, String> =
SomeObject.builder()
.withType( new Integer(1) )
.withList( Arrays.asList("abc","def") )
.withType( new Long(1L) ) // <-- changing T here
.create();
public static class OneBuilder<T, S> {
private final int number;
private final T typed;
private final List<S> list;
private OneBuilder(int number, T typed, List<S> list) {
this.number = number;
this.typed = typed;
this.list = list;
}
public OneBuilder<T, S> withNumber(int number) {
return new OneBuilder<T, S>(number, this.typed, this.list);
}
public <TR> OneBuilder<TR, S> withTyped(TR typed) {
// we could return 'this' at the risk of heap pollution
return new OneBuilder<TR, S>(this.number, typed, this.list);
}
public <SR> OneBuilder<T, SR> withList(List<SR> list) {
// we could return 'this' at the risk of heap pollution
return new OneBuilder<T, SR>(this.number, this.typed, list);
}
public SomeObject<T, S> create() {
return new SomeObject<T, S>(number, typed, list);
}
}
// As a side note,
// we could return e.g. <?, ?> here if we wanted to restrict
// the return type of create() in the case that somebody
// calls it immediately.
// The type arguments we specify here are just whatever
// we want create() to return before withTyped(...) and
// withList(...) are each called at least once.
public static OneBuilder<Object, Object> builder() {
return new OneBuilder<Object, Object>(0, null, null);
}
创建副本和堆污染也是一样的.
现在我们变得非常新颖.这里的想法是我们可以通过导致捕获转换错误来“禁用”每个方法.
解释起来有点复杂,但基本思路是:
>每种方法都以某种方式依赖于在类上声明的类型变量.
>通过将其类型变量设置为?的返回类型来“禁用”该方法.
>如果我们尝试在该返回值上调用该方法,则会导致捕获转换错误.
这个例子和前面例子之间的区别在于,如果我们第二次尝试调用setter,我们将得到一个编译器错误:
SomeObject<Long, String> =
SomeObject.builder()
.withType( new Integer(1) )
.withList( Arrays.asList("abc","def") )
.withType( new Long(1L) ) // <-- compiler error here
.create();
因此,我们只能调用每个setter一次.
这里的两个主要缺点是你:
>出于正当理由,不能再次拨打二传手
>并且可以使用null文字第二次调用setter.
我认为这是一个非常有趣的概念验证,即使它有点不切实际.
public static class OneBuilder<T, S, TCAP, SCAP> {
private final int number;
private final T typed;
private final List<S> list;
private OneBuilder(int number, T typed, List<S> list) {
this.number = number;
this.typed = typed;
this.list = list;
}
public OneBuilder<T, S, TCAP, SCAP> withNumber(int number) {
return new OneBuilder<T, S, TCAP, SCAP>(number, this.typed, this.list);
}
public <TR extends TCAP> OneBuilder<TR, S, ?, SCAP> withTyped(TR typed) {
// we could return 'this' at the risk of heap pollution
return new OneBuilder<TR, S, TCAP, SCAP>(this.number, typed, this.list);
}
public <SR extends SCAP> OneBuilder<T, SR, TCAP, ?> withList(List<SR> list) {
// we could return 'this' at the risk of heap pollution
return new OneBuilder<T, SR, TCAP, SCAP>(this.number, this.typed, list);
}
public SomeObject<T, S> create() {
return new SomeObject<T, S>(number, typed, list);
}
}
// Same thing as the previous example,
// we could return <?, ?, Object, Object> if we wanted
// to restrict the return type of create() in the case
// that someone called it immediately.
// (The type arguments to TCAP and SCAP should stay
// Object because they are the initial bound of TR and SR.)
public static OneBuilder<Object, Object, Object, Object> builder() {
return new OneBuilder<Object, Object, Object, Object>(0, null, null);
}
同样,关于创建副本和堆污染也是如此.
无论如何,我希望这会给你一些想法让你陷入困境.
内容总结
以上是互联网集市为您收集整理的Java中的通用流利生成器全部内容,希望文章能够帮你解决Java中的通用流利生成器所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。