0

I've been trying to make a class to represent a deck of cards. However, I wanted to create it in a way which it could be any kind of cards, it doesn't need to know which kind it just has be able to store them, shuffle and draw them one at a time be them uno cards, regular playing cards, or trading cards. For this, I've been trying something I've heard of but have not used -- Generics.

However, I've had no luck at all trying to get it to work. It won't instantiate, populate cards, or return the correct type when drawing the card. I've tried mixing and matching and I just simply can't get it to work.

Old Code that was buggy was truncated to save space, look at previous edits to see. Summary: I used Cardable instead of T and lacked to express generics in general.

So how would this work, I'm completely new to generics. I've been looking around everywhere and I keep hearing about Type Erasure and that the class literal should be a parameter and yadda yadda... But then how does ArrayList do it? Why is it that you can just type ArrayList<String>() and it will just work without needing something ridiculous like ArrayList<String>(String.GetClass())?

Thanks for your time.

Edit: Cardable is a class in which any card that can be put into the deck will extend.

Edit2: Perception's suggestion has thus fixed my code, but I am not sure how I could call to populate the deck. Right now I have it to accept an array, but it would be nice to have it internal, and I'm not entirely sure I grasp the entire factory method.

public class Deck<T extends Cardable>
{
    private ArrayList<T> cardsInDeck;

    public Deck()
    {
        cardsInDeck = new ArrayList<T>();
    }

    public void populate( T[] newCards )
    {
        cardsInDeck.clear();
        for( T card : newCards )
        {
            cardsInDeck.add( card );
        }
        shuffle();
    }

    public T drawCard()
    {
        T card = null;
        try
        {
            card = cardsInDeck.get( 0 );
        }
        catch( IndexOutOfBoundsException e )
        {
            System.out.println( "Ran out of Cards" );
            e.printStackTrace();
        }
        cardsInDeck.remove( 0 );
        return card;
    }

    public void shuffle()
    {
        ArrayList<T> newDeck = new ArrayList<T>();
        Random rand = new Random();
        while( !cardsInDeck.isEmpty() )
        {
            int index = rand.nextInt( cardsInDeck.size() );
            newDeck.add( cardsInDeck.get( index ) );
            cardsInDeck.remove( index );
        }
        cardsInDeck = newDeck;
    }

    public int getSize()
    {
        return cardsInDeck.size();
    }
}
4

4 回答 4

2

问题是: 的实现ArrayList<E>不依赖于实际的类型 E。这就是为什么你不需要在构造函数中传递类型(如你所说,new ArrayList<String>(String.class))。

如果您编写一个泛型类,由于某种原因,它必须确切地知道泛型类型在运行时代表什么,那么您需要在构造函数中传递该类型,因为正如您所说,类型擦除不允许您获取该类从T. 你需要new MyClassThatNeedToKnowItsActualTypeParameter<String>(String.class).

例如,假设一个类访问数据库并检索给定类的实例。类的实例存储在以该类命名的表中。例如:

class MyRepository<T> {
  T load(int id) { ... }
}

该方法load需要在运行时准确地知道 T 是什么,因为它需要能够构造一个查询,该查询将使用 T 表示的实际类的名称。但是,在 Java 中,您无法从 中获取此信息T,因为T会因类型擦除而消失。此外,该load方法需要一种方法来创建正确类型的实例并将数据库中的数据写入其中。要创建一个类的实例,您可以使用反射,clazz.newInstance()例如。在这里,再次,你需要确切地知道你正在处理什么类。你最终会得到这样的东西:

class MyRepository<T> 
  private final Class<T> clazz;
  MyRepository(Class<T> clazz) { this.clazz = clazz; }
  T load(int id) {
    final String tableName = clazz.getSimpleName() + "Table";
    /* connect, retrieve data, disconnect */
    final T t = clazz.newInstance(); // must be inside try/catch
    /* fill instance t with data from database somehow (using reflection probably, which, again, needs to know what clazz is */
    return t;
  }
}

...
final MyRepository<User> userRepository = new MyRepository<User>(User.class);
final User user = userRepository.load(123);
...
于 2013-01-13T06:16:16.717 回答
1

您可以为此使用泛型,但卡片组将无法自行填充——它不知道要创建什么类的对象!根据您的描述,您想要的基本上是一个可以是任何东西的ArrayList<T>地方;T它甚至不必是卡片。但是,您需要的是一种填充套牌的方法。为此,可以使用工厂对象:

public interface CardGenerator<T> {
    T[] generateAllCards();
}

public class Deck<T> {
    private ArrayList<T> cardsInDeck;

    public Deck(CardGenerator<T> generator) {
        cardsInDeck = new ArrayList<T>();
        cardsInDeck.addAll(generator.generateAllCards());
    }
    . . .
}

如果您愿意,您可以将类型限制T为 extend Cardable,但就(就您所描述的而言)的逻辑而言Deck,这不是必需的。

于 2013-01-13T06:17:14.513 回答
0

不要使用泛型。

如果你想处理不同类型的卡片,你只会使用遗传学,例如

  • 52张牌组
  • 塔罗牌
  • 63张5手500副牌
  • ETC

此问题不适用于您的情况。

于 2013-01-13T07:35:46.140 回答
0

查看代码我不认为你真的需要一个泛型类,因为你几乎不使用 T。如果你只是将你的 populate 方法更改为这样的东西,你的工作应该完成:

public void populate(Cardable t)
    {
        cardsInDeck.clear();
        Cardable[] cardsMade;

        cardsMade = t.makeAllCards();

        for( Cardable card : cardsMade )
        {
            cardsInDeck.add( card );
        }


        shuffle();
    }
于 2013-01-13T06:18:37.480 回答