As usual in a more traditional, compiled language we need some scaffolding before we can enjoy the result of the our "Hello World" program.
public class Hello { public static final void main(String[] args) { System.out.println("Hello World"); } }
To run this program, we put this code into a file called Hello.java, call the compiler with the command javac Hello.java, and finally execute the resulting byte code (contained in the class file Hello.class) with the Java interpreter using java -classpath . Hello (no .class suffix here!). The classpath option tells the interpreter where to look for classes. In our case, we want it to pick up the class we have just compiled in the current directory.
Two things catch our attention immediately. First, a Java program looks a lot like C (or C++), and second, Java seems to be serious about object orientation in the sense that nothing goes without a class. C's main function as the entry point to a program becomes a static method of a class, that is, a method bound to a class and not an instance of a class. In our example, the surrounding class provides no more than a namespace for the function, since it does not use the class at all.
Java's classes are directly mapped to the file system. A Java source file typically contains a single class (to be precise, exactly one public, non-nested class), whose name is reflected in the name of the source file by just adding the .java suffix. The same holds for packages, Java's means to organize the source code in a hierarchical manner. The package hierarchy maps to the directory tree of the underlying file system. As an example, we can put our "Hello World" program in the sample package by adding the package declaration as the first line to the source code and putting the source file in the subdirectory sample.
package sample; public class Hello { public static final void main(String[] args) { System.out.println("Hello World"); } }
Of course, we also have to tell the compiler which new source file to compile using the command javac sample/Hello.java. Similarly, we have to supply the fully qualified class name sample.Hello when running the program with the Java interpreter, that is, use the command java -classpath . sample.Hello.
With this knowledge about packages, we can continue dissecting the program. The actual print statement System.out.println("Hello World"); is a call to the method println of the standard output stream which is available as a class member (static member in Java parlance) of the System class. The fully qualified name of this class is actually java.lang.System, that is, it is defined in the standard library package java.lang. The same is true for the String class used in the signature of the main method. Hence, we could have written
public class Hello { public static final void main(java.lang.String[] args) { java.lang.System.out.println("Hello World"); } }
Alternatively, we could have imported the standard package with the import statement before the class definition.
import java.lang.*; public class Hello { public static final void main(String[] args) { System.out.println("Hello World"); } }
Since the classes of the standard package java.lang are used everywhere in a Java program, the compiler uses the import statement import java.lang.* implicitly.
Before we finally leave this example for more exciting tasks, I would like to point out two differences to the early member of the C family: There is separation of declaration and definition as in C++ and Objective C, and the array brackets are normally placed behind the type rather than the variable (although the latter is still allowed).
The fact that Java is an object oriented programming language does not mean that everything is an object like in Smalltalk. Instead, following its C/C++ ancestors, there is a clear divide between primitive types such as integers on the one hand and objects on the other.
Primitive values are plain scalar values. They have no methods, live on the stack, and use value semantics (no direct pointers in Java!). Their type is only known at compile time and they can't be used in a generic context (e.g., a collection).
public class Sample { public static void main(String[] args) { double x = 1.25; double y = 2.5 / x + 1; System.out.println(Double.toString(y)); // true int m = 5, n = 10; System.out.println(Boolean.toString(m+2*n == 25)); // true } }
Java supports all of C's scalar types with the exception of unsigned integers. As an addition, there is also a boolean type. Since one of Java's main goals is portability, the representation of the primitive types is well-defined. A long integer, for example, is always a 64 bit long independent of the underlying operating system.
Note that Java follows C++ and allows us to define variables in middle of block of statements. To print the results, we use the static toString methods of the standard classes Double and Boolean.
All other data in Java (including arrays, structures, etc.) is represented as objects. Objects are quite the opposite of primitive values. They have attributes and methods, live on the heap, and use reference semantics. You can ask for an object's type (the class) at run-time and do most of the things we know from Smalltalk. Objects are created using the new operator (another C++ heritage) and automatically deleted by Java's garbage collection if there is no reference to the object anymore and the virtual machine needs more memory.
public class Sample { public static void main(String[] args) { String a = new String("blah"); String b = new String("blah"); String c = a; System.out.println(Boolean.toString(a == b)); // false System.out.println(Boolean.toString(a == c)); // true System.out.println(Boolean.toString(a.equals(b))); // true } }
In its effort to radically simplify C++, Java dropped all features deemed complex or dangerous: pointers, operator overloading, parameterized types (generics, templates), default arguments, variable argument lists, macros, and so forth. As a consequence of the lack of operator overloading, the equality operator == always refers to object identity. To test equality in terms of the contents of the object, one has to call the equals method. Especially for strings this is one of the gotchas for the beginning Java developer. [1]
Strings are not a very good example for objects, since they play a special role in Java. Unlike most other objects, strings are immutable, that is, they can be changed once they have been created. Furthermore, Java supports strings them with some special syntax. We can directly assign a string literal to a string object (reference variable, to be precise), and the plus operator is overloaded with string concatenation including the conversion of any other type to a string. Therefore, we could have written the last example as follows.
public class Sample { public static void main(String[] args) { String a = "blah"; String b = "blah"; String c = a; System.out.println("" + (a == b)); // false System.out.println("" + (a == c)); // true System.out.println("" + (a.equals(b))); // true } }
None of these special syntax features is available to user defined classes.
It is about time to define our first Java class.
Like most modern languages, Java uses exceptions to handle error conditions. Exceptions a objects extending the Throwable class. To handle an exception, the code in question has to be placed in a try block, followed by an arbitrary number of catch blocks handling different exceptions (matching by inheritance), and an optional finally block containing actions (such as closing a resource) to be performed after the try block whether an exceptions occurs or not.
public class ExceptionSample { static void testException(int i) { try { System.out.println("start try " + i); if (i == 0) { throw new MyException(i); } else if (i == 1) { throw new Exception("oops"); } System.out.println("end try"); } catch (MyException e) { System.out.println("caught my exception: " + e.getMessage()); } catch (Exception e) { System.out.println("caught exception: " + e.getMessage()); } finally { System.out.println("clean up"); } } public static void main(String[] args) { testException(0); testException(1); testException(2); } } class MyException extends Exception { MyException(int i) { super("i=" + i); } }
The example defines a custom exception class MyException which is thrown during the first run of the testException method and caught by the first catch block. During the second call, the general exception passes the first catch block, but is caught by the second one. In any case, the clean-up message of the finally clause is printed.
start try 0 start try 0 caught my exception: i=0 clean up start try 1 caught exception: oops clean up start try 2 end try clean up
Due to the garbage collection, the finally block is much more important than in C++, since we can't rely on destructors to release resources.
As a peculiarity, Java distinguishes checked and unchecked exceptions. If a method may throw a checked exception (and not catch it), it must declare the exception with a throws clause.
public class ExceptionSample1 { static void throwingMethod(int i) throws MyException { if (i == 0) { throw new MyException("oops"); } } public static void main(String[] args) { try { throwingMethod(0); } catch (Exception e) { System.out.println("exception: " + e.getMessage()); } } } class MyException extends Exception { MyException(String message) { super(message); } }
In other words, checked exceptions become part of the signature or a method. An exception is a check
[1] | The equals method does not really belong to any of the two objects involved. The underlying equality test is a symmetric operation and should be implemented depending on both objects. It is therefore a good example for a situation where a generic function makes more sense. In the Java equals implementations one will instead always find a cast operator (if not an explicit type check). |