GIST NOTES 3 - Java Assignments
[DISCLAIMER: This is solely for non-commercial use. I don't claim ownership of this content. This is a crux of all my readings studies and analysis. Some of them are excerpts from famous books on the subject. Some of them are my contemplations upon experiments with direct hand coded code samples using IDE or notepad.
I've created this mainly to reduce an entire book into few pages of critical content that we should never forget. Even after years, you don't need to read the entire book again to get back its philosophy. I hope these notes will help you to replay the entire book in your mind once again.]
[JDK 7]
-instance variables and objects live on the heap memory
-local variables live on the stack memory
int e=5;//this e doesn't conflict with the e in the next line
float fVal = 10e+3f;
System.out.println(fVal);//prints "10000.0"
e.g. int num = 08; //compiler error, why? (octal numbers start with 0, and symbol 8 is not available in octal base, hence 08 is invalid.
-in java prefixing numbers with 0 carelessly is dangerous (0 prefix should be used only when you intend to use octal form)
-long values are indicated by suffix L or l (mandatory); decimal, octal and hexadecial values can use long format
-float values are indicated by suffix F or f (mandatory)
-double values are indicated by suffix D or d (not mandatory); that is literal numbers are double by default
The following are legal hexadecial numbers:-
int x = 0X0001;
int y = 0x7ffff;
int z = 0xDeadCafe;
int p = 0XFEED; //65261 in decimal
-comma is not allowed in numerical values
-from java 7.0 onwards underscore char is allowed between any two digits; any number of underscores are allowed
-in java numbers cannot be used in places of boolean
-to type unicode value of a character "\u" prefix is used as in [char a = '\u004E';//letter N]
-number literals can be assigned to char variables, when the value is out of range (more than 16bits), an explicit cast to char is required for such assigements
char d = (char) -98; //Ridiculous, but legal
char a = -29; //compile error: possible loss of precision, needs a cast
char f = 70000; //compile error: possible loss of precision, needs a cast
char c = '\"'; //escape code for double quote char
char r = '\n'; //escape code for new line
-string literals are source code representation of a value of a String object
-strings are not primitives but still they have literal form
-arrays are not primitives but still they have literal form
-only strings and arrays are the non-primitive types that have literal form
Variables
----------
-variables are holders or containers containing binary bits
-primitive variables contain the value itself in bits
-object variables contain a pointer(in bits) to the object it is referring, not the object itself
-an object variable explicitly assigned to null contains the value null itself (in bits)
"So variable is just a little box of bits"
Weird int
---------
> integer literals such as 3 are implicitly int type
> so integer literals can be easily assigned to int or long variables
int a = 30;//no problem
long al = 30;//no problem
> what about assigning integer literals to short, char or byte?
short s = 40;//no problem
char c = 40;//no problem
byte b = 40;//no problem
//because compiler automatically casts these literals to appropriate short, char or byte
> int type is 32 bit; byte, char and short are smaller than int
> any integer literal is int type; also, the result of an expression involving anything int-size or smaller is always int
> addition of two bytes gives int (though those bytes are very small and the result is still byte-sized, the end result is always int)
> multiplication of int and short is int
> division of short by byte is an int
byte a = 2;
byte b = 2;
byte c = a + b; //compilation error because the result is an int but it is not literal in this line;
//it is a result from an expression (a + b)
(it makes sense actully; when biggest possible byte added itself gives to a value that is no longer byte-sized)
> the above code needs explicit cast in the third line
> implicit cast(automatic) for primitives happens when smaller value is assigned to bigger container (widening)
> explicit cast for primitives is required when bigger values are assigned to smaller containers (narrowing)
long g = 130L;
byte b = (byte) g; //byte can store only upto 127
//printing b displays -126
//this is because when 130 is narrowed, the bits leftmost to 8th bit are discarded and if the 8th bit(on left side)
//is 1, the number becomes negative
Floating Point Numbers
----------------------
> every floating point literal is implicitly double (e.g. 1.1), not float
> float is 32bit, double is 64bit
> float f = 1.1; //gives compile error; either cast or f/F suffix is required here
> byte a = 128; //gives compile error; 128 is too large to hold
byte a = (byte) 128;//compile
//printing 'a' displays -128. why?
//what happens during narrowing of 128?
//after chopping off the unwanted bits from 32bit(because 128 is int type) representation of 128,
//the last 8 bits that remain are "10000000"
//since leftmost bit is 1, it is negative number
//for negative numbers, 2's complement is used to compute the value of the negative number
2's Complement:-
[Flip all bits (01111111); then add 1 (10000000 back again); we got 128; so final value is -128]
> so when you use explicit cast to narrow down a value, be wary of the unpredicted result you would get
> compound assignment operators lets us skip explicit casts
byte b = 3;
b += 7; //compiles
b = (byte)(b + 7); //doesn't compile without the explicit cast
> assigning one primitive variable to another causes the contents of one variable just copied to another; they don't share the same value; they have separate copies of the same value
Variable Scope
--------------
>static variable scope, instance variable scope, local variable scope(method level) and block variable scope are the four scopes
>method local variables are available as long as the method is on the stack
>sometimes method variables are available on stack but not accessible; this happens when method within a method is being executed, the caller method variables are still on stack but are inaccessible(out of scope) to called method
>variables declared in switch, try-catch, for, do and while loops are not available outside their block
Uninitialized variables
-----------------------
>attempt to use uninitialized variable gives different behavior based on the variable type as well as variable scope
>attempt to use uninitialized method variable gives compile error
>instance variables are given default values if not initialized
Default values:
Object - null
byte/short/int/long - 0
float/double - 0.0
boolean - false
char - '\u0000'
>string variables are object type; so they are initialized with null by default not with empty string ""
>an instance variable will be initialized with null
>an empty array's(initialized array with new operator) elements will be initialized with default values according to their individual type as though they were an instance variable
>even if the array itself is a local variable(not instance variable), its elements get default values as though they were instance variables
>array elements always get initialized regardless of where they are declared
>but a local array variable never gets initialized and you will get compiler error if you attempt to use (it gets null only when it is instance variable)
>local or stack variables sometimes called automatic variables; but there is nothing automatic about them; it is just a name
>attempt to use uninitialized local variable gives compile error
>if the initialization code of the local variable is not visible(is put in a code block like if loop), again any attempt to use that variable in areas where visibility is not there will cause compilation error
>assigning object reference variable to each other does nothing but copy the bit patterns of one variable to another just like the primitive variable values are copied
>so in java assignment means, copy copy copy and copy the contents of the container, that is all
>modifying an object through two copies of its reference affects the same object in the heap (except for String object)
>string objects are immutable and they are given special treatment; whenever you modify a string, java makes up a new string object for result and the original objects involved in the compuatation are left unchanged; this is due to the use of String Constant Literal Pool maintained by JVM; so two copies of reference to a same string object doesn't always operate on the same object forever
>so any operation on a string object creates a new object leaving the originals untouched; copy copy and copy here too; string operations work with copies only
Passing Variables into a Method
-------------------------------
>when you pass an object reference to a method, mind it you are only passing a copy of the reference, not the object itself(like a copy of the address to the original object in the heap)
>although java passes copies of the reference to objects, java is NOT pass-by-reference sematics; it is INDEED pass-by-value (value of the reference to be precise)
>java is pass-by-value for all variables running within a single VM
>pass-by-variable-value or pass-by-copy-of-the-variable
>either a copy of the primitive value or a copy of the reference of the object
>called method cannot change the variables of the caller, but can change the objects referred by those variables
>in case of primitives essentially the same thing, that is the called method can't change the value of the variables of the caller, because it only received a copy of the primitive value
>one can shadow an instance variable by declaring the same again in a method; modifying the shadowing variable doesn't affect the instance variable with the same name (to really get to the instance variable use this operator) - this is for primitives
>while shadowing an object variable, modifying the shadowing variable can still affect the instance variable because it might receive the reference to the instance variable object reference
Array Declaration, Construction and Initialization
--------------------------------------------------
>there is no such thing as primitive array; every array is an object in the heap; either it holds primitives or objects doesn't matter
int key []; //space between key and square bracket is legal
int[] key; //recommended format
String[][] records; //multi dimensional array
String[] records[]; //same multidimensional array and legal
>size of the array should be specified when you create the array object not when you declare it
int[] scores = new int[5]; //creates an array in the heap with 5 int elements initialized to value 0
Thread[] workers = new Thread[5]; //creates an array object in the heap with 5 Thread elements initialized to null
//still no Thread objects created yet
How many objects got created by the previous code line? Just one. The array object. It has 5 reference variables of Thread type. No Thread objects created yet.
>without size, any attempt to create an array object gives compile error
>multi dimensional arrays (not all sizes need to be mentioned)
int[][] scores = new int[5][]; //legal - each element of scores is again array object whose size not mentioned
//each array within this array can be of different size
int[][][] scores = new int[5][][7];//illegal, dimension after empty dimension is not allowed
>initializing an array means putting things into it
>array of primitive type holds primitive values in them
>array of objects doesn't hold objects, but rather a set of references to some objects
>object arrays can be sources of NullPointerException and ArrayIndexOutOfBoundsException (runtime)
>using negative index also causes runtime exception (look, these are not caught by compiler)
>"length" is the only, single, public variable available is array objects(not mean object arrays)
>shorthand syntax to create arrays (without new operator, in this way you can create objects!)
int x = 9;
int[] dots = {1,x,3}; //declaring, constructing and initializing in one line
int[][][] scores = {{1,2,3,4},{1,2,3},{1,2}};//this creates a total of 4 objects of different size in the heap
Anonymous Array Creation(another shortcut to create arrays)
-----------------------------------------------------------
display(new int[] {1,2,3});//display method takes int[]
//no need to specify size for anonymous array; it is even NOT LEGAL to specify
display( new Object[3] {new ArrayList(), new Vector(), new Hashtable()} ); //compilation error here
>array of int can hold byte, char and short values as well
>an object array can hold subtype objects as well
>arrays can hold anything that passes IS-A test
References to arrays themselves
-------------------------------
>you can't assign one type of array reference to another type of array reference (strictly prohibited)
int[] ints = new int[2];
char[] chars = new char[2];
ints = chars; //illegal - though char is compatible with int (what an irony, only children can mingle it seems)
//but for object arrays, even PARENTS can mingle
List[] lists;
ArrayList[] aLists = new ArrayList[5];
lists = aLists; //legal because ArrayList is of List type
Thread[] threads = aLists; //illegal, because ArrayList is not Thread type
>arrays with different dimensions can't be assigned to one another even if they are same type or compatible
>in a multidimensional array of primitives, only the last dimension can accept primitives; others can only accept array objects of that primitive type
Initialization blocks
---------------------
>apart from constructors and methods, initialization blocks also can run codes
>static initialization block - runs when class is loaded
>instance initialization block - runs when an instance is created
class Blocks {
static
{
int a; //static init block
}
{
int b; //instance init block
}
}
>instance initialization blocks run right after the call to super() in the constructor
>one can have many initialization blocks in a class
>the order in which init blocks appear in a class matters; they are run in sequence from top to bottom
>all instance init blocks are run before the control reaches the 2nd line of each contructor of the current class
>The following is the order of execution when we run child class (which attempts to create its own instance)
1. Static init blocks of Parent class
2. Static init blocks of the current(Child) class
3. Instance init blocks of Parent class
4. 2nd line of Parent constructor
5. Instance init blocks of Child class
6. 2nd line of Child constructor
(pretty clear, huh?)
> From the above, it is clear that though we ran Child class, first Parent class is loaded into JVM (hence its static init blocks are run first)
> static init blocks are run once only; but instance init blocks are run everytime an instance is created
> instance init blocks are often used as a place to put code that should be shared by all constructors
> any exception in static init blocks produces java.lang.ExceptionInInitializerError
> any exception in instance init blocks are just treated as regular exceptions and reported as usual in the course of program execution
Wrapper Classes
---------------
>every primitive in java has a wrapper class (Boolean, Byte, Character, Double, Float, Integer, Long, Short)
>wrapper classes are immutable; once an object got created, its value cannot be changed after
>all wrapper classes provide two constructors(except for Character class) of which one takes primitive value, and the other takes string representation of the value
Integer val = new Integer(23);
Integer valval = new Integer("23");
>Character class provides only one constructor which takes char type argument
> Boolean bool = new Boolean("TrUe"); //case doesn't matter in strin rep. for boolean values
> only due to autoboxing/unboxing (from java 5.0 onwards), a boolean object can be directly used for boolean test
valueOf() methods
------------------
>another way to create wrapper objects is to use valueOf() methods
>there are two versions; first version takes string form of primitive value; second version takes string form of primitive value plus base/radix if it is a number type (base: binary, octal, decimal and hexadecial --> 2, 8, 10 and 16)
>byteValue(), shortValue(), doubleValue() and such methods are provided by wrapper classes for conversion purpose; bit truncation is done whenever necessary
>parseBlaBla() and valueOf() methods are similar; both take string form of primitives; both can use bases(radix) for number types; both throw NumberFormatException when the string form is not proper; but parseBlaBla() methods return primitive values while valueOf() methods return wrapper objects of the concerned type
>wrapper classes are marked final
>toString() method is inherited by all classes in java from Object class; this method is not static
>however, wrapper classes support static toString() version in addition to Object class toString(); this static method takes primitive value as argument and returns a string form ofit;
>in addition to that, Long and Integer classes provide a third toString() version which is static;
>this static method takes primitive value as the first argument and radix as the second argument; this is useful to print numbers in different bases
>Integer and Long also have toHexString(10), toOctalString(10), toBinaryString(10) static methods
>Boolean and Character classes do not provide most of the above mentioned methods; Character provides only the simple version of toString() method; Boolean is same as Character but it provides one more method which is a simple valueOf() without radix support
>Double and Float classes do not provide any methods relating to radixes; but essentially they are same in all other respects to Integer/Long/Short/Byte classes
blablaValue() - wrapper to primitive (blabla = int/double/float/short/byte)
parseBlabla(String) - String to primitive
valueOf(String) - String to wrapper
(Happy?)
equals(==) operator and equals() method
----------------------------------------
== operator compares the values or object references are same
equals() method compares the contents of the object
== operator generally considers two objects to be not equal even if they have same content
but, in the world of autoboxing/unboxing, equals(==) operator behaves a little differently (to some extent) when it comes to wrapper objects
Even == operator treats two different wrapper objects to be equal sometimes if their content is same.
when is that sometime? and why?
when?
when the following wrapper objects are created through autoboxing:- 1. Boolean, 2. Byte, 3. Character from \u0000 to \u007f (0 to 127), 4. Short and Integer objects having values between -128 to 127
why?
to save memory :-)
[This is because the autoboxing code is replaced by the compiler with Integer.valueOf() method call. This method caches the above said values for fast performance and to save memory. So, wrapper objects obtained through autoboxing are same objects throughout the application if their value is within the above specified ranges]
{
Integer a = 1;
Integer b = 1;
System.out.println(a == b); //prints true
}
when a primitive and a wrapper is mingled in == operation, the comparison is done in primitive only(wrapper is unboxed).
Autoboxing/Unboxing - is a compiler gimmicks (java language didn't change w.r.to wrapper objects; only the compiler changed)
That is compiler automatically puts code for unboxing and autoboxing wherever required. Saves us some effort.
Autoboxing can obscure NullPointerException occuring due to uninitialized wrapper references. You will be in pinch at runtime though your code might look like using only primitive operations in the code.(duh?)
Overloading
-----------
which overloaded method to use based on the type of the input we pass (especially when primitives/wrappers are involved)?
[we are assuming that all overloaded methods only differ by the type of one input argument in the same position in the argument list; even return type is same across all overloaded methods; the situation turns grave when we have to decide which overloaded method we have to use based on that one input arg's type]
>passing byte and short automatically chooses overloaded method which takes int if there are not methods that take byte or short
>as long as the passed input is accommodatable the overloaded method with int arg is chosen over the method with long arg(widening in play)
>smallest possible YET accommodating argument is given preference over other accommodating ones (int over long and float over double)
>Firstly, when a choice is given between "widening" and "autoboxing" for choosing an overloaded method, "widening" gets the first preference. Lazy compiler doesn't want to put all those autoboxing code; so it simply uses widening; e.g value "5" to be passed as "Integer" object or "long" value - it chooses "long" option
>this preference comes from the decision of Java 5's designers to let the preexisting code to function the way it used to (hence widening comes first than autoboxing)
>Secondly, given a choice between "widening" and "var-args", widening is given first preference to choose appropriate overloaded method; e.g. (short, short) will be passed as (int, int) rather than (short...)
[again old style is preferred first]
>Thirdly, between Autoboxing and Var-args, Autoboxing wins
>"Widening" is the KING; "Var-args" is the LOSER;
>"Var-args" is used as a last resort; when nothing else works, we come to var-args
>It is legal to widen the primitives; so is for object references if polymorphic relationship is there, widening works well; e.g. ArrayList passed as List;
>But wrapper classes donot have any polymorphic relationships with one another; they are all peers; For example Short object cannot be passed as Integer; same goes for all wrapper classes
>Compiler will not do two adjustments; e.g. first widening, then boxing is not permissible (byte value 5 to be passed as Long object is not allowed)
>first boxing and then widening is allowed; like 5 to Byte(5) to Object; but not 5 to Byte(5) to Long(5); (remember we said object widening needs polymorphic relationship)
[Combining widening or boxing with vararg]
>widen + vararg is possible (first widens, then uses vararg to choose the correct method)
>boxing + vararg is possible (first boxes, then uses vararg to choose the correct method)
Garbage Collection
------------------
>Java garbage collector provides automatic solution to memory management; no memory management logic is necessary in your application code
>the downside of this automatic solution is you can't control when it runs and when doesn't
>Java memory areas: stack, heap, constant pools, method areas
>Heap is where java objects live
>Heap is the only place where garbage collection happens
>Heap is only one; only one heap
>when no live threads can reach/access an object, it becomes eligible for garbage collection
>where there are no references to an object it is eligible for garbage collection
>by setting all existing references to null we can force the object to become orphan and so it is eligible for garbage collection
>method local objects become eligible for garbage collection once the method is over, if no references were returned back to the calling method and saved there
Islands of Isolation: It is nothing but an island of object cutoff from the rest of the JVM world. The objects in that island may still be holding references to each other, but the island as a whole is not accessible to any live threads. Hence the whole island becomes eligible for garbage collection
>One can request for garbage collection using System.gc() call; but no guarantees that JVM will actually run gc
>In Java 6 and later, using System.gc() is almost useless because gc has advanced so much that JVM itself is capable of taking care of gc without your intervention
>Even after running gc, there is no guarantee that all inaccessible object would be garbage collected
>gc related routines/methods are part of Runtime class. Runtime is a singleton class for each main program
>Runtime object provides mechanism to directly communicate with the VM
>Runtime.getRuntime() returns the singleton object
>Sytem.gc() uses Runtime object internally
>Guaranteed:: GC will be run for sure before it throws OutOfMemoryError
>finalize() method is inherited from Object class by all objects in JVM; GC will call finalize() before it deletes an object; one can use this method to cleanup or free resources before it gets deleted
>however, since GC itself is not guaranteed to be run at the time you expect, the chance of the finalize() method running is also not guaranteed
>GC calls the finalize() method on an object only once; in case you saved this object from getting deleted using finalize() method, it won't work second time, because GC will not call finalize() again on the same object
>it is recommended not to override finalize() method because that method ever running is unpredictable
>You can't know GC algorithm for sure
>outer class can access the private members of the static inner class (verified) [non-static inner class private members also are accessible]
Reference: Kathy Sierra's SCJP Book
[DISCLAIMER: This is solely for non-commercial use. I don't claim ownership of this content. This is a crux of all my readings studies and analysis. Some of them are excerpts from famous books on the subject. Some of them are my contemplations upon experiments with direct hand coded code samples using IDE or notepad.
I've created this mainly to reduce an entire book into few pages of critical content that we should never forget. Even after years, you don't need to read the entire book again to get back its philosophy. I hope these notes will help you to replay the entire book in your mind once again.]
[JDK 7]
-instance variables and objects live on the heap memory
-local variables live on the stack memory
int e=5;//this e doesn't conflict with the e in the next line
float fVal = 10e+3f;
System.out.println(fVal);//prints "10000.0"
e.g. int num = 08; //compiler error, why? (octal numbers start with 0, and symbol 8 is not available in octal base, hence 08 is invalid.
-in java prefixing numbers with 0 carelessly is dangerous (0 prefix should be used only when you intend to use octal form)
-long values are indicated by suffix L or l (mandatory); decimal, octal and hexadecial values can use long format
-float values are indicated by suffix F or f (mandatory)
-double values are indicated by suffix D or d (not mandatory); that is literal numbers are double by default
The following are legal hexadecial numbers:-
int x = 0X0001;
int y = 0x7ffff;
int z = 0xDeadCafe;
int p = 0XFEED; //65261 in decimal
-comma is not allowed in numerical values
-from java 7.0 onwards underscore char is allowed between any two digits; any number of underscores are allowed
-in java numbers cannot be used in places of boolean
-to type unicode value of a character "\u" prefix is used as in [char a = '\u004E';//letter N]
-number literals can be assigned to char variables, when the value is out of range (more than 16bits), an explicit cast to char is required for such assigements
char d = (char) -98; //Ridiculous, but legal
char a = -29; //compile error: possible loss of precision, needs a cast
char f = 70000; //compile error: possible loss of precision, needs a cast
char c = '\"'; //escape code for double quote char
char r = '\n'; //escape code for new line
-string literals are source code representation of a value of a String object
-strings are not primitives but still they have literal form
-arrays are not primitives but still they have literal form
-only strings and arrays are the non-primitive types that have literal form
Variables
----------
-variables are holders or containers containing binary bits
-primitive variables contain the value itself in bits
-object variables contain a pointer(in bits) to the object it is referring, not the object itself
-an object variable explicitly assigned to null contains the value null itself (in bits)
"So variable is just a little box of bits"
Weird int
---------
> integer literals such as 3 are implicitly int type
> so integer literals can be easily assigned to int or long variables
int a = 30;//no problem
long al = 30;//no problem
> what about assigning integer literals to short, char or byte?
short s = 40;//no problem
char c = 40;//no problem
byte b = 40;//no problem
//because compiler automatically casts these literals to appropriate short, char or byte
> int type is 32 bit; byte, char and short are smaller than int
> any integer literal is int type; also, the result of an expression involving anything int-size or smaller is always int
> addition of two bytes gives int (though those bytes are very small and the result is still byte-sized, the end result is always int)
> multiplication of int and short is int
> division of short by byte is an int
byte a = 2;
byte b = 2;
byte c = a + b; //compilation error because the result is an int but it is not literal in this line;
//it is a result from an expression (a + b)
(it makes sense actully; when biggest possible byte added itself gives to a value that is no longer byte-sized)
> the above code needs explicit cast in the third line
> implicit cast(automatic) for primitives happens when smaller value is assigned to bigger container (widening)
> explicit cast for primitives is required when bigger values are assigned to smaller containers (narrowing)
long g = 130L;
byte b = (byte) g; //byte can store only upto 127
//printing b displays -126
//this is because when 130 is narrowed, the bits leftmost to 8th bit are discarded and if the 8th bit(on left side)
//is 1, the number becomes negative
Floating Point Numbers
----------------------
> every floating point literal is implicitly double (e.g. 1.1), not float
> float is 32bit, double is 64bit
> float f = 1.1; //gives compile error; either cast or f/F suffix is required here
> byte a = 128; //gives compile error; 128 is too large to hold
byte a = (byte) 128;//compile
//printing 'a' displays -128. why?
//what happens during narrowing of 128?
//after chopping off the unwanted bits from 32bit(because 128 is int type) representation of 128,
//the last 8 bits that remain are "10000000"
//since leftmost bit is 1, it is negative number
//for negative numbers, 2's complement is used to compute the value of the negative number
2's Complement:-
[Flip all bits (01111111); then add 1 (10000000 back again); we got 128; so final value is -128]
> so when you use explicit cast to narrow down a value, be wary of the unpredicted result you would get
> compound assignment operators lets us skip explicit casts
byte b = 3;
b += 7; //compiles
b = (byte)(b + 7); //doesn't compile without the explicit cast
> assigning one primitive variable to another causes the contents of one variable just copied to another; they don't share the same value; they have separate copies of the same value
Variable Scope
--------------
>static variable scope, instance variable scope, local variable scope(method level) and block variable scope are the four scopes
>method local variables are available as long as the method is on the stack
>sometimes method variables are available on stack but not accessible; this happens when method within a method is being executed, the caller method variables are still on stack but are inaccessible(out of scope) to called method
>variables declared in switch, try-catch, for, do and while loops are not available outside their block
Uninitialized variables
-----------------------
>attempt to use uninitialized variable gives different behavior based on the variable type as well as variable scope
>attempt to use uninitialized method variable gives compile error
>instance variables are given default values if not initialized
Default values:
Object - null
byte/short/int/long - 0
float/double - 0.0
boolean - false
char - '\u0000'
>string variables are object type; so they are initialized with null by default not with empty string ""
>an instance variable will be initialized with null
>an empty array's(initialized array with new operator) elements will be initialized with default values according to their individual type as though they were an instance variable
>even if the array itself is a local variable(not instance variable), its elements get default values as though they were instance variables
>array elements always get initialized regardless of where they are declared
>but a local array variable never gets initialized and you will get compiler error if you attempt to use (it gets null only when it is instance variable)
>local or stack variables sometimes called automatic variables; but there is nothing automatic about them; it is just a name
>attempt to use uninitialized local variable gives compile error
>if the initialization code of the local variable is not visible(is put in a code block like if loop), again any attempt to use that variable in areas where visibility is not there will cause compilation error
>assigning object reference variable to each other does nothing but copy the bit patterns of one variable to another just like the primitive variable values are copied
>so in java assignment means, copy copy copy and copy the contents of the container, that is all
>modifying an object through two copies of its reference affects the same object in the heap (except for String object)
>string objects are immutable and they are given special treatment; whenever you modify a string, java makes up a new string object for result and the original objects involved in the compuatation are left unchanged; this is due to the use of String Constant Literal Pool maintained by JVM; so two copies of reference to a same string object doesn't always operate on the same object forever
>so any operation on a string object creates a new object leaving the originals untouched; copy copy and copy here too; string operations work with copies only
Passing Variables into a Method
-------------------------------
>when you pass an object reference to a method, mind it you are only passing a copy of the reference, not the object itself(like a copy of the address to the original object in the heap)
>although java passes copies of the reference to objects, java is NOT pass-by-reference sematics; it is INDEED pass-by-value (value of the reference to be precise)
>java is pass-by-value for all variables running within a single VM
>pass-by-variable-value or pass-by-copy-of-the-variable
>either a copy of the primitive value or a copy of the reference of the object
>called method cannot change the variables of the caller, but can change the objects referred by those variables
>in case of primitives essentially the same thing, that is the called method can't change the value of the variables of the caller, because it only received a copy of the primitive value
>one can shadow an instance variable by declaring the same again in a method; modifying the shadowing variable doesn't affect the instance variable with the same name (to really get to the instance variable use this operator) - this is for primitives
>while shadowing an object variable, modifying the shadowing variable can still affect the instance variable because it might receive the reference to the instance variable object reference
Array Declaration, Construction and Initialization
--------------------------------------------------
>there is no such thing as primitive array; every array is an object in the heap; either it holds primitives or objects doesn't matter
int key []; //space between key and square bracket is legal
int[] key; //recommended format
String[][] records; //multi dimensional array
String[] records[]; //same multidimensional array and legal
>size of the array should be specified when you create the array object not when you declare it
int[] scores = new int[5]; //creates an array in the heap with 5 int elements initialized to value 0
Thread[] workers = new Thread[5]; //creates an array object in the heap with 5 Thread elements initialized to null
//still no Thread objects created yet
How many objects got created by the previous code line? Just one. The array object. It has 5 reference variables of Thread type. No Thread objects created yet.
>without size, any attempt to create an array object gives compile error
>multi dimensional arrays (not all sizes need to be mentioned)
int[][] scores = new int[5][]; //legal - each element of scores is again array object whose size not mentioned
//each array within this array can be of different size
int[][][] scores = new int[5][][7];//illegal, dimension after empty dimension is not allowed
>initializing an array means putting things into it
>array of primitive type holds primitive values in them
>array of objects doesn't hold objects, but rather a set of references to some objects
>object arrays can be sources of NullPointerException and ArrayIndexOutOfBoundsException (runtime)
>using negative index also causes runtime exception (look, these are not caught by compiler)
>"length" is the only, single, public variable available is array objects(not mean object arrays)
>shorthand syntax to create arrays (without new operator, in this way you can create objects!)
int x = 9;
int[] dots = {1,x,3}; //declaring, constructing and initializing in one line
int[][][] scores = {{1,2,3,4},{1,2,3},{1,2}};//this creates a total of 4 objects of different size in the heap
Anonymous Array Creation(another shortcut to create arrays)
-----------------------------------------------------------
display(new int[] {1,2,3});//display method takes int[]
//no need to specify size for anonymous array; it is even NOT LEGAL to specify
display( new Object[3] {new ArrayList(), new Vector(), new Hashtable()} ); //compilation error here
>array of int can hold byte, char and short values as well
>an object array can hold subtype objects as well
>arrays can hold anything that passes IS-A test
References to arrays themselves
-------------------------------
>you can't assign one type of array reference to another type of array reference (strictly prohibited)
int[] ints = new int[2];
char[] chars = new char[2];
ints = chars; //illegal - though char is compatible with int (what an irony, only children can mingle it seems)
//but for object arrays, even PARENTS can mingle
List[] lists;
ArrayList[] aLists = new ArrayList[5];
lists = aLists; //legal because ArrayList is of List type
Thread[] threads = aLists; //illegal, because ArrayList is not Thread type
>arrays with different dimensions can't be assigned to one another even if they are same type or compatible
>in a multidimensional array of primitives, only the last dimension can accept primitives; others can only accept array objects of that primitive type
Initialization blocks
---------------------
>apart from constructors and methods, initialization blocks also can run codes
>static initialization block - runs when class is loaded
>instance initialization block - runs when an instance is created
class Blocks {
static
{
int a; //static init block
}
{
int b; //instance init block
}
}
>instance initialization blocks run right after the call to super() in the constructor
>one can have many initialization blocks in a class
>the order in which init blocks appear in a class matters; they are run in sequence from top to bottom
>all instance init blocks are run before the control reaches the 2nd line of each contructor of the current class
>The following is the order of execution when we run child class (which attempts to create its own instance)
1. Static init blocks of Parent class
2. Static init blocks of the current(Child) class
3. Instance init blocks of Parent class
4. 2nd line of Parent constructor
5. Instance init blocks of Child class
6. 2nd line of Child constructor
(pretty clear, huh?)
> From the above, it is clear that though we ran Child class, first Parent class is loaded into JVM (hence its static init blocks are run first)
> static init blocks are run once only; but instance init blocks are run everytime an instance is created
> instance init blocks are often used as a place to put code that should be shared by all constructors
> any exception in static init blocks produces java.lang.ExceptionInInitializerError
> any exception in instance init blocks are just treated as regular exceptions and reported as usual in the course of program execution
Wrapper Classes
---------------
>every primitive in java has a wrapper class (Boolean, Byte, Character, Double, Float, Integer, Long, Short)
>wrapper classes are immutable; once an object got created, its value cannot be changed after
>all wrapper classes provide two constructors(except for Character class) of which one takes primitive value, and the other takes string representation of the value
Integer val = new Integer(23);
Integer valval = new Integer("23");
>Character class provides only one constructor which takes char type argument
> Boolean bool = new Boolean("TrUe"); //case doesn't matter in strin rep. for boolean values
> only due to autoboxing/unboxing (from java 5.0 onwards), a boolean object can be directly used for boolean test
valueOf() methods
------------------
>another way to create wrapper objects is to use valueOf() methods
>there are two versions; first version takes string form of primitive value; second version takes string form of primitive value plus base/radix if it is a number type (base: binary, octal, decimal and hexadecial --> 2, 8, 10 and 16)
>byteValue(), shortValue(), doubleValue() and such methods are provided by wrapper classes for conversion purpose; bit truncation is done whenever necessary
>parseBlaBla() and valueOf() methods are similar; both take string form of primitives; both can use bases(radix) for number types; both throw NumberFormatException when the string form is not proper; but parseBlaBla() methods return primitive values while valueOf() methods return wrapper objects of the concerned type
>wrapper classes are marked final
>toString() method is inherited by all classes in java from Object class; this method is not static
>however, wrapper classes support static toString() version in addition to Object class toString(); this static method takes primitive value as argument and returns a string form ofit;
>in addition to that, Long and Integer classes provide a third toString() version which is static;
>this static method takes primitive value as the first argument and radix as the second argument; this is useful to print numbers in different bases
>Integer and Long also have toHexString(10), toOctalString(10), toBinaryString(10) static methods
>Boolean and Character classes do not provide most of the above mentioned methods; Character provides only the simple version of toString() method; Boolean is same as Character but it provides one more method which is a simple valueOf() without radix support
>Double and Float classes do not provide any methods relating to radixes; but essentially they are same in all other respects to Integer/Long/Short/Byte classes
blablaValue() - wrapper to primitive (blabla = int/double/float/short/byte)
parseBlabla(String) - String to primitive
valueOf(String) - String to wrapper
(Happy?)
equals(==) operator and equals() method
----------------------------------------
== operator compares the values or object references are same
equals() method compares the contents of the object
== operator generally considers two objects to be not equal even if they have same content
but, in the world of autoboxing/unboxing, equals(==) operator behaves a little differently (to some extent) when it comes to wrapper objects
Even == operator treats two different wrapper objects to be equal sometimes if their content is same.
when is that sometime? and why?
when?
when the following wrapper objects are created through autoboxing:- 1. Boolean, 2. Byte, 3. Character from \u0000 to \u007f (0 to 127), 4. Short and Integer objects having values between -128 to 127
why?
to save memory :-)
[This is because the autoboxing code is replaced by the compiler with Integer.valueOf() method call. This method caches the above said values for fast performance and to save memory. So, wrapper objects obtained through autoboxing are same objects throughout the application if their value is within the above specified ranges]
{
Integer a = 1;
Integer b = 1;
System.out.println(a == b); //prints true
}
when a primitive and a wrapper is mingled in == operation, the comparison is done in primitive only(wrapper is unboxed).
Autoboxing/Unboxing - is a compiler gimmicks (java language didn't change w.r.to wrapper objects; only the compiler changed)
That is compiler automatically puts code for unboxing and autoboxing wherever required. Saves us some effort.
Autoboxing can obscure NullPointerException occuring due to uninitialized wrapper references. You will be in pinch at runtime though your code might look like using only primitive operations in the code.(duh?)
Overloading
-----------
which overloaded method to use based on the type of the input we pass (especially when primitives/wrappers are involved)?
[we are assuming that all overloaded methods only differ by the type of one input argument in the same position in the argument list; even return type is same across all overloaded methods; the situation turns grave when we have to decide which overloaded method we have to use based on that one input arg's type]
>passing byte and short automatically chooses overloaded method which takes int if there are not methods that take byte or short
>as long as the passed input is accommodatable the overloaded method with int arg is chosen over the method with long arg(widening in play)
>smallest possible YET accommodating argument is given preference over other accommodating ones (int over long and float over double)
>Firstly, when a choice is given between "widening" and "autoboxing" for choosing an overloaded method, "widening" gets the first preference. Lazy compiler doesn't want to put all those autoboxing code; so it simply uses widening; e.g value "5" to be passed as "Integer" object or "long" value - it chooses "long" option
>this preference comes from the decision of Java 5's designers to let the preexisting code to function the way it used to (hence widening comes first than autoboxing)
>Secondly, given a choice between "widening" and "var-args", widening is given first preference to choose appropriate overloaded method; e.g. (short, short) will be passed as (int, int) rather than (short...)
[again old style is preferred first]
>Thirdly, between Autoboxing and Var-args, Autoboxing wins
>"Widening" is the KING; "Var-args" is the LOSER;
>"Var-args" is used as a last resort; when nothing else works, we come to var-args
>It is legal to widen the primitives; so is for object references if polymorphic relationship is there, widening works well; e.g. ArrayList passed as List;
>But wrapper classes donot have any polymorphic relationships with one another; they are all peers; For example Short object cannot be passed as Integer; same goes for all wrapper classes
>Compiler will not do two adjustments; e.g. first widening, then boxing is not permissible (byte value 5 to be passed as Long object is not allowed)
>first boxing and then widening is allowed; like 5 to Byte(5) to Object; but not 5 to Byte(5) to Long(5); (remember we said object widening needs polymorphic relationship)
[Combining widening or boxing with vararg]
>widen + vararg is possible (first widens, then uses vararg to choose the correct method)
>boxing + vararg is possible (first boxes, then uses vararg to choose the correct method)
Garbage Collection
------------------
>Java garbage collector provides automatic solution to memory management; no memory management logic is necessary in your application code
>the downside of this automatic solution is you can't control when it runs and when doesn't
>Java memory areas: stack, heap, constant pools, method areas
>Heap is where java objects live
>Heap is the only place where garbage collection happens
>Heap is only one; only one heap
>when no live threads can reach/access an object, it becomes eligible for garbage collection
>where there are no references to an object it is eligible for garbage collection
>by setting all existing references to null we can force the object to become orphan and so it is eligible for garbage collection
>method local objects become eligible for garbage collection once the method is over, if no references were returned back to the calling method and saved there
Islands of Isolation: It is nothing but an island of object cutoff from the rest of the JVM world. The objects in that island may still be holding references to each other, but the island as a whole is not accessible to any live threads. Hence the whole island becomes eligible for garbage collection
>One can request for garbage collection using System.gc() call; but no guarantees that JVM will actually run gc
>In Java 6 and later, using System.gc() is almost useless because gc has advanced so much that JVM itself is capable of taking care of gc without your intervention
>Even after running gc, there is no guarantee that all inaccessible object would be garbage collected
>gc related routines/methods are part of Runtime class. Runtime is a singleton class for each main program
>Runtime object provides mechanism to directly communicate with the VM
>Runtime.getRuntime() returns the singleton object
>Sytem.gc() uses Runtime object internally
>Guaranteed:: GC will be run for sure before it throws OutOfMemoryError
>finalize() method is inherited from Object class by all objects in JVM; GC will call finalize() before it deletes an object; one can use this method to cleanup or free resources before it gets deleted
>however, since GC itself is not guaranteed to be run at the time you expect, the chance of the finalize() method running is also not guaranteed
>GC calls the finalize() method on an object only once; in case you saved this object from getting deleted using finalize() method, it won't work second time, because GC will not call finalize() again on the same object
>it is recommended not to override finalize() method because that method ever running is unpredictable
>You can't know GC algorithm for sure
>outer class can access the private members of the static inner class (verified) [non-static inner class private members also are accessible]
Reference: Kathy Sierra's SCJP Book