tags:

views:

80

answers:

7

I'm trying to write a class that has a generic member variable but is not, itself, generic. Specifically, I want to say that I have an List of values of "some type that implements comparable to itself", so that I can call sort on that list... I hope that makes sense.

The end result of what I'm trying to do is to create a class such that I can create an instance of said class with an array of (any given type) and have it generate a string representation for that list. In the real code, I also pass in the class of the types I'm passing in:

String s = new MyClass(Integer.class, 1,2,3).asString();
assertEquals("1 or 2 or 3", s);
String s = new MyClass(String.class, "c", "b", "a").asString();
assertEquals("\"a\" or \"b\" or \"c\"", s);

Originally I didn't even want to pass in the class, I just wanted to pass in the values and have the code examine the resulting array to pick out the class of the values... but that was giving me troubles too.

The following is the code I have, but I can't come up with the right mojo to put for the variable type.

public class MyClass {
    // This doesn't work as T isn't defined
    final List<T extends Comparable<? super T>> values;

    public <T extends Comparable<? super T>> MyClass (T... values) {
        this.values = new ArrayList<T>();
        for(T item : values) {
            this.values.add(item);
        }
    }

    public <T extends Comparable<? super T>> List<T> getSortedLst() {
        Collections.sort(this.values);
        return this.values;
    }
}

error on variable declaration line:

Syntax error on token "extends", , expected

Any help would be very much appreciated.

Edit: updated code to use List instead of array, because I'm not sure it can be done with arrays.

@Mark: From everything I've read, I really want to say "T is a type that is comparable to itself", not just "T is a type that is comparable". That being said, the following code doesn't work either:

public class MyClass {
    // This doesn't work
    final List<? extends Comparable> values;

    public <T extends Comparable> MyClass (T... values) {
        this.values = new ArrayList<T>();
        for(T item : values) {
            this.values.add(item);
        }
    }

    public <T extends Comparable> List<T> getSortedLst() {
        Collections.sort(this.values);
        return this.values;
    }
}

error on add line:

The method add(capture#2-of ? extends Comparable) in the type List<capture#2-of ? extends Comparable> is not applicable for the arguments (T)

error on sort line:

Type mismatch: cannot convert from List<capture#4-of ? extends Comparable> to List<T>

Conclusion:

What it comes down to, it appears, is that Java can't quite handle what I want to do. The problem is because what I'm trying to say is:

I want a list of items that are comparable against themselves, and I create the whole list at once from the data passed in at creation.

However, Java sees that I have that list and can't nail down that all the information for my situation is available at compile time, since I could try to add things to the list later and, due to type erasure, it can't guarantee that safety. It's not really possible to communicate to Java the conditions involved in my situation without applying the generic type to the class.

+1  A: 

I think that the simple answer is that you cannot do that. If the type of one of a classes attributes depends on a type parameter, that parameter has to be declared at the class level. And I don't think that it "makes sense" any other way.

If T in your example is not a type parameter of the class, what is it? It cannot be the type parameter of the method, because that type is determined by how the method is called. (If the method is called in different static contexts with different inferred types for T, what is the notional type of T in the context of the attribute declaration?)

So to bring this back to what you are trying to do here, an instance of MyClass will hold elements of some type, and you want to be able to insert and remove elements in a statically typesafe fashion. But at the same time you don't want to be able to say what that type is. So how is the compiler supposed to statically distinguish between a MyClass instance that holds (say) Integer objects and one that holds String objects?

I don't even think you could implement this with explicit dynamic typechecks. (I think that type erasure means that the implementation of the getSortedList() method cannot find out what actual type is bound to its return type.)

No. The real solution is to make MyClass a generic class that declares the type parameter T; e.g.

public class MyClass <T extends Comparable<T>> {

and remove the declaration of the method-level type parameter T from the two methods.

Stephen C
I agree that T doesn't have any context in that piece of code, but that's the problem. All I want to do is have "a list of a type that can be compared against itself, so that I can sort it". That is, apparently, harder than it sounds in a class that isn't generic.
RHSeeger
It's only hard (actually impossible) because you are trying to avoid making the class itself generic.
Stephen C
That's just it, though. Conceptually, the class isn't generic. Sure, I can make the class generic to solve the problem, but I was hoping there was a way to avoid it because it's a code smell. Possibly one required by the language, but a code smell nonetheless.
RHSeeger
I don't want to dynamically add elements to the list. I want to create a list of the (comparable to themselves) items passed in at creation time, and store that list in a member variable. The class itself doesn't need to know anything about the type... the member variable itself should hold that information.
RHSeeger
Marking this one as accepted. I think it's slightly off base with what I'm "trying" to do, certain things stated make it clear that Java just can't handle this type of thing, mainly due to type erasure... even though it's because it would break under conditions I don't want to handle anyways.
RHSeeger
I think the key factor here is that the the member variable storing the `List` of items need not be required to be defined as a list of (comparable to themselves) items because it's established by the constructor's definition that what's being passed in is already such a list.
Mark E
+1  A: 

Consider it like this (what I am about to say isn't reality. but it illustrates why you need to do what you need to do):

class Foo<T>
{
    private T value;

    T getValue() { return value; }
    void setValue(T val) {value = val; }
}

// some code that uses the above class

Foo<Integer> iFoo = new Foo<Integer>();
Foo<String> sFoo = new Foo<String>();
iFoo.setValue(5);
sFoo.setValue("Hello");

When this happens the compiler (DOES NOT REALLY DO WHAT I AM ABOUT TO SAY!) generates the following code:

class IntegerFoo
{
    private Integer value;

    Integer getValue() { return value; }
    void setValue(Integer val) {value = val; }
}

class StringFoo
{
    private String value;

    String getValue() { return value; }
    void setValue(String val) {value = val; }
}

// some code that uses the above class

IntegerFoo iFoo = new IntegerFoo();
StringFoo< sFoo = new StringFoo();
iFoo.setValue(5);
sFoo.setValue("Hello");

If you were able to have the instance variables/methods parameterized without parameterizing the class the above thing (WHICH IS NOT REALITY!) wouldn't work.

What you are trying to do should be possible with static methods, but I don't think that is what you want.

Can you explain why you want to do the code you are trying to do? Perhaps we can figure out a better way to do what you want to do that works within the language.

TofuBeer
I added slightly more details on what it is I'm trying to do. The short of it is that I have a member variable that's a list that I want to sort, but can be a list of any single (sortable) type. The class itself isn't a class "of that type", per se.. just that variable is.
RHSeeger
Something like new MyClass<Integer>(Integer.class, .........); is not an uncommon practice.
TofuBeer
+1  A: 

I believe the following will achieve what you want (stronger typing of Comparable). This will prevent people adding Comparable objects which are not from your interface to the list and allow multiple implementations.

public class test<T extends ComparableType> {


final List<T> values = new ArrayList<T>();
  public test (T... values) {
      for(T item : values) {
          this.values.add(item);
      }
  }

  public List<T> getSortedLst() {
      Collections.sort(this.values);
      return Collections.unmodifiableList(this.values);
  }
}

public interface ComparableType extends Comparable<ComparableType> {}

public class ConcreteComparableA implements ComparableType {
  @Override
  public int compareTo(ComparableType o) {
    return 0;
  }
}

public class ConcreteComparableB implements ComparableType {
  @Override
  public int compareTo(ComparableType o) {
    return 0;
  }
}

edit:

I know this may be obvious; but if you do not wish the class to be Generic this solution will also work with:

 public class test {
  final List<ComparableType> values = new ArrayList<ComparableType>();

  public test (ComparableType... values) {
      for(ComparableType item : values) {
          this.values.add(item);
      }
  }

  public List<ComparableType> getSortedLst() {
      Collections.sort(this.values);
      return Collections.unmodifiableList(this.values);
  }
}
Syntax
You still wind up with a generic class there, plus you remove the ability to create an instance with system classes such as String.
RHSeeger
Edited to indicate that it easily works without the primary class being Generic. I believe that the goal is that only approved Comparables will be able to be used (i.e. String is not usable by design).
Syntax
+1  A: 

There's plenty of unchecked warnings in this, but in principle it's not necessary to keep the List as anything but something containing things you know are Comparable. You enforce the rules you need to in the constructor, and everything else should be fine. How about something like this:

public class MyClass {

    final private List<Comparable> values;

    public <T extends Comparable<? super T>>MyClass(T... values){
        this.values = new ArrayList<Comparable>();
        for(T item : values) {
            this.values.add(item);
        }
    }

    public <T extends Comparable<? super T>> List<T> getSortedLst() {
        Collections.sort(this.values);
        return (List<T>)this.values;
    }

}

A quick test using the following shows that for classes that implement Comparable (like Integer and String) MyClass behaves as expected, but will throw a compilation error for classes that do not implement Comparable:

    class Junk { }

    public static void main(String[] args){
        MyClass s = new MyClass(1,2,3);
        System.out.println(s.getSortedLst());

        MyClass a = new MyClass("c", "a", "b");
        System.out.println(a.getSortedLst());

        MyClass c = new MyClass(new Junk());
    }
Mark E
This comes pretty close to what I'm looking for. That being said, there's another piece of the puzzle (somewhat related but making it more complex than I want to handle in the quest) that's biting me when I go this way. I do appreciate the answer, though.
RHSeeger
@RHSeeger, since this basically answers your question I'd appreciate a bit more info on why it's not sufficient...
Mark E
@Mark, the main problem was that I really wanted member variables that said "some type that implements comparable to itself", rather than just "some type that implements comparable", as much for self documenting code as for other pieces of code that want such a value as input. That being said, I also hate seeing unchecked warnings, personal peeve of mine that's more with Java's poor design than anything else.
RHSeeger
@RHSeeger: self-documenting code is nice, but your access functions clearly make the case for "some type that implements comparable to itself", the class' internal storage method is somewhat irrelevant.
Mark E
A: 
public class MyClass<T extends Comparable<? super T>> {
    // This doesn't work as T isn't defined
    final List<T> values;

    public MyClass (T... values) {
        this.values = new ArrayList<T>(Arrays.asList(values));
    }

    public List<T> getSortedLst() {
        Collections.sort(this.values);
        return this.values;
    }
}
newacct
A: 

I'd do it this way (I did it as a list or as an array), unless you really need the instance variable/methods:

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;


public class MyClass
{
    public static <T extends Comparable<T>> List<T> asSortedList(final T ... vals)
    {
        final List<T> temp;

        temp = new ArrayList<T>(vals.length);
        temp.addAll(Arrays.asList(vals));
        Collections.sort(temp);

        return (Collections.unmodifiableList(temp));
    }

    public static <T extends Comparable<T>> T[] asSortedArray(final Class<?> clazz,
                                                              final T ...    vals)
    {
        final T[] temp;

        temp = (T[])Array.newInstance(clazz,
                                 vals.length);
        System.arraycopy(vals,
                         0,
                         temp,
                         0,
                         vals.length);
        Arrays.sort(temp);

        return (temp);
    }

    public static void main(final String[] argv)
    {
        final List<String> list;
        final String[]     array;

        list = MyClass2.asSortedList("c", "a", "b");
        System.out.println(list);

        array = MyClass2.asSortedArray(String.class, "z", "y", "x");
        System.out.println(Arrays.deepToString(array));
    }
}
TofuBeer
A: 

the type constraint you want on the variable can't be expressed directly. you can introduce a new type to bridge the problem.

static class MyList<T extends Comparable<? super T>> extends ArrayList<T>{}

final MyList<?> values;

however, there is no point to be extremely type safe in a private piece of code. Generic is there to help you clarify your types, not to obfuscate them.

irreputable