开发者

Java: Bounded heterogeneous collections when items are not related

开发者 https://www.devze.com 2023-01-24 19:10 出处:网络
If i have heterogeneous collection for which I know exactly the types i\'m going to place is there a way to enforce this.

If i have heterogeneous collection for which I know exactly the types i'm going to place is there a way to enforce this.

For example take this scenario say i have a map that has a String key and value which can be on of three unrelated types. Now I know that I will only put ClassA and ClassB or java.lang.String

for example here is the code

public HetroCollection
{
    public Map<String,Object> values;
}

public ClassA
{
}

public ClassB
{
}

public static void Main(String args[])
{
   HetroCollection collection = new HetroCollection();
   collection.values.add("first", new ClassA());
   collections.values.add("second", new ClassB());
   collections.values.add("third" , "someString");
   //BAD want to stop random adds
   collections.values.("fourth" , new SomeRandomClass());
}

The Options I have thought of are:

  • have the classes implement a common interface and use Generics on the Map (Problem with this is if this also involves library classes either JDK or third party then changing class is not an option

  • hide the Map and provide put Methods which are paratemized like

    put(String key , ClassA value); put(String key , ClassB value); put(String key, String value); get(String key);

  • Rethink design and 开发者_StackOverflownot use heterogeneous collection (not sure how I would represent this any other way)

Looking for the best practice answer for this.


I think that the "best practice" solutions are either your first and third options, provided that circumstances allow it.

Another option that you haven't considered is something like this:

public class MyMap extends HashMap<String, Object> {
    ...
    // constructors
    ...
    @Override
    public void put(String key, Object value) {
        if (value instanceof ClassA || value instanceof ClassB) {
            super.put(key, value);
        } else {
            throw new IllegalArgumentException("Verbotten!");
        }
    }
    ...
}

You could combine this with your second option so that there is a statically typed option, and possibly even label the put(String, Object) method as deprecated to discourage its use.

And finally, there is the option of just ignoring the problem, and relying on the application programmer to not put random stuff into the map. Depending on the circumstances, this might even be the best solution.


Well, you've already proven your first thought to not be an option. The second thought would be the best option, if you really need this functionality. Otherwise the best option is to rethink your approach. But, it's easier to help if we knew a little context.


There is a fourth option:

If you want to stick instances of exactly these types into a collection, chances are they have something in common. If you can not introduce a common supertype to express that commonality, you can still introduce a parallel class hierarchy with such a common superclass, and declare your map to hold items of that type.

// You can find a better name ;-)
abstract class Foo {
    public abstract void foo();

    public void bar() {
        // something generic
    }

    public abstract void visit(FooVisitor visitor);
}

class ClassAFoo {
    final ClassA delegate;

    // Constructor and implementations for foo()

}

class ClassBFoo {
    final ClassB delegate;

    // Constructor and implementations for foo()

}

class StringFoo {
    final String delegate;

    // Constructor and implementations for foo()
}

Advantages:

  1. statically type safe
  2. you can add methods to the common type or implement the visitor pattern to switch on the type of wrapped value
  3. the compiler can check that you have handled all types when working with the map (in contrast to using a series of if-statements to switch on the type)

Disadvantages:

  1. boilerplate code, complexity


There's a great facility in Java for this called classes. Given your example, you might write one like this:

public class Foo {
  private ClassA first;
  private ClassB second;
  private String someString;

  ...

  public void setFirst(ClassA first) {
    this.first = first;
  }

  public ClassA getFirst() {
    return first;
  }

  ...
}

Seriously, given what you've said this sounds like exactly what you want. If you only want to allow specific keys, with values that may only be of specific types (that depend on the key itself)... that's a class. If there's some really strong reason that you need to use String map keys here (and this seems unlikely to me), please explain.

Edit:

When I answered this I was under the impression for some reason that you needed to enforce only specific keys mapping to specific types of values. Looking at it again, it seems like that may not be the case. If that isn't the case, I think your best option is rethinking the design (giving an example of why you need to do this might be helpful). If you do that and don't come up with anything, I think #2 is the best option. It enforces your restrictions on the types of values the map can have in a somewhat typesafe way.


In theory type safety with mixed objects from a List can be achieved using HList in Functional Java. See blog post and Examples. Also relevant this article from IBM developerworks. I wrote in theory because in practice the type declaration can only cope with a limited number of elements and it grows rapidly.

0

精彩评论

暂无评论...
验证码 换一张
取 消