9. Constructor and Garbage Collector The Past and Present Life of Objects

9.1 Living space of objects and variables

Stack and Heap: Living Space

In Java, programmers care about two areas in memory: the living space heap of objects and the living space (stack) of method calls and variables. When the Java virtual machine starts, it obtains a block of memory from the underlying operating system and executes Java programs in this section. How much memory is available and whether you can adjust it depends on the version of the Java virtual machine and platform. But usually you have no control over these things. If the program is well designed, you probably don’t need to care.

We know that all objects live on the garbage collected heap, but we haven’t looked at the living space of variables. The space in which a variable exists depends on what kind of variable it is. The “which one” mentioned here is not its type, but instance variables or local variables. The latter area variable is also called a stack variable, and the name already describes the area in which it exists.

Instance variables are declared in the class rather than inside it. They represent “fields” of each individual object (each instance can have different values). Instance variables exist in the object they belong to

Local variables and method parameters are declared in the method. They are temporary and their life cycle is limited to the period during which the method is placed on the stack (that is, until the method is called until it is executed)

9.2 Methods on stack

Methods will be stacked together

When you call a method, the method is placed at the top of the call stack. What is actually stacked on the stack is the stack block, which contains the status of the method, including which line of program is executed and the values of all local variables.
The method on the top of the stack is the method currently being executed (assuming there is only one, more explanation in Chapter 14). The method will stay here until execution is completed. If the foo() method calls the bar() method, the bar() method will be placed above the foo() method.

Stack situation

There are three methods below. The first method will call the second method during execution, and the second method will call the third method. Each method declares a local variable in the content, and the go() method also declares a parameter (this means that the go() method has two local variables)

1. A certain program code calls doStuff(), causing doStuff() to be placed in the top stack block of the stack

2.doStuff() calls go(), and go() is placed on the top of the stack

3.go() calls crazy() again so that crazy() is now on the top of the stack

4. When crazy() is executed, its stack will be released soon. Execution returns to go()

9.3 Space for local variables

About object local variables

Remember that non-primitive variables only hold references to objects, not the objects themselves. You already know where objects live – the heap. Regardless of whether the object is declared or created, if a local variable is a reference to the object, only the variable itself will be placed on the stack.

The object itself will only exist on the heap.

9.4 Space for instance variables

When you want to create a new CellPhone(), Java must find a location for CellPhone on the heap. Requires enough space to store all instance variables of the object. Instance variables exist on the heap space where the object belongs.

The value of an object’s instance variable is stored in the object. If the instance variables are all of primitive main data type, Java will leave space for the instance variable according to the size of the primitive main data type. int requires 32 bits, long requires 64 bits, and so on. Java doesn’t care about the value of the private variable, whether it is 32 or 32,000,000 ints will occupy 32 bits.

But what if the instance variable is an object? What if the CellPhone object has an Antenna object? In other words, does CellPhone have a reference variable of Antenna type? When a new object is created with a variable that references the object, the real question at this point is: Do I need to reserve the space of all the objects that the object has? not like this. Regardless, Java leaves room for instance variable values. However, the value of the reference variable is not the object itself, so if CellPhone has Antenna, Java will only leave the Antenna reference instead of the space used by the object itself.

So will the Antenna object obtain space on the heap? We must first know when the Antenna object was created. It depends on how the instance variable is declared. If a variable is declared but not assigned a value, only space for the variable is left:
private Antenna ant;
A new Antenna object will not occupy space on the heap until the reference variable is assigned a value:
private Antenna ant = new Antenna();

9.5 The miracle of creating objects

Review of 3 steps: declaration, creation, assignment

1. Declare reference variables

2. Create objects

3. Connect objects and references

Duck myDuck = new Duck();

9.6 Constructor

Duck myDuck = new Duck();

Call Duck’s constructor

A constructor looks like a method and feels like a method, but it is not a method. It contains program code that will be executed when new is used. This program code will be executed when you initialize an object.
The only way to call the constructor is to create a new class. (Strictly speaking, this is the only way to call a constructor outside of a constructor, as we’ll discuss later in this chapter.)

You can write a constructor for a class. If you don’t write one, the compiler will write one for you:

public Duck() {

}

Methods have return types, constructors do not. The constructor must have the same name as the class

Constructing Duck

A key characteristic of a constructor is that it executes before the object can be assigned to a reference. This means you have the opportunity to intervene before the object is used. That is, the subject has the opportunity to assist in the construction process before anyone obtains the subject’s remote control. In Duck’s constructor, we don’t do anything meaningful, but we still show the sequence of events.

public class Duck {

    public Duck() {
        System.out.println("Quack");
    }
}
public class UseADuck {

    public static void main (String[] args)
        Duck d = new Duck();
}

9.7 The initial state of the duck

Initialization of new Duck state

Most people use constructors to initialize the state of an object. That is to say, setting and assigning values to the object’s instance variables.

public Duck() {
    size = 34;
}

This is fine if the developer knows how big the Duck class should be. But what if the decision is up to programmers using Duck?
You can use the class’s setSize( to set the size. But this leaves Duck temporarily without a size value (the instance variable has no default value), and requires two lines to do it. Here’s how it does it:

public class Duck() {
    int size;
    
    public Duck() {
        System.out.println("Quack");
    }

    public void setSize(int newSize) {
        size = newSize;
    }
}
public class UseADuck {
    
    public static void main(String[] args) {
        Duck d = new Duck();

        d.setSize(42);
    }
}

Use the constructor to initialize Duck’s state

If an object shouldn’t be used before the state is initialized, don’t let anyone get that object without initialization! It is dangerous to let the user construct the Duck object first and then set the size. What if the user doesn’t know or forgets to execute setSize()?
The best way is to put the initialization program code in the constructor, and then set the constructor to require parameters.

public class Duck {
    int size;
    
    public Duck(int duckSize) {
        System.out.println("Quack");

        size = duckSize;

        System.out.println("size is " + size);
    }
}
public class UseADuck {
    
    public static void main (String[] args) {
        Duck d = new Duck(42);
    }
}

The simple method of raising Duck must have a constructor that does not require parameters

What happens if Duck’s constructor requires a parameter? The Duck on the previous page has only one constructor, and it requires an int type size parameter. This may not be a problem, but it makes it more difficult for programmers, especially if the size of the Duck is not known. Wouldn’t it be better if there were preset sizes that allowed programmers to create Ducks even if they didn’t know the appropriate size?

Imagine you could give the user two options when creating a Duck: one to specify the size of the Duck (via a constructor parameter), and another to use the default value without specifying the size.

You can’t clearly achieve this with just a single constructor. Remember, if a method or constructor has a parameter, you must pass in the appropriate parameter when calling the method or constructor. You can’t make a method that uses default values when no parameters are given, because in this case the compiler will not pass without giving parameters. Perhaps you could use the following less-than-ideal approach instead:

public class Duck {
    int size;

    public Duck(int newSize) {
        if (newSize == 0) {
            size = 27;
        } else {
            size = newSize;
        }
    }
}

This means that the programmer must know that passing 0 to the Duck constructor means using the default size instead of the actual 0. What if the programmer really makes a 0-size Duck? The problem with this is that the intent of passing in 0 cannot be reliably determined.

There are two methods to create a new Duck:

public class Duck2 {
    int size;

    public Duck2() {
        //Specify default value
        size = 27;
    }

    public Duck2(int duckSize) {
        //Specify using parameters
        size = duckSize;
    }
}

When the size is known:

Duck2 d = new Duck2(15);

When the size is not known:

Duck2 d = new Duck2();

So this would require two constructors to distinguish between the two options. One requires parameters, the other does not. If a class has more than one constructor, this means that they are also overloaded.

The compiler will only call it if you don’t specify a constructor at all. If you have written a constructor with parameters and you need a constructor without parameters, you must write it yourself!

If a class has more than one constructor, the parameters must be different. This includes the order and type of parameters, as long as they are different. This is the same as method overloading, but the details will be discussed in other chapters.

9.8 Constructor coverage

Overloaded constructor means there is more than one constructor with different parameters

The constructors listed below are all legal because the parameters are all different. Assuming that there are two constructor parameters with only one int, the compiler will definitely fail. The compiler looks at the type and order of parameters rather than their names. You can make parameters of the same type but in different orders. The order of parameters using String and int types is different from the order of parameters using int and String types.

9.9 Constructor of parent class

The role that the constructor of the parent class plays in the life of the object

When creating a new object, all inherited constructors will be executed

This means that each parent class has a constructor (because every class will have at least one constructor), and each constructor will be executed during the creation of the child class object.
Executing the new instruction is a major event, which will start the constructor chain reaction. Also, even abstract classes have constructors. Although you cannot perform the new operation on an abstract class, the abstract class is still a parent class, so its constructor will be executed when an instance of the concrete subclass is created.
Use super in the constructor to call the constructor of the parent class. Remember that subclasses may depend on the parent class
State to inherit methods (that is, instance variables of the parent class). A complete object needs to be also a complete parent class core, so that’s why the parent class constructor must be executed. Even if there are some variables on Animal that Hippo will not use, Hippo may use some inherited methods that must read Animal’s instance variables.

When the constructor is executed, the first thing is to execute the constructor of its parent class, which will have a chain reaction to the Object class.

Creating Hippo also means creating Animal and Object

public class Animal {
    public Animal() {
        System.out.println("Making an Animal");
    }
}
public class Hippo extends Animal {
    public Hippo() {
        System.out.println("Making a Hippo");
    }
}
public class TestHippo {
    public static void main (String[] args) {
        System.out.println("Starting...");
        Hippo h = new Hippo();
    }
}

1. A certain program executes the action of new Hippo(), and the constructor of Hippo() enters the stack block at the top of the stack

2.Hippo() calls the constructor of the parent class, causing the constructor of Animal() to enter the top of the stack

3.Animal() calls the constructor of the parent class, causing the constructor of Object() to enter the top of the stack

4. After the execution of Object() is completed, its stack block is popped, and then the execution of Animal() continues

Call the constructor of the parent class

call super()

public class Duck extends Animal {
    int size;


    public Duck(int newSize) {

        super();
        size = newSize;
    }
}

Calling super() in your constructor puts the parent class’s constructor at the top of the stack. The parent class’s constructor calls its parent class’s constructor. This goes all the way up to Object’s constructor. Then execute it all the way and pop back to the original constructor.

What would happen if we didn’t call super()?
The compiler will help us add the super() call. So the compiler has two ways of getting involved in the constructor:

  • If you don’t write a constructor.
public ClassName() {
    super();
}
  • If you have a constructor but don’t call super().

The compiler will help you add the following call to each overloaded version of the constructor:

super();

The compiler will definitely add the version without parameters. If the parent class has multiple overloaded versions, only the version without parameters will be called.

The parts of the parent class must be fully formed before the child class is created. Subclass objects may need to inherit from the parent class
Things that have been inherited, so those things must be completed first. The constructor of the parent class must be completed before the constructor of the child class.
bundle.

The call to super() must be the first statement of the constructor.

Parent class constructor with parameters

Hippo has the getName() method but no name instance variable. Hippo relies on the Animal part to maintain the name instance variable, and then returns this value from getName(), but how does Animal obtain this value? The only opportunity is to reference the parent class through super(), so pass the value of name from here and let Animal store it in the private name instance variable.

public class Animal {
    private String name;

    public String getName() {
        return name;
    }

    public Animal(String theName) {
        name = theName;
    }
}
public class Hippo extends Animal{
    public Hippo(String name) {
        super(name);
    }
}
public class MakeHippo {
    public static void main(String[] args) {
        Hippo h = new Hippo("Buffy");
        System.out.println(h.getName());
    }
}

9.10 Using this()

Calling an overloaded version of a constructor from another constructor

What if there was an overloaded version of the constructor that could handle all the work, except that it couldn’t handle parameters of different types? You don’t want the same program code to appear in every constructor (which is troublesome to maintain), so you want to place the program code only in a certain constructor (including the call to super()). In this way, all constructors will call this constructor first, allowing it to execute the real constructor. It’s easy, just call this() or this(aString) or this(27, x).

In other words, this is a reference to the object itself.

This() can only be used in the constructor, and it must be the first line of statement!

Each constructor can choose to call super() or this(), but not both at the same time!

Use this() to call a constructor from another constructor

9.11 Object Life Cycle

The life cycle of an object depends on the life cycle of the reference variable

1. Local variables will only survive in the method in which they are declared

2. The lifespan of instance variables is the same as that of the object. If the object is alive, the instance variables will also be alive

Life

As long as the variable’s stack block still exists on the stack, the local variable is alive until the method is executed.

Scope

The scope of a local variable is limited to the method in which it is declared. When this method calls another method, the variable is still alive, but not within the current scope. When other methods are executed and returned, the scope will also return.

While a local variable is alive, its state is saved. As long as doStuff() is still on the stack, the b variable will retain its value. But the b variable can only be used when doStuff() stays on the top of the stack. That is, a local variable can only be used while the method in which it is declared is executing.

9.12 Resource Recycling

The rules for referencing are the same as for primitive main data types. A reference variable can only be referenced within its scope
“How does the lifetime of a variable affect the lifetime of an object?”
As long as there are live references, the object will be alive. If a reference to an object is no longer in its scope, but the reference is still alive, the object will continue to live on the heap.
If the only reference to an object dies, the object is kicked off the heap. The reference variable will be disbanded together with the stack block, so it will be kicked away

The object is officially declared out. The key is to know when an object becomes reclaimable by the garbage collector.
Once an object is eligible for the garbage collector (GC), you don’t need to worry about reclaiming memory. If the program has insufficient memory, the GC will destroy some or all recyclable objects. You may still run into an out-of-memory situation, but that won’t happen until all the recyclables have been reclaimed and that’s not enough. What you need to pay attention to is that objects must be discarded when they are used up, so that the garbage collector can have something to recycle. If you hold on to the object, the garbage collector can’t help.

Unless there is a reference to an object, the object is meaningless. If a reference to an object cannot be obtained, the object is just a waste of space. At this time, the GC will know what to do, and that kind of object will be lost in the hands of the garbage collector sooner or later

The object becomes recyclable when the last reference disappears:

There are 3 ways to release the reference of an object:

1. The reference permanently leaves its scope

void go() {
    Life z = new Life();
}

2. References are assigned to other objects

Life z = new Life();
z = new Life();

3. Directly set the reference to null

Life z = new Life();
z = null;