Showing posts with label generic methods. Show all posts
Showing posts with label generic methods. Show all posts

Sunday, October 25, 2009

Bookmark and Share

Update (10/21/2009): There is now a second part to this post, which can be found here.

Using data transfer objects (DTO's) is a common technique for transferring data between different modules of an application. Especially within distributed systems DTO's are of great help for transporting data over remote boundaries.

Classic DTO's are basically standard JavaBeans, having a getter/setter pair for each attribute. In larger applications usually quite a lot of DTO's are required, and implementing them is a tedious and time consuming task.

That's why the idea of a generic DTO was born – basically a key/value map, which can store any sort of attribute. This approach saves a lot of time for writing dedicated DTO's, but it comes at the cost of losing type safety at compile time.

A type-safe generic DTO

This problem can be solved by identifying attributes not with plain strings, but with unique parametrized identifiers. To do so, let's define a parametrized class Attribute as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Attribute<T> implements Serializable {

    private static final long serialVersionUID = 1L;

    private final String name;

    private Attribute(String name) {

        if (name == null) {
            throw new IllegalArgumentException();
        }
        this.name = name;
    }

    public static <S> Attribute<S> getInstance(String name) {
        return new Attribute<S>(name);
    }

    @Override
    public String toString() {
        return name;
    }

    //equals(), hashCode() ...

}

The type parameter <T> defines the data type of a given attribute. For each required attribute a constant identifier should be created, e.g. in form of static members of an interface:

1
2
3
4
5
6
7
public interface CustomerAttributes {

    public final static Attribute<String> NAME = Attribute.getInstance("CustomerAttributes.Name");

    public final static Attribute<Integer> ID = Attribute.getInstance("CustomerAttributes.Id");

}

Note, that Attribute instances are retrieved using a static factory method instead of directly invoking the constructor. This allows for the type of <T> of the created Attribute being determined automatically by the compiler by using type argument inference.

Now let's have a look a the generic DTO itself:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class GenericDTO implements Serializable {

    private static final long serialVersionUID = 1L;

    private Map<Attribute<?>, Object> attributes = new LinkedHashMap<Attribute<?>, Object>();

    public <T> GenericDTO set(Attribute<T> identifier, T value) {

        attributes.put(identifier, value);

        return this;
    }

    public <T> T get(Attribute<T> identifier) {

        @SuppressWarnings("unchecked")
        T theValue = (T) attributes.get(identifier);

        return theValue;
    }

    public <T> T unset(Attribute<T> identifier) {

        @SuppressWarnings("unchecked")
        T theValue = (T) attributes.remove(identifier);

        return theValue;
    }

    public void clear() {
        attributes.clear();
    }

    public int size() {
        return attributes.size();
    }

    public Set<Attribute<?>> getAttributes() {
        return attributes.keySet();
    }

    public boolean contains(Attribute<?>... identifiers) {

        for (Attribute<?> oneIdentifier : identifiers) {
            if (!attributes.containsKey(oneIdentifier)) {
                return false;
            }
        }

        return true;
    }

    @Override
    public String toString() {
        return "GenericDTO [" + attributes + "]";
    }

    //equals(), hashCode() ...

}

As with the "classic" generic DTO the attributes are stored in a map (by using a LinkedHashMap the toString() method returns the attributes in order of insertion), but the set() and get() methods are parametrized with <T>.

That way for an Attribute<T> only a value of type T can be stored in or retrieved from the DTO. For instance the call

1
dto.set(CustomerAttributes.ID, "Bob"); 

would yield in a compiler error, as "Bob" is of type String instead of Integer.

Note that the unchecked casts within the get() and unset() methods are actually safe, as attributes can be put into the map only by using the type-safe set() method.

As convinience the set() method returns the DTO itself. That allows for chaining multiple invocations:

1
dto.set(CustomerAttributes.ID, 123).set(CustomerAttributes.NAME, "Bob");

Summary

By using unique parametrized attribute identifiers a generic DTO can be created, that ensures type safety when putting attributes into it or retrieving them from it.

But one problem remains: the contract of methods that use GenericDTO as parameter or return value is rather loose. It is not obvious, which attributes are allowed/can be expected within one given GenericDTO instance.

This might be documented using JavaDoc, but actually there is another interesting approach for tackling this problem, which is shown in this post.

Sunday, January 18, 2009

Bookmark and Share

With the advent of generics in Java 5, also generic methods were introduced. Generic methods have one ore more type parameters, which can be used to specify the parameters or return type of a method. For example the JDK's collection utility class java.util.Collections declares a wide range of generic methods.

When using those methods, the concrete types to be used as parameters or return value are derived from the invocation context using type inference (Angelika Langer goes into detail on this topic in her great Generics FAQ here).

So for instance you can use the method emptyMap() of the mentioned class Collections as follows to initialize an empty Map<String, Long>:

1
2
3
4
5
6
7
8
9
public class GenericMethodsTest {

    @Test
    public void testTypeInference() {

        Map<String, Long> map = Collections.emptyMap();
        assertTrue(map.isEmpty());
    }
}

Unfortunately, this doesn't work, if the return value of the generic method isn't assigned to a variable, but rather is used as parameter for another method invocation. So the following code fails with a compiler error at line 6 saying "The method doSomething(Map<String,Long>) in the type GenericMethodsTest is not applicable for the arguments (Map<Object,Object>)":

1
2
3
4
5
6
7
8
9
10
11
12
public class GenericMethodsTest {

    @Test
    public void testTypeInference() {

        doSomething(Collections.emptyMap()); //compiler error
    }

    private void doSomething(Map<String, Long> map) {
        assertTrue(map.isEmpty());
    }
}

What I did till today in such a case was to assign the result of the generic method to a local variable and using this variable as parameter. This is somewhat ugly and actually not neccessary at all, as it is possible to specify the type parameters explicitely as follows:

1
2
3
4
5
6
7
8
9
10
11
12
public class GenericMethodsTest {

    @Test
    public void testTypeInference() {

        doSomething(Collections.<String, Long> emptyMap());
    }

    private void doSomething(Map<String, Long> map) {
        assertTrue(map.isEmpty());
    }
}

Actually a practical but rather unknown feature of the Java language, which makes the code shorter while more readable at the same time. More information on explicitely specifying type parameters when calling generic methods can again be found at Angelika Langer's FAQ here.