0

There are things that have weight, so here's their interface, for example:

public interface Banana {
    public Double getWeight();
}

    // This function is a static function in a public tool class.
    @Nullable
    public static Banana pickItemByTheirWeight(List<Banana> originalVideos) {
        // ...
        return randomOneByWeight;
    }

now I want to create things that have weight, so:

class Video implements Banana {
   @Override
   public Double getWeight() {
       //....
       return weight;
   }
}

now, when I want to pick a video by weight, for example:

List<Video> videos = new ArrayList<>();
// ...
Video randomOne = pickItemByTheirWeight(videos);

I get a compile error. so what is the correct way to do it?


Hey guys, it's not that easy.

I tried

public static <T extends Banana> T pickOneByTheirWeight(List<T> domains) {
    // ...
}

public abstract class Banana {
   public abstract Double getWeight();
}

And it's still not working, I cannot call the function without cast the class type.

* can someone tell my why Java choose not to let it just work ???*


After reading some basic knowledge to Java Generics, here comes a solution:

public static <T extends Banana> T pickOneByTheirWeight(List<? extends Banana> domains) {
        if (domains.isEmpty()) {
            return null;
        }       
        // calculate total weight
        Double totalWeight = domains.stream().map(x -> {
                Double weight = x.getWeight();
                return weight == null ? 0.0 : weight;
            }).reduce((first, second) -> {
            Double firstWeight = first != null ? first : 0.0;
            Double secondWeight = second != null ? second : 0.0;
            return firstWeight + secondWeight;
        }).get();
        // random process
        final Double randomSeed = Math.random() * totalWeight;
        double currentWeight = 0.0;
        for (Banana v: domains) {
            Double weight = v.getWeight();
            weight = weight == null ? 0.0 : weight;
            currentWeight += weight;
            if (currentWeight >= randomSeed) {
                return (T) v;
            }
        }
        // it'll not reach here indeed.
        logger.warn("pickDomainByTheirWeight cannot pick an element from list by their weights.");
        return null;
    }

But declear the function like this, we can simply call:

List<Video> videos = new ArrayList<>();
// ...
Video p = Utility.pickOneByTheirWeight(videos);

no more cast outside, however, still a cast inside the function. Any better idea?

Andronicus
  • 25,419
  • 17
  • 47
  • 88
nimbus_debug
  • 620
  • 2
  • 7
  • 17

4 Answers4

1

Because List<Banana> is not a List<Video>. Change method signature to:

@Nullable
public static Banana pickItemByTheirWeight(List<Video> originalVideos) {
    // ...
    return randomOneByWeight;
}

Or better - using common superclass:

@Nullable
public static Banana pickItemByTheirWeight(List<? extends Banana> originalVideos) {
    // ...
    return randomOneByWeight;
}
Andronicus
  • 25,419
  • 17
  • 47
  • 88
  • what I want to do is to create a function that can handle all kinds of class that implements Banana. – nimbus_debug Aug 30 '19 at 04:10
  • Check out my edit - it works if `Video` is a subclass of `Banana` – Andronicus Aug 30 '19 at 04:12
  • it's not working, still bad grammar, cannot compile – nimbus_debug Aug 30 '19 at 04:27
  • Can you be mo precise? What's not compiling? – Andronicus Aug 30 '19 at 04:36
  • Well if it works, consider upvoting / marking as correct;> – Andronicus Aug 30 '19 at 08:01
  • the concept is called "Using Wildcards with Generics", since the List<...> is already using generics, and it's the only way java can handle generics of Generics. see my edit of original post. – nimbus_debug Aug 30 '19 at 08:05
  • 1
    you help me find the "Using Wildcards with Generics" concept, you got it :-) – nimbus_debug Aug 30 '19 at 08:09
  • btw, can you tell me why the most natural thought: public static T pickOneByTheirWeight(List domains) is not woking out of the box? I mean, why java not support it? – nimbus_debug Aug 30 '19 at 08:18
  • That's the idiom. I think what you proposed is counterintuitive - it would suggesting, that it must be the same type, which isn't. One of them could be `Video` other - `Banana` – Andronicus Aug 30 '19 at 08:23
  • Yes, I would like the type T in the return be exactly the same as the type T in parameter.(You can check the function code for detail.) Maybe the feature is not considered to be important in Java, since programmers can make it sure in their function implementations. However the lack of what I am proposing will result in some kind of inconvenience. – nimbus_debug Aug 30 '19 at 08:29
  • Unfortunately it cannot be done without for example passing an instance of type Class – Andronicus Aug 30 '19 at 08:32
  • hmmm... what a pity. – nimbus_debug Aug 30 '19 at 08:33
  • Yeah, generics in java are not well designed, unfortunately. The compiler won't help in many situations. You can learn more here: https://stackoverflow.com/questions/520527/why-do-some-claim-that-javas-implementation-of-generics-is-bad – Andronicus Aug 30 '19 at 08:38
1

You get the error because List<Video> is not List<Banana>. Have a look at this SO question.You could make use of generics and change your method to :

@Nullable
public static <T extends Banana> T pickItemByTheirWeight(List<T> bananas) {
    // ... pick something that implements banana from the list and return it
    return randomOneByWeight;
}

This is when you want to return subtype of Banana from your method. If you do not want to do it, you can get rid of this error by changing your method to :

@Nullable
public static Banana pickItemByTheirWeight(List<? extends Banana> bananas) {
    // ...
    return randomOneByWeight;
}

and since it always returns an Banana use it like :

Banana randomOne = pickItemByTheirWeight(videos);
Michał Krzywański
  • 15,659
  • 4
  • 36
  • 63
0

The pickItemByTheirWeight method expects a list of Banana as a parameter and you give it a list of Video. As a Video is a Banana, you must declare a list of Banana to store your video into.

List<Banana> videos = new ArrayList<>();
// ...
// The method returns a Banana, not a Video
Banana randomOne = pickItemByTheirWeight(videos);
// But you may explicitly cast the Banana as a Video (even if you are not sure the Banana you got from this call is really a Video
Video randomTwo = (Video) pickItemByTheirWeight(videos);
Olivier Depriester
  • 1,615
  • 1
  • 7
  • 20
  • what I want to do is to create a function that can handle all kinds of class that have weight, so the function can easily use the same logic to pick item from list. – nimbus_debug Aug 30 '19 at 04:11
0

there are 2 concept of Generics here:

  1. Using Wildcards with Generics for List parameters: since it's already using generics, and you have to use wildcard with generics.

  2. generics function template, it's simple basic.

combining 1 + 2, we get the answer:

public static <T extends Banana> T pickOneByTheirWeight(List<? extends Banana> domains) {
   // ...
}

see my original post edits for more information.

nimbus_debug
  • 620
  • 2
  • 7
  • 17