Understanding best use of Java Generics in this example case

為{幸葍}努か 提交于 2020-01-07 06:22:25

问题


Let's say I have a manufacturing scheduling system, which is made up of four parts:

  • There are factories that can manufacture a certain type of product and know if they are busy:

    interface Factory<ProductType> {
        void buildProduct(ProductType product);
        boolean isBusy();
    }
    
  • There is a set of different products, which (among other things) know in which factory they are built:

    interface Product<ActualProductType extends Product<ActualProductType>> {
        Factory<ActualProductType> getFactory();
    }
    
  • Then there is an ordering system that can generate requests for products to be built:

    interface OrderSystem {
        Product<?> getNextProduct();
    }
    
  • Finally, there's a dispatcher that grabs the orders and maintains a work-queue for each factory:

    class Dispatcher {
        Map<Factory<?>, Queue<Product<?>>> workQueues
                          = new HashMap<Factory<?>, Queue<Product<?>>>();
    
        public void addNextOrder(OrderSystem orderSystem) {
            Product<?> nextProduct = orderSystem.getNextProduct();
            workQueues.get(nextProduct.getFactory()).add(nextProduct);
        }
    
        public void assignWork() {
            for (Factory<?> factory: workQueues.keySet())
                if (!factory.isBusy())
                    factory.buildProduct(workQueues.get(factory).poll());
        }
    }
    

Disclaimer: This code is merely an example and has several bugs (check if factory exists as a key in workQueues missing, ...) and is highly non-optimal (could iterate over entryset instead of keyset, ...)

Now the question:

The last line in the Dispatcher (factory.buildProduct(workqueues.get(factory).poll());) throws this compile-error:

The method buildProduct(capture#5-of ?) in the type Factory<capture#5-of ?> is not applicable for the arguments (Product<capture#7-of ?>)

I've been racking my brain over how to fix this in a type-safe way, but my Generics-skills have failed me here...

Changing it to the following, for example, doesn't help either:

public void assignWork() {
    for (Factory<?> factory: workQueues.keySet())
        if (!factory.isBusy()) {
            Product<?> product = workQueues.get(factory).poll();
            product.getFactory().buildProduct(product);
        }
}

Even though in this case it should be clear that this is ok...

I guess I could add a "buildMe()" function to every Product that calls factory.buildProduct(this), but I have a hard time believing that this should be my most elegant solution.

Any ideas?

EDIT:

A quick example for an implementation of Product and Factory:

class Widget implements Product<Widget> {
    public String color;

    @Override
        public Factory<Widget> getFactory() {
            return WidgetFactory.INSTANCE;
    }
}

class WidgetFactory implements Factory<Widget> {
    static final INSTANCE = new WidgetFactory();

    @Override
    public void buildProduct(Widget product) {
        // Build the widget of the given color (product.color)
    }

    @Override
    public boolean isBusy() {
        return false; // It's really quick to make this widget
    }
}

回答1:


Your code is weird.
Your problem is that you are passing A Product<?> to a method which expects a ProductType which is actually T.
Also I have no idea what Product is as you don't mention its definition in the OP.
You need to pass a Product<?> to work. I don't know where you will get it as I can not understand what you are trying to do with your code




回答2:


Map<Factory<?>, Queue<Product<?>>> workQueues = new HashMap<Factory<?>, Queue<Product<?>>>();

// factory has the type "Factory of ?"
for (Factory<?> factory: workqueues.keySet())       
    // the queue is of type "Queue of Product of ?"
    Queue<Product<?>> q = workqueues.get(factory);

    // thus you put a "Product of ?" into a method that expects a "?"
    // the compiler can't do anything with that.
    factory.buildProduct(q.poll());
}



回答3:


Got it! Thanks to meriton who answered this version of the question:

How to replace run-time instanceof check with compile-time generics validation

I need to baby-step the compiler through the product.getFactory().buildProduct(product)-part by doing this in a separate generic function. Here are the changes that I needed to make to the code to get it to work (what a mess):

  • Be more specific about the OrderSystem:

    interface OrderSystem {
        <ProductType extends Product<ProductType>> ProductType getNextProduct();
    }
    
  • Define my own, more strongly typed queue to hold the products:

    @SuppressWarnings("serial")
    class MyQueue<T extends Product<T>> extends LinkedList<T> {};
    
  • And finally, changing the Dispatcher to this beast:

    class Dispatcher {
        Map<Factory<?>, MyQueue<?>> workQueues = new HashMap<Factory<?>, MyQueue<?>>();
    
        @SuppressWarnings("unchecked")
        public <ProductType extends Product<ProductType>> void addNextOrder(OrderSystem orderSystem) {
            ProductType nextProduct = orderSystem.getNextProduct();
            MyQueue<ProductType> myQueue = (MyQueue<ProductType>) workQueues.get(nextProduct.getFactory());
            myQueue.add(nextProduct);
        }       
    
        public void assignWork() {
            for (Factory<?> factory: workQueues.keySet())
                if (!factory.isBusy())
                    buildProduct(workQueues.get(factory).poll());
        }
    
        public <ProductType extends Product<ProductType>> void buildProduct(ProductType product) {
            product.getFactory().buildProduct(product);
        }
    }   
    

Notice all the generic functions, especially the last one. Also notice, that I can NOT inline this function back into my for loop as I did in the original question.

Also note, that the @SuppressWarnings("unchecked") annotation on the addNextOrder() function is needed for the typecast of the queue, not some Product object. Since I only call "add" on this queue, which, after compilation and type-erasure, stores all elements simply as objects, this should not result in any run-time casting exceptions, ever. (Please do correct me if this is wrong!)



来源:https://stackoverflow.com/questions/13634616/understanding-best-use-of-java-generics-in-this-example-case

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!