【问题标题】:This convoluted generics pattern crashes Eclipse - can I make it work?这种复杂的泛型模式使 Eclipse 崩溃——我可以让它工作吗?
【发布时间】:2014-10-16 23:58:46
【问题描述】:

(我使用的是 Eclipse Luna 4.4.0,JDK 1.8.0_05)

我在做一个游戏,游戏世界的拓扑结构可以大致分解为World -> Level -> Tile,其中Tile是一小块地形。我设置了三个项目,一个包含这些结构的一些基类,另外两个是服务器和客户端,它们扩展了基础项目中的结构,以满足每个项目所需的额外内容。像这样:

基础项目:

public class BaseWorld{/* ...code... */}
public class BaseLevel{/* ...code... */}
public class BaseTile{/* ...code... */}

在服务器和客户端项目中:

public class World extends BaseWorld{/* ...extended code... */}
public class Level extends BaseLevel{/* ...extended code... */}
public class Tile extends BaseTile{/* ...extended code... */}

现在在基础项目的许多部分,都有返回基类的方法,但在服务器和客户端项目中,这通常意味着我必须重写方法并强制转换为子类型,因为子项目专门使用子类型。像这样:

public class BaseLevel{
  BaseLevel getNextLevel(){/* ...code... */}
}

public class Level extends BaseLevel{
  Level getNextLevel(){
    return (Level)super.getNextLevel();
  }
}

这有点难以维持。我发现我可以使用泛型来解决其中的一些问题,使用以下模式:

public class BaseLevel<Level extends BaseLevel<Level>>{
  Level getNextLevel(){/* ...code... */}
}

public class Level extends BaseLevel<Level>{
  //All sorted! getNextLevel() already returns this subclass type.
}

嗯,我有一个疯狂的想法,将上述模式弯曲到断点,并能够在所有超类中的任何地方使用所有子类。现在我必须说,我绝不认可下面的代码是好的编程,我只是有这个想法,并尽可能地把它拿来看看它会如何工作。无论如何,我想出了以下怪物。

public class BaseWorld<Tile extends BaseTile<Tile, Level, World>
                      ,Level extends BaseLevel<Tile, Level, World>
                      ,World extends BaseWorld<Tile, Level, World>>{
  /* ...code... */
}

public class BaseLevel<Tile extends BaseTile<Tile, Level, World>
                      ,Level extends BaseLevel<Tile, Level, World>
                      ,World extends BaseWorld<Tile, Level, World>>{
  /* ...code... */
}

public class BaseTile<Tile extends BaseTile<Tile, Level, World>
                 ,Level extends BaseLevel<Tile, Level, World>
                 ,World extends BaseWorld<Tile, Level, World>>{
  /* ...code... */
}

现在,每当我需要从任何其他类返回任何类时,我都可以无需在子项目中使用任何强制转换!

不幸的是,没有,因为当 Eclipse 重建工作区时:

A stack overflow error has occurred.
You are recommended to exit the workbench. [...]

我想当一个类的泛型被解析时,它必须去解析另一个类的泛型,然后陷入递归循环。

提问时间:

这个错误是Eclipse编辑器中风,还是java编译器?

以上内容没有在编辑器中突出显示为编译器警告/错误 - 是否应该这样做,即使它在技术上看起来是有效的?

这种递归可能是无限深,还是非常深?

是否有可能在避免此错误的同时实现我在这里尝试的目标? (我再次强调,它绝对不是一个好的模式,我只是想知道它是否可以工作)

【问题讨论】:

    标签: java eclipse generics compiler-errors


    【解决方案1】:

    是Eclipse编辑器中风,还是java编译器?

    本例中的 Eclipse 编辑器。以下声明一起(当然在不同的文件中)是有效的 Java 代码。

    public class BaseFoo<F extends BaseFoo<F,B>, B extends BaseBar<F,B>> {}
    public class BaseBar<F extends BaseFoo<F,B> ,B extends BaseBar<F,B>> {}
    public class Foo<F extends BaseFoo<F,B>,B extends BaseBar<F,B>> extends BaseFoo<F,B> {}
    public class Bar<F extends BaseFoo<F,B>,B extends BaseBar<F,B>> extends BaseBar<F,B> {}
    

    以上内容并未在编辑器中突出显示为编译器警告/错误 - 是否应该这样做,即使它在技术上看起来是有效的?

    它可能应该(好吧,好吧,好吧,我在这里开玩笑......)不。这是有效的 Java。只是……呃……

    这种递归可能是无限深,还是非常深?

    无限,我在这里猜测(稍后会详细介绍我的猜测)。

    考虑尝试实例化Foo 的实例。

    继续,使用默认构造函数写出一个初始化器。

    Foo<Foo, Bar> foo = new Foo<Foo, Bar>();
    

    这里有四个关于通用边界不匹配的错误。

    让我们尝试解决它。

    Foo<Foo<Foo,Bar>, Bar<Foo,Bar>> foo = new Foo<Foo<Foo,Bar>, Bar<Foo,Bar>>();
    

    现在有 12 个错误。我们可以一直尝试初始化它。

    是否有可能在避免此错误的同时实现我在这里尝试的目标? (我再次强调,它绝对不是一个好的模式,我只是想知道它是否可以工作)

    考虑以下基本结构:

    class World {
        SomeCollection<Level> levels;
    }
    
    class Level {
        SomeCollection<Tile> tiles;
    }
    
    class Tile { }
    

    为什么?

    上述代码的基本结构是首选composition over inheritance,除了一些内置的泛型集合类型外不使用泛型。这是为您提供的一个很好的初始化样式示例是静态构建器,最终编写类似这样的内容来初始化World

    World gameWorld = new World.Builder().
                     .loadLevels(levelSource)
                     .loadTiles(tileset)
                     .build();
    

    我希望这会有所帮助。

    结束语

    不要继续。这是为您的问题提供可能解决方案的探索路径。不要再去这里了。

    【讨论】:

    • 感谢您的详细解释。我的结论和你的一样,我只是从来没有见过这样的事情,所以潜水到兔子洞的深度哈哈。
    • 我不确定你要用Foo 举例说明什么,但实际上可以在带有类型参数的上下文中实例化Foo(它们可以相互作为界限) )。使用编译器在进行类型推断时使用的隐式类型参数:Foo&lt;?, ?&gt; foo = new Foo&lt;&gt;();。或者在带有类型参数的方法中:&lt;F extends BaseFoo&lt;F, B&gt;, B extends BaseBar&lt;F, B&gt;&gt; void m() { Foo&lt;F, B&gt; foo = new Foo&lt;F, B&gt;(); }
    【解决方案2】:

    多么美妙的怪物啊!

    您的代码在 Eclipse 4.4.1 和 javac 1.8.0_45 中对我来说编译得很好,并且似乎可以正常工作。我认为他们在 Java 8 早期版本中类型检查的更精细部分存在一些问题。也许他们现在已经解决了。

    与 jdphenix 的观点相反,我认为这种设计可能很有用。在静态类型控制和代码重用方面,我看不出他建议的设计如何具有您的任何优势。

    例子:

    public class ElegantMonstrosity {
        public void m() {
            SubWorld world = new SubWorld();
            List<SubLevel> levels = world.getTiles(); 
            SubLevel level = levels.get(0);
            List<SubTile> tiles = level.getTiles();
            SubLevel nextLevel = level.getNextLevel();
        }
    }
    
    class BaseWorld<
        Tile extends BaseTile<Tile, Level, World>,
        Level extends BaseLevel<Tile, Level, World>,
        World extends BaseWorld<Tile, Level, World>> {
    
        List<Level> getTiles() { return null; }
    }
    
    class BaseLevel<
        Tile extends BaseTile<Tile, Level, World>,
        Level extends BaseLevel<Tile, Level, World>,
        World extends BaseWorld<Tile, Level, World>> {
    
        Level getNextLevel() { return null; }
        List<Tile> getTiles() { return null; }
    }
    
    class BaseTile<
        Tile extends BaseTile<Tile, Level, World>,
        Level extends BaseLevel<Tile, Level, World>,
        World extends BaseWorld<Tile, Level, World>> {
    }
    
    class SubTile extends BaseTile<SubTile, SubLevel, SubWorld> {}
    class SubLevel extends BaseLevel<SubTile, SubLevel, SubWorld> {}
    class SubWorld extends BaseWorld<SubTile, SubLevel, SubWorld> {}
    

    【讨论】:

      猜你喜欢
      • 2012-02-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-20
      • 2011-08-07
      • 1970-01-01
      • 1970-01-01
      • 2023-03-17
      相关资源
      最近更新 更多