$ cat foo.java
public class foo{
public static void main(String[] args){
System.out.println(12345);
}
}
$ javac foo.java
$ java foo
12345
$ java -verbose:class foo | head -n 5
[Opened /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.Object from /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.io.Serializable from /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.Comparable from /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.CharSequence from /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/rt.jar]
$ java -verbose:class foo | wc -l
420
Now, 2000 classes isn't nothing, but it's not a particularly large number. Any medium sized application using a few third party libraries easily reaches that number.
I understand that traits are something like typeclasses or interfaces: are single-method traits kept around as traits, or is the function itself passed around in the optimized output?
I'm actually suddenly curious about how other functional language compilers look compared to GHC.
Abstract classes can be efficiently encoded, since the JVM understands that. The Scala collections collapse common sets of traits into abstract classes.
Traits are copied, always I think. Only traits with all final methods could even possibly be not duplicated.
That's not just because of the JVM; idomadic Scala generates volumes of byte code, due to anon functions and traits.