The Java data types can be divided into a set of primitive types and a reference type. Variable of primitive types hold primitive values and variables of the reference type hold reference values. Reference values refer to objects, but are not objects themselves. Primitive values, by contrast do not refer anything. They are actual data themselves.
References in Java :
Reference : Reference is an abstract identifier for a block of memory in the heap.
Special Reference: There are three special references in Java source code.
- null
- this
- super
null reference : The null reference is an invalid reference. It has no type , and may be assigned to a variable of any reference type. When a reference variable is declared but not constructed , it is initially equal to null.
this reference : The this reference always refers to the current object, method .
Example: int j = this.x;
super reference : The super reference refers to the methods and fields of the immediate super class. You need to use super prefix only if a field or method in the subclass has same name as a field or method in super class.
If you use super() as the first statement in a constructor, it calls the matching constructor in the immediate super class based on parameters passed to super() .
If you do not include an explicit call to super() as the first statement in your constructor, then the compiler will insert such a call into the byte code. The compiler always chooses to no arguments super() constructor if you don't explicitly choose a different one.
Example:
public class SuperClass{
public SuperClass(int i){
}
}
class SubClass extends SuperClass{
public SubClass(){
}
}
Note: If you try to compile this program you get the error message "no constructor matching SuperClass() found in class SuperClass. Because constructor chaining is mandatory in super-sub hierarchy.If you provide a parameterised constructor in super class then always put a noargs constructor in super class.
Example:
public class SuperClass{
public SuperClass(int i){
}
}
class SubClass extends SuperClass{
public SubClass(){
}
}
Note: If you try to compile this program you get the error message "no constructor matching SuperClass() found in class SuperClass. Because constructor chaining is mandatory in super-sub hierarchy.If you provide a parameterised constructor in super class then always put a noargs constructor in super class.
About fail-fast nature of Iterator
The iterators returned by this class's iterator and listIterator methods are fail-fast. If the list is structurally modified at any time after the iterator is created, in any way except through the Iterator's own remove or add methods, the iterator will throw a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.
Example:
List<String> staff = new ArrayList<String>();
staff.add("Ajay");
staff.add("Kumar");
staff.add("Singh");
staff.add("sunny");
ListIterator listItr = staff.listIterator(); //get the ListIterator
// after getting iterator if we change(add or remove) here by ArrayList.add of ArrayList.remove then it will throw a ConcurrentModificationException except ListIterator.add or ListIterotr.remove.
staff.add("Ajay Kumar"); // will throw a ConcurrentModificationException
listItr.add("Ajay Kumar"); // OK
But there may be a exceptional situation where more than one thread of Iterator is trying to modify a collection in unsynchronized block or method then it will throw ConcurrentModificationException.
ListIterator listItr = staff.listIterator(); //get the ListIterator
Iterator itr = staff.iterator(); //get the Iterator
listItr.add("Ajay Kumar"); // throw ConcurrentModificationException
Example:
List<String> staff = new ArrayList<String>();
staff.add("Ajay");
staff.add("Kumar");
staff.add("Singh");
staff.add("sunny");
ListIterator listItr = staff.listIterator(); //get the ListIterator
// after getting iterator if we change(add or remove) here by ArrayList.add of ArrayList.remove then it will throw a ConcurrentModificationException except ListIterator.add or ListIterotr.remove.
staff.add("Ajay Kumar"); // will throw a ConcurrentModificationException
listItr.add("Ajay Kumar"); // OK
But there may be a exceptional situation where more than one thread of Iterator is trying to modify a collection in unsynchronized block or method then it will throw ConcurrentModificationException.
ListIterator listItr = staff.listIterator(); //get the ListIterator
Iterator itr = staff.iterator(); //get the Iterator
listItr.add("Ajay Kumar"); // throw ConcurrentModificationException
Secrets of HashSet Collection :
HashSet is data structures that let you find elements much faster. The drawback is that those data structures give you no control over the order in which the elements appear. It uses hash table, A hash table computes an integer, called the hash code, for each object. A hash code is an integer that is somehow derived from the instance fields of an object, preferably such that objects with different data yield different codes.
In Java, hash tables are implemented as arrays of linked lists. Each list is called a bucket. To find the place of an object in the table, compute its hash code and reduce it modulus the total number of buckets.
LoadFactor: The load factor determines when a hash table is rehashed. For example, if the load factor is 0.75 (which is the default) and the table is more than 75% full, then it is automatically rehashed, with twice as many buckets.
This class implements the Set interface, backed by a hash table (actually a HashMap instance). It uses HashMap object internally in constructor.
private transient HashMap<E,Object> map;
public HashSet() {
map = new HashMap<E,Object>(); }
Constructs a new, empty set; the backing HashMap instance has default initial capacity (16) and load factor (0.75). There are four more constructors of HashSet.
In Java, hash tables are implemented as arrays of linked lists. Each list is called a bucket. To find the place of an object in the table, compute its hash code and reduce it modulus the total number of buckets.
LoadFactor: The load factor determines when a hash table is rehashed. For example, if the load factor is 0.75 (which is the default) and the table is more than 75% full, then it is automatically rehashed, with twice as many buckets.
Internal architecture of HashSet :
private transient HashMap<E,Object> map;
public HashSet() {
map = new HashMap<E,Object>(); }
Constructs a new, empty set; the backing HashMap instance has default initial capacity (16) and load factor (0.75). There are four more constructors of HashSet.
It internally uses HashMap methods. See below
public boolean add(E e) {return map.put(e, PRESENT)==null; //calls put method of Map
}
}
Secrets of final keyword :
In Java, you use the keyword final to denote a constant.
final double PI=3.14;
The keyword final indicates that you can assign to the variable once, then its value is set
once and for all.
public final class System
{
. . .
public final static PrintStream out = nullPrintStream();
. . .
}
Since out has been declared as final, you cannot reassign another print stream to it:
out = new PrintStream(. . .); // ERROR--out is final
If you look at the System class, you will notice a method setOut that lets you set System.out to a different stream. You may wonder how that method can change the value of a final variable. However, the setOut method is a static method which calls native method, not implemented in the Java programming language.
public static void setOut(PrintStream out) {
checkIO();
setOut0(out); //This is a native method
}
private static native void setOut0(PrintStream out);
final double PI=3.14;
The keyword final indicates that you can assign to the variable once, then its value is set
once and for all.
Important on final keyword:
System.out is a final variable . It is declared in the System class aspublic final class System
{
. . .
public final static PrintStream out = nullPrintStream();
. . .
}
Since out has been declared as final, you cannot reassign another print stream to it:
out = new PrintStream(. . .); // ERROR--out is final
If you look at the System class, you will notice a method setOut that lets you set System.out to a different stream. You may wonder how that method can change the value of a final variable. However, the setOut method is a static method which calls native method, not implemented in the Java programming language.
public static void setOut(PrintStream out) {
checkIO();
setOut0(out); //This is a native method
}
private static native void setOut0(PrintStream out);
Native methods can bypass the access control mechanisms of the Java language.
Finally Block. Use But Carefully
Finally clause is a block that guarantees that this block will be executed whether there is exception occurred in try-catch block on not. If we call system.exit(0) then finally block will not be executed.
You can use the finally clause without a catch clause. For example, consider the following
try statement:
Graphics g = image.getGraphics();
try{code that mightthrow exceptions}finally{g.dispose();}
The g.dispose() command in the finally clause is executed whether or not an exception is
encountered in the try block.
Point to remember using finally block:
The finally clause leads to unexpected control flow when you exit the middle of a try block with a return statement. Before the method returns, the contents of the finally block is executed. If it also contains a return statement, then it masks the original return value.
Consider this example:
public static int f(int n)
{
try
{
int r = n * n;
return r;
}
finally
{
if (n == 2) return 0;
}
}
{
try
{
int r = n * n;
return r;
}
finally
{
if (n == 2) return 0;
}
}
If you call f(2), then the try block computes r = 4 and executes the return statement. However, the finally clause is executed before the method actually returns. The finally clause causes the method to return 0, ignoring the original return value of 4.
Sometimes the finally clause gives you grief, namely if the cleanup method can also throw an exception. A typical case is closing a stream.Suppose you want to make sure that you close a stream when an exception hits in the stream processing code.
InputStream in;
try
{
code that might
throw exceptions
}
catch (IOException e)
{
show error dialog
}
finally
{
in.close();
}
Now suppose that the code in the try block throws some exception other than an IOException that is of interest to the caller of the code. The finally block executes, and the close method is called. That method can itself throw an IOException! When it does, then the original exception is lost and the IOException is thrown instead. That is very much against the spirit of exception handling.
Secrets of toString() method:
toString() method that is declared and implemented in Object class that returns a string representation of the object.
whenever an object is concatenated with a string, using the “+” operator, the Java compiler automatically invokes the toString method to obtain a string representation of the object.
For example:
Point p = new Point(10, 20);
String message = "The current position is " + p; // automatically invokes p.toString()
If x is any object and you callSystem.out.println(x);
then the println method simply calls x.toString() and prints the resulting string.
Object's toString method:
The Object class defines the toString method to print the class name and the memorylocation of the object. For example, the call
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
For example, the call:System.out.println(System.out);
produces an output that looks like this:
java.io.PrintStream@2f6684
The reason is that the implementor of the PrintStream class didn't bother to override the toString method.
Polymorphism in Java
Polymorphism, which means "many forms," is the ability to treat an object of any subclass of a base class as if it were an object of the base class Object variables are polymorphic. A variable of type Employee can refer to an object of type Employee or an object of any subclass of the Employee class (such as Manager, Executive, Secretary, and so on). A base class has, therefore, many forms: the base class itself, and any of its subclasses.
Subclasses of a class can define their own unique behaviors and yet share some of the same functionality of the parent class.
Liquid myFavoriteBeverage = new Liquid();
The myFavoriteBeverage variable holds a reference to a Liquid object. This is a sensible arrangement;
however, there is another possibility brought to you courtesy of polymorphism. Because of polymorphism, you can assign a reference to any object that is-a Liquid to a variable of type Liquid. So, assuming the inheritance hierarchy shown in Figure 8-1, either of the following assignments will also work:
Liquid myFavoriteBeverage = new Coffee();
// or... Liquid myFavoriteBeverage = new Milk();
Therefore, you can sprinkle some polymorphism in your Java program simply by using a variable with a base type to hold a reference to an object of a derived type.
To fully realize the wonders of polymorphism, you must send a message to an object without knowing the actual class of the object. To do this in Java, you just invoke a method defined in a base type on an object referenced by a variable of the base type. As you saw above, the object referred to by a base class reference might be of the base class or any of its subclasses. Therefore, when you write the code to invoke the method, you don't necessarily know the actual class of the object. Likewise, when you compile the code, the compiler doesn't necessarily know the actual class of the object. At run-time, the Java Virtual Machine determines the actual class of the object each time the method invocation is requested by your program. Based on this information, the Java Virtual Machine invokes the method implementation belonging to the object's actual class. Letting the Java Virtual Machine determine which method implementation to invoke, based on the actual class of the object, is how you realize the full power of polymorphism in your programs.
public class TestBikes {
public static void main(String[] args){
Bicycle bike01, bike02, bike03;
bike01 = new Bicycle(20, 10, 1); //Polymorphism too.
bike02 = new MountainBike(20, 10, 5, "Dual"); //Polymorphism
bike03 = new RoadBike(40, 20, 8, 23); bike01.printDescription(); //Polymorphism
bike02.printDescription(); //Dynamic Dispatch
bike03.printDescription(); //Dynamic Dispatch
}
bike02 = new MountainBike(20, 10, 5, "Dual"); //Polymorphism
bike03 = new RoadBike(40, 20, 8, 23); bike01.printDescription(); //Polymorphism
bike02.printDescription(); //Dynamic Dispatch
bike03.printDescription(); //Dynamic Dispatch
}
}
Confusion at polymorphism :
- There is no any run-time polymorphism because polymorphism is achieved at run-time automatically by the JVM, if not then it is not polymorphism.
- Method overriding is always connected to polymorphism but this is false (source: oracle.com). But is used to get full benefits of polymorphism and dynamic dispatch.
Abstract Classes versus Interfaces :
Unlike interfaces, abstract classes can contain fields that are not static and final, and they can contain
implemented methods. Such abstract classes are similar to interfaces, except that they provide a partial implementation, leaving it to subclasses to complete the implementation. If an abstract class contains only abstract method declarations, it should be declared as an interface instead.
Multiple interfaces can be implemented by classes anywhere in the class hierarchy, whether or not they are related to one another in any way. Think of Comparable orCloneable, for example.
By comparison, abstract classes are most commonly subclassed to share pieces of implementation. A single abstract class is subclassed by similar classes that have a lot in common (the implemented parts of the abstract class), but also have some differences (the abstract methods).
In the section on Interfaces, it was noted that a class that implements an interface must implement all of the interface's methods. It is possible, however, to define a class that does not implement all of the interface methods, provided that the class is declared to be abstract. For example,
abstract class X implements Y {
// implements all but one method of Y
}
class XX extends X {
// implements the remaining method in Y
}
In this case, class X must be abstract because it does not fully implement Y, but class XX does, in fact, implement Y.
implemented methods. Such abstract classes are similar to interfaces, except that they provide a partial implementation, leaving it to subclasses to complete the implementation. If an abstract class contains only abstract method declarations, it should be declared as an interface instead.
Multiple interfaces can be implemented by classes anywhere in the class hierarchy, whether or not they are related to one another in any way. Think of Comparable orCloneable, for example.
By comparison, abstract classes are most commonly subclassed to share pieces of implementation. A single abstract class is subclassed by similar classes that have a lot in common (the implemented parts of the abstract class), but also have some differences (the abstract methods).
When an Abstract Class Implements an Interface
In the section on Interfaces, it was noted that a class that implements an interface must implement all of the interface's methods. It is possible, however, to define a class that does not implement all of the interface methods, provided that the class is declared to be abstract. For example,
abstract class X implements Y {
// implements all but one method of Y
}
class XX extends X {
// implements the remaining method in Y
}
In this case, class X must be abstract because it does not fully implement Y, but class XX does, in fact, implement Y.
What Is an Object?
Real-world objects share two characteristics: They all have state and behavior. Dogs have state (name, color, breed, hungry) and behavior (barking, fetching, wagging tail). Bicycles also have state (current gear, current pedal cadence, current speed) and behavior (changing gear, changing pedal cadence, applying brakes). Identifying the state and behavior for real-world objects is a great way to begin thinking in terms of object-oriented programming.
Software objects are conceptually similar to real-world objects: they too consist of state and related behavior. An object stores its state in fields (variables in some programming languages) and exposes its behavior through methods (functions in some programming languages). Methods operate on an object's internal state and serve as the primary mechanism for object-to-object communication. Hiding internal state and requiring all interaction to be performed through an object's methods is known as data encapsulation a fundamental principle of object-oriented programming.
Object provides many benefits:
- Modularity: The source code for an object can be written and maintained independently of the source code for other objects. Once created, an object can be easily passed around inside the system.
- Information-hiding: By interacting only with an object's methods, the details of its internal implementation remain hidden from the outside world.
- Code re-use: If an object already exists (perhaps written by another software developer), you can use that object in your program. This allows specialists to implement/test/debug complex, task-specific objects, which you can then trust to run in your own code.
- Pluggability and debugging ease: If a particular object turns out to be problematic, you can simply remove it from your application and plug in a different object as its replacement. This is analogous to fixing mechanical problems in the real world. If a bolt breaks, you replace it, not the entire machine.
Rewriting Interfaces :
Consider an interface that you have developed called
Suppose that, at a later time, you want to add a third method to
If you make this change, all classes that implement the old
Try to anticipate all uses for your interface and to specify it completely from the beginning. Given that this is often impossible, you may need to create more interfaces later. For example, you could create a
Now users of your code can choose to continue to use the old interface or to upgrade to the new interface.
DoIt
:public interface DoIt {
void doSomething(int i, double x);
int doSomethingElse(String s);
}
DoIt
, so that the interface now becomes:public interface DoIt {
void doSomething(int i, double x);
int doSomethingElse(String s);
boolean didItWork(int i, double x, String s);
}
DoIt
interface will break because they don't implement the interface anymore. Programmers relying on this interface will protest loudly.Try to anticipate all uses for your interface and to specify it completely from the beginning. Given that this is often impossible, you may need to create more interfaces later. For example, you could create a
DoItPlus
interface that extends DoIt
:public interface DoItPlus extends DoIt {
boolean didItWork(int i, double x, String s);
}
- An interface defines a protocol of communication between two objects.
- An interface declaration contains signatures, but no implementations, for a set of methods, and might also contain constant definitions.
- A class that implements an interface must implement all the methods declared in the interface.
- An interface name can be used anywhere a type can be used.
Interface in Java
As you've already learned, objects define their interaction with the outside world through the methods that they expose. Methods form the object's interface with the outside world; the buttons on the front of your television set, for example, are the interface between you and the electrical wiring on the other side of its plastic casing. You press the "power" button to turn the television on and off.
In its most common form, an interface is a group of related methods with empty bodies. A bicycle's behavior, if specified as an interface, might appear as follows:
To implement this interface, the name of your class would change (to a particular brand of bicycle, for example, such as
Implementing an interface allows a class to become more formal about the behavior it promises to provide. Interfaces form a contract between the class and the outside world, and this contract is enforced at build time by the compiler. If your class claims to implement an interface, all methods defined by that interface must appear in its source code before the class will successfully compile.
In its most common form, an interface is a group of related methods with empty bodies. A bicycle's behavior, if specified as an interface, might appear as follows:
interface Bicycle {
// wheel revolutions per minute
void changeCadence(int newValue);
void changeGear(int newValue);
void speedUp(int increment);
void applyBrakes(int decrement);
}
HeroBicycle
), and you'd use the implements
keyword in the class declaration:class HeroBicycle implements Bicycle {
// remainder of this class
// implemented as before
}
Restrictions on Generics :
Cannot Instantiate Generic Types with Primitive Types:
Consider the following parameterized type:
class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
// ...
}
Pair<int, char> p = new Pair<>(8, 'a'); // compile-time error
Pair<Integer, Character> p = new Pair<>(8, 'a');
Pair<Integer, Character> p = new Pair<>(Integer.valueOf(8), new Character('a'));
When creating a Pair object, you cannot subsitute a primitive type for the type parameter K or V:private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
// ...
}
Pair<int, char> p = new Pair<>(8, 'a'); // compile-time error
Pair<Integer, Character> p = new Pair<>(8, 'a');
Pair<Integer, Character> p = new Pair<>(Integer.valueOf(8), new Character('a'));
You can substitute only non-primitive types for the type parameters K and V:
Note that the Java compiler autoboxes 8 to Integer.valueOf(8) and 'a' to Character('a').
Cannot Declare Static Fields Whose Types are Type Parameters
public class MobileDevice<T> {private static T os;
// ...
}
MobileDevice<Smartphone> phone = new MobileDevice<>();
MobileDevice<Pager> pager = new MobileDevice<>();
MobileDevice<TabletPC> pc = new MobileDevice<>();
A class's static field is a class-level variable shared by all non-static objects of the class. Hence, static fields of type parameters are not allowed. Consider the following class:
If static fields of type parameters were allowed, then the following code would be confused:
Because the static field os is shared by phone, pager, and pc, what is the actual type of os? It cannot be Smartphone, Pager, and TabletPC at the same time. You cannot, therefore, create static fields of type parameters.
Cannot Create Arrays of Parameterized Types:
You cannot create arrays of parameterized types. For example, the following code does not compile:List<Integer>[] arrayOfLists = new List<Integer>[2]; // compile-time error
Cannot Create, Catch, or Throw Objects of Parameterized Types :
A generic class cannot extend the Throwable class directly or indirectly. For example, the following classes will not compile:// Extends Throwable indirectly
class MathException<T> extends Exception { /* ... */ } // compile-time error
// Extends Throwable directly
class QueueFullException<T> extends Throwable { /* ... */ // compile-time error
A method cannot catch an instance of a type parameter:
public static <T extends Exception, J> void execute(List<J> jobs) {
try {
for (J job : jobs)
// ...
} catch (T e) { // compile-time error
// ...
}
}
You can, however, use a type parameter in a throws clause:
class Parser<T extends Exception> {
public void parse(File file) throws T { // OK
// ...
}
}
Wildcards in Generics
In generic code, the question mark (?), called the wildcard, represents an unknown type. The wildcard can be used in a variety of situations: as the type of a parameter, field, or local variable; sometimes as a return type (though it is better programming practice to be more specific). The wildcard is never used as a type argument for a generic method invocation, a generic class instance creation, or a supertype.
Upper Bounded Wildcards:
You can use an upper bounded wildcard to relax the restrictions on a variable. For example, say you want to write a method that works on List<Integer>, List<Double>,and List<Number>; you can achieve this by using an upper bounded wildcard.
To declare an upper-bounded wildcard, use the wildcard character ('?'), followed by the extends keyword, followed by its upper bound. Note that, in this context, extends is used in a sense to mean either "extends" (as in classes) or "implements" (as in interfaces). To write the method that works on lists of Number and the sub-types of Number, such as Integer, Double, and Float, you would specify List<? extends Number>. The term List<Number> is more restrictive than List<? extends Number> because the former matches a list of type Number only, whereas the latter matches a list of type Number or any of its subclasses.
Consider the following process method:
The upper bounded wildcard, <? extends Test>, where Test is any type, matches Test and any subtype of Test. The process method can access the list elements as type Test:
It's important to note that List<Object> and List<?> are not the same. You can insert an Object, or any subtype of Object, into a List<Object>. But you can only insert null into a List<?>.
You can specify upper bound for a wild card, or you can specify lower bound, but you can not specify both.
To declare an upper-bounded wildcard, use the wildcard character ('?'), followed by the extends keyword, followed by its upper bound. Note that, in this context, extends is used in a sense to mean either "extends" (as in classes) or "implements" (as in interfaces). To write the method that works on lists of Number and the sub-types of Number, such as Integer, Double, and Float, you would specify List<? extends Number>. The term List<Number> is more restrictive than List<? extends Number> because the former matches a list of type Number only, whereas the latter matches a list of type Number or any of its subclasses.
Consider the following process method:
public static void process(List<? extends Test> list) { /* ... */ }
public static void process(List<? extends Test> list) {
for (Test var : list) {
// ...
}
}
Unbounded Wildcards:
The unbounded wildcard type is specified using the wildcard character (?), for example, List<?>. This is called a list of unknown type. There are two scenarios where an unbounded wildcard is a useful approach:
- If you are writing a method that can be implemented using functionality provided in the Object class.
- When the code is using methods in the generic class that don't depend on the type parameter. For example, List.size or List.clear. In fact, Class<?> is so often used because most of the methods in Class<T> do not depend on T.
Consider the following method, printList:
The goal of printList is to print a list of any type, but it fails to achieve that goal — it prints only a list of Object instances; it cannot print List<Integer>, List<String>,List<Double>, and so on, because they are not subtypes of List<Object>.
To write a generic printList method, use List<?>:
public static void printList(List<Object> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}
The goal of printList is to print a list of any type, but it fails to achieve that goal — it prints only a list of Object instances; it cannot print List<Integer>, List<String>,List<Double>, and so on, because they are not subtypes of List<Object>.
To write a generic printList method, use List<?>:
public static void printList(List<?> list) {
for (Object elem: list)
System.out.print(elem + " ");
System.out.println();
}
Lower Bounded Wildcards:
A lower bounded wildcard restricts the unknown type to be a specific type or a super type of that type.
A lower bounded wildcard is expressed using the wildcard character ('?'), following by the super
keyword, followed by its lower bound: <? super A>.
To write the method that works on lists of Integer and the supertypes of Integer, such as Integer, Number, and Object, you would specify List<? super Integer>.
The term List<Integer> is more restrictive than List<? super Integer> because the former matches a list of type Integer only, whereas the latter matches a list of any type that is a supertype of Integer.
The following code adds the numbers 1 through 10 to the end of a list:
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
You can specify upper bound for a wild card, or you can specify lower bound, but you can not specify both.
Benefit of using Generics in Java
Generics enable types (classes and interfaces) to be parameters when defining classes, interfaces and methods.
Code that uses generics has many benefits over non-generic code:
Code that uses generics has many benefits over non-generic code:
- Stronger type checks at compile time.
A Java compiler applies strong type checking to generic code and issues errors if the code violates type safety. Fixing compile-time errors is easier than fixing run-time errors, which can be difficult to find.
- Elimination of casts.
The following code snippet without generics requires casting:List list = new ArrayList(); list.add("hello"); String s = (String) list.get(0);
List<String> list = new ArrayList<String>(); list.add("hello"); String s = list.get(0); // no cast