【问题标题】:Avoiding generic types of form Foo<ActualType extends Foo<ActualType>>避免表单的泛型类型 For<Actual Type extends Foo<Actual Type>>
【发布时间】:2015-05-21 19:36:43
【问题描述】:

我经常发现自己想要编写格式为

的通用类定义
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
        }
    });
}

这里的更改事件只是一个示例。每当我想允许超类为每个特定的子类提供“个性化”的功能时,这更像是一个普遍的问题(不知道如何更好地表达这一点......在示例中在“个性化”之上的事实是,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

【问题讨论】:

  • 这是 Java 版本的 CRTP。这很典型。
  • 圣杯会很好,但它不存在(还)。由于继承,静态检查似乎很困难。
  • 你不能做Foo&lt;T extends Foo&lt;?&gt;&gt;,用T替换所有出现的ActualType吗?不再需要ActualType。不确定这是否是您所说的“更清洁”
  • @Radiodef 也许可以隐式地将&lt;ActualType extends Foo&lt;ActualType&gt;&gt; 构造添加到每个类定义中并以这种方式处理它?
  • 很难静态检查的情况是这样的:假设你有一个class Foo { void m(this.Class in) {} } 和一个class Bar extends Foo {}。现在假设您有一个Foo foo = new Bar();foo.m 应该接受什么类型?

标签: java generics


【解决方案1】:

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

public class List<T extends List<T>>

这是试图表达(替代)什么?一个只允许保存(T 扩展)其他列表的列表,这些列表本身保存有 Ts(列表),正如我们之前所知,这些列表是只允许保存的列表......等等。有点循环,我不明白你会怎么做这样的事情?

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

你永远不需要施放……是吗?我不确定我是否理解这个问题。

【讨论】:

  • 假设您的Foo 具有Source 没有的某些属性(例如title),您希望能够编写一个ChangeHandler 传递一个@ 987654328@ 类型为Foo,所以它可以做类似myLabel.setText(source.getTitle()); 的事情。在您的提案中,source(在onChange 内)的类型为Source,即没有标题。因此,你需要到处写myLabel.setText(((Foo) source).getTitle()); 之类的东西。从技术上讲,既然你现在可以将这个ChangeHandler 传递给其他Sources,你应该首先确保source 甚至是Foo 类型。
  • @MarkusA。如果您想在源代码中包含标题,那么您应该在 Source 类中包含 public abstract String getTitle();。但我猜你正在寻找不是每个来源都有标题的东西,如果你需要强制转换,如果可能的话,你想在编译时检测到错误吗?
  • 完全正确!事实上,在许多情况下,Source 可以如此笼统,以至于它甚至不必知道其潜在的子类。特别是如果它是图书馆的一部分。以我的问题中的Foo 课程为例。您可以设想将其重命名为 AbstractChangeReportingObject 并将其粘贴在库中,以便为任何潜在的子类(无论它们可能是什么)轻松提供变更处理程序管理。但是更改处理程序仍然希望传递他们正在监视的对象类型的源,因为 AbstractChangeReportingObject 将毫无用处......
  • @MarkusA。很有趣。我想要获得一个非常好的答案,您需要 Java 库设计方面的专家。如果它不是一个库,你可以有一个(抽象的)子类 TitledSource 覆盖(当然不能将其设为私有)notify(int index) 并进行强制转换。您还可以扩展接口(@98​​7654346@ 和 onChange(TitledSource src);)并收紧方法以添加侦听器,例如addChangeHandler(TitledSource src) in TitledSource... 可以在编译时检测错误,但不够灵活。
【解决方案2】:

我建议我们简单地使用&lt;This&gt; 来表示“自我类型”。不需要绑定,因为它看起来很复杂,没有传达意图,也无法强制执行约束。

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 generics: Use this type as return type?

【讨论】:

    【解决方案3】:

    我从不使用类型参数来传递“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);
            }
        }
    }
    

    就个人而言,在将更改广播到侦听器/更改处理程序的情况下,我倾向于将流程外部化到其他类,这样可以正确使用参数类型并避免不安全的强制转换。如果您仍然愿意使用 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 真的是对象的工作吗?)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-12-11
      • 1970-01-01
      • 2014-03-09
      • 1970-01-01
      • 1970-01-01
      • 2021-03-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多