In Java, the class designer can guarantee initialization of every object by providing a constructor.
The coding style of making the first letter of all methods lowercase does not apply to constructors.
The constructor can also have arguments to allow you to specify how an object is created.
The constructor is an unusual type of method because it has no return value. This is distinctly different from a void return value, in which the method returns nothing but you still have the option to make it return something else.
The new expression does return a reference to the newly created object.
Method overloading
When you create an object, you give a name to a region of storage. A method is a name for an action.
Because the constructor’s name is predetermined by the name of the class, there can be only one constructor name.
Thus, method overloading is essential to allow the same method name to be used with different argument types.
Distinguishing overloaded methods
Each overloaded method must take a unique list of argument types.
Even differences in the ordering of arguments are sufficient to distinguish two methods.
Overloading with primitives
If you have a data type that is smaller than the argument in the method, that data type is promoted.
If your argument is wider, then you must perform a narrowing conversion with a cast.
Overloading on return values
You can also call a method and ignore the return value. Because of this sort of problem, you cannot use return value types to distinguish overloaded methods.
Default constructors
If you create a class that has no constructors, the compiler will automatically create a default constructor for you.
However, if you define any constructors (with or without arguments), the compiler will not synthesize one for you.
The this keyword
There’s a secret first argument passed to the method peel( ), and that argument is the reference to the object that’s being manipulated.
The this keyword—which can be used only inside a non-static method—produces the reference to the object that the method has been called for.
If you’re calling a method of your class from within another method of your class, you don’t need to use this. You simply call the method. The current this reference is automatically used for the other method.
The this keyword is used only for those special cases in which you need to explicitly use the reference to the current object.
It’s often used in return statements when you want to return the reference to the current object.
The this keyword is also useful for passing the current object to another method.
Calling constructors from constructors
There are times when you’d like to call one constructor from another to avoid duplicating code. You can make such a call by using the this keyword.
In a constructor, the this keyword takes on a different meaning when you give it an argument list.
While you can call one constructor using this, you cannot call two. In addition, the constructor call must be the first thing you do, or you’ll get a compiler error message.
The meaning of static
With the this keyword in mind, you can more fully understand what it means to make a method static. It means that there is no this for that particular method.
You cannot call non-static methods from inside static methods(although the reverse is possible), and you can call a static method for the class itself, without any object.
Putting the static method inside a class allows it access to other static methods and to static fields. It’s as if you’re creating the equivalent of a global method.
Cleanup: finalization and garbage collection
The garbage collector only knows how to release memory allocated with new.
When the garbage collector is ready to release the storage used for your object, it will first call finalize( ), and only on the next garbage-collection pass will it reclaim the object’s memory.
It gives you the ability to perform some important cleanup at the time of garbage collection.
In C++, objects always get destroyed, whereas in Java, objects do not always get garbage collected.
Java has no destructor or similar concept, so you must create an ordinary method to perform this cleanup.
What is finalize() for?
Garbage collection is only about memory.
The sole reason for the existence of the garbage collector is to recover memory that your program is no longer using.
It turns out that the need for finalize( ) is limited to special cases in which your object can allocate storage in some way other than creating an object.
This can happen primarily through native methods, which are a way to call non-Java code from Java.
You must perform cleanup
The presence of a garbage collector does not remove the need for or the utility of destructors.
If you want some kind of cleanup performed other than storage release, you must still explicitly call an appropriate method in Java, which is the equivalent of a C++ destructor without the convenience.
Neither garbage collection nor finalization is guaranteed.
The termination condition
In general, you can’t rely on finalize( ) being called, and you must create separate “cleanup” methods and call them explicitly.
There is an interesting use of finalize( ) that does not rely on it being called every time. This is the verification of the termination condition of an object.
How a garbage collector works
Allocating storage for heap objects in Java can be nearly as fast as creating storage on the stack in other languages.
The Java heap is more like a conveyor belt that moves forward every time you allocate a new object.
While the garbage collector collects the garbage it compacts all the objects in the heap so that you’ve effectively moved the “heap pointer” closer to the beginning of the conveyor belt and farther away from a page fault.
The garbage collector moves through the entire list of objects, and when it finds one with a reference count of zero it releases that storage.
The one drawback is that if objects circularly refer to each other they can have nonzero reference counts while still being garbage.
Any non-dead object must ultimately be traceable back to a reference that lives either on the stack or in static storage.
Copy collectors:The program is first stopped. Then, each live object is copied from one heap to another, leaving behind all the garbage. In addition, as the objects are copied into the new heap, they are packed end-to-end, thus compacting the new heap.
mark-and-sweep: when you know you’re generating little or no garbage, it’s fast. Only when the marking process is finished does the sweep occur.
generation count: only the blocks created since the last garbage collection are compacted; all other blocks get their generation count bumped if they have been referenced from somewhere.
A JIT compiler partially or fully converts a program into native machine code so that it doesn’t need to be interpreted by the JVM and thus runs much faster.
Lazy evaluation, which means that the code is not JIT compiled until necessary.
Member initialization
In the case of a method’s local variables, this guarantee comes in the form of a compile-time error.
Forcing the programmer to provide an initialization value is more likely to catch a bug.
Each primitive field of a class is guaranteed to get an initial value.
When you define an object reference inside a class without initializing it to a new object, that reference is given a special value of null.
Specifying initialization
Constructor initialization
You aren’t precluding the automatic initialization, which happens before the constructor is entered.
Order of initialization
The variable definitions may be scattered throughout and in between method definitions, but the variables are initialized before any methods can be called.
static data initialization
You can’t apply the static keyword to local variables, so it only applies to fields.
The constructor is actually a static method.
The Java interpreter must locate Dog.class, which it does by searching through the classpath.
Thus, static initialization takes place only once, as the Class object is loaded for the first time.
Explicit static initialization
The first time you make an object of that class or the first time you access a static member of that class
Non-static instance initialization
The instance initialization clause is executed before either one of the constructors.
This syntax is necessary to support the initialization of anonymous inner classes.
It also allows you to guarantee that certain operations occur regardless of which explicit constructor is called.
Array initialization
To define an array reference, you simply follow your type name with empty square brackets, The compiler doesn’t allow you to tell it how big the array is, All that you have at this point is a reference to an array.
To create storage for the array, you must write an initialization expression.
You can also use a special kind of initialization expression that must occur at the point where the array is created. This special initialization is a set of values surrounded by curly braces.
You simply use new to create the elements in the array. Here, new works even though it’s creating an array of primitives.
If you create a non-primitive array, you create an array of references.
It’s also possible to initialize arrays of objects by using the curly brace-enclosed list.
You can use the second and third forms anywhere, even inside a method call.
Variable argument lists
You can now use ellipses to define a variable argument list.
It’s possible to pass zero arguments to a vararg list.
It’s possible to use any type of argument in varargs, including a primitive type.
This verifies that using varargs does not depend on autoboxing, but that it actually uses the primitive types.
Varargs complicate the process of overloading, although it seems safe enough at first.
You should generally only use a variable argument list on one version of an overloaded method. Or consider not doing it at all.
Enumerated types
When you need to group together and use a set of enumerated types.
Because the instances of enumerated types are constants, they are in all capital letters by convention.
To use an enum, you create a reference of that type and assign it to an instance.
In many ways you can treat an enum as if it were any other class.
In fact, enums are classes and have their own methods.
Since a switch is intended to select from a limited set of possibilities, it’s an ideal match for an enum.
In general you can use an enum as if it were another way to create a data type, and then just put the results to work.