Advance Java
03:17-
Introduction to Generics
Generic Methods
-
Java Generic methods and generic classes enable programmers to specify, with a single method declaration, a set of related methods or, with a single class declaration, a set of related types, respectively.
Generics also provide compile-time type safety that allows programmers to catch invalid types at compile time.
Using Java Generic concept we might write a generic method for sorting an array of objects, then invoke the generic method with Integer arrays, Double arrays, String arrays and so on, to sort the array elements.
Generic Methods:
You can write a single generic method declaration that can be called with arguments of different types. Based on the types of the arguments passed to the generic method, the compiler handles each method call appropriately. Following are the rules to define Generic Methods:
All generic method declarations have a type parameter section delimited by angle brackets (< and >) that precedes the method's return type ( < E > in the next example).
Each type parameter section contains one or more type parameters separated by commas. A type parameter, also known as a type variable, is an identifier that specifies a generic type name.
The type parameters can be used to declare the return type and act as placeholders for the types of the arguments passed to the generic method, which are known as actual type arguments.
A generic method's body is declared like that of any other method. Note that type parameters can represent only reference types not primitive types (like int, double and char).
Example:
Following example illustrate how we can print array of different type using a single Generic method:
public class GenericMethodTest { // generic method printArray public static < E > void printArray( E[] inputArray ) { // Display array elements for ( E element : inputArray ){ System.out.printf( "%s ", element ); } System.out.println(); } public static void main( String args[] ) { // Create arrays of Integer, Double and Character Integer[] intArray = { 1, 2, 3, 4, 5 }; Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 }; Character[] charArray = { 'H', 'E', 'L', 'L', 'O' }; System.out.println( "Array integerArray contains:" ); printArray( intArray ); // pass an Integer array System.out.println( "\nArray doubleArray contains:" ); printArray( doubleArray ); // pass a Double array System.out.println( "\nArray characterArray contains:" ); printArray( charArray ); // pass a Character array } }
This would produce following result:
Array integerArray contains: 1 2 3 4 5 6 Array doubleArray contains: 1.1 2.2 3.3 4.4 Array characterArray contains: H E L L O
-
Strengths and Weaknesses of Generics
If you want to take an element from the Collection, you have to cast the type of element that is stored in the collection.The compiler does not check that your cast is the same as the collection's type or not, which makes the cast fail at run time.To overcome this situation Generics provides a way to communicate the collection type to the compiler which aware the compiler about the element type of the collection, and can insert the correct casts on values being taken out of the collection.
Generics provide a mechanism for annotating type information for the use of the compiler. By providing this information, the compiler can in turn verify the consistent use of "genericized" classes and automatically insert explicit casts, eliminating the need for programmers to do so manually.
The Generics makes its information available only at compile time and not translated into the compiled bytes. This allows genericized classes to interoperate cleanly with legacy, or raw, types.
It has the ability for library authors to express their intent more cleanly. Generics help to enforce the contents of collections and other genericized classes.
Generics can be used in more than just collection classes which allows developers to write their own genericized classes and genericized methods with generics. -
Generic Wildcards
The generic wildcard operator is a solution to the problem explained above. The generic wildcards target two primary needs:
- Reading from a generic collection
- Inserting into a generic collection
List<?> listUknown = new ArrayList<A>(); List<? extends A> listUknown = new ArrayList<A>(); List<? super A> listUknown = new ArrayList<A>();
The following sections explain what these wildcards mean.
The Unknown Wildcard
List<?>
means a list typed to an unknown type. This could be aList<A>
, aList<B>
, aList<String>
etc.
Since the you do not know what type theList
is typed to, you can only read from the collection, and you can only treat the objects read as beingObject
instances. Here is an example:
public void processElements(List<?> elements){ for(Object o : elements){ System.out.println(o); } }
TheprocessElements()
method can now be called with any genericList
as parameter. For instance aList<A>
, aList<B>
,List<C>
, aList<String>
etc. Here is a legal example:
List<A> listA = new ArrayList<A>(); processElements(listA);
The extends Wildcard Boundary
List<? extends A>
means aList
of objects that are instances of the class A, or subclasses of A (e.g. B and C).
When you know that the instances in the collection are of instances of A or subclasses of A, it is safe to read the instances of the collection and cast them to A instances. Here is an example:
public void processElements(List<? extends A> elements){ for(A a : elements){ System.out.println(a.getValue()); } }
You can now call theprocessElements()
method with either aList<A>
,List<B>
orList<C>
. Hence, all of these examples are valid:
List<A> listA = new ArrayList<A>(); processElements(listA); List<B> listB = new ArrayList<B>(); processElements(listB); List<C> listC = new ArrayList<C>(); processElements(listC);
TheprocessElements()
method still cannot insert elements into the list, because you don't know if the list passed as parameter is typed to the classA
,B
orC
.
The super Wildcard Boundary
List<? super A>
means that the list is typed to either the A class, or a superclass of A.
When you know that the list is typed to either A, or a superclass of A, it is safe to insert instances of A or subclasses of A (e.g. B or C) into the list. Here is an example:
public static void insertElements(List<? super A> list){ list.add(new A()); list.add(new B()); list.add(new C()); }
All of the elements inserted here are either A instances, or instances of A's superclass. Since both B and C extend A, if A had a superclass, B and C would also be instances of that superclass.
You can now callinsertElements()
with either aList<A>
, or aList
typed to a superclass of A. Thus, this example is now valid:
List<A> listA = new ArrayList<A>(); insertElements(listA); List<Object> listObject = new ArrayList<Object>(); insertElements(listObject);
TheinsertElements()
method cannot read from the list though, except if it casts the read objects toObject
. The elements already present in the list wheninsertElements()
is called could be of any type that is either an A or superclass of A, but it is not possible to know exactly which class it is. However, since any class eventually subclassObject
you can read objects from the list if you cast them toObject
. Thus, this is legal:
-
-
Threads
Threads
In computer science, a thread of execution is the smallest sequence of programmed instructions that can be managed independently by an operating system scheduler. A thread is a light-weight process. The implementation of threads and processes differs from one operating system to another, but in most cases, a thread is contained inside a process. Multiple threads can exist within the same process and share resources such as memory, while different processes do not share these resources. In particular, the threads of a process share the latter's instructions (its code) and its context (the values that its variables reference at any given moment).
Multithreading
Multithreading refers to two or more tasks executing concurrently within a single program. A thread is an independent path of execution within a program. Many threads can run concurrently within a program. Every thread in Java is created and controlled by the java.lang.Thread class. A Java program can have many threads, and these threads can run concurrently, either asynchronously or synchronously.
Multi-threading is a widespread programming and execution model that allows multiple threads to exist within the context of a single process. These threads share the process' resources, but are able to execute independently. The threaded programming model provides developers with a useful abstraction of concurrent execution. However, perhaps the most interesting application of the technology is when it is applied to a single process to enable parallel execution on a multiprocessing system.
This advantage of a multithreaded program allows it to operate faster on computer systems that have multiple CPUs, CPU s with multiple cores, or across a cluster of machines because the threads of the program naturally lend themselves to truly concurrent execution. In such a case, the programmer needs to be careful to avoid race conditions, and other non-intuitive behaviors. In order for data to be correctly manipulated, threads will often need to rendezvous in time in order to process the data in the correct order. Threads may also require mutually exclusive operations (often implemented using semaphores) in order to prevent common data from being simultaneously modified, or read while in the process of being modified. Careless use of such primitives can lead to deadlocks.
Another use of multitasking, applicable even for single-CPU systems, is the ability for an application to remain responsive to input. In a single-threaded program, if the main execution thread blocks on a long-running task, the entire application can appear to freeze. By moving such long-running tasks to a worker thread that runs concurrently with the main execution thread, it is possible for the application to remain responsive to user input while executing tasks in the background. On the other hand, in most cases multithreading is not the only way to keep a program responsive, with non-blocking I/O and/or Unix signals being available for gaining similar results.[1]
Operating systems schedule threads in one of two ways:
Preemptive multitasking is generally considered the superior approach, as it allows the operating system to determine when a context switch should occur. The disadvantage to preemptive multithreading is that the system may make a context switch at an inappropriate time, causing lock convoy, priority inversion or other negative effects which may be avoided by cooperative multithreading.
Cooperative multithreading, on the other hand, relies on the threads themselves to relinquish control once they are at a stopping point. This can create problems if a thread is waiting for a resource to become available.
Until late 1990s, CPU s in desktop computers did not have much support for multithreading, although threads were still used on such computers because switching between threads was generally still quicker than full process context switches. Processors in embedded systems, which have higher requirements for real-time behaviors, might support multithreading by decreasing the thread-switch time, perhaps by allocating a dedicated register file for each thread instead of saving/restoring a common register file. In the late 1990s, the idea of executing instructions from multiple threads simultaneously, known as simultaneous multithreading, had reached desktops with Intel's Pentium 4 processor, under the name hyper threading. It has been dropped from Intel Core and Core 2 architectures, but later was re-instated in Core i3 Core i5 and Core i7 architectures.
A Simple Thread Example
The simple example shown in full on the first page of this lesson defines two classes: SimpleThread and TwoThreadsTest. Let's begin our exploration of the application with the SimpleThread class--a subclass of the Thread class, which is provided by the java.lang package:
class SimpleThread extends Thread { public SimpleThread(String str) { super(str); } public void run() { for (int i = 0; i < 10; i++) { System.out.println(i + " " + getName()); try { sleep((int)(Math.random() * 1000)); } catch (InterruptedException e) {} } System.out.println("DONE! " + getName()); } }
The next method in the SimpleThread class is therun()
method. Therun()
method is the heart of any Thread and where the action of the Thread takes place. Therun()
method of the SimpleThread class contains afor
loop that iterates ten times. In each iteration the method displays the iteration number and the name of the Thread, then sleeps for a random interval of up to 1 second. After the loop has finished, therun()
method prints "DONE!" along with the name of the thread. That's it for the SimpleThread class. -
Collections
The collections framework was designed to meet several goals.
The framework had to be high-performance. The implementations for the fundamental collections (dynamic arrays, linked lists, trees, and hash tables) are highly efficient.
The framework had to allow different types of collections to work in a similar manner and with a high degree of interoperability.
Extending and/or adapting a collection had to be easy.
Toward this end, the entire collections framework is designed around a set of standard interfaces. Several standard implementations such as LinkedList, HashSet, and TreeSet, of these interfaces are provided that you may use as-is and you may also implement your own collection, if you choose.
A collections framework is a unified architecture for representing and manipulating collections. All collections frameworks contain the following:
Interfaces: These are abstract data types that represent collections. Interfaces allow collections to be manipulated independently of the details of their representation. In object-oriented languages, interfaces generally form a hierarchy.
Implementations i.e. Classes: These are the concrete implementations of the collection interfaces. In essence, they are reusable data structures.
Algorithms: These are the methods that perform useful computations, such as searching and sorting, on objects that implement collection interfaces. The algorithms are said to be polymorphic: that is, the same method can be used on many different implementations of the appropriate collection interface.
In addition to collections, the framework defines several map interfaces and classes. Maps store key/value pairs. Although maps are not collections in the proper use of the term, but they are fully integrated with collections.
Architecture
Almost all collections in Java are derived from the java.util.Collection interface. Collection defines the basic parts of all collections. The interface states the add() and remove() methods for adding to and removing from a collection respectively. Also required is the toArray() method, which converts the collection into a simple array of all the elements in the collection. Finally, the contains() method checks if a specified element is in the collection. The Collection interface is a subinterface of java.util.Iterable, so the iterator() method is also provided. All collections have an iterator that goes through all of the elements in the collection. Additionally, Collection is a generic. Any collection can be written to store any class. For example, Collectioncan hold strings, and the elements from the collection can be used as strings without any casting required.
There are three main types of collections:
- Lists: always ordered, may contain duplicates and can be handled the same way as usual arrays.
- Sets: cannot contain duplicates and provide random access to their elements.
- Maps: connect unique keys with values, provide random access to its keys and may host duplicate values.
List Interface
Lists are implemented in the JCF via the java.util.List interface. It defines a list as essentially a more flexible version of an array. Elements have a specific order, and duplicate elements are allowed. Elements can be placed in a specific position. They can also be searched for within the list. Two concrete classes implement List. The first is java.util.ArrayList, which implements the list as an array. Whenever functions specific to a list are required, the class moves the elements around within the array in order to do it. The other implementation is java.util.LinkedList. This class stores the elements in nodes that each have a pointer to the previous and next nodes in the list. The list can be traversed by following the pointers, and elements can be added or removed simply by changing the pointers around to place the node in its proper place.
Set Interface
Java's java.util.Set interface defines the set. A set can't have any duplicate elements in it. Additionally, the set has no set order. As such, elements can't be found by index. Set is implemented by java.util.HashSet, java.util.LinkedHashSet, and java.util.TreeSet. HashSet uses a hash table. More specifically, it uses a java.util.HashMap to store the hashes and elements and to prevent duplicates. java.util.LinkedHashSet extends this by creating a doubly linked list that links all of the elements by their insertion order. This ensures that the iteration order over the set is predictable. java.util.TreeSet uses a red-black tree implemented by a java.util.TreeMap. The red-black tree makes sure that there are no duplicates. Additionally, it allows TreeSet to implement java.util.SortedSet.[16] The java.util.Set interface is extended by the java.util.SortedSet interface. Unlike a regular set, the elements in a sorted set are sorted, either by the element's compareTo() method, or a method provided to the constructor of the sorted set. The first and last elements of the sorted set can be retrieved, and subsets can be created via minimum and maximum values, as well as beginning or ending at the beginning or ending of the sorted set. The SortedSet interface is implemented by java.util.TreeSet[17] java.util.SortedSet is extended further via the java.util.NavigableSet interface. It's similar to SortedSet, but there are a few additional methods. The floor(), ceiling(), lower(), and higher() methods find an element in the set that's close to the parameter. Additionally, a descending iterator over the items in the set is provided. As with SortedSet, java.util.TreeSet implements NavigableSet
Mapp Interface
- Maps are defined by the java.util.Map interface in Java. Maps are simple data structures that associate a key with a value. The element is the value. This lets the map be very flexible. If the key is the hash code of the element, the map is essentially a set. If it's just an increasing number, it becomes a list. Maps are implemented by java.util.HashMap, java.util.LinkedHashMap, and java.util.TreeMap. HashMap uses a hash table. The hashes of the keys are used to find the values in various buckets. LinkedHashMap extends this by creating a doubly linked list between the elements. This allows the elements to be accessed in the order in which they were inserted into the map. TreeMap, in contrast to HashMap and LinkedHashMap, uses a red-black tree. The keys are used as the values for the nodes in the tree, and the nodes point to the values in the map.
- The java.util.Map interface is extended by its subinterface, java.util.SortedMap. This interface defines a map that's sorted by the keys provided. Using, once again, the compareTo() method or a method provided in the constructor to the sorted map, the key-value pairs are sorted by the keys. The first and last keys in the map can be called. Additionally, submaps can be created from minimum and maximum keys. SortedMap is implemented by java.util.TreeMap.
- The java.util.NavigableMap interface extends java.util.SortedMap in various ways. Methods can be called that find the key or map entry that's closest to the given key in either direction. The map can also be reversed, and an iterator in reverse order can be generated from it. It's implemented by java.util.TreeMap.
-
Annotations
Annotation is code about the code, that is metadata about the program itself. In other words, organized data about the code, embedded within the code itself. It can be parsed by the compiler, annotation processing tools and can also be made available at run-time too.
We have basic java comments infrastructure using which we add information about the code / logic so that in future, another programmer or the same programmer can understand the code in a better way. Javadoc is an additional step over it, where we add information about the class, methods, variables in the source code. The way we need to add is organized using a syntax. Therefore, we can use a tool and parse those comments and prepare a javadoc document which can be distributed separately.
Built-In Annotations
- Java defines a set of annotations that are built into the language.
- Annotations applied to java code:
- @Override - Checks that the function is an override. Causes a compile warning if the function is not found in one of the parent classes.
- @Deprecated - Marks the function as obsolete. Causes a compile warning if the function is used.
- @SuppressWarnings - Instructs the compiler to suppress the compile time warnings specified in the annotation parameters.
Annotations applied to other annotations:
- @Retention - Specifies how the marked annotation is storedWhether in code only, compiled into the class, or available at runtime through reflection.
- @Documented - Marks another annotation for inclusion in the documentation.
- @Target - Marks another annotation to restrict what kind of java elements the annotation may be applied to
- @Inherited - Marks another annotation to be inherited to subclasses of annotated class (by default annotations are not inherited to subclasses).
-
Serialization
Java provides a mechanism, called object serialization where an object can be represented as a sequence of bytes that includes the object's data as well as information about the object's type and the types of data stored in the object.
After a serialized object has been written into a file, it can be read from the file and deserialized that is, the type information and bytes that represent the object and its data can be used to recreate the object in memory.
Most impressive is that the entire process is JVM independent, meaning an object can be serialized on one platform and deserialized on an entirely different platform.
Classes ObjectInputStream and ObjectOutputStream are high-level streams that contain the methods for serializing and deserializing an object.
The ObjectOutputStream class contains many write methods for writing various data types, but one method in particular stands out:
public final void writeObject(Object x) throws IOException
public final Object readObject() throws IOException, ClassNotFoundException
To demonstrate how serialization works in Java, I am going to use the Employee class that we discussed early on in the book. Suppose that we have the following Employee class, which implements the Serializable interface:
public class Employee implements java.io.Serializable { public String name; public String address; public int transient SSN; public int number; public void mailCheck() { System.out.println("Mailing a check to " + name + " " + address); } }
- The class must implement the java.io.Serializable interface.
- All of the fields in the class must be serializable. If a field is not serializable, it must be marked transient.
Serializing an Object:
The ObjectOutputStream class is used to serialize an Object. The following SerializeDemo program instantiates an Employee object and serializes it to a file.
When the program is done executing, a file named employee.ser is created. The program does not generate any output, but study the code and try to determine what the program is doing.
Note: When serializing an object to a file, the standard convention in Java is to give the file a .ser extension.
import java.io.*; public class SerializeDemo { public static void main(String [] args) { Employee e = new Employee(); e.name = "Reyan Ali"; e.address = "Phokka Kuan, Ambehta Peer"; e.SSN = 11122333; e.number = 101; try { FileOutputStream fileOut = new FileOutputStream("employee.ser"); ObjectOutputStream out = new ObjectOutputStream(fileOut); out.writeObject(e); out.close(); fileOut.close(); }catch(IOException i) { i.printStackTrace(); } } }Deserializing an Object:
import java.io.*; public class DeserializeDemo { public static void main(String [] args) { Employee e = null; try { FileInputStream fileIn = new FileInputStream("employee.ser"); ObjectInputStream in = new ObjectInputStream(fileIn); e = (Employee) in.readObject(); in.close(); fileIn.close(); }catch(IOException i) { i.printStackTrace(); return; }catch(ClassNotFoundException c) { System.out.println(.Employee class not found.); c.printStackTrace(); return; } System.out.println("Deserialized Employee..."); System.out.println("Name: " + e.name); System.out.println("Address: " + e.address); System.out.println("SSN: " + e.SSN); System.out.println("Number: " + e.number); } }
Deserialized Employee... Name: Reyan Ali Address:Phokka Kuan, Ambehta Peer SSN: 0 Number:101
- The try/catch block tries to catch a ClassNotFoundException, which is declared by the readObject() method. For a JVM to be able to deserialize an object, it must be able to find the bytecode for the class. If the JVM can't find a class during the deserialization of an object, it throws a ClassNotFoundException.
- Notice that the return value of readObject() is cast to an Employee reference.
- The value of the SSN field was 11122333 when the object was serialized, but because the field is transient, this value was not sent to the output stream. The SSN field of the deserialized Employee object is 0.
-
Introduction to Row Sets
The interface that adds support to the JDBC API for the JavaBeansTM component model. A rowset, which can be used as a JavaBeans component in a visual Bean development environment, can be created and configured at design time and executed at run time.
The RowSet interface provides a set of JavaBeans properties that allow a RowSet instance to be configured to connect to a JDBC data source and read some data from the data source. A group of setter methods (setInt, setBytes, setString, and so on) provide a way to pass input parameters to a rowset's command property. This command is the SQL query the rowset uses when it gets its data from a relational database, which is generally the case.
The RowSet interface supports JavaBeans events, allowing other components in an application to be notified when an event occurs on a rowset, such as a change in its value.
The RowSet interface is unique in that it is intended to be implemented using the rest of the JDBC API. In other words, a RowSet implementation is a layer of software that executes "on top" of a JDBC driver. Implementations of the RowSet interface can be provided by anyone, including JDBC driver vendors who want to provide a RowSet implementation as part of their JDBC products.
A RowSet object may make a connection with a data source and maintain that connection throughout its life cycle, in which case it is called a connected rowset. A rowset may also make a connection with a data source, get data from it, and then close the connection. Such a rowset is called a disconnected rowset. A disconnected rowset may make changes to its data while it is disconnected and then send the changes back to the original source of the data, but it must reestablish a connection to do so.
A disconnected rowset may have a reader (a RowSetReader object) and a writer (a RowSetWriter object) associated with it. The reader may be implemented in many different ways to populate a rowset with data, including getting data from a non-relational data source. The writer can also be implemented in many different ways to propagate changes made to the rowset's data back to the underlying data source.
Rowsets are easy to use. The RowSet interface extends the standard java.sql.ResultSet interface. The RowSetMetaData interface extends the java.sql.ResultSetMetaData interface. Thus, developers familiar with the JDBC API will have to learn a minimal number of new APIs to use rowsets. In addition, third-party software tools that work with JDBC ResultSet objects will also easily be made to work with rowsets. -
Design Patterns
The terminology of "Design Pattern" in software developed is based on the GOF (Gang of Four) book "Design Patterns - Elements of Reusable Object-Oriented Software" from Erich Gamma, Richard Helm, Ralph Johnson und John Vlissides. Design Pattern are proven solutions approaches to specific problems. A design pattern is not framework and is not directly deployed via code.
Design Pattern have two main usages:
Common language for developers: They provide developer a common language for certain problems. For example if a developer tells another developer that he is using a Singleton, the another developer (should) know exactly what this means.
Capture best practices: Design patterns capture solutions which have been applied to certain problems. By learning these patterns and the problem they are trying to solve a unexperienced developer can learn a lot about software design.
Design pattern are based on the base principles of object orientated design.
- Program to an interface not an implementation
- Favor object composition over inheritance
- Design Patterns can be divided into:
- Creational Patterns
- Structural Patterns
- Behavioral Patterns
0 comments: