Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
4.1k views
in Technique[技术] by (71.8m points)

java - How to use generics with interfaces

I'm fairly new to programming and we do have an exercise, we have to use the Consumer interface, we have a generic class (genClass) that has a Consumer<T> attribute (conAtt). In another class we have to use the accept method of Consumer, but somehow it doesn't work. I have already read through the Java API of the Consumer interface, but it didn't help.

The error message says:

The method accept(capture#4-of ?) in the type Consumer<capture#4-of ?> is not applicable for the arguments (capture#5-of ?)

I know it says not applicable, but why not?

    public abstract class GenClass<T> {
        protected Consumer<T> conAtt;
        public abstract T getData();
    }
    
    class Otherclass{
        private List<GenClass<?>> helparray= new ArrayList<>();
    
    private void testmethod() {
            Iterator<GenClass<?>> hilfe = helparray.iterator();
            while (hilfe.hasNext()) {
                GenClass<?> help = hilfe.next();
                help.conAtt.accept(help.getData());//here is the problem
            }
        }
    }

public class thirdclass extends GenClass<Character> {

    @Override
    public Character getData() {
        return 't';//t is just an example

    }

}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This is not really a question about how the Consumer - or other interfaces - in Java work, but about Generics.

Generics aim to simplify the way of writing code and avoid code repetitions. E.g. you need to do a similar task, but for different types you can write it once by using Generics instead of writing it over and over again, just with concrete types being replaced.

For example one day you have the need to keep track of a list of Strings. As easy as that, your going ahead and implementing a solution for that, whereby the first implementation can look like the following (note: a very simplified example, but it'll show the purpose):

public class CustomListString {

    private String[] elements = new String[10];
    
    public void add(String newElement) {
        int nextFreeIndex = findNextFreeIndex();
        elements[nextFreeIndex] = newElement;
    }
    
    public String get(int index) {
        return elements[index];
    }
}

So you can use the above implementation of the List in your code like the following:

public static void main(String[] args) {
    CustomListString listOfStrings = new CustomListString();
    listOfStrings.add("A");
    listOfStrings.add("B");
}

Simple, specific and sufficient!

But the other day, you also have the requirement to keep track of a list of Integers. What to do now?

A way to solve this is to just repeat your previous approach and to implement another CustomList only for the Integers now. Where the corresponding implementation would look like this (the implementation of CustomListString has been copied and all occurrences of String have been replaced by Integer):

public class CustomListInteger {

    private Integer[] elements = new Integer[10];
    
    public void add(Integer newElement) {
        int nextFreeIndex = findNextFreeIndex();
        elements[nextFreeIndex] = newElement;
    }
    
    public Integer get(int index) {
        return elements[index];
    }
}

As you can imagine now already, this is not flexible and can be very cumbersome in the future. This approach will require a new implementation of each type you want to store in the future. So you might end up to also create implementations like CustomListDouble, CustomListCharacter, ... and so on, in which only the type of the elements within the array change - nothing else which would be of importance!

This will additionally lead to the situation, that you'll duplicate a lot of similar code (like findNextFreeIndex() method would have been) and in case of a bugfix need to adjust it in a lot of places instead of in only one.

To solve this issue and remain the type safety in the CustomList.get method Generics have been introduced to Java!

With the Generics approach you'll be able to create a single implementation of the CustomList to store all of your data types without unnecessarily duplicating any shared, basic code and remain the type safety!

public class CustomList<T> {

    private Object[] elements = new Object[10]; // Java doesn't supprort easily support generic arrays, so using Object
                                                // here. But the compiler ensures only elements of the generic type T
                                                // will end up here

    public void add(T newElement) {
        int nextFreeIndex = findNextFreeIndex();
        elements[nextFreeIndex] = newElement;
    }

    @SuppressWarnings("unchecked")
    public T get(int index) {
        return (T) elements[index];
    }
}

Using the new list following the Generics approach we can use it like this now:

public static void main(String[] args) {
    CustomList<String> genericList = new CustomList<>();
    genericList.add("Hello World");
    genericList.add(5); // Compile error! Integer and String types cannot be mixed in
                        // a single instance of the list anymore => Nice, prevents errors!
    
    genericList.get(0).substring(6); // No compile error, also the compiler knows Strings
                                     // are contained in the list
}

The generic CustomList can now also be reused for any other type and still provide type safety.


What does it mean for your implementation

You can see how we specified the generic type in the CustomList class as T - this is similar like you specified it with ? (probably you'll also want to replace it with T, since you'll run into other issues later when working with the Consumer). But when we used the implementation in our other classes, it wouldn't have been possible to specify it as CustomList<T> or CustomList<?> anymore. We needed to decide and specifiy which exact type of elements the list should contain. This has been the String class, so we specified it as CustomList<String>.

Note: ? is a generic wildcard and means something like "I don't know the real type of the classes now and I'll also don't know it in the future". That's why it'll be hard for you working with the concrete types later in the Consumer. You'll be not able to call any conrete methods on your objects therein. Therefore ? should be avoided as a generic type argument and something like T should be used instead. T means something like "I don't know the real type of the classes now, but I'll do later, as soon as you tell me". Therfore you'll be able to call concrete methods on the objects later in the Consumer, what will simplify your work there a lot.

For your code this means, wherever you want to use your implementation of GenClass<T> you need to specify with which exact kind of elements the class is going to work with. In case of String it is GenClass<String> in case of Character GenClass<Character>.

So the place you'll need to replace the occurrences of GenClass<?> is wherever you refer to it in Otherclass and Otherclass.testmethod.

The way you used the Consumer is fine


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...