Object-oriented programming (OOP) is at the core of Java.
Encapsulation binds together code and the data it manipulates, and keeps both safe from outside interference and misuse. In Java, the basis of encapsulation is the class.
Inheritance is the process by which one object acquires the properties of another object. By use of inheritance, an object need only define those qualities that make it unique within its class. It can inherit its general attributes from its parent.
Polymorphism is a feature that allows one interface to be used for a general set of actions.
It refers to defining a skeleton of the class that should be implemented by its subclass. This can be achieved using abstract keyword in Java.
In
Java, all code must reside inside a class. Java program is basically a
text file with one or more class definitions. This text or source file
is called a compilation unit.
The Java compiler requires that a source file use the .java filename extension.
By convention, the name of the class should match by case the name of the file that holds the program.
All statements in Java end with a semicolon.
Java is a Strongly Typed Language
Every variable and every expression has a type, and every type is strictly defined.
All
assignments, whether explicit or via parameter passing in method calls,
are checked for type compatibility. The Java compiler checks all
expressions and parameters to ensure that the types are compatible. Any
type mismatches are errors that must be corrected before class
compilation.
Click for NEXT article
Please feel free to correct me by commenting your suggestions and feedback.
Applets
are small applications that are accessed on an Internet server,
transported over the Internet, automatically installed, and run as part
of a web document. After an applet arrives on the client, it has limited
access to resources so that it can produce a graphical user interface
and run complex computations without introducing the risk of viruses or
breaching data integrity.
Let’s begin with the simple applet shown here:
import java.awt.*;
import java.applet.*;
public class SimpleApplet extends Applet
{
public void paint(Graphics g)
{
g.drawString("A Simple Applet", 20, 20);
}
}
This
applet begins with two import statements.
The first imports the
Abstract Window Toolkit (AWT) classes. Applets interact with the user
(either directly or indirectly) through the AWT, not through the
console-based I/O classes. The AWT contains support for a window-based,
graphical user interface.
The
second imports the applet package, which contains the
class Applet. Every applet that you create must be a subclass of Applet.
The
class SimpleApplet, must be declared as public, because it will be
accessed by code that is outside the program.
paint( ) method is defined by the AWT and must be
overridden by the applet. paint( ) is called each time that the applet
must redisplay its output. paint( ) is also called when the
applet begins execution. The paint( ) method has one
parameter of type Graphics. This parameter contains the graphics
context, which describes the graphics environment in which the applet is
running. This context is used whenever output to the applet is
required.
Inside
paint( ) is a call to drawString( ), which is a member of the Graphics
class. This method outputs a string beginning at the specified X,Y
location. It has the following general form:
void drawString(String message, int x, int y)
Here,
message is the string to be output beginning at x,y. In a Java window,
the upper-left corner is location 0,0. The call to drawString( ) in the
applet causes the message “A Simple Applet” to be displayed beginning at
location 20,20.
Unlike Java programs,
applets do not begin execution at main( ). In fact, most applets don’t
even have a main( ) method. Instead, an applet begins execution when the
name of its class is passed to an applet viewer or to a network
browser.
There are two ways in which we can run an applet:
Executing the applet within a Java-compatible web browser.
Using an applet viewer,
appletviewer. An applet viewer executes applet in a window. This is
generally the fastest and easiest way to test an applet.
To
execute an applet in a web browser, we need to write a short HTML text
file that contains a tag APPLET. Here is the HTML file
that executes SimpleApplet:
<applet code="SimpleApplet" width=200 height=60> </applet>
The
width and height statements specify the dimensions of the display area
used by the applet. After we create this file, we can execute our
browser and then load this file, which causes SimpleApplet to be
executed.
To
execute SimpleApplet with an applet viewer, we may also execute the
HTML file shown earlier. For example, if the preceding HTML file is
called RunApp.html, then the following command line will run
SimpleApplet:
C:\>appletviewer RunApp.html
A more convenient method is to include a comment at the head of our Java source code file that
contains the APPLET tag. By doing so, our code is documented with a
prototype of the necessary HTML statements, and we can test our
compiled applet merely by starting the applet viewer with our Java
source code file. If we use this method, the SimpleApplet source file
looks like this:
import java.awt.*;
import java.applet.*;
/*<applet code="SimpleApplet" width=200 height=60> </applet>*/
public class SimpleApplet extends Applet
{
public void paint(Graphics g)
{
g.drawString("A Simple Applet", 20, 20);
}
}
Compile the program and execute the applet viewer, specifying the name of applet’s source
file. The applet viewer will encounter the APPLET tag within the comment
and execute the applet.
Click for NEXT article.
Please feel free to correct me by commenting your suggestions and feedback.
When an instance variable is declared as transient, then its value need not persist when an object is stored. For example:
class T
{
transient int a; // will not persist
int b; // will persist
}
Here,
if an object of type T is written to a persistent storage area, the
contents of "a" would not be saved, but the contents of "b" would.
Variable
modified by volatile can be changed unexpectedly by other parts of the program. Specifying the variable as volatile tells the compiler that
it must always use the master copy of a volatile variable (or, at least,
always keep any private copies up-to-date with the master copy, and
vice versa). Used mostly in multithreaded programming.
Sometimes,
knowing the type of an object during run time is useful. For example, we might have one thread of execution that generates various types of
objects, and another thread that processes these objects. In this
situation, it might be useful for the processing thread to know the type
of each object when it receives it. The instanceof operator has this general form:
objref instanceof type
Here,
objref is a reference to an instance of a class, and type is a class
type. If objref is of the specified type or can be cast into the
specified type, then the instanceof operator evaluates to true.
Otherwise, its result is false. The
following program demonstrates instanceof:
// Demonstrate instanceof operator.
class A
{
int i, j;
}
class B
{
int i, j;
}
class C extends A
{
int k;
}
class InstanceOf
{
public static void main(String args[])
{
A a = new A();
B b = new B();
C c = new C();
if(a instanceof A)
System.out.println("a is instance of A");
if(b instanceof B)
System.out.println("b is instance of B");
if(c instanceof C)
System.out.println("c is instance of C");
if(c instanceof A)
System.out.println("c can be cast to A");
System.out.println();
// compare types of derived types
A ob = c; // A reference to c
System.out.println("ob now refers to c");
if(ob instanceof A)
System.out.println("ob can be cast to A");
System.out.println();
// all objects can be cast to Object
if(a instanceof Object)
System.out.println("a may be cast to Object");
if(b instanceof Object)
System.out.println("b may be cast to Object");
if(c instanceof Object)
System.out.println("c may be cast to Object");
}
}
By modifying a class or a method with strictfp, we ensure that floating-point calculations (and thus all truncations)
take place precisely as they did in earlier versions of Java. When
a class is modified by strictfp, all the methods in the class are also
modified by strictfp automatically. For example, the following fragment
tells Java to use the original floating-point model for calculations in all methods defined within MyClass:
strictfp class MyClass { //...
Frankly, most programmers never need to use strictfp, because it affects only a very small class of problems.
Keyword assert is used
during program development to create an assertion, which is a condition
that should be true during the execution of the program. For example,we might have a method that should always return a positive integer
value. We might test this by asserting that the return value is greater
than zero using an assert statement. At run time, if the condition
actually is true, no other action takes place. However, if the condition
is false, then an AssertionError is thrown. Assertions are often used
during testing to verify that some expected condition is actually met.
They are not usually used for released code.
The assert keyword has two forms. The first is shown here:
assert condition;
Here,
condition is an expression that must evaluate to a Boolean result. If
the result is true, then the assertion is true and no other action takes
place. If the condition is false, then the assertion fails and a
default AssertionError object is thrown.
The second form of assert is
shown here:
assert condition : expr;
In
this version, expr is a value that is passed to the AssertionError
constructor. This value is converted to its string format and displayed
if an assertion fails. Typically, we will specify a string for expr,
but any non-void expression is allowed as long as it defines a
reasonable string conversion.
Here is an example that uses assert. It
verifies that the return value of getnum( ) is positive.
// Demonstrate assert.
class AssertDemo
{
static int val = 3;
// Return an integer.
static int getnum()
{
return val--;
}
public static void main(String args[])
{
int n;
for(int i=0; i < 10; i++)
{
n = getnum();
assert n > 0; // will fail when n is 0
System.out.println("n is " + n);
}
}
}
To
enable assertion checking at run time, we must specify the -ea option.
For example, to enable assertions for AssertDemo, execute it using this
line:
java -ea AssertDemo
As
explained, we can specify the message displayed when an assertion
fails. For example, we can specify:
assert n > 0 : "n is
negative!";
One
important point to understand about assertions is that we must not
rely on them to perform any action actually required by the program. The
reason is that normally, released code will be run with assertions
disabled. For ex:
assert (n = getnum()) > 0; // This is not a good idea!
This works fine if assertions are enabled, it
will cause a malfunction when assertions are disabled, because the call
to getnum( ) will never be executed! In fact, n must now be
initialized, because the compiler will recognize that it might not be
assigned a value by the assert statement.
Assertions
are a good addition to Java because they streamline the type of error
checking that is common during development. For example, prior to
assert, if we wanted to verify that n was positive in the preceding
program, we had to use a sequence of code similar to this:
if(n < 0){System.out.println("n is negative!"); return; // or throw an exception}
With
assert, you need only one line of code. Furthermore, we don’t have to
remove the assert statements from our released code.
When
executing code, we can disable assertions by using the -da option. We can enable or disable a specific package by specifying its name after
the -ea or -da option. For example, to enable assertions in a package
called MyPack:
-ea:MyPack
To disable assertions in MyPack:
-da:MyPack
To enable or disable all subpackages of a package, follow the package name with three dots:
-ea:MyPack...
We can also specify a class with the -ea or -da option. For example, this enables AssertDemo individually:
-ea:AssertDemo
A new feature to Java called static import that expands the
capabilities of the import keyword. By following import with the keyword
static, an import statement can be used to import the static members of
a class or interface. When using static import, it is possible to
refer to static members directly by their names, without having to
qualify them with the name of their class.
To
understand the usefulness of static import, let’s begin with an example
that does not use it. The following code fragment computes the hypotenuse of a
right triangle. It uses two static methods from Java’s built-in math
class Math, which is part of java.lang. The first is Math.pow( ), which
returns a value raised to a specified power. The second is Math.sqrt( ),
which returns the square root of its argument.
// Notice how sqrt() and pow() must be qualified by their class name, which is Math.
hypot = Math.sqrt(Math.pow(side1, 2) + Math.pow(side2, 2));
Because
pow( ) and sqrt( ) are static methods, they must be called through the
use of their class’ name, Math. As
this simple example illustrates, having to specify the class name each
time pow( ) or sqrt( ) is used can grow tedious. We can use static
import in such cases:
import static java.lang.Math.sqrt;
import static java.lang.Math.pow;
class Hypot {//...
hypot = sqrt(pow(side1, 2) + pow(side2, 2));
There are two general forms of the import static statement:
The
first, brings into view a
single name. Its general form is shown here:
import static
pkg.type-name.static-member-name;
Here,
type-name is the name of a class or interface that contains the desired
static member. Its full package name is specified by pkg. The name of
the member is specified by static-member-name.
The
second form, imports all static members of a given class or interface.
Its general form is shown here:
import static pkg.type-name.*;
One
other point: in addition to importing the static members of classes and
interfaces defined by the Java API, we can also use static import to
import the static members of classes and interfaces that we create. If using a static member once or twice
in the program, it’s best not to import it. Static import is designed for those situations in which we are using a static member repeatedly, such as when performing a series
of mathematical computations.
When
working with overloaded constructors, it is sometimes useful for one
constructor to invoke another. In Java, this is accomplished by using
another form of the this keyword. The general form is shown here:
this(arg-list)
When
this( ) is executed, the overloaded constructor that matches the
parameter list specified by arg-list is executed first. The call to this( ) must be the first statement within the constructor. To
understand how this( ) can be used, let’s work through a short example.
First, consider the following class that does not use this( ):
class MyClass
{
int a, b;
// initialize a and b individually
MyClass(int i, int j) { a = i; b = j; }
// initialize a and b to the same value
MyClass(int i) { a = b = i; }
// give a and b default values of 0
MyClass( ) { a = b = 0; }
}
By using this( ), it is
possible to rewrite MyClass as shown here:
// initialize a and b individually
MyClass(int i, int j) { a = i; b = j; }
// initialize a and b to the same value
MyClass(int i)
{
this(i, i); // invokes MyClass(i, i)
}
// give a and b default values of 0
MyClass( )
{
this(0); // invokes MyClass(0)
}
In
this version of MyClass, the only constructor that actually assigns
values to the a and b fields is MyClass(int, int). The other two
constructors simply invoke that constructor (either directly or
indirectly) through this( ). For example, consider what happens when
this statement executes:
MyClass mc = new MyClass(8);
The
call to MyClass(8) causes this(8, 8) to be executed, which translates
into a call to MyClass(8, 8), because this is the version of the MyClass
constructor whose parameter list matches the arguments passed via this(
). Now, consider the following statement, which uses the default
constructor:
MyClass mc2 = new MyClass();
In
this case, this(0) is called. This causes MyClass(0) to be invoked
because it is the constructor with the matching parameter list. Of
course, MyClass(0) then calls MyClass(0, 0) as just described.
One
reason why invoking overloaded constructors through this( ) can be
useful is that it can prevent the unnecessary duplication of code.
Constructors that call this( ) will execute a bit slower
than those that contain all of their initialization code inline. This
is because the call and return mechanism used when the second
constructor is invoked adds overhead.
There are two restrictions when using this( ).
First, we cannot use any instance variable of the constructor’s class in a call to this( ).
Second, we cannot use super( ) and this( ) in the same
constructor because each must be the first statement in the constructor.
Click for NEXT article.
Please feel free to correct me by commenting your suggestions and feedback.
The java.io package supports Java’s basic I/O (input/output) system, including file I/O.
Java programs perform I/O through streams.
A stream is an abstraction that either produces or consumes information. A stream is linked to a physical device by the Java I/O system. All streams behave in the same manner, even if the actual physical devices to which they are linked differ. Thus, the same I/O classes and methods can be applied to any type of device.
Java defines two types of streams: byte and character.
Byte streams are used for handling input and output of bytes. Byte streams are used, for example, when reading or writing binary data.
Character streams are used for handling input and output of characters. They use Unicode and, therefore, can be internationalized.
At the lowest level, all I/O is still byte-oriented. The character-based streams simply provide a convenient and efficient means for handling characters.
Byte streams are defined by using two class hierarchies. At the top are two abstract classes: InputStream and OutputStream. Each of these abstract classes has several concrete subclasses that handle the differences between various devices, such as disk files, network connections, and even memory buffers. Two important methods read( ) and write( ), which, respectively, read and write bytes of data.
Character streams are defined by using two class hierarchies. At the top are two abstract classes, Reader and Writer. These abstract classes handle Unicode character streams. Two important methods are read( ) and write( ), which read and write characters of data.
Java programs automatically import the java.lang package. This package defines a class called System, which encapsulates several aspects of the run-time environment. For example, using some of its methods, we can obtain the current time and the settings of various properties associated with the system. System also contains three predefined stream variables: in, out, and err. These fields are declared as public, static, and final within System.
System.out refers to the standard output stream. By default, this is the console. System.in refers to standard input, which is the keyboard by default. System.err refers to the standard error stream, which also is the console by default. However, these streams may be redirected to any compatible I/O device.
System.in is an object of type InputStream; System.out and System.err are objects of type PrintStream. These are byte streams, even though they typically are used to read and write characters from and to the console.
The preferred method of reading console input is to use a character-oriented stream, which makes your program easier to internationalize and maintain.
To obtain a character- based stream that is attached to the console, wrap System.in in a BufferedReader object. BufferedReader supports a buffered input stream. Its most commonly used constructor is shown here:
BufferedReader(Reader inputReader)
Here, inputReader is the stream that is linked to the instance of BufferedReader that is being created. Reader is an abstract class. One of its concrete subclasses is InputStreamReader, which converts bytes to characters. To obtain an InputStreamReader object that is linked to System.in, use the following constructor:
InputStreamReader(InputStream inputStream)
Because System.in refers to an object of type InputStream, it can be used for inputStream. Putting it all together, the following line of code creates a BufferedReader that is connected to the keyboard:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
After this statement executes, br is a character-based stream that is linked to the console through System.in.
To read a character from a BufferedReader, use read( ).
int read( ) throws IOException
Each time that read( ) is called, it reads a character from the input stream and returns it as an integer value. It returns -1 when the end of the stream is encountered. It can throw an IOException.
The following program demonstrates read( ) by reading characters from the console until the user types a "q.” Notice that any I/O exceptions that might be generated are simply thrown out of main( ). Such an approach is common when reading from the console, but we can handle these types of errors.
// Use a BufferedReader to read characters from the console.
import java.io.*;
class BRRead
{
public static void main(String args[]) throws IOException
{
char c;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Enter characters, 'q' to quit.");
// read characters
do
{
c = (char) br.read();
System.out.println(c);
} while(c != 'q');
}
}
System.in is line buffered, by default. This means that no input is actually passed to the program until we press ENTER. This does not make read( ) particularly valuable for interactive console input.
To read a string from the keyboard, use readLine( ) that is a member of the BufferedReader class. Its general form is shown here:
String readLine( ) throws IOException
Sample code fragment:
//Create a BufferedReader using System.in
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str;
System.out.println("Enter lines of text.");
System.out.println("Enter 'stop' to quit.");
do
{
str = br.readLine();
System.out.println(str);
} while(!str.equals("stop"));
Console output is most easily accomplished with print( ) and println( ) . These methods are defined by the class PrintStream (which is the type of object referenced by System.out). Because PrintStream is an output stream derived from OutputStream, it also implements the low-level method write( ). Thus, write( ) can be used to write to the console. The simplest form of write( ) defined by PrintStream is shown here:
void write(int byteval)
This method writes to the stream the byte specified by byteval. Although byteval is declared as an integer, only the low-order eight bits are written.
// Demonstrate System.out.write().
int b = 'A';
System.out.write(b + '\n');
We will not often use write( ) to perform console output because print( ) and println( ) are substantially easier to use.
Although using System.out to write to the console is acceptable, its use is recommended mostly for debugging purposes. For real-world programs, the recommended method of writing to the console when using Java is through a PrintWriter stream. PrintWriter is one of the character-based class for console output and makes it easier to internationalize our program. PrintWriter defines several constructors. The one we will use is shown here:
PrintWriter(OutputStream outputStream, boolean flushOnNewline)
Here, outputStream is an object of type OutputStream, and flushOnNewline controls whether Java flushes the output stream every time a println( ) method is called. If flushOnNewline is true, flushing automatically takes place. If false, flushing is not automatic.
PrintWriter supports the print( ) and println( ) methods for all types including Object. If an argument is not a simple type, the PrintWriter methods call the object’s toString( ) method and then print the result.
To write to the console by using a PrintWriter, specify System.out for the output stream and flush the stream after each newline. For example, this line of code creates a PrintWriter that is connected to console output:
PrintWriter pw = new PrintWriter(System.out, true);
// Demonstrate PrintWriter
PrintWriter pw = new PrintWriter(System.out, true);
int i = -7;
double d = 4.5e-7;
pw.println("This is a string");
pw.println(i);
pw.println(d);
In Java, all files are byte-oriented, and Java provides methods to read and write bytes from and to a file. However, Java allows us to wrap a byte-oriented file stream within a character-based object.
Two of the most often-used stream classes are FileInputStream and FileOutputStream, which create byte streams linked to files. To open a file, we simply create an object of one of these classes, specifying the name of the file as an argument to the constructor. While both classes support additional, overridden constructors, the following are the forms that we will be using:
FileInputStream(String fileName) throws FileNotFoundException
FileOutputStream(String fileName) throws FileNotFoundException
Here, fileName specifies the name of the file that we want to open. When we create an input stream, if the file does not exist and for output streams, if the file cannot be created, then FileNotFoundException is thrown. When an output file is opened, any preexisting file by the same name is destroyed.
When we are done with a file, we should close it by calling close( ). It is defined by both FileInputStream and FileOutputStream, as shown here:
void close( ) throws IOException
To read from a file, we can use a version of read( ) that is defined within FileInputStream. The one that we will use is shown here:
int read( ) throws IOException
Each time that it is called, it reads a single byte from the file and returns the byte as an integer value. read( ) returns -1 when the end of the file is encountered. It can throw an IOException.
/* Display a text file. To see a file called TEST.TXT, use the following commandline. java ShowFile TEST.TXT */
import java.io.*;
//Inside main()
int i;
FileInputStream fin;
try
{
fin = new FileInputStream(args[0]);
}
catch(FileNotFoundException e)
{
System.out.println("File Not Found");
return;
}
catch(ArrayIndexOutOfBoundsException e)
{
System.out.println("Usage: ShowFile File");
return;
}
// read characters until EOF(-1) is encountered
do
{
i = fin.read();
if(i != -1)
System.out.print((char) i);
} while(i != -1);
fin.close();
To write to a file, we can use the write( ) method defined by FileOutputStream. Its simplest form is shown here:
void write(int byteval) throws IOException
This method writes the byte specified by byteval to the file. Although byteval is declared as an integer, only the low-order eight bits are written to the file. If an error occurs during writing, an IOException is thrown.
Click for NEXT article.
Please feel free to correct me by commenting your suggestions and feedback.
We can embed
supplemental information into a source file. This information, called an
annotation( or metadata), does not change the actions of a program. However, this
information can be used by various tools during both development and
deployment. For example, an annotation might be processed by a
source-code generator.
An
annotation is created through a mechanism based on the interface. Let’s
begin with an example. Here is the declaration for an annotation called
MyAnno:
// A simple annotation type.
@interface MyAnno
{
String str();
int val();
}
First,
notice the @ that precedes the keyword interface. This tells the
compiler that an annotation type is being declared. Next, notice the two
members str( ) and val( ). All annotations consist solely of method
declarations without body. Java implements these methods. Moreover, the
methods act much like fields.
An
annotation cannot include an extends clause. However, all annotation
types automatically extend the Annotation interface. Thus, Annotation is
a super-interface of all annotations. It is declared within the
java.lang.annotation package. It overrides hashCode( ), equals( ), and
toString( ), which are defined by Object. It also specifies
annotationType( ), which returns a Class object that represents the
invoking annotation.
Any type of declaration can have an annotation associated
with it. For example, classes, methods, fields, parameters, and enum
constants can be annotated. Even an annotation can be annotated. In all
cases, the annotation precedes the declaration.
When
we apply an annotation, we give values to its members. For example,
here is an example of MyAnno being applied to a method:
// Annotate a method.
@MyAnno(str = "Annotation Example", val = 100)
public static void myMeth() { // ...
Look closely at the
annotation syntax. The name of the annotation, preceded by an @, is
followed by a parenthesized list of member initializations. To give a
member a value, that member’s name is assigned a value. Therefore, in
the example, the string “Annotation Example” is assigned to the str
member of MyAnno. Notice that no parentheses follow str in this
assignment. When an annotation member is given a value, only its name is
used. Thus, annotation members look like fields in this context.
A
retention policy determines at what point an annotation is discarded.
Java defines three such policies, which are encapsulated within the
java.lang.annotation.RetentionPolicy enumeration. They are SOURCE,
CLASS, and RUNTIME.
An
annotation with a retention policy of SOURCE is retained only in the
source file and is discarded during compilation.
An
annotation with a retention policy of CLASS is stored in the .class file during compilation.
However, it is not available through the JVM during run time.
An
annotation with a retention policy of RUNTIME is stored in the .class
file during compilation and is available through the JVM during run
time. Thus, RUNTIME retention offers the greatest annotation
persistence.
A
retention policy is specified for an annotation by using one of Java’s
built-in annotations: @Retention. Its general form is shown here:
@Retention(retention-policy)
Here,
retention-policy must be one of the enumeration
constants. If no retention policy is specified for an annotation, then
the default policy of CLASS is used.
The following version of MyAnno
uses @Retention to specify the RUNTIME retention policy. Thus, MyAnno
will be available to the JVM during program execution.
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno
{
String str();
int val();
}
Although
annotations are designed mostly for use by other development or
deployment tools, if they specify a retention policy of RUNTIME, then
they can be queried at run time by any Java program through the use of
reflection. Reflection is the feature that enables information about a
class to be obtained at run time. The reflection API is contained in the
java.lang.reflect package.
The first step to using reflection is to obtain a Class object that
represents the class whose annotations we want to obtain. (Class is one
of Java’s built-in classes and is defined in java.lang. There are
various ways to obtain a Class object. One of the easiest is to call
getClass( ), which is a method defined by Object. Its general form is
shown here:
final Class getClass( ) //It returns the Class object that
represents the invoking object.
After we have obtained a Class object, we can use its methods to
obtain information about the various items declared by the class,
including its annotations. If we want to obtain the annotations
associated with a specific item declared within a class, we must first
obtain an object that represents that item. For example, Class supplies
(among others) the getMethod( ), getField( ), and getConstructor( )
methods, which obtain information about a method, field, and
constructor, respectively. These methods return objects of type Method,
Field, and Constructor.
An example that obtains the
annotations associated with a method. First obtain a
Class object that represents the class, and then call getMethod( ) on
that Class object, specifying the name of the method. getMethod( ) has
this general form:
Method getMethod(String methName, Class ...
paramTypes)
The
name of the method is passed in methName. If the method has arguments,
then Class objects representing those types must also be specified by
paramTypes. Notice that paramTypes is a varargs parameter. getMethod( ) returns a Method object that represents the method. If the
method can’t be found, NoSuchMethodException is thrown.
From
a Class, Method, Field, or Constructor object, we can obtain a
specific annotation associated with that object by calling
getAnnotation( ). Its general form is shown here:
Annotation getAnnotation(Class annoType)
Here,
annoType is a Class object that represents the annotation in which we are interested. The method returns a reference to the annotation. Using
this reference, we can obtain the values associated with the
annotation’s members. The method returns null if the annotation is not
found, which will be the case if the annotation does not have RUNTIME
retention.
Here
is a program that assembles all of the pieces shown earlier and uses
reflection to display the annotation associated with a method.
import java.lang.annotation.*;
import java.lang.reflect.*;
// An annotation type declaration.
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno {
String str();
int val();
}
class Meta {
// Annotate a method.
@MyAnno(str = "Annotation Example", val = 100)
public static void myMeth() {
Meta ob = new Meta();
// Obtain the annotation for this method and display the values of the members.
try {
// First, get a Class object that represents this class.
Class c = ob.getClass();
// Now, get a Method object that represents this method.
Method m = c.getMethod("myMeth");
// Next, get the annotation for this class.
MyAnno anno = m.getAnnotation(MyAnno.class);
// Finally, display the values.
System.out.println(anno.str() + " " + anno.val());
} catch (NoSuchMethodException exc) {
System.out.println("Method Not Found.");
}
}
public static void main(String args[]) {
myMeth();
}
}
The output from the program is shown here: Annotation Example 100
In the program, the expression MyAnno.class evaluates to a Class
object of type MyAnno, the annotation. This construct is called a class
literal. We can use this type of expression whenever a Class object of a
known class is needed. For example, this statement could have been used
to obtain the Class object for Meta:
Class c = Meta.class;
Of
course, this approach only works when we know the class name of an
object in advance, which might not always be the case. In general, we can obtain a class literal for classes, interfaces, primitive types, and
arrays.
In
the preceding example, myMeth( ) has no parameters. Thus, when
getMethod( ) was called, only the name myMeth was passed. However, to
obtain a method that has parameters, we must specify class objects
representing the types of those parameters as arguments to getMethod( ).
For example, here is a slightly different version of the preceding
program:
// myMeth now has two arguments.
@MyAnno(str = "Two Parameters", val = 19)
public static void myMeth(String str, int i)
//...
// Here, the parameter types are specified.
Method m = c.getMethod("myMeth", String.class, int.class); //The Class objects representing String and int are passed as additional arguments.
//...
public static void main(String args[]) {
myMeth("test", 10);
}
We can obtain all annotations that have RUNTIME retention that are
associated with an item by calling getAnnotations( ) on that item. It
has this general form:
Annotation[ ] getAnnotations( )
It
returns an array of the annotations. getAnnotations( ) can be called on
objects of type Class, Method, Constructor, and Field.
Here
is another reflection example that shows how to obtain all annotations
associated with a class and with a method. It declares two annotations.
It then uses those annotations to annotate a class and a method.
// Show all annotations for a class and a method.
import java.lang.annotation.*;
import java.lang.reflect.*;
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno {
String str();
int val();
}
@Retention(RetentionPolicy.RUNTIME)
@interface What {
String description();
}
@What(description = "An annotation test class")
@MyAnno(str = "Meta2", val = 99)
class Meta2 {
@What(description = "An annotation test method")
@MyAnno(str = "Testing", val = 100)
public static void myMeth() {
Meta2 ob = new Meta2();
try {
Annotation annos[] = ob.getClass().getAnnotations();
// Display all annotations for Meta2.
System.out.println("All annotations for Meta2:");
for(Annotation a : annos)
System.out.println(a);
System.out.println();
// Display all annotations for myMeth.
Method m = ob.getClass( ).getMethod("myMeth");
annos = m.getAnnotations();
System.out.println("All annotations for myMeth:");
for(Annotation a : annos)
System.out.println(a);
} catch (NoSuchMethodException exc) {
System.out.println("Method Not Found.");
}
}
public static void main(String args[]) {
myMeth();
}
}
The output is shown here:
All annotations for Meta2:
@What(description=An annotation test class)
@MyAnno(str=Meta2, val=99)
All annotations for myMeth:
@What(description=An annotation test method)
@MyAnno(str=Testing, val=100)
The
methods getAnnotation( ) and getAnnotations( ) are defined by the AnnotatedElement interface, which is defined
in java.lang.reflect.
In
addition, AnnotatedElement
defines two other methods. The first is getDeclaredAnnotations( ), which
has this general form:
Annotation[ ] getDeclaredAnnotations( )
It returns all non-inherited annotations present in the invoking object.
The second is isAnnotationPresent( ), which has this general form:
boolean isAnnotationPresent(Class annoType)
It returns true if the annotation specified by annoType is associated with the invoking object. It returns false otherwise.
We can give annotation members default values that will be used if no
value is specified when the annotation is applied. A default value is
specified by adding a default clause to a member’s declaration. It has
this general form:
type member( ) default value;// value must be of a type compatible with type.
Here is @MyAnno rewritten to include default values:
// An annotation type declaration that includes defaults.
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno {
String str() default "Testing";
int val() default 9000;
}
This
declaration gives a default value of “Testing” to str and 9000 to val.
This means that neither value needs to be specified when @MyAnno is
used. However, either or both can be given values if desired. Therefore,
following are the four ways that @MyAnno can be used:
@MyAnno() // both str and val default
@MyAnno(str = "some string") // val defaults
@MyAnno(val = 100) // str defaults
@MyAnno(str = "Testing", val = 100) // no defaults
A
marker annotation is a special kind of annotation that contains no
members. Its sole purpose is to mark a declaration. The best way to determine if a marker
annotation is present is to use the method isAnnotationPresent( ), which
is a defined by the AnnotatedElement interface. Here
is an example that uses a marker annotation. Because a marker interface
contains no members, simply determining whether it is present or absent
is sufficient.
import java.lang.annotation.*;
import java.lang.reflect.*;
// A marker annotation.
@Retention(RetentionPolicy.RUNTIME)
@interface MyMarker { }
class Marker {
// Annotate a method using a marker. Notice that no ( ) is needed.
@MyMarker
public static void myMeth() {
Marker ob = new Marker();
try {
Method m = ob.getClass().getMethod("myMeth");
// Determine if the annotation is present.
if(m.isAnnotationPresent(MyMarker.class))
System.out.println("MyMarker is present.");
} catch (NoSuchMethodException exc) {
System.out.println("Method Not Found.");
}
}
public static void main(String args[]) {
myMeth();
}
}
It is not wrong to supply an empty set of parentheses when linking marker with the method, but they are not needed.
A
single-member annotation contains only one member. It works like a
normal annotation except that it allows a shorthand form of specifying
the value of the member. When only one member is present, we can simply
specify the value for that member when the annotation is applied—we don’t need to specify the name of the member. However, in order to use
this shorthand, the name of the member must be value. Here is an example
that creates and uses a single-member annotation:
// A single-member annotation.
@Retention(RetentionPolicy.RUNTIME)
@interface MySingle {
int value(); // this variable name must be value
}
//...
// Annotate a method using a single-member annotation.
@MySingle(100) //Notice that value = need not be specified.
public static void myMeth() {
//...
We can use the single-value syntax when applying an annotation that has
other members, but those other members must all have default values. For
example, here the value xyz is added, with a default value of zero:
@interface SomeAnno {
int value();
int xyz() default 0;
}
In
cases in which we want to use the default for xyz, we can apply
@SomeAnno by simply specifying the value of value() by
using the single-member syntax.
@SomeAnno(88)
In
this case, xyz defaults to zero, and value gets the value 88. Of
course, to specify a different value for xyz requires that both members
be explicitly named, as shown here:
@SomeAnno(value = 88, xyz = 99)
Java
defines many built-in annotations. Of these, four are imported from java.lang.annotation:
@Retention, @Documented, @Target, and @Inherited. Three—@Override,
@Deprecated, and @SuppressWarnings—are included in java.lang.
@Retention - @Retention is designed to be used only as an annotation to another annotation. It specifies the retention policy.
@Documented - The
@Documented annotation is a marker interface that tells a tool that an
annotation is to be documented. It is designed to be used only as an
annotation to an annotation declaration.
@Target - The
@Target annotation specifies the types of declarations to which an
annotation can be applied. It is designed to be used only as an
annotation to another annotation.
@Target takes one argument, which must
be a constant from the ElementType enumeration. This argument specifies
the types of declarations to which the annotation can be applied. The
constants are shown here along with the type of declaration to which
they correspond.
ANNOTATION_TYPE - Another annotation
CONSTRUCTOR - Constructor
FIELD - Field
LOCAL_VARIABLE - Local variable
METHOD - Method
PACKAGE - Package
PARAMETER - Parameter
TYPE - Class, interface, or enumeration
We can specify one or more of these values in a @Target annotation. To
specify multiple values, we must specify them within a braces-delimited
list. For example, to specify that an annotation applies only to fields
and local variables, we can use this @Target annotation:
@Target( { ElementType.FIELD, ElementType.LOCAL_VARIABLE } )
@Inherited - @Inherited
is a marker annotation that can be used only on another annotation
declaration. Furthermore, it affects only annotations that will be used
on class declarations.
@Inherited causes the annotation for a superclass
to be inherited by a subclass. Therefore, when a request for a specific
annotation is made to the subclass, if that annotation is not present
in the subclass, then its superclass is checked. If that annotation is
present in the superclass, and if it is annotated with @Inherited, then
that annotation will be returned.
@Override - @Override
is a marker annotation that can be used only on methods.
A method
annotated with @Override must override a method from a superclass. If it
doesn’t, a compile-time error will result. It is used to ensure that a
superclass method is actually overridden, and not simply overloaded.
@Deprecated - @Deprecated is a marker annotation. It indicates that a declaration is obsolete and has been replaced by a newer form.
@SuppressWarnings - @SuppressWarnings
specifies that one or more warnings that might be issued by the
compiler are to be suppressed. The warnings to suppress are specified by
name, in string form. This annotation can be applied to any type of
declaration.
There are a number of restrictions that apply to annotation declarations:
First, no annotation can inherit another.
Second, all methods declared by an annotation must be without parameters. Furthermore, they must return one of the following:
A primitive type, such as int or double
An object of type String or Class
An enum type
Another annotation type
An array of one of the preceding types
Annotations cannot be generic. In other words, they cannot take type parameters.
Finally, annotation methods cannot specify a throws clause.
Click for NEXT article.
Please feel free to correct me by commenting your suggestions and feedback.