This question is about interesting behavior of Java: it produces
additional (not default) constructor for nested classes in some
situations.
This question is also about strange anonymous class, which Java
produces with that strange constructor.
Consider the following code:
package a;
import java.lang.reflect.Constructor;
public class TestNested {
class A {
A() {
}
A(int a) {
}
}
public static void main(String[] args) {
Class<A> aClass = A.class;
for (Constructor c : aClass.getDeclaredConstructors()) {
System.out.println(c);
}
}
}
This will prints:
a.TestNested$A(a.TestNested)
a.TestNested$A(a.TestNested,int)
Ok. Next, lets make constructor A(int a) private:
private A(int a) {
}
Run program again. Receive:
a.TestNested$A(a.TestNested)
private a.TestNested$A(a.TestNested,int)
It is also ok. But now, lets modify main() method in such way (addition of new instance of class A creation):
public static void main(String[] args) {
Class<A> aClass = A.class;
for (Constructor c : aClass.getDeclaredConstructors()) {
System.out.println(c);
}
A a = new TestNested().new A(123); // new line of code
}
Then input becomes:
a.TestNested$A(a.TestNested)
private a.TestNested$A(a.TestNested,int)
a.TestNested$A(a.TestNested,int,a.TestNested$1)
What is it: a.TestNested$A(a.TestNested,int,a.TestNested$1) <<<---??
Ok, lets again make constructor A(int a) package local:
A(int a) {
}
Rerun program again (we don't remove line with instance of A creation!), output is as in the first time:
a.TestNested$A(a.TestNested)
a.TestNested$A(a.TestNested,int)
Questions:
1) How this could be explained?
2) What is this third strange constructor?
UPDATE: Investigation shown following.
1) Lets try to call this strange constructor using reflection from other class.
We will not able to do this, because there isn't any way to create instance of that strange TestNested$1 class.
2) Ok. Lets do the trick. Lets add to the class TestNested such static field:
public static Object object = new Object() {
public void print() {
System.out.println("sss");
}
};
Well? Ok, now we could call this third strange constructor from another class:
TestNested tn = new TestNested();
TestNested.A a = (TestNested.A)TestNested.A.class.getDeclaredConstructors()[2].newInstance(tn, 123, TestNested.object);
Sorry, but I absolutely don't understand it.
UPDATE-2: Further questions are:
3) Why Java use special anonymous inner class for an argument type for this third synthetic constructor? Why not just Object type, of constructor with special name?
4) What Java could use already defined anonymous inner class for those purposes? Isn't this some kind of violation of security?
The third constructor is a synthetic constructor generated by the compiler, in order to allow access to the private constructor from the outer class. This is because inner classes (and their enclosing classes' access to their private members) only exist for the Java language and not the JVM, so the compiler has to bridge the gap behind the scenes.
Reflection will tell you if a member is synthetic:
for (Constructor c : aClass.getDeclaredConstructors()) {
System.out.println(c + " " + c.isSynthetic());
}
This prints:
a.TestNested$A(a.TestNested) false
private a.TestNested$A(a.TestNested,int) false
a.TestNested$A(a.TestNested,int,a.TestNested$1) true
See this post for further discussion: Eclipse warning about synthetic accessor for private static nested classes in Java?
EDIT: interestingly, the eclipse compiler does it differently than javac. When using eclipse, it adds an argument of the type of the inner class itself:
a.TestNested$A(a.TestNested) false
private a.TestNested$A(a.TestNested,int) false
a.TestNested$A(a.TestNested,int,a.TestNested$A) true
I tried to trip it up by exposing that constructor ahead of time:
class A {
A() {
}
private A(int a) {
}
A(int a, A another) { }
}
It dealt with this by simply adding another argument to the synthetic constructor:
a.TestNested$A(a.TestNested) false
private a.TestNested$A(a.TestNested,int) false
a.TestNested$A(a.TestNested,int,a.TestNested$A) false
a.TestNested$A(a.TestNested,int,a.TestNested$A,a.TestNested$A) true
First of all, thank you for this interesting question. I was so intrigued that I could not resist taking a look at the bytecode. This is the bytecode of TestNested:
Compiled from "TestNested.java"
public class a.TestNested {
public a.TestNested();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc_w #2 // class a/TestNested$A
3: astore_1
4: aload_1
5: invokevirtual #3 // Method java/lang/Class.getDeclaredConstructors:()[Ljava/lang/reflect/Constructor;
8: astore_2
9: aload_2
10: arraylength
11: istore_3
12: iconst_0
13: istore 4
15: iload 4
17: iload_3
18: if_icmpge 41
21: aload_2
22: iload 4
24: aaload
25: astore 5
27: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
30: aload 5
32: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
35: iinc 4, 1
38: goto 15
41: new #2 // class a/TestNested$A
44: dup
45: new #6 // class a/TestNested
48: dup
49: invokespecial #7 // Method "<init>":()V
52: dup
53: invokevirtual #8 // Method java/lang/Object.getClass:()Ljava/lang/Class;
56: pop
57: bipush 123
59: aconst_null
60: invokespecial #9 // Method a/TestNested$A."<init>":(La/TestNested;ILa/TestNested$1;)V
63: astore_2
64: return
}
As you can see, the constructor a.TestNested$A(a.TestNested,int,a.TestNested$1) is invoked from your main method. Furthermore, null is passed as the value of the a.TestNested$1 parameter.
So let's take a look at the mysterious anonymous class a.TestNested$1:
Compiled from "TestNested.java"
class a.TestNested$1 {
}
Strange - I would have expected this class to actually do something. To understand it, let's take a look at the constructors in a.TestNested$A:
class a.TestNested$A {
final a.TestNested this$0;
a.TestNested$A(a.TestNested);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field this$0:La/TestNested;
5: aload_0
6: invokespecial #3 // Method java/lang/Object."<init>":()V
9: return
private a.TestNested$A(a.TestNested, int);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field this$0:La/TestNested;
5: aload_0
6: invokespecial #3 // Method java/lang/Object."<init>":()V
9: return
a.TestNested$A(a.TestNested, int, a.TestNested$1);
Code:
0: aload_0
1: aload_1
2: iload_2
3: invokespecial #1 // Method "<init>":(La/TestNested;I)V
6: return
}
Looking at the package-visible constructor a.TestNested$A(a.TestNested, int, a.TestNested$1), we can see that the third argument is ignored.
Now we can explain the constructor and the anonymous inner class. The additional constructor is required in order to circumvent the visibility restriction on the private constructor. This additional constructor simply delegates to the private constructor. However, it cannot have the exact same signature as the private constructor. Because of this, the anonymous inner class is added to provide a unique signature without colliding with other possible overloaded constructors, such as a constructor with signature (int,int) or (int,Object). Since this anonymous inner class is only needed to create a unique signature, it does not need to be instantiated and does not need to have content.
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 am a novice to Java byte code and would like to understand the following byte code of Dispatch.class relative to Dispatch.java source code below :
Compiled from "Dispatch.java"
class Dispatch {
Dispatch();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class B
3: dup
4: invokespecial #3 // Method B."<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #4 // Method A.run:()V
12: return
}
//=====================Dispatch.java==============================
class Dispatch{
public static void main(String args[]){
A var = new B();
var.run(); // prints : This is B
}
}
//======================A.java===========================
public class A {
public void run(){
System.out.println("This is A");
}
}
//======================B.java===========================
public class B extends A {
public void run(){
System.out.println("This is B");
}
}
After doing some reading on the internet I had a first grasp of how JVM stack and opcodes work. I still however do not get what these command lines are good for :
3: dup //what are we duplicating here exactly?
4: invokespecial #3 //what does the #3 in operand stand for?
invokevirtual VS invokespecial //what difference there is between these opcodes?
It really sounds like you need to read the docs some more, but to answer your updated questions,
dup duplicates the top value on the operand stack. In this case, it would be the uninitialized B object that was pushed by the previous new instruction.
The #3 means that invokespecial is operating on the 3rd slot in the classfile's constant pool. This is where the method to be invoked is specified. You can see the constant pool by passing -c -verbose to javap.
invokevirtual is used for ordinary (non interface) virtual method calls. (Ignoring default interface methods for the moment) invokespecial is used for a variety of special cases - private method calls, constructor invocations, and superclass method calls.
I've asked several questions regarding the subject, but it seems like every time I get an answer, I have more questions.
This question is continuation of my other question: Initialization in polymorphism of variables
Anyways, consider the following example.
class A{ //1
int a = 1; //2
}
i heard this conceptually looks like,
class A { //1
int a = 0; //2
A() { //3
super(); //4
a = 1; //5
}
From what I understand, this is because every time an object is created, instance objects are initialized to its default values.
If I put initialization block say,
System.out.print(i);
right below line 2 for both examples, top will print 1 and bottom will print 0. As far as I know, initialization block is executed before constructor. So is this only conceptual representation of constructors only? Or does the code actually change as such when the default constructor is called? Can someone clarify this for me?
Why does it behave this way? In my other question, it seemed to only cause confusion as to which variable is called. Can't instance variable just be declared a=1 and gets used throughout the class? Shouldn't that make it simpler?
As you said, the equivalence between the two classes in your question is only conceptual.
In fact, if an non-static data field has an initialization value, it is initialized before calling the constructor. The initialization block is copied by the compiler to the beginning of every constructor (after the super line), so it is executed after the initialization of the field and before the constructor code itself.
Your description of how int a = 1 gets converted to a constructor is correct, but it is not the whole story.
If, in addition to a, there are other instance fields with initializers, all of their initializers are collected into a single block that runs as part of constructors
If, in addition to field initialization you have general-purpose initializer blocks, their content gets collected into that same block, along with field initializers.
For example, if you have
class A {
{
System.out.println(a);
}
int a = 1;
{
System.out.println(a);
System.out.println(b);
}
int b = 2;
{
System.out.println(b);
}
public A() {
// Code of A
}
}
then the code block prior to Code of A looks like this:
System.out.println(a);
a = 1;
System.out.println(a);
System.out.println(b);
b = 2;
System.out.println(b);
// Code of A
It should be clear now why zero is printed in the initialization block prior to int a = 1 in the block preceding the initializer: initialization blocks are not treated separately from field initializers, their code gets mixed together in the same order that they appear in the source code.
The difference between your example is the order of operations. In your first example, with the initializer block where you said, the order is:
Assign 1 to a (in the declaration)
Output a (in the initializer block)
...but in your example example, it's
Assign 0 (the default value) to a (effectively in the declaration)
Output a (in the initialization block)
Assign 1 to a (in the constructor)
The key to understanding instance initialization for me is this: Instance initialization code is literally copied into the constructors — all of them, including the default one — by the compiler. It's copied in source code order, and it's before anything in the constructor (including super).
Here's a more complete example. Consider this class:
class Example {
// Instance field with initializer
private int i = 5;
// Instance initialization block
{
System.out.println(this.i);
}
// constructor 1
Example() {
System.out.println(this.i * 2);
}
// constructor 2
Example(int _i) {
this.i = _i;
System.out.println(this.i * 3);
}
}
That's compiled into bytecode exactly as though it were this:
class Example {
// Instance field
private int i;
// constructor 1
Example() {
// begin copied code
this.i = 5;
System.out.println(this.i);
// end copied code
System.out.println(i * 2);
}
// constructor 2
Example(int _i) {
// begin copied code
this.i = 5;
System.out.println(this.i);
// end copied code
this.i = _i;
System.out.println(this.i * 3);
}
}
In both cases above, Oracle's Java 8 outputs the exact same bytecode (as viewed by using javap -c Example after compiling):
Compiled from "Example.java"
class Example {
Example();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: aload_0
5: iconst_5
6: putfield #2 // Field i:I
9: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
12: aload_0
13: getfield #2 // Field i:I
16: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
19: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
22: aload_0
23: getfield #2 // Field i:I
26: iconst_2
27: imul
28: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
31: return
Example(int);
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: aload_0
5: iconst_5
6: putfield #2 // Field i:I
9: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
12: aload_0
13: getfield #2 // Field i:I
16: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
19: aload_0
20: iload_1
21: putfield #2 // Field i:I
24: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
27: aload_0
28: getfield #2 // Field i:I
31: iconst_3
32: imul
33: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
36: return
}
Instance variables are immediately available with the default variable if not otherwise set: Objects are set to null and primitive types to 0, false etc.
You have 3 options to set the value of an instance variable in Java:
1) Declare and instantiate immediately
class A {
int i = 1;
}
2) Instantiate it in a instance initializer block
class A {
int a; // it is default value 0 at this point
{ a = 1; } //instance initializer block
}
3) Instantiate it in the constructor
class A{
int a; // it is default value 0 at this point
A() {
a = 1;
}
}
During the instantiation of the A object, Java will
first instantiate the variable a to its default if not done by the user,
then it will go through any instance initializer block in the order they appear, and lastly
it will enter the constructor.
Say I have a class that extends java.lang.Object as follows:
package pack;
public class Person {
}
And the following three instances:
Object one = new Object();
Object two = new Person();
Person three = new Person();
In order to determine the runtime-type, I just need invoke the getClass() method on the instances as follows:
System.out.println("One runtime-type : " + one.getClass());
System.out.println("Two runtime-type : " + two.getClass());
System.out.println("Three runtime-type : " + three.getClass());
Which outputs:
One runtime-type : class java.lang.Object
Two runtime-type : class pack.Person
Three runtime-type : class pack.Person
Now my question is how do I programmatically determine the static/compile-type of the instances above?
By static/compile-type I mean the type "on the left". It would output:
One compile-type : class java.lang.Object
Two compile-type : class java.lang.Object
Three compile-type : class pack.Person
You didn't specify when you want to find out about the compile time types. From your sample output I assume you want to print out the compile-time types at runtime.
This is not possible, (update:) unless you do it manually, knowing all types you want to use beforehand.
If you know that you will be using only Object and Person classes, you can try the code below. You need to define a method for each of the used classes and the compiler is smart enough to use the best matching method.
public class Program {
static class Person {
}
public static void main(String[] params) throws Exception {
Object one = new Object();
Object two = new Person();
Person three = new Person();
System.out.println("One compile-type : " + getStaticType(one));
System.out.println("Two compile-type : " + getStaticType(two));
System.out.println("Three compile-type : " + getStaticType(three));
}
public static Class getStaticType(Person p) {
return Person.class;
}
public static Class getStaticType(Object o) {
return Object.class;
}
}
This prints out:
One compile-type : class java.lang.Object
Two compile-type : class java.lang.Object
Three compile-type : class Program$Person
Note that this method may break if you want to apply it to interfaces, where you may run into situation when the compiler cannot decide which method to use.
Original answer:
You are basically asking what are the types of the variables in your source code. The only thing left from your source code at runtime is the bytecode produced by Java compiler. This bytecode doesn't include any information about the types of your variables.
Here is what the bytecode may look like for your source code:
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: new #2 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: astore_1
8: new #3 // class Program$Person
11: dup
12: invokespecial #4 // Method Program$Person."<init>":()V
15: astore_2
16: new #3 // class Program$Person
19: dup
20: invokespecial #4 // Method Program$Person."<init>":()V
23: astore_3
24: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
27: new #6 // class java/lang/StringBuilder
30: dup
31: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V
34: ldc #8 // String Two runtime-type :
36: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
39: aload_2
40: invokevirtual #10 // Method java/lang/Object.getClass:()Ljava/lang/Class;
43: invokevirtual #11 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
46: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
49: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
52: return
}
You can see that except for the invocation of Person's constructor, there is no reference to type Person, therefore the information that e.g. variable three was of type Person is lost. Also, Java doesn't have any built-in operator that you could use to capture the type of the variable at compile time.
I have been working on some of my AP cs projects and came to the wonder the difference between doing this:
public class CalculateTaxes {
private Scanner in;
public CalculateTaxes(){
in = new Scanner(System.in);
}
}
and this:
public class CalculateTaxes {
private Scanner in = new Scanner(System.in);
public CalculateTaxes(){
}
}
I've seen many examples were they declare an object in one line and instantiate it somewhere else in the code. Why not just declare and instantiate an object in the same line?
Lets test how these classes will be compiled.
public class Test1 {
private Scanner in;
public Test1() {
in = new Scanner(System.in);
}
}
and
public class Test2 {
private Scanner in = new Scanner(System.in);
public Test2() {
}
}
If we use javap -c Test1 we will see
Compiled from "Test1.java"
public class Test1 {
public Test1();
Code:
0: aload_0
1: invokespecial #10 // Method java/lang/Object."<init>":()V
4: aload_0
5: new #12 // class java/util/Scanner
8: dup
9: getstatic #14 // Field java/lang/System.in:Ljava/io/InputStream;
12: invokespecial #19 // Method java/util/Scanner."<init>":(Ljava/io/InputStream;)V
15: putfield #22 // Field in:Ljava/util/Scanner;
18: return
}
and if we use it on Test2 we will get
Compiled from "Test2.java"
public class Test2 {
public Test2();
Code:
0: aload_0
1: invokespecial #10 // Method java/lang/Object."<init>":()V
4: aload_0
5: new #12 // class java/util/Scanner
8: dup
9: getstatic #14 // Field java/lang/System.in:Ljava/io/InputStream;
12: invokespecial #19 // Method java/util/Scanner."<init>":(Ljava/io/InputStream;)V
15: putfield #22 // Field in:Ljava/util/Scanner;
18: return
}
So as you can see the initialization of in field in Test2 class was automatically moved by compiler at start of constructor.
In fact this code will be moved at start of each constructor of that class (right after super() call if any) so only difference is that if you have few constructors you can initialize in field in one place outside constructors instead of doing it in every one of them.
But if lets say you want to initialize field depending on some argument passed in constructor then you have to do it in constructors block.
If you intend to assign an object a value in one scope but need it to be visible in another it can be useful to split up the declaration and the assignment.
Pseudocode:
{
// Outer loop
SomeObject a;
if (condition_one == condition_two)
a = new SomeObject(4);
else
a = new SomeObject(12);
a.doStuff();
}
If a had been declared only inside of the if statements, it wouldn't be visible outside of that loop.
{
if (a == b)
SomeObject a = new SomeObject(5);
a.doStuff(); // ERROR
}
There's not much difference in this case. In some cases, you use different parameters depending on what parameters are sent in the constructor.
Ultimately, in a professional programming environment, your goal is not what is best "right now", but what will help you understand what's going on in 6 months when you need to reread your code and understand WHY you were doing something. HOW you implemented it will help document the intent.