简体   繁体   中英

What is the difference between using generics and interfaces in methods and classes

I'm wondering what is the difference between using an interface (or a supertype) in a generic class or method and using the generic approach with bounding (T extends Interface).

Let's say that I have two classes that implement and interface (naive examples):

public interface MeasurableDistance {
    public Point getPosition();
}

public class Person implements MeasurableDistance {
    private Point position;

    public Point getPosition() {
        return position;
    }
}

public class House implements MeasurableDistance {
    private Point position;

    public Point getPosition() {
        return position;
    }
}

1) What would be the difference between writing a method in the following ways:

public int computeDistance(MeasurableDistance a, MeasurableDistance b) {
    Point a = a.getPosition();
    Point b = b.getPosition();
    //compute distance
    return distance,
}

and something like

public <T extends MeasurableDistance, S extends MeasurableDistance> int computeDistance(T a, S b) {
    Point a = a.getPosition();
    Point b = b.getPosition();
    //compute distance
    return distance,
}

2) And if I wanted a class to hold a MeasurableDistance object:

public class Holder {
    private MeasurableDistance holder;

    public Holder() {};
    public add(MeasurableDistance a) {
        holder = a;
    }
}

or something like

public class Holder<T extends MeasurableDistance> {
    private T holder;

    public Holder<T>() {};
    public add(T a) {
        holder = a;
    };
}

For 1, I think they should be pretty much identical, right? Obviously calling the non generic version

Person a = new Person();
Person b = new Person();
House c = new House();
House d = new House();
computeDistance(a,c);
computeDistance(a,b);
computeDistance(c,d);

would always work because computeDistance() would be seeing those objects as MeasurableDistance because of polymorphism. The generic version instead should work too right? The compiler would infer the MeasurableDistance type and add casts accordingly. Even if I wanted to call it this way:

<Person, House>computeDistance(a,c);

it would have the same effect, the method would look like

    Point a = (MeasurableDistance) a.getPosition();
    Point b = (MeasurableDistance) b.getPosition();

if I'm not mistaken. Or, now that I think about it, it should look like this:

    Point a = (Person) a.getPosition();
    Point b = (House) b.getPosition();

and it would work because both Person and House are implementing that method.

So what would be the advantage of the generic method? I've read that it's typesafe because casts are always right, but with the non generic method you won't have casts at all.

You seem to be confusing the reason to use generics, with the reason to use bounded generics.

A reason for using bounded generics, implies that there is already a reason to use generics.

Let me recap.


The reason to use generics is explained in the Java tutorials , and one of the most visible reasons is this:

The following code snippet without generics requires casting:

 List list = new ArrayList(); list.add("hello"); String s = (String) list.get(0); 

When re-written to use generics, the code does not require casting:

 List<String> list = new ArrayList<String>(); list.add("hello"); String s = list.get(0); // no cast 

The reason to use bounded generics is separate from the reason to use generics. If you have a reason to use generics , using bounds let's you require a certain level of functionality of a type:

public class MyClass<T> {
    private List<T> list;
    ...
    public void disposeAll() {
        for(T e : list)
            e.dispose(); // compile time error
    }
}

The above error can be solved by adding a bound:

public class MyClass<T extends Disposable> {...}

The cases that you have shown don't actually have a reason to use bounded generics, like you have pointed out. But this is because they don't have a (good) reason to use generics in the first place.

In case 2, generics let you limit the type of object that can be held by Holder :

Holder<House> holder = new Holder<>();
holder.add(new Person()); // compile time error
holder.add(new House());

Which isn't very useful. There is no real reason to use generics there.

But if you were also retrieving the value, generics would be useful:

Holder<House> holder = new Holder<>();
// holder.add(new Person());
holder.add(new House());
House h = holder.get(); // no cast

First of all, when you define a method like

public <T extends MeasurableDistance, S extends MeasurableDistance>
    int computeDistance(T a, S b) {

    Point a = a.getPosition();
    Point b = b.getPosition();
    //compute distance
    return distance,
}

There will be no type cast at all. This method will do exactly the same as with the method signature

public int computeDistance(MeasurableDistance a, MeasurableDistance b)

which is the reason why the generic type parameters make no sense here; they don't change anything.


As a rule of thumb, type parameters on methods are useful when you want to declare a relationship between two or more parameters or between parameter(s) and the return type.

public <T extends MeasurableDistance> T validate(T a, Rectangle area) {
    Point p = a.getPosition();
    if(!area.contains(p))
        throw new IllegalArgumentException();
    return a,
}

Here, we have a relationship between the parameter and return type, which allows use to use it like:

Person person;
Rectangle localArea;

public void setPerson(Person newPerson) {
    this.person = validate(newPerson, localArea);
}

because the generic signature specifies that when we substitute Person for T , we have to pass in a Person instance and are guaranteed to get back a Person instance.


Likewise, generic signatures at classes are useful, if we can express a relationship between two or more members of the class. Eg a List expresses such relationship as the type of the objects we add or set is the same type we can retrieve via get .

So your Holder class would be an appropriate use case for a type parameter, when you add a method to retrieve the encapsulated object, as then, there is a relationship between the storage and retrieval methods. As a consequence, there is also a relationship between these two methods and the type of the member variable holding that reference, which is required for correctly implementing the logic.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM