简体   繁体   English

泛型并调用属于该类型的方法

[英]Generics and calling a method that belongs to the type

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. 简介:我使用Cardable而不是T,并且通常不表达泛型。

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? 我到处都是,并且不断听到有关类型擦除的信息 ,并且类文字应该是一个参数,并且是yadda yadda ...但是ArrayList怎么做呢? Why is it that you can just type ArrayList<String>() and it will just work without needing something ridiculous like ArrayList<String>(String.GetClass()) ? 为什么只键入ArrayList<String>()却可以正常工作而不需要像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. Edit2:Perception的建议已经修复了我的代码,但是我不确定如何调用来填充卡片组。 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();
    }
}

The thing is: the implementation of ArrayList<E> does not depend on the actual type E. That's why you don't need to pass the type in the constructor (as you say, new ArrayList<String>(String.class) ). 关键是: ArrayList<E>实现不依赖于实际的类型E。这就是为什么您不需要在构造函数中传递类型的原因(如您所说, new ArrayList<String>(String.class) ) 。

If you write a generic class that, for some reason, must know exactly what the generic type represents at runtime, then you need to pass the type in the constructor, because, as you said, type erasure will not allow you to get the class from T . 如果您编写了一个通用类,由于某种原因它必须在运行时确切知道通用类型代表什么,那么您需要在构造函数中传递该类型,因为正如您所说, 类型擦除将不允许您获取该类来自T You'd need new MyClassThatNeedToKnowItsActualTypeParameter<String>(String.class) . 您需要new MyClassThatNeedToKnowItsActualTypeParameter<String>(String.class)

For instance, suppose a class that accesses a database and retrieves an instance of a given class. 例如,假设有一个访问数据库并检索给定类实例的类。 The instances of a class are stored in a table named after the class. 类的实例存储在以该类命名的表中。 For example: 例如:

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

The method load needs to know exactly what T is at runtime, because it needs to be able to construct a query that will use the name of the actual class which T represents. 方法load需要准确地知道运行时T是什么,因为它需要能够构造一个查询,该查询将使用T表示的实际类的名称。 However, in Java, you cannot obtain this information from T , since T will disappear due to type erasure. 但是,在Java中,您不能从T获得此信息,因为T将由于类型擦除而消失。 Furthermore, the load method needs a way to create an instance of the correct type and write data from the database to it. 此外, load方法需要一种创建正确类型的实例并将数据从数据库写入该实例的方法。 To create an instance of a class, you'd use reflection, doing clazz.newInstance() for example. 要创建类的实例,可以使用反射,例如执行clazz.newInstance() Here, again, you need to know exactly what class you are dealing with. 同样,在这里,您需要确切地知道您要处理的是什么类。 You'd end up with something like this: 您最终将得到如下结果:

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);
...

You could use generics for this, but the deck will not be able to populate itself—it doesn't know what class of object to create! 您可以为此使用泛型,但是平台本身将无法填充-它不知道要创建什么类的对象! From your description, what you want is basically an ArrayList<T> where T can be anything; 根据您的描述,您想要的基本上是一个ArrayList<T> ,其中T可以是任何值; it doesn't even have to be cards. 它甚至不必是卡片。 What you need, though, is a way to populate the deck. 不过,您需要的是一种填充平台的方法。 For that, a factory object can be used: 为此,可以使用工厂对象:

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

If you like, you can restrict the type of T to extend Cardable , but in terms of the logic of Deck (as far as you have described it), that isn't necessary. 如果愿意,可以限制T的类型以扩展Cardable ,但是就Deck的逻辑而言(就您所描述的而言),这不是必需的。

Looking at the code i dont think you really need a generic class as you are hardly using T. If you simply change your populate method to something like this your job should be done: 查看代码,我认为您真的不需要通用类,因为您几乎不使用T。如果您只是将填充方法更改为类似这样的内容,则应该完成以下工作:

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

        cardsMade = t.makeAllCards();

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


        shuffle();
    }

Don't use generics. 不要使用泛型。

You would only use genetics if you wanted to handle different types of cards, eg 仅当您想处理不同类型的卡片时才使用遗传学,例如

  • 52 card deck 52张牌
  • tarot cards 塔罗牌
  • 63 card 5-handed 500 deck 63张五手牌500张牌
  • etc 等等

This problem doesn't supply to your situation. 此问题不适合您的情况。

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

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