繁体   English   中英

避免表单 Foo 的通用类型<ActualType extends Foo<ActualType> &gt;

[英]Avoiding generic types of form Foo<ActualType extends Foo<ActualType>>

我经常发现自己想要编写表单的通用类定义

public class Foo<ActualType extends Foo<ActualType>>

例如在这样的设置中:

public interface ChangeHandler<SourceType> {
    public void onChange(SourceType source);
}


public class Foo<ActualType extends Foo<ActualType>> {

    private final List<ChangeHandler<ActualType>> handlers = new ArrayList<>();

    public void addChangeHandler(ChangeHandler<ActualType> handler) {
        handlers.add(handler);
    }

    @SuppressWarnings("unchecked")
    protected void reportChange() {
        for (ChangeHandler<ActualType> handler: handlers)
            handler.onChange((ActualType) this);
    }
}


public class Bar extends Foo<Bar> {
    // things happen in here that call super.reportChange();
}


public static void main(String[] args) throws IOException {

    Bar bar = new Bar();
    bar.addChangeHandler(new ChangeHandler<Bar>() {

        @Override
        public void onChange(Bar source) {
            // Do something with the changed object
        }
    });
}

这里的 change-event 只是一个例子。 这更像是一个普遍的问题,每当我想允许超类为每个特定的子类提供“个性化”的功能时(不知道如何更好地表达这一点......在示例中)在“个性化”之上是这样一个事实,即ChangeHandler是使用实际子类型 ( Bar ) 的对象而不是调用处理程序的超类 ( Foo ) 的类型调用的。

不知何故,这种方法对我来说似乎有点混乱。 它实际上允许潜在问题,因为没有什么可以阻止我定义:

public class Baz extends Foo<Bar> { /* ... */ }

有更清洁的替代品吗?

圣杯将是一些总是被定义为包含当前类的类型参数,比如this.getClass()的静态版本,它允许我写这样的东西:

public class Foo {

    private final List<ChangeHandler<this.Class>> handlers = new ArrayList<>();

    public void addChangeHandler(ChangeHandler<this.Class> handler) {
        handlers.add(handler);
    }

    protected void reportChange() {
        for (ChangeHandler<this.Class> handler: handlers)
            handler.onChange(this);
    }
}

对于Bar类型的类, this.Class将等于Bar

这是一个非常抽象的问题。 在我看来,“如何使这个更清洁”的简短回答是:只在需要的地方使用泛型。

public class List<T extends List<T>>

这是什么意思表达(替代)? 一个只允许保持(T扩展)其他列表的列表,这些列表本身就是我们之前知道的Ts(List),它们只是允许持有的列表......等等。 有点循环,我不知道你最终会怎么样?

public interface ChangeHandler<SourceType> {
    public void onChange(SourceType source);
}

你为什么要在这里使用泛型? 如果您想拥有一个可以处理多种资源类型的更改处理程序,那么您可以创建一个所有实际源继承的超类,或者创建一个由源实现的接口。 像这样,您可以准确指定源所公开的内容。 或者,源可以在通知时创建源对象,而不是传递“this”(然后它更像是一条消息)。 例如:

public interface ChangeHandler {
    public void onChange(Source source);
}

public abstract class Source {
    private List<ChangeHandler> handlers;
    protected int nr;
    public Source(int nr) {
      this.nr = nr;
    }
    public abstract Something getSomething();
    public String toString() {
        return "SRC" + nr;
    }
    ...
    private notify(int index) {
        handlers.get(i).onChange(this);
    }
}

public class Foo extends Source {
    public Foo(int nr) {
        super(nr);
    }
    public String toString() {
        return super.toString() + "Foo";
    }
    public Something getSomething() {
        return new Something();
    }
}

你永远不需要演员......或者你呢? 我不确定我是否理解这个问题。

我建议我们只使用<This>来表示“自我类型”。 不需要绑定,因为它看起来很复杂,没有提供意图,并且无论如何都不能强制执行约束。

public class Foo<This> {

    private final List<ChangeHandler<This>> handlers = new ArrayList<>();

    public void addChangeHandler(ChangeHandler<This> handler) {
        handlers.add(handler);
    }

    @SuppressWarnings("unchecked")
    protected void reportChange() {
        for (ChangeHandler<This> handler: handlers)
            handler.onChange( (This)this );
    }
}

注意演员(This)this

另请参阅Java泛型:使用此类型作为返回类型?

我从不使用类型参数来传递“ActualType”,因为这样就不可能扩展对象:

public class Bar extends Foo<Bar> {
    // things happen in here that call super.reportChange();
}
public class Bar2 extends Bar{
    // ...
}

Bar2 "ActualType" 仍然是 Bar,您无能为力:您将无法为 Bar2 使用 ChangeHandlers

为了避免这个问题,我看到的唯一可能的解决方法是将强制转换操作委托给另一个类(您也可以在 ChangeHandler 接口中使用默认方法)。 这是一种可能性:

public class Foo // no more type parameter
{
    private final List<FooCaster<?>> casterHandlers = new ArrayList<>();
    
    /**
     * unsafe because you could register a ChangerHandler of any type.
     * worst of all, everything is unchecked cast so the error could show up late.
     */
    public <T> void addChangeHandler(ChangeHandler<T> handler) {
        casterHandlers.add(new FooCaster<T>(handler));
    }

    protected void reportChange() {
        for (FooCaster<?> caster: casterHandlers) {
            caster.reportChange(this);
        }
    }
    
    class FooCaster<T> {
        protected ChangeHandler<T> ch;
        protected FooCaster(ChangeHandler<T> ch) {
            this.ch = ch;
        }
        
        @SuppressWarnings("unchecked")
        public void reportChange(Foo f) {
            ch.onChange((T)f);
        }
    }
}

Personnaly 在广播更改侦听器/更改处理程序的情况下,我倾向于将过程外部化到其他类,这使得可以正确使用参数类型并避免不安全的强制转换。如果您仍然愿意使用 reportChange() 从在 foo 对象中,这是一个可能的实现(否则您可以在 Broadcaster 中存储 T 引用)。

public class Broadcaster<T extends Foo> {
    protected final List<ChangeHandler<? super T>> changeHandlers;
    
    public Broadcaster() {
        this.changeHandlers = new ArrayList<>();
    }
    
    public void startListeningTo(T obj) {// only T type objects are accepted
        obj.registerBroadcaster(this);
    }
    
    public void addChangeHandler(ChangeHandler<? super T> changeHandler) {
        changeHandlers.add(changeHandler);
    }
    
    void reportChange(Foo obj) {
        T o = (T)obj;
        for(ChangeHandler<? super T> c : changeHandlers) {
            c.onChange(o);
        }
    }
}

public class Foo {
    private final List<Broadcaster<?>> broadcasters = new ArrayList<>();
    
    // cannot be accessed from outside of the package, only Broadcaster.startListeningTo(T o) can be used
    final void registerBroadcaster(Broadcaster<?> b) {
        broadcasters.add(b);
    }

    public final void reportChange() {
        for (Broadcaster<?> b: broadcasters) {
            b.reportChange(this);
        }
    }
}

public class Bar extends Foo {
    // things happen in here that call super.reportChange();
}

public static void test() {
    Broadcaster<Bar> broadcaster = new Broadcaster<>();
    broadcaster.addChangeHandler(new ChangeHandler<Bar>() {
        @Override
        public void onChange(Bar obj) {
            // do something
        }
    });
    
    //note that you can use the same broadcaster for multiple objects.
    Bar b = new Bar();
    broadcaster.startListeningTo(b);
    b.reportChange();
}

请注意,您将无法从 Bar 中添加 changeHandlers(但为自己注册 changeHandlers 真的是对象的工作吗?)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM