I tried the following code :
class testingFinally {
public static String getMessage() {
String s = "hi";
try {
return s;
} finally {
s = null;
}
}
public static void main(String a[]) {
System.out.println(getMessage());
}
}
The output is obviously "hi". But when I looked at the byte code using javap -v, I get the following.
public static java.lang.String getMessage();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=0
0: ldc #16 // String hi
2: astore_0
3: aload_0
4: astore_2
5: aconst_null
6: astore_0
7: aload_2
8: areturn
9: astore_1
10: aconst_null
11: astore_0
12: aload_1
13: athrow
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 s Ljava/lang/String;
There is only one Local variable shown in the Table where as 3 local variables (0,1,2 check byte code load and store instructions) are being used. Is there an explanation for this?. Are local variables with value null (eventual value) being ignored?
Related
I was teaching students the old-school Generics and came across an unseen! behavior while I was presenting! :(
I have a simple class
public class ObjectUtility {
public static void main(String[] args) {
System.out.println(castToType(10,new HashMap<Integer,Integer>()));
}
private static <V,T> T castToType(V value, T type){
return (T) value;
}
}
This gives output as 10,without any error!!! I was expecting this to give me a ClassCastException, with some error like Integer cannot be cast to HashMap.
Curious and Furious, I tried getClass() on the return value, something like below
System.out.println(castToType(10,new HashMap<Integer,Integer>()).getClass());
which is throwing a ClassCastException as I expected.
Also, when I break the same statement into two, something like
Object o = castToType(10,new HashMap<Integer,Integer>());
System.out.println(o.getClass());
It is not throwing any error and prints class java.lang.Integer
All are executed with
openjdk version "1.7.0_181"
OpenJDK Runtime Environment (Zulu 7.23.0.1-macosx) (build 1.7.0_181-b01)
OpenJDK 64-Bit Server VM (Zulu 7.23.0.1-macosx) (build 24.181-b01, mixed mode)
Can someone point me in the right direction on Why this behaviour is happening?
T doesn't exist at runtime. It resolves to the lower bound of the constraint. In this case, there are none, so it resolves to Object. Everything can be cast to Object, so no class cast exception.
If you were to do change the constraint to this
private static <V,T extends Map<?,?>> T castToType(V value, T type){
return (T) value;
}
then the cast to T becomes a cast to the lower bound Map, which obviously Integer is not, and you get the class cast exception you're expecting.
Also, when I break the same statement into two, something like
Object o = castToType(10,new HashMap<Integer,Integer>());
System.out.println(o.getClass());
It is not throwing any error
castToType(10,new HashMap<Integer,Integer>()).getClass()
This throws a class cast exception because it statically links to the method HashMap::getClass (not Object::getClass) since the signature says to expect HashMap as a return value. This necessitates an implicit cast to HashMap which fails because castToType returns an Integer at runtime.
When you use this first
Object o = castToType(10,new HashMap<Integer,Integer>());
you are now statically linking against Object::getClass which is fine regardless of what's actually returned.
The "unsplit" version is equivalent to this
final HashMap<Integer, Integer> map = castToType(10, new HashMap<>());
System.out.println(map.getClass());
which hopefully demonstrates the difference
You could see the differences using javap tool.
The compiling process by default makes code optimizations that changes the Generic types into the primitive ones
First code:
public class ObjectUtility {
public static void main(String[] args) {
System.out.println(castToType(10,new java.util.HashMap<Integer,Integer>()));
}
private static <V,T> T castToType(V value, T type){
return (T) value;
}
}
Real PseudoCode:
Compiled from "ObjectUtility.java"
public class ObjectUtility {
public ObjectUtility();
descriptor: ()V
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: bipush 10
5: invokestatic #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
8: new #4 // class java/util/HashMap
11: dup
12: invokespecial #5 // Method java/util/HashMap."<init>":()V
15: invokestatic #6 // Method castToType:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
18: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
21: return
LineNumberTable:
line 4: 0
line 5: 21
private static <V, T> T castToType(V, T);
descriptor: (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
Code:
0: aload_0
1: areturn
LineNumberTable:
line 8: 0
}
The calls of the Generic types are changed to Object and an Integer.valueOf is added on the System out print.
Second code:
public class ObjectUtility {
public static void main(String[] args) {
System.out.println(castToType(10,new java.util.HashMap<Integer,Integer>()).getClass());
}
private static <V,T> T castToType(V value, T type){
return (T) value;
}
}
Real Pseudo Code:
Compiled from "ObjectUtility.java"
public class ObjectUtility {
public ObjectUtility();
descriptor: ()V
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: bipush 10
5: invokestatic #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
8: new #4 // class java/util/HashMap
11: dup
12: invokespecial #5 // Method java/util/HashMap."<init>":()V
15: invokestatic #6 // Method castToType:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
18: checkcast #4 // class java/util/HashMap
21: invokevirtual #7 // Method java/lang/Object.getClass:()Ljava/lang/Class;
24: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
27: return
LineNumberTable:
line 4: 0
line 5: 27
private static <V, T> T castToType(V, T);
descriptor: (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
Code:
0: aload_0
1: areturn
LineNumberTable:
line 8: 0
}
The checkcast is invoqued over HashMap but the signature is changed to Object and the returnt is the value as int without the cast inside castToType. The "int" primitive type causes an invalid cast
Third Code:
public class ObjectUtility {
public static void main(String[] args) {
Object o = castToType(10,new java.util.HashMap<Integer,Integer>());
System.out.println(o.getClass());
}
private static <V,T> T castToType(V value, T type){
return (T) value;
}
}
Real Pseudo Code:
Compiled from "ObjectUtility.java"
public class ObjectUtility {
public ObjectUtility();
descriptor: ()V
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
Code:
0: bipush 10
2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: new #3 // class java/util/HashMap
8: dup
9: invokespecial #4 // Method java/util/HashMap."<init>":()V
12: invokestatic #5 // Method castToType:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
15: astore_1
16: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
19: aload_1
20: invokevirtual #7 // Method java/lang/Object.getClass:()Ljava/lang/Class;
23: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
26: return
LineNumberTable:
line 4: 0
line 5: 16
line 6: 26
private static <V, T> T castToType(V, T);
descriptor: (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
Code:
0: aload_0
1: areturn
LineNumberTable:
line 9: 0
}
At this case the method is similar to the first one. castToType returns the first parameter without change.
As you can see the java compiler mades some "performance" changes that could affect in some cases. The Generics are an "invention" of the source code that are finally converted to the real type required in any case.
I've this java source file:
import java.util.function.*;
public class t {
public static void main(String[] args) {
Function<Integer,Integer> r = (a) -> a*a+2*a+1;
System.out.println(r.apply(2));
}
}
I compile it and it works as expected. Here's the output of javap -c -v t, and I can't find the location of lambda in it. Where's the bytecode which tells the jvm to compute the expression with the input Integer whenever the lambda is envoked?
If you want to see the code of your lambda body a*a+2*a+1, you should call javap -c -v -p t to see also the private methods:
private static java.lang.Integer lambda$main$0(java.lang.Integer);
descriptor: (Ljava/lang/Integer;)Ljava/lang/Integer;
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: invokevirtual #7 // Method java/lang/Integer.intValue:()I
4: aload_0
5: invokevirtual #7 // Method java/lang/Integer.intValue:()I
8: imul
9: iconst_2
10: aload_0
11: invokevirtual #7 // Method java/lang/Integer.intValue:()I
14: imul
15: iadd
16: iconst_1
17: iadd
18: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
21: areturn
LineNumberTable:
line 4: 0
More detailed answer about the lambda inner implementation is here: How will Java lambda functions be compiled?
I need to convert an ordinal int value to an enum value in Java. Which is simple:
MyEnumType value = MyEnumType.values()[ordinal];
The values() method is implicit, and I cannot locate the source code for it, hence the question.
Does the MyEnumType.values() allocate a new array or not? And if it does, should I cache the array when first called? Suppose that the conversion will be called quite often.
Yes.
Java doesn't have mechanism which lets us create unmodifiable array. So if values() would return same mutable array, we risk that someone could change its content for everyone.
So until unmodifiable arrays will be introduced to Java, for safety values() must return new/separate array holding all values.
We can test it with == operator:
MyEnumType[] arr1 = MyEnumType.values();
MyEnumType[] arr2 = MyEnumType.values();
System.out.println(arr1 == arr2); //false
If you want to avoid recreating this array you can simply store it and reuse result of values() later. There are few ways to do it, like.
you can create private array and allow access to its content only via getter method like
private static final MyEnumType[] VALUES = values();// to avoid recreating array
MyEnumType getByOrdinal(int){
return VALUES[int];
}
you can store result of values() in unmodifiable collection like List to ensure that its content will not be changed (now such list can be public).
public static final List<MyEnumType> VALUES = Collections.unmodifiableList(Arrays.asList(values()));
Theoretically, the values() method must return a new array every time, since Java doesn't have immutable arrays. If it always returned the same array it could not prevent callers muddling each other up by modifying the array.
I cannot locate the source code for it
The values() method has no ordinary source code, being compiler-generated. For javac, the code that generates the values() method is in com.sun.tools.javac.comp.Lower.visitEnumDef. For ECJ (Eclipse's compiler), the code is in org.eclipse.jdt.internal.compiler.codegen.CodeStream.generateSyntheticBodyForEnumValues.
An easier way to find the implementation of the values() method is by disassembling a compiled enum. First create some silly enum:
enum MyEnumType {
A, B, C;
public static void main(String[] args) {
System.out.println(values()[0]);
}
}
Then compile it, and disassemble it using the javap tool included in the JDK:
javac MyEnumType.java && javap -c -p MyEnumType
Visible in the output are all the compiler-generated implicit members of the enum, including (1) a static final field for each enum constant, (2) a hidden $VALUES array containing all the constants, (3) a static initializer block that instantiates each constant and assigns each one to its named field and to the array, and (4) the values() method that works by calling .clone() on the $VALUES array and returning the result:
final class MyEnumType extends java.lang.Enum<MyEnumType> {
public static final MyEnumType A;
public static final MyEnumType B;
public static final MyEnumType C;
private static final MyEnumType[] $VALUES;
public static MyEnumType[] values();
Code:
0: getstatic #1 // Field $VALUES:[LMyEnumType;
3: invokevirtual #2 // Method "[LMyEnumType;".clone:()Ljava/lang/Object;
6: checkcast #3 // class "[LMyEnumType;"
9: areturn
public static MyEnumType valueOf(java.lang.String);
Code:
0: ldc #4 // class MyEnumType
2: aload_0
3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #4 // class MyEnumType
9: areturn
private MyEnumType(java.lang.String, int);
Code:
0: aload_0
1: aload_1
2: iload_2
3: invokespecial #6 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
6: return
public static void main(java.lang.String[]);
Code:
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: invokestatic #8 // Method values:()[LMyEnumType;
6: iconst_0
7: aaload
8: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
11: return
static {};
Code:
0: new #4 // class MyEnumType
3: dup
4: ldc #10 // String A
6: iconst_0
7: invokespecial #11 // Method "<init>":(Ljava/lang/String;I)V
10: putstatic #12 // Field A:LMyEnumType;
13: new #4 // class MyEnumType
16: dup
17: ldc #13 // String B
19: iconst_1
20: invokespecial #11 // Method "<init>":(Ljava/lang/String;I)V
23: putstatic #14 // Field B:LMyEnumType;
26: new #4 // class MyEnumType
29: dup
30: ldc #15 // String C
32: iconst_2
33: invokespecial #11 // Method "<init>":(Ljava/lang/String;I)V
36: putstatic #16 // Field C:LMyEnumType;
39: iconst_3
40: anewarray #4 // class MyEnumType
43: dup
44: iconst_0
45: getstatic #12 // Field A:LMyEnumType;
48: aastore
49: dup
50: iconst_1
51: getstatic #14 // Field B:LMyEnumType;
54: aastore
55: dup
56: iconst_2
57: getstatic #16 // Field C:LMyEnumType;
60: aastore
61: putstatic #1 // Field $VALUES:[LMyEnumType;
64: return
}
However, the fact that the values() method has to return a new array, doesn't mean the compiler has to use the method. Potentially a compiler could detect use of MyEnumType.values()[ordinal] and, seeing that the array is not modified, it could bypass the method and use the underlying $VALUES array. The above disassembly of the main method shows that javac does not make such an optimization.
I also tested ECJ. The disassembly shows ECJ also initializes a hidden array to store the constants (although the Java langspec doesn't require that), but interestingly its values() method prefers to create a blank array then fill it with System.arraycopy, rather than calling .clone(). Either way, values() returns a new array every time. Like javac, it doesn't attempt to optimize the ordinal lookup:
final class MyEnumType extends java.lang.Enum<MyEnumType> {
public static final MyEnumType A;
public static final MyEnumType B;
public static final MyEnumType C;
private static final MyEnumType[] ENUM$VALUES;
static {};
Code:
0: new #1 // class MyEnumType
3: dup
4: ldc #14 // String A
6: iconst_0
7: invokespecial #15 // Method "<init>":(Ljava/lang/String;I)V
10: putstatic #19 // Field A:LMyEnumType;
13: new #1 // class MyEnumType
16: dup
17: ldc #21 // String B
19: iconst_1
20: invokespecial #15 // Method "<init>":(Ljava/lang/String;I)V
23: putstatic #22 // Field B:LMyEnumType;
26: new #1 // class MyEnumType
29: dup
30: ldc #24 // String C
32: iconst_2
33: invokespecial #15 // Method "<init>":(Ljava/lang/String;I)V
36: putstatic #25 // Field C:LMyEnumType;
39: iconst_3
40: anewarray #1 // class MyEnumType
43: dup
44: iconst_0
45: getstatic #19 // Field A:LMyEnumType;
48: aastore
49: dup
50: iconst_1
51: getstatic #22 // Field B:LMyEnumType;
54: aastore
55: dup
56: iconst_2
57: getstatic #25 // Field C:LMyEnumType;
60: aastore
61: putstatic #27 // Field ENUM$VALUES:[LMyEnumType;
64: return
private MyEnumType(java.lang.String, int);
Code:
0: aload_0
1: aload_1
2: iload_2
3: invokespecial #31 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
6: return
public static void main(java.lang.String[]);
Code:
0: getstatic #35 // Field java/lang/System.out:Ljava/io/PrintStream;
3: invokestatic #41 // Method values:()[LMyEnumType;
6: iconst_0
7: aaload
8: invokevirtual #45 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
11: return
public static MyEnumType[] values();
Code:
0: getstatic #27 // Field ENUM$VALUES:[LMyEnumType;
3: dup
4: astore_0
5: iconst_0
6: aload_0
7: arraylength
8: dup
9: istore_1
10: anewarray #1 // class MyEnumType
13: dup
14: astore_2
15: iconst_0
16: iload_1
17: invokestatic #53 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V
20: aload_2
21: areturn
public static MyEnumType valueOf(java.lang.String);
Code:
0: ldc #1 // class MyEnumType
2: aload_0
3: invokestatic #59 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #1 // class MyEnumType
9: areturn
}
However, it's still potentially possible that the JVM could have an optimization that detects the fact that the array is copied and then thrown away, and avoids it. To test that, I ran the following pair of benchmark programs that test ordinal lookup in a loop, one which calls values() each time and the other that uses a private copy of the array. The result of the ordinal lookup is assigned to a volatile field to prevent it being optimized away:
enum MyEnumType1 {
A, B, C;
public static void main(String[] args) {
long t = System.nanoTime();
for (int n = 0; n < 100_000_000; n++) {
for (int i = 0; i < 3; i++) {
dummy = values()[i];
}
}
System.out.printf("Done in %.2f seconds.\n", (System.nanoTime() - t) / 1e9);
}
public static volatile Object dummy;
}
enum MyEnumType2 {
A, B, C;
public static void main(String[] args) {
long t = System.nanoTime();
for (int n = 0; n < 100_000_000; n++) {
for (int i = 0; i < 3; i++) {
dummy = values[i];
}
}
System.out.printf("Done in %.2f seconds.\n", (System.nanoTime() - t) / 1e9);
}
public static volatile Object dummy;
private static final MyEnumType2[] values = values();
}
I ran this on Java 8u60, on the Server VM. Each test using the values() method took around 10 seconds, while each test using the private array took around 2 seconds. Using the -verbose:gc JVM argument showed there was significant garbage collection activity when the values() method was used, and none when using the private array. Running the same tests on the Client VM, the private array was still fast, but the values() method became even slower, taking over a minute to finish. Calling values() also took longer the more enum constants were defined. All this indicates that the values() method really does allocate a new array each time, and that avoiding it can be advantageous.
Note that both java.util.EnumSet and java.util.EnumMap need to use the array of enum constants. For performance they call JRE proprietary code that caches the result of values() in a shared array stored in java.lang.Class. You can get access to that shared array yourself by calling sun.misc.SharedSecrets.getJavaLangAccess().getEnumConstantsShared(MyEnumType.class), but it is unsafe to depend on it as such APIs are not part of any spec and can be changed or removed in any Java update.
Conclusion:
The enum values() method has to behave as if it always allocates a new array, in case callers modify it.
Compilers or VMs could potentially optimize that allocation away in some cases, but apparently they don't.
In performance-critical code, it is well worth taking your own copy of the array.
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
The operator + is undefined for the argument type(s) java.lang.Integer,
java.lang.Integer
at TestClass.Print3.main(Print3.java:14)
public class Print3 {
public static Integer wiggler(Integer x) {
Integer y = x+10;
x++;
System.out.println(x);
return y;
}
public static void main(String[] args) {
Integer dataWrapper = new Integer(5);
Integer value = wiggler(dataWrapper);
System.out.println(dataWrapper+value);
}
}
I haven't seen this compile error before, its very strange to me because normally Java compiler will unbox the Integer object into a int primitve type and use the addition.
I have compiled your file and look at the binary code:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=3, args_size=1
0: new #6 // class java/lang/Integer
3: dup
4: iconst_5
5: invokespecial #7 // Method java/lang/Integer."<init>":(I)V
8: astore_1
9: aload_1
10: invokestatic #8 // Method wiggler:(Ljava/lang/Integer;)Ljava/lang/Integer;
13: astore_2
14: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
17: aload_1
18: invokevirtual #2 // Method java/lang/Integer.intValue:()I
21: aload_2
22: invokevirtual #2 // Method java/lang/Integer.intValue:()I
25: iadd
26: invokevirtual #9 // Method java/io/PrintStream.println:(I)V
29: return
LineNumberTable:
line 11: 0
line 12: 9
line 13: 14
line 14: 29
}
I omitted the uninteresting binary code because the interessting part is in the main Method.
What i want to show you is that the Java Compiler will automatically unbox the Wrapper class into an int primitive type with the call Method java/lang/Integer.intValue:()I
in line 18 and 22 after that the addition will be executed.
So the other answers will no help neither.
I will suggest to use another Java Compiler respectively update your jdk
and try to compiler it on another machine.
You can also test you code with ideone and as you see it works perfectly.
http://ideone.com/9tJYf1
Try using this code instead:
public class Print3 {
public static Integer wiggler(Integer x) {
Integer y = new Integer(x.intValue() + 10);
x = new Integer(x.intValue() + 1);
System.out.println(x);
return y;
}
public static void main(String[] args) {
Integer dataWrapper = new Integer(5);
Integer value = wiggler(dataWrapper);
int result = dataWrapper.intValue() + value.intValue();
System.out.println(result);
}
}
As #Eran mentioned, I would have expected your code to work without issues. Apparently your version of Java is choking on the addition of two Integers.
This is a question that arose mostly of pure curiosity (and killing some time). I'm asking specifically about Java for the sake of concreteness.
What happens, in memory, if I concatenate a string (any string) with an empty string, e.g.:
String s = "any old string";
s += "";
I know that afterward, the contents of s will still be "any old string", since an empty ASCII string is stored in memory as just an ASCII null (since, at least in Java, strings are always null-terminated). But I am curious to know if Java (the compiler? the VM?) performs enough optimization to know that s will be unchanged, and it can just completely omit that instruction in the bytecode, or if something different happens at compile and run times.
It's bytecode time!
class EmptyString {
public static void main(String[] args) {
String s = "any old string";
s += "";
}
}
javap -c EmptyString:
Compiled from "EmptyString.java"
class EmptyString extends java.lang.Object{
EmptyString();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2; //String any old string
2: astore_1
3: new #3; //class java/lang/StringBuilder
6: dup
7: invokespecial #4; //Method java/lang/StringBuilder."":()V
10: aload_1
11: invokevirtual #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
14: ldc #6; //String
16: invokevirtual #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: invokevirtual #7; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: astore_1
23: return
}
You can see that += causes a StringBuilder to be created regardless of what it's concatenating, so it can't be optimized at runtime.
On the other hand, if you put both String literals in the same expression, they are concatenated by the compiler:
class EmptyString {
public static void main(String[] args) {
String s = "any old string" + "";
}
}
javap -c EmptyString:
Compiled from "EmptyString.java"
class EmptyString extends java.lang.Object{
EmptyString();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2; //String any old string
2: astore_1
3: return
}
You'll get a new String after executing the line
s += "";
Java allocates a new String object and assigns it to s after the string concatenation. If you have eclipse handy (and I assume you can do the same thing in NetBeans, but I've only ever used eclipse) you can breakpoint that line and watch the object IDs of the object that s points to before and after executing that line. In my case, the object ID of s before that line of code was id=20, and afterward was id=24.