从抽象泛型到非抽象非泛型

时间:2014-02-02 04:28:31

标签: java algorithm generics abstract-class bounded-wildcard

我最近一直致力于使用通用最佳搜索算法的Java项目。为了使算法本身成为Generic,我在算法中使用的所有类上使用了一堆泛型。

以下是所有类/接口声明:

public class StateSearch<E extends AbstractState<E>>
public class AbstractState<E> implements State<E>
public class StateNode<E extends AbstractState<E>> implements Comparable<StateNode<E>>
public class StateQueue<E extends AbstractState<E>>
public interface State<E> extends ComparableState<E>>

现在理论上,所有这些都很好。然而,当我应用算法时,我计划'E'成为某种游戏状态(说跳棋板,纸牌游戏,探路者,你有什么)。因此,我创建了以下类:

public class GameState extends AbstractState<String>

我对此特定设置的意图是让GameState成为String的容器,表示特定游戏的状态。但是,这会导致尝试使用以下代码创建StateSearch时出现问题:

new StateSearch<GameState>(new GameState(initialState), new GameState(goalState));

我最终收到绑定不匹配错误,指出GameState不是<E extends AbstractState<E>>的有效替代。现在,我相信我理解为什么会这样。我的想法是因为GameState扩展了AbstractState<String>,而不是AbstractState<E>

不幸的是,我不希望我的GameState类具有通用类型。它被设计用于实际执行某些操作,从而从Generics切换到Strings。例如,我可能想在一个完全不同的项目中创建一个GameState类,该项目使用Integers来实现它。

鉴于我在GameState类中使用和执行的方法,变量和操作,它不能是通用的。

我的问题是:有没有办法将GameState类作为非泛型类实现,以满足我{{{1}的限制要求{1}}上课?

我不介意我是否必须更改一些类声明。关键是我需要算法 Generic ,而实现要 Non-Generic StateSearch类必须是我在两者之间转换的点。

<小时/> 修改
我需要GameState类来充当某个游戏的实现。 GameStateAbstractState接口旨在允许我构建状态搜索算法而无需实际实现。以这种方式设置允许我在多个游戏中应用此设置。

State类基本上只从我的StateSearch中拉出状态,StateQueue只是由优先级系统排序的AbstractState类的向量(因此可比较实现)。扩展状态树的方式是使用getNextState()类中的AbstractState函数。这很好,但是getNextState()函数基本上从我的getSuccessors()函数中获取了最佳状态,GameState函数只能在<{>> 中定义{ {1}}班级。其他任何地方都只是摘要。

我无法以通用方式定义getSuccessors()函数,因为它纯粹基于实现。此函数用于从当前状态返回所有可能的状态。例如,如果游戏是Tic-Tac-Toe,getSuccessors()函数将返回一个游戏状态列表,其中每个状态代表一个可能的移动。

GameState类还包含getEstimatedCost()getRunningCost(),它们基本上充当 h(x) g(x)分别是功能。这也可以在GameState类中定义 ,因为两者都取决于实现。

3 个答案:

答案 0 :(得分:3)

定义事物的方式实际上是Curiously recurring template pattern

的实现

当你有:

public class StateSearch<E extends AbstractState<E>>

这意味着要使用GameState作为其通用类型,它需要定义为:

public class GameState extends AbstractState<GameState>

至于你真正需要什么,目前还不清楚你为什么需要泛型。为什么GameState接口不够用?

答案 1 :(得分:1)

搜索算法需要两件事:

  1. 数据(连接图)
  2. 一种比较数据的方法(图中的节点)
  3. 每个节点都知道其连接的节点,表示图形。节点可以相互比较。这满足了搜索算法需要的两件事。

    因此,在我们的设计中,节点将此信息提供给搜索算法是很自然的。应该从搜索算法中隐藏所有其他节点状态以保持良好的OO主体(封装。)

    由于这些原因,我建议重构这个更简单的设计。

    //Comparable interface satisfies measuring a node's value.
    //getAdjacentNodes abstract method exposes graph structure.
    abstract class StateNode implements Comparable<StateNode>{
        abstract StateNode[] getAdjacentNodes();
    }
    
    //StateSearch can perform any search algorithm it needs StateNode.
    class StateSearch<E extends StateNode>
    
    //StateQueue is probably unnecessary. Consider replacing with a PriorityQueue.
    class StateQueue<E extends StateNode> queue;
    

    StateNode是否完全使用内部状态是实施代码的决定。 在下面的示例中,GameNode确实保持游戏状态,但搜索算法不知道这一点。所有它知道的是它需要知道的最小值; StateNode暴露的是什么。如果我完全离开状态,搜索算法将不受影响。

    public class GameNode extends StateNode{
        String state;
        public GameNode(String state){
            this.state = state;
        }
    
        public StateNode[] getAdjacentNodes(){
            //return adjacent nodes.
        }
    
        public String getGameState(){
            return state;
        }
        public int compareTo(StateNode other){
            //comparison code
        }
    }
    

答案 2 :(得分:-3)

对我来说,Java Generic机制通过使集合库成为可能来提供其大部分好处。它似乎是针对那种用例开发的 - 这充分证明了它 - 它只是管理它。如果您查看集合强制转换的实现,您可能会看到编译警告或注释,因为无法创建通用数组等问题而关闭警告。请注意,因为它通过类型擦除而不是代码生成来工作,所以它保证没有代码膨胀,但是不像C ++那样提供额外的效率 - 尽管你确实可以获得关于在运行时永远不会发生的类型转换异常的一些保证。当我使用它时,我通常尝试将它用于类似于集合的东西,我尝试将它们作为模型。

我怀疑用泛型做任何你想做的事情是不可能的,而且我会担心尝试做太多 - 因为我会使用任何语言功能,你不经常使用它来成为反射级别现在需要花费一个小时才能完成工作的东西会花费你更长的时间来理解你以后何时回来修改或扩展该代码。