-1

I have a CustomObject declared as raw type of <T>. And when I populate a List<CustomObject> with new instances of it, I can't get them back as a CustomObject, only as an Object.

public class CustomObject<T> {
    private String name;
    private T value;

    // getters and setters
}

But obviously when I use subclass, all is working as expecting;

public class CustomObject {

    private class SubCustomObject<T> {
        private String name;
        private T value;
    }

    public CustomObject() {
        this.customObject = new SubCustomObject();

    private SubCustomObject customObject;

    // getters and setters
}

Is there a way to make the first example to behave like the second one, and avoid using extra object and so I could do this:

public class CustomObject<T> {
    private String name;
    private T value;
    private boolean isGroup;

    // getters and setters

    private void setValue(T value) {

        if (value instanceof String) {
            this.value = value;
            this.isGroup = false;
        }

        if (value instanceof CustomObject) {
            if (isGroup()) {
                ((List<CustomObject>) this.value).add((CustomObject) value);
            } else {
                this.value = (T) new ArrayList<CustomObject>();
                this.isGroup = true;
                setValue(value);
            }
        }
    }
}

public void getItemByName(String name) {
    // say the list is already populated
    for (CustomObject object : listOfCustomObject) {
        String nameField = object.getName();
        if (name.equals(nameField) {
            System.out.println(nameField);
        }
    }
}

Instead of this one:

public void getItemByName(String name) {
    // say the list is already populated
    for (Object object : listOfCustomObject) {
        String nameField = ((CustomObject)object).getName();
        if (name.equals(nameField) {
            System.out.println(nameField);
        }
    }
}

// Apply that behavior to this and avoid to use inner class.

public class MetadataEntry {

    public MetadataEntry() {
        this.entity = new Entry();
    }

    private class Entry<T> {
        private String name;
        private T value;
        private boolean isGroup;

        private void setValue(T value) {

            if (value instanceof String) {
                this.value = value;
                this.isGroup = false;
            }

            if (value instanceof MetadataEntry) {
                if (isGroup()) {
                    ((List<MetadataEntry>) this.value).add((MetadataEntry) value);
                } else {
                    this.value = (T) new ArrayList<MetadataEntry>();
                    this.isGroup = true;
                    setValue(value);
                }
            }
        }

    }

    private Entry entity;


    public void setName(String name) {
        this.entity.name = name;
    }

    public String getName() {
        return this.entity.name;
    }

    public void setValue(String value) {
        entity.setValue(value);
    }

    public void setValue(MetadataEntry value) {
        entity.setValue(value);
    }

    public boolean isGroup() {
        return this.entity.isGroup;
    }

    public List<MetadataEntity> getChildNodes() {
        if (isGroup()) {
            return (List<MetadataEntry>) this.entity.value;
        }

        return null;
    }

    public String getValue() {
        if (!isGroup()) {
            return (String) this.entity.value;
        }

        return null;
    }
}
SuperStormer
  • 4,997
  • 5
  • 25
  • 35
Katherine
  • 576
  • 1
  • 7
  • 19
  • 3
    Looks rather like you want the list containing the custom objects to be generic. – Calculator Dec 27 '16 at 23:12
  • I see now. And everything is possible. My immediate thought is you’re misusing generics. One option is to use `Object` instead of `T`. I’ll see if I can come up with something better. – Ole V.V. Dec 27 '16 at 23:28
  • @Calculator So you'd rather make a custom list that gonna use just once, instead of even casting the object anytime you want. Thanks, but no. – Katherine Dec 27 '16 at 23:30
  • @Ole V.V. Even using `Object` instead of `T` I would still need to cast the object. I want to avoid this, but keeping raw type for the field so I could set it with a value of any type I want. – Katherine Dec 27 '16 at 23:36
  • A few minutes ago you added a code example and shortly after deleted it again. Was that the actual code you wanted to improve? – Calculator Dec 27 '16 at 23:38
  • I don’t know what to tell you now. You briefly showed me your `MetadataEntry` class with the raw instance of the generic inner class, and I tink I have a better suggestion for that one, but it may not make sense in the context of the question as it looks now. – Ole V.V. Dec 27 '16 at 23:38
  • If you want the raw type for the field, you can hardly avoid the cast. If you accept the cast, I don’t see much point in using generics. On the other hand, to get rid of the cast you need to get rid of the raw field. That’s certainly possible. – Ole V.V. Dec 27 '16 at 23:40
  • 1
    Can you please add a https://stackoverflow.com/help/mcve that shows the problem? Your code works for me, but then again I don't know how you declare `listOfCustomObject`. – Robert Dec 27 '16 at 23:44
  • The `void setValue(T value)` method you want does not seem type safe here: `this.value = value;` – Brian Kent Dec 27 '16 at 23:58
  • Please make up your mind. Your code does not even compile because of name mixups. It does not have the needed import statements, but way too many getters and setters. At this time, the best pointer I can give you is that you're probably using generics the wrong way if you try to use `instanceof` on the type to then force the type into something else (a list of Ts in this case). – Robert Dec 28 '16 at 00:00
  • 1
    I'd rather get an elaborated comment instead of a down-vote on the question. Because all who left a comment on the subject by now is telling that I don't use generics right, but no one told where and why. Moreover did make a suggestion how to make it better. – Katherine Dec 28 '16 at 00:08
  • What we're in the proces of reinventing is the *Composite* design pattern. Go look it up. :-) – Ole V.V. Dec 28 '16 at 02:34
  • https://en.wikipedia.org/wiki/Composite_pattern – Ole V.V. Dec 28 '16 at 11:29
  • IMHO it’s a great question, but complex enough that it is difficult to put on a terse form. I was one out of more asking for the full code to gain the full understanding, while I understand those that think it’s too much code now. – Ole V.V. Dec 28 '16 at 11:37
  • 1
    @Ole V.V. OP opened a new question which clarifies the purpose of this data structure a bit. [here](http://stackoverflow.com/q/41354246/7274990) – Calculator Dec 28 '16 at 11:42
  • You've received a few answers, did any solve your problem? – user1803551 Dec 29 '16 at 12:32

4 Answers4

0

You can not make a list of different types X,Y,Z and put it in a single container of type W. You need to define a bounding parameter on your raw type so that your items and list are of same type. probably your T should be bounded by some interface type or it should extends some class.

hhafeez
  • 73
  • 9
  • Actually, I can put it in a field of raw type and then make checks on it. – Katherine Dec 27 '16 at 23:45
  • You can, if you make the container to hold a super type of the items, e.g., `List`, which defeats the purpose of templates, or `List extends Base>`. – Robert Dec 27 '16 at 23:46
  • I have it working as I have described it in the question. – Katherine Dec 27 '16 at 23:48
  • 2
    yes but that destroys the purpose of generics and you have to check the types all the time at retrieval. You are not using generics the right way – hhafeez Dec 27 '16 at 23:50
  • I need just two value types which are `String` and `MetadataEntry`. – Katherine Dec 27 '16 at 23:53
  • Because of the recursive nature of your CustomObject, I would like to see how you creating your listOfCustomObject – hhafeez Dec 28 '16 at 01:15
0

Here’s my suggestion. I have abandoned the generics. Instead of just one inner class there is now an abstract inner class with two subclasses, one for groups and one for entries that are not groups. The good news: no cast is necessary anywhere.

public class MetadataEntry {

    private String name;

    static abstract class Entry {
        abstract Entry setValue(String value);
        abstract Entry setValue(MetadataEntry value);
        abstract boolean isGroup();
        abstract List<MetadataEntry> getChildNodes();
        abstract String getSimpleValue();

    }

    static class SimpleEntry extends Entry {
        private String value;

        public SimpleEntry(String value) {
            this.value = value;
        }

        @Override
        Entry setValue(String value) {
            this.value = value;
            return this;
        }

        @Override
        Entry setValue(MetadataEntry value) {
            return new GroupEntry(value);
        }

        @Override
        public boolean isGroup() {
            return false;
        }

        @Override
        public List<MetadataEntry> getChildNodes() {
            return null;
        }

        @Override
        public String getSimpleValue() {
            return value;
        }
    }

    static class GroupEntry extends Entry {
        List<MetadataEntry> value;

        public GroupEntry(MetadataEntry value) {
            this.value = new ArrayList<>();
            this.value.add(value);
        }

        @Override
        Entry setValue(String value) {
            return new SimpleEntry(value);
        }

        @Override
        Entry setValue(MetadataEntry value) {
            this.value.add(value);
            return this;
        }

        @Override
        public boolean isGroup() {
            return true;
        }

        @Override
        public List<MetadataEntry> getChildNodes() {
            return value;
        }

        @Override
        public String getSimpleValue() {
            return null;
        }
    }

    private Entry entity;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public void setValue(String value) {
        entity = entity.setValue(value);
    }

    public void setValue(MetadataEntry value) {
        entity = entity.setValue(value);
    }

    public boolean isGroup() {
        return this.entity.isGroup();
    }

    public List<MetadataEntry> getChildNodes() {
        return entity.getChildNodes();
    }

    public String getValue() {
        return entity.getSimpleValue();
    }
}

I have used an idea similar to what m 1987 said about a class that returns an instance of itself. I applied it to the inner classes only to free the users of the outer class from caring about this trickery. If you prefer, I am sure it could be applied to the outer class instead. Then you would have an abstrat class on the outer level with two subclasses, and would no longer need the inner classes. This is one of the things you asked for, so you may prefer it, but it comes at a cost: anyone calling setValue() on the outer class would need to remember that they got a new instance back.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • Maybe it would better to stick with some of an implementation of a tree's node to serialize files (XML). Do you know good ones, which would support multiple children of a type of `itself` or a singular value of `String`? – Katherine Dec 28 '16 at 00:49
  • Because my implementation that uses `` and which do you say is not right, uses the same raw types as [this one](http://stackoverflow.com/a/22419453/5233074) – Katherine Dec 28 '16 at 00:54
  • That tree example is different. In a tree, `T` is the same for all nodes — if it is `String`, then it's `String` throughout the tree. You also see no casts in the code. So that is fine. – Ole V.V. Dec 28 '16 at 02:33
0

I have a CustomObject declared as raw type of <T>.

That doesn't makes sense. You either have a raw type or a generic, not a raw type of a generic.

And when I populate a List with new instances of it, I can't get them back as a CustomObject, only as an Object.

Because your list is not generic (always bad). When you declare a List<Something> it will return Something on a get call. That Something can be generic or a raw type. A List<CustomObject<String>> will not accept a CustomObject<Integer> and using the raw type List<CustomObject> will end in disaster, hence the danger in raw types.

Now let's look at your code. The class

public class CustomObject<T> {
    private String name;
    private T value;
}

defines an object that behaves the same for any type. In essence what you have here is just a glorified Object with a String serving as its name attached to it.

However, now you do

private void setValue(T value) {

    if (value instanceof String)
        // ...

    if (value instanceof CustomObject)
        // ...
}

which separates the behavior for different types. and what happens if the generic type is not a String or a CustomObject?

Let's try to solve your problem. Since generics are meant to unify the behavior, let's look at what the unified behavior is that you're trying to get:

public void getItemByName(String name) {

    for (CustomObject object : listOfCustomObject) {
        String nameField = object.getName();
        // ...
        }
    }
}

Basically, you require that all the items in listOfCustomObject implement a String getName() method. That's it as far as I can see from your question. That means that your CustomObject<T> should either implement an interface or extend a class (call it Superthing) with that method. Then you will just declare your list as List<? extends Superthing>.

As for the CustomObject itself, it doesn't need to be generic as you hint that there are only 2 types of generics you want to deal with (you have 2 ifs, but no else to deal with a general case). It looks like what you want are 2 different classes with different behaviors that both implement or extend a common supertype.

Try something like this:

abstract class AbstractEntry {

    private String name;
    protected boolean isGroup;

    public void setName(String name) {

        this.name = name;
    }

    public String getName() {

        return name;
    }

    public boolean isGroup() {

        return isGroup;
    }
}

class MetaEntry extends AbstractEntry {

    AbstractEntry value;

    MetaEntry(AbstractEntry value) {

        this.value = value;
        // handle isGroup
    }   

    public void setValue(AbstractEntry value) {

        this.value = value;
    }

    public AbstractEntry getValue() {

        if (!isGroup)
            return value;
        return null;
    }
}

class StringEntry extends AbstractEntry {

    String value;

    StringEntry(String value) {

        this.value = value;
        isGroup = false;
    }

    public void setValue(String value) {

        this.value = value;
    }

    public String getValue() {

        return value;
    }
}
user1803551
  • 12,965
  • 5
  • 47
  • 74
0

I think there is no need of list as it always hold only one element. As @Ole V.V mentioned, the requirement naturally calls for the use of composition and in fact, generic does not fit into your requirements. Here is how I would tackle your requirements:

public interface Named {

public String getName();
public String getValue();
}

public class CustomObject implements Named {
private String name;
private String value;
private boolean isGroup;

// getters and setters

private boolean isGroup() {

    return isGroup;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public String getValue() {
    return value;
}

public void setValue(String value) {
    this.value = value;
}
}

public class CustomObject2 implements Named {

private String name;
private CustomObject value;


public String getName() {
    return name;
}
public void setName(String name) {
    this.name = name;
}
public String getValue() {
    return value.getValue();
}
public void setValue(CustomObject value) {
    this.value = value;
}

}

public class DriverCustomObject {

public static void main(String arg[]) {
    CustomObject t = new CustomObject();
    t.setName("key1");
    t.setValue("value1");
    CustomObject2 t2 = new CustomObject2();
    t2.setName("complex");
    t2.setValue(t);

    List<Named> list = new ArrayList<Named>();
    list.add(t);
    list.add(t2);
    for (Named l : list) {
        System.out.println(l.getName());
        System.out.println(l.getValue());

    }
}

}
hhafeez
  • 73
  • 9