user63904
user63904

Reputation:

Accessing a Java generics stored in a Map

I'm trying to access a custom Java generic stored in a map as below.
Unfortunately I get a type mis-match error.
Now, I can cast it to the type I want because but this seems messy to me.
Is there a clean way of doing the assignment?
Thanks

public interface BusinessObject {
}

public class SalesItemA implements BusinessObject {
}

public interface BusinessRuleSuite<T extends BusinessObject> {
    public void fire(T shell);
}

public abstract class BusinessRuleSuiteCommon<T extends BusinessObject>
    implements BusinessRuleSuite<T> {
        public synchronized void fire(T bo) {
            // do something with bo;
        }
    }


    public class SalesBusinessRuleSuite extends
        BusinessRuleSuiteCommon<SalesItemA> {
    }


    public class SalesProcessor {

        private final Map<Class<? extends BusinessObject>, BusinessRuleSuite<? extends BusinessObject>> businessRules;

        public SalesProcessor(Map<Class<? extends BusinessObject>, BusinessRuleSuite<? extends BusinessObject>> businessRules) {
            this.businessRules = businessRules;
        }


        public void processItem(SalesItemA sia) {
            /// This assignment doesn't work??? Why?
            BusinessRuleSuite<SalesItemA> p = this.businessRules.get(sia.getClass());
            p.fire(sia);
        }
    }
}

Upvotes: 1

Views: 282

Answers (4)

erickson
erickson

Reputation: 269647

It looks like you are trying to create a single SalesProcessor class, and change its processItem method to accept any implementation of BusinessObject.

Neal Gafter tried to make this work by using a "super type tokens" to expand Josh Block's "typesafe heterogenous container" pattern, but it has some flaws.

Upvotes: 0

aepurniet
aepurniet

Reputation: 1727

because businessRules is a

private final Map<Class<? extends BusinessObject>, BusinessRuleSuite<? extends BusinessObject>> businessRules

so instead of

BusinessRuleSuite<SalesItemA> p = this.businessRules.get(sia.getClass());

do

BusinessRuleSuite<? extends BusinessObject> p = this.businessRules.get(sia.getClass());

the Map places no garuantees that for each key ( Class<K>, BusinessRuleSuite<V> ), K=V, which i assume is true in your code.

alternatively:

public class RuleProcessor<T extends BusinessObject> {
  private final Map<Class<T>, BusinessRuleSuite<T>> businessRules;
  public SalesProcessor(Map<Class<T>, BusinessRuleSuite<T>> businessRules) {
    this.businessRules = businessRules;
  }
  // - or have a blank constructor, and add them one by one
  public void add(Class<T> c, BusinessRuleSuite<T> rs) {
    businessRules.add(c, rs);
  }
  public void processItem(T sia) {
    BusinessRuleSuite<T> p = this.businessRules.get(sia.getClass());
    p.fire(sia);
  }
}

Upvotes: 1

Simon Groenewolt
Simon Groenewolt

Reputation: 10665

Your map does not contain items of type SalesItemA but of type <? extends BusinessObject>

change

BusinessRuleSuite<SalesItemA> p = this.businessRules.get(sia.getClass());

to

BusinessRuleSuite<BusinessRuleSuite<? extends BusinessObject>> p = this.businessRules.get(sia.getClass());

... not actually tested, so this might not work at all.

Upvotes: 0

Aaron Digulla
Aaron Digulla

Reputation: 328556

Because the return type of get() is a BusinessRuleSuite<? extends BusinessObject>.

This means it will accept anything that inherits from BusinessObject during put(). But when you use it on the right hand side of an assign, Java can't make assumptions. It has to play safe, so the get() behaves as if you had used BusinessRuleSuite<BusinessObject> (without the extends).

There are two ways to achieve what you want:

  1. Use BusinessRuleSuite<SalesItemA> in the map declaration

  2. Use a cast

Upvotes: 3

Related Questions