2

There is a method which receives an argument of type Collection and it needs to use some of the methods that are found in the List class when it does work with that argument. Is up-casting expensive in terms of speed?

List<Stuff> list = (List<Stuff>) collection;

I would also like to note that the collection object is never used after this, only the list, and that this will be compiled and run on Oracle Java 1.6.

assylias
  • 321,522
  • 82
  • 660
  • 783
stackular
  • 1,431
  • 1
  • 19
  • 41
  • 2
    Well, that's not upcasting. That's downcasting. And you shouldn't be bother about expenses, when you have no other options. But, I think that would be negligible. But better thing to worry is, some `ClassCastException` at runtime. – Rohit Jain Jul 20 '13 at 16:13
  • 1
    "when it does work with that argument" <-- uh, what do you mean? If this is a `List`, why not return a `List` in the first place? – fge Jul 20 '13 at 16:18
  • 2
    Considering Generics are casting constantly anyway because of how they were implemented in Java, and it's unlikely a nanosecond or two is going to make any difference ever in anything you're writing, I highly suggest you abandon this particular premature optimization conundrum. – Brian Roach Jul 20 '13 at 16:53
  • @fge I think the idea is that if the argument is a `List`, then the method does something that's optional besides what it does for all `Collections`. Of course it's hard to tell what the point is with a vague made-up example. – millimoose Jul 20 '13 at 17:09
  • It is quite realistic to be stuck with an interface method which, in our particular application, always receives a list. – Marko Topolnik Jul 20 '13 at 17:21

1 Answers1

16

Serious answers are given by actual benchmarks. For example, I used this jmh-targeting code:

public class Benchmark1
{
  static final List<Integer>[] lists = new List[10000]; static {
    for (int i = 0; i < lists.length; i++) {
      lists[i] = new ArrayList<Integer>(1);
      lists[i].add(1);
    }
  }
  static final Collection<Integer>[] colls = new Collection[lists.length]; static {
    for (int i = 0; i < colls.length; i++) colls[i] = lists[i];
  }


  @GenerateMicroBenchmark
  public long testNoDowncast() {
    long sum = (long)Math.random()*10;
    for (int i = 0; i < lists.length; i++) sum += lists[i].get(0);
    return sum;
  }
  @GenerateMicroBenchmark
  public long testDowncast() {
    long sum = (long)Math.random()*10;
    for (int i = 0; i < colls.length; i++) sum += ((List<Integer>)colls[i]).get(0);
    return sum;
  }
}

And jmh provided the following results:

Benchmark          Mode Thr    Cnt  Sec         Mean   Mean error    Units
testDowncast      thrpt   1      5    5       18.545        0.019 ops/msec
testNoDowncast    thrpt   1      5    5       19.102        0.655 ops/msec

If you need interpretation, it is the following: there is no difference whatsoever.

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
  • 7
    +1 I want to have babies with this post. – millimoose Jul 20 '13 at 17:08
  • Side question: how do you compare jmh and caliper, provided you know the latter? – fge Jul 20 '13 at 17:13
  • @fge I've first worked with caliper and am now on jmh, primarily on assylias's advice. They both seem very solid, but since this one is from the horse's mouth, so to speak, now I'm with it. – Marko Topolnik Jul 20 '13 at 17:14
  • OK, thanks for the information... This is the first time I've heard of jmh, actually. I'll have a look at it! – fge Jul 20 '13 at 17:18
  • 2
    “the difference is 4%” – the confidence interval is more relevant. Since the mean ± 1 SD (derived from the MSE) overlap there is *no* statistically significant difference. – Konrad Rudolph Jul 20 '13 at 17:36
  • @Marko Don’t take my comment seriously, I’m actually overjoyed because this is the first statistically accurate microbenchmark **ever** that I’ve seen on Stack Overflow. You’re my hero today. :-) – Konrad Rudolph Jul 20 '13 at 18:37
  • Isn't `(long) Math.random()` just `0`? – Peter Lawrey Jul 20 '13 at 19:54
  • 1
    @PeterLawrey Oops... some parens are missing, aren't they? But it's not really important, the point is to foil the JITter, which I think this does anyway. I will correct it in the future code though, thanks for noticing. – Marko Topolnik Jul 20 '13 at 20:16
  • @PeterLawrey BTW I think that none of this was in fact necessary because a downcast may never be JITted away since that would change the behavior of the code. – Marko Topolnik Jul 20 '13 at 20:18
  • @PeterLawrey Now I've tested with just `for (int i = 0; i < lists.length; i++) { Integer x = lists[i].get(0); }` and similar for the downcast case and, paradoxically, the results are even closer, the *downcast* case even leading by a thin margin! The magnitude of the numbers is virtually the same as above, so no elision happened. – Marko Topolnik Jul 20 '13 at 20:23
  • 1
    @MarkoTopolnik My expectation is that it takes about 15 nano-seconds per up-cast which appears to be similar to the results you are seeing. Your code is much more expensive than the up cast itself, which is a good answer for the OP. ;) – Peter Lawrey Jul 20 '13 at 20:35