New Language Features

Tiger has several new language features designed to eradicate some of Java's annoyances. Many of these features are borrowed from other languages such as Python or Perl, so if you have experience with these languages, these new features should be familiar to you. However, there are idiosyncrasies of these features that Python and Perl users may not be familiar with.

For Each

One useful feature of Tiger is the new for-each syntax. The purpose of this syntax is to make it easier to iterate through collections and arrays. For example, consider the following JDK 1.4 code:

package oracle.hcj.tiger.
public class ForEach {
 public final static String[] importantPeople = new String[] {
 "Robert", "Jim", "Sacir", "Aida", "Alma",
 "Amila", "Selma", "Nefisa", "Mustafa",
 "Paul", "Debbie", "Marco", "Bettina", "Ana",
 "Maria" };
 public static void someMethod(final String prefix) {
 for (int idx = 0; idx < importantPeople.length; idx++) {
 if (importantPeople[idx].startsWith(prefix)) {
 System.out.print(importantPeople[idx] + " ");
 }
 }
 System.out.println( );
 }
}


Although this code works fine, the for statement is fairly wordy, which makes it annoying to type if it is used hundreds of times in an app. The new for-each feature makes this code much simpler:

package oracle.hcj.tiger.
public class ForEach {
 public static void someTigerMethod(final String prefix) {
 for (String person: importantPeople) {
 if (person.startsWith(prefix)) {
 System.out.print(person + " ");
 }
 }
 System.out.println( );
 }
}


Screenshot

If you are wondering why Sun didn't create a new keyword named foreach for the implementation of the for-each functionality, you are not alone. The official explanation from Sun is that they didn't do this because they didn't want to break the code of those who used foreach as an identifier.


You should read this code as "for each string importantPeople, assign it to the variable person and do the following code." The first statement in the for-each loop must be an object initialization statement that specifies the variable that will be used to store the contents of the iteration. If you try to use the wrong type for the iteration variable, the compiler will give an error on the for line. For example, if you substituted Integer for String in the previous example, you would get the following errors at compile time:

>c:\j2sdk1.5.0\bin\javac -source 1.5 javac *.java ForEach.java:44: incompatible types found : java.lang.String required: java.lang.Integer
 for (Integer person: importantPeople) {
 ^
ForEach.java:45: cannot find symbol symbol : method startsWith(java.lang.String)
location: class java.lang.Integer
 if (person.startsWith(prefix)) {


The second statement in the for-each loop must be an evaluation that evaluates to an array or an instance of the new interface java.lang.Iterable. Since the collection classes in the new JDK have been updated to implement this interface, you can use this syntax to iterate through them as well. The following example shows the old way of iterating through collections:

public class ForEach {
 public static void someCollectionMethod(final String prefix) {
 List people = Arrays.asList(importantPeople);
 Iterator iter = people.iterator( );
 for (String person = (String)iter.next( ); iter.hasNext( ); 
 person = (String)iter.next( )) {
 if (person.startsWith(prefix)) {
 System.out.print(person + " ");
 }
 }
 System.out.println( );
 }
}


Compare this to the new version using a for-each loop:

public class ForEach {
 public static void someTigerCollectionMethod(final String prefix) {
 List<String> people = Arrays.asList(importantPeople);
 for (String person: people) {
 if (person.startsWith(prefix)) {
 System.out.print(person + " ");
 }
 }
 System.out.println( );
 }
}


The second code sample is much more concise and easier to read than the first; the improved readability translates into easier debugging and maintenance.

Autoboxing with Variables

One of the persistent annoyances in Java is the frequent need to cast to other types:

Integer x = new Integer(25);
Number y = (Number)x;


In this code, the casting between the types is superfluous. Since an Integer is a subclass of Number, you should be able to just assign y to x without casting. Because this is a deficiency that needlessly bloats code, the concepts of autoboxing and autounboxing have been added to the language.

Screenshot

The boxing and unboxing features are broken in the prototype implementation at the time of this writing, so you may not be able to use them until Sun releases a new prototype or the beta. However, Sun has announced that a new prototype is on the way; furthermore, the beta of Tiger may come out before this tutorial hits the shelves.


Autoboxing is the process of converting a type to a higher-order type, while autounboxing is the inverse of this process. Autounboxing is shown in the following code:

Integer x = new Integer(5);
int y = x;


In JDK 1.4 and below, this would not compile and give an "incompatible types" error. However, in Tiger, the compiler knows that it can convert the wrapper Integer type to an int and does so automatically. Similarly, you can go the other direction and autobox your int:

int x = 5;
Integer y = x;


This functionality avoids the interminable casting and intValue( ) calls that must be done if you want to use mathematics with wrapper types. This technique also works for all other primitive types in the JDK. However, one thing you cannot do is autounbox null:

Integer x = null;
int y = x; // <= Error


In this code, you attempted to autounbox null into a primitive type. If this error can be caught at compile time, the compiler will complain that you can't stuff a null into a primitive. However, sometimes this problem can't be resolved by the compiler at compile time, as in the following code:

public void someMehtod(final Integer x) {
 int y = x;
 // . . . other code
}


In this case, the compiler doesn't know what the user of this method will pass at compile time, so it can't check the assignment. If the user sends null in the parameter x at runtime, the virtual machine will throw a NullPointerException to indicate the erroneous autounboxing attempt. Although you should check your methods for null in this case, not even the check for null would have altered the fact that the autounboxing attempt can't be checked at compile time.

Implicit casting

Primitives are not the only types to benefit from autoboxing and autounboxing. Constructed classes can also benefit:

String str = new String( );
Object obj = str;
String str2 = obj;


In this code, the casts of type are implicit; instead of having to manually type in the casts, the compiler will do it for you. You can use this scheme to implement implicit type conversion. Furthermore, if it is possible to check for a legal conversion at compile time, the compiler will check and return errors on any illegal conversions. If the legality of the cast cannot be determined at compile time, then an illegal conversion would result in a ClassCastException at runtime. The following code shows an example of both in action:

public class SomeClass {
 public void someMethod(final Object obj) {
 String str = obj; // <== can't be resolved at compile time because
 // user could pass you anything. Object obj2 = str; // <== resolved at compile time and OK. Integer x = 5; // <== resolved at compile time and OK.
 }
}


Note that autoboxing and autounboxing don't let you convert a reference to a different type. Reference conversions must be supertype to subtype or vice versa; this complies with RTTI in Java. Similarly, you can't convert an array of one reference type to an array of another reference type unless the components of the array have a subtype/supertype relationship:

Integer[] values = new Integer[1];
Number[] nums = values; // <== Legal String[] strs = values; // <== Illegal


Since there is a conversion between Integer and Number, the first cast works fine. The second one doesn't work because String and Integer don't share a supertype/subtype relationship. In addition to manipulating variables, you can use the autoboxing mechanism for method calls:

public class SomeClass {
 public void someMethod(final Object obj) {
 // . . . code
 }
 public void someMethod(final String obj) {
 // . . . code
 }
 public void someMethod(final Integer obj) {
 // . . . code
 }
 public final static void main(final String[] args) {
 someMethod(5); // <== 1st:calls someMethod(Integer) 
 someMethod("hello"); // <== 2nd:calls someMethod(String);
 someMethod(new Boolean(false)); // <== 4th: calls someMethod(Object);
 }
}


The first two calls in the main method are easy to understand. In the third example, the Boolean is unboxed to Object and someMethod(Object) is called. This is a potential trap waiting to happen. The programmer of this class may not have intended to process Boolean arguments in someMethod(Object). The lesson to be learned here is that when defining methods using Tiger, you should try to be as specific as possible in your method signatures. The problem of calling a method that takes Object accidentally is one of the biggest dangers of porting code from JDK 1.4 and below to Tiger. When you port your code in the future, make sure you watch out for this bug and use assertions liberally. When it comes to autoboxing interfaces, there are a couple of more rules to learn. You can't autounbox a class to an interface type unless the class actually implements that interface, nor can you convert an interface type to another type that implements a method in the interface that has the same signature but a different return type:

interface ISomeInterface{
 public void doSomething( );
}
interface ISomeOtherInterface( ) {
 public int doSomething( );
}
public void someMethod(final ISomeInterface obj) {
 IsomeOtherInterface someObj = obj; // <== forbidden
}


Screenshot

Remember that the signature of a method is composed of only its name and parameter types and does not include the return type.


In this example, both interfaces implement the method doSomething( ), but one implements it to return void and the other implements it to return an int. This means that you can't autounbox one to the other because both interfaces could never be implemented in the same class since every method in a class must have a unique signature.

Screenshot

It's unclear at this time why autounboxing to disparate interface types is allowed at all. I really can't see any potential uses for it. The rule should be simply that you can't autounbox anything to anything if the autoboxing doesn't comply with the rules of RTTI.


You can autounbox an array to the interfaces Serializable and Cloneable because these are the only interfaces that all arrays implement. Also, you can autounbox an array only to a type of Object—again according to the rules of RTTI. Finally, all reference types can be autounboxed to String because all references implement a toString( ) method declared in Object. However, you can also autounbox a primitive to a String. Therefore, the following code is completely legal:

int x = 25;
Set y = new HashSet( );
String x2 = x;
String y2 = y;


Ambiguous autoboxing

Since you have the autoboxing functionality of Tiger, you may be tempted to write code such as the following:

public class Boxing {
 public static void someMethod(final Float x) {
 System.out.println("Float");
 }
 public static void someMethod(final Integer x) {
 System.out.println("Integer");
 }
 public final static void main(final String[] args) {
 someMethod(5);
 }
}


The semantics of this code look harmless, but in fact, if you try to compile this code, you will get the following compiler error:

>c:\j2sdk1.5.0\bin\javac -source 1.5 oracle/hcj/tiger/*.java oracle/hcj/tiger/Boxing.java:28: reference to someMethod is ambiguous, both method someMethod(java.lang.Float) in oracle.hcj.tiger.Boxing and method someMethod(java.lang.Integer) in oracle.hcj.tiger.Boxing match
 someMethod(5);
 ^
1 error


The problem here is that autoboxing can't decide whether to use the Float or Integer version of the method. There are two ways around the problem. The first is to specify which type of number 5 is using a suffix to the number, as in the following code:

someMethod(5f);


In this code, you specify that 5 is a float. This allows the compiler to call the someMethod(Float) version. Similarly, you can use the other single-letter suffixes to specify the type of a literal number, such as L for a long and d for a double.

Screenshot

Although it is permissible to use the letter l to specify that a number is a long, it isn't a good idea because the letter l looks a lot like the number 1. Instead, I recommend you use a capital L when specifying long values.


There is no single-letter suffix that specifies that a number is an integer since the Java language specification dictates that if there is no appending suffix, the number is treated as an integer. However, there is a way around this that will let you call the Integer method; you just have to revert back to casts:

someMethod((Integer)5);
someMethod((int)5); // <== Compiler error!?!


The first cast works because the virtual machine knows how to use autoboxing to convert an int literal into an Integer. Strangely, the second call doesn't work, even though it should. The second call first specifies that the literal 5 is an int, which should indicate that you are calling the Integer method that can perform the autoboxing conversion. Unfortunately, the compiler can't handle the double autoboxing and indicates that the call is ambiguous.

Enums

Enums in Tiger are almost exactly like constant objects (see ) with a shorthand for declaration. The following is an example of an enum:

public class SomeClass {
 public enum ErrorLevel { DEBUG, INFO, WARNING, ERROR };
 public void someMethod(final ErrorLevel level) {
 switch (level) {
 case ErrorLevel.DEBUG: // do debug code.
 break;
 case ErrorLevel.INFO: // do info code.
 break;
 case ErrorLevel.WARNING: // do warning code.
 break;
 case ErrorLevel.ERROR: // do error code.
 break;
 default: assert (false);
 }
 }
}


This class declares an embedded enum with the given names as instances of the enum. The use of an enum makes the level parameter to someMethod( ) type-safe. The user must pass an ErrorLevel to someMethod( ) and can't create anymore ErrorLevel instances without altering the ErrorLevel class. The only apparent difference between an enum and a constant object is the manner of declaration. You can declare an enum in its own file instead of embedding it in another class:

// File: ErrorLevel.java public enum ErrorLevel {
 DEBUG, INFO, WARNING, ERROR
}


In this case, you use the keyword enum instead of class or interface. The identifiers immediately following the opening brace of the enum declaration are used to name the various instances of the class that will be created. The same concept could have been created with the constant-object pattern using the following code:

public final class ErrorLevel extends ConstantObject {
 public final static ErrorLevel DEBUG = new ErrorLevel("DEBUG ");
 public final static ErrorLevel INFO = new ErrorLevel("INFO ");
 public final static ErrorLevel WARNING = new ErrorLevel("WARNING");
 public final static ErrorLevel ERROR = new ErrorLevel("ERROR");
 private ErrorLevel(final String name) {
 super(name);
 }
}


This code does pretty much the same job as an enum. However, the constant-object class is much more verbose and subject to error. For example, if the user accidentally adds an extra space to the end of the name, there would be problems.

Screenshot

Although you can use any legal identifier for an enum constant, I suggest you name your enums with capital letters to indicate that the enum identifier is an instance that can't be changed.


Unlike enums, constant objects cannot be used in a switch statement; this gives enums a bit more flexibility than constant objects have. Also, enums can work seemlessly with the for-each construct (discussed earlier in the chapter):

for (ErrorLevel level : level.VALUES) {
 // . . . code
}


In this example, the special member VALUES was declared automatically by the compiler to access the list of potential values for the particular enum. The VALUES member holds an unmodifiable set of instances declared in the enum. Furthermore, these values are type-safe and the identifiers in an enum declaration are assigned an ordinal number in the order they are declared. In this example, DEBUG would be associated with the ordinal number 0, and ERROR with the ordinal number 3. This allows you to implement comparisons such as the following:

if (level > ErrorLevel.INFO) {
 // . . . do some logging code.
}


There are other methods and fields automatically declared for enums. For example:


ordinal

This field holds the ordinal value of the enum.


name

This field holds the name of the enum that corresponds automatically with the identifier name.


compareTo( )

Enums implement the comparable interface, which allows you to sort based on the order of the enums. This method sorts according to the ordinal value, not the name.

Finally, each enum has an equals( ), hashCode( ), and toString( ) method, which allows enums to act like objects. In fact, they can even be used as objects. You can place enums in a collection and pass them to methods that take a type of object.

Adding functionality to enums

In addition to declaring the enum values, you can declare other methods in an enum class. For example, here are some changes made to the ErrorLevel class:

public enum ErrorLevel {
 DEBUG, INFO, WARNING, ERROR;
 public ErrorLevel next(final ErrorLevel level) {
 return VALUES.get(level.ordinal + 1);
 }
 public ErrorLevel previous(final ErrorLevel level) {
 return VALUES.get(level.ordinal - 1);
 }
}


In this version of ErrorLevel, two methods were added to the class, which allows you to get the previous and next enum values. These methods work exactly like a method defined on a normal class. Additionally, you can add constructors to an enum using the following code:

public enum ErrorLevel {
 DEBUG(0x1), INFO(0x2), WARNING(0x4), ERROR(0x8);
 final int bit;
 public ErrorLevel(final int bit) {
 this.bit = bit;
 }
}


In this example, each of the enums define a bit that may be used in a bit mask. When the enum instances are created at static initialization, they will call the constructor that takes an int. However, this doesn't affect the actual construction of the enum by the virtual machine. The name and ordinal fields will still be there. In fact, the compiler uses a code-generation paradigm to create an enum. Therefore, you can declare as many special constructors as you want without worrying about interfering with the enum mechanism.

Interfaces and hierarchies

Although an enum class can implement interfaces, it can't use inheritance. For example, the following would be illegal:

public abstract enum EnumBase {
 public ErrorLevel next(final ErrorLevel level) {
 }
 public ErrorLevel previous(final ErrorLevel level) {
 }
}
public enum ErrorLevel extends EnumBase {
 DEBUG(0x1), INFO(0x2), WARNING(0x4), ERROR(0x8);
 final int bit;
 public ErrorLevel(final int bit) {
 this.bit = bit;
 }
}


Unfortunately, this code is not allowed by the compiler because multiple inheritance is not allowed in Java, and all enums automatically and implicitly inherit from the built-in class java.lang.Enum. Also, you are not allowed to explicitly extend java.lang.Enum yourself. Therefore, if you want to add functionality (such as a utility method) to your enums, you are out of luck. This is one of the major problems with enums. However, enums do offer another brand of inheritance that is rather strange:

public abstract enum EnumAttach {
 one {
 public void someMethod( ) { // . . . code
 }
 },
 two {
 public void someMethod( ) { // . . . code
 }
 };
 abstract void someMethod( );
}


In this code, EnumAttach is a base class that declares an abstract method. Subsequently, each enum instance must implement the abstract method. The syntax is extremely peculiar, and I'm not convinced it is altogether useful. When using this syntax, you are conceptually defining a method on an instance. Such a definition is far outside the Java and object-oriented coding mainstream.

Screenshot

I can't think of a situation in which I would use this syntax (if you find a good use for it, I would love to hear about it). In fact, the enum paradigm would be excellent if you could write base enums to contain functionality. Therefore, I believe that the functionality of attaching a method to an object should be changed to allow normal inheritance of enums as long as an enum can inherit only from an abstract enum.


Static Imports

The static-import facility of Tiger was designed solely for developers writing Java code. This feature allows you to import all of the static members of a particular class and use them without referring to their type. For example, consider the following class:

package oracle.hcj.tiger.
import java.awt.Color;
public class StatusColors {
 public static final Color DEFAULT = Color.green;
 public static final Color WARNING = Color.yellow;
 public static final Color ERROR = Color.red;
 public static void someMethod( ) {
 }
}


If you want to use this class in your code the old way, you would need to access it using the following code:

package oracle.hcj.tiger.
public class StaticImports {
 public void firstMethod(final Graphics g, final int errCode) {
 if (errCode >= 3) {
 g.setColor(StatusColors.ERROR);
 } else if (errCode == 2) {
 g.setColor(StatusColors.WARNING);
 } else if (errCode == 1) {
 g.setColor(StatusColors.DEFAULT);
 } else {
 assert (false);
 }
 }
}


The problem here is one of readability and ease of use. Whenever you want to use a color constant in the code, you must type out the full name of the constant. If there are only three constants, this isn't too bad. However, if you have a huge field of constants to work with, such as when you make constants for an XML DTD, using the constant type can get tedious. To solve this problem, many developers declare their constants in interfaces instead of classes, and then have the classes they want to use the constants implement these interfaces. However, this can lead to messy classes that implement 20 interfaces that contain only constants. Also, if one of these classes has to use four or five different sets of constants, the number of types grows rapidly. The static import facility allows you to declare your constants in classes; they can also be referred to without the fully qualified name. You can rewrite the previous example using static imports:

package oracle.hcj.tiger.
import static oracle.hcj.tiger.StatusColors.*;
public class StaticImports {
 public void secondMethod(final Graphics g, final int errCode) {
 if (errCode >= 3) {
 g.setColor(ERROR);
 } else if (errCode == 2) {
 g.setColor(WARNING);
 } else if (errCode == 1) {
 g.setColor(DEFAULT);
 } else {
 assert (false);
 }
 }
}


Adding the keyword static to import causes the compiler to import the static identifiers from the class. After they are imported, they can be used as if they were declared in the class.

Screenshot

Static imports import only static identifiers. Therefore, if you have methods in the class that are instance-based or static, you will have to use the instance-based methods, as you did before. However, since you shouldn't create mixes of constant and static elements in a class (see ), this shouldn't be a big issue.


One nice feature about this mechanism is that it applies to static methods and static attributes equally. This is convenient when using sets of static functions such as those declared in the java.lang.Math class:

package oracle.hcj.tiger.
import static java.lang.Math.*;
public class StaticImports {
 public void mathMethod(final int x, final int y) {
 int z = max(x, y); // instead of Math.max( );
 double sqrt = sqrt(z^5); // instead of Math.sqrt( );
 }
}


Static imports make the code read much cleaner without sacrificing the benefits of modularity.

Screenshot

As long as companies continue to use outdated JDKs, the ConstantObject paradigm will remain important. In many companies, it is politically and physically impossible to adopt a new JDK immediately, no matter how good the JDK version is. I still work with JDK 1.3 quite often in my consulting career.


Variable Arguments

The purpose of Tiger's variable arguments (or varargs) facility is to allow a method to take any number of arguments and then act on them. It is modeled after a similar facility in C and C++. This technique is used in the printf( ) method, which the Tiger prototype implements in its test code:

printf("Addition: % plus % equals %\n", 1, 1, 2);


Screenshot

At the time of this writing, there is a limited amount of information publicly available about the implementation of varargs.


This method will print a formatted string to the console. It determines the format by using the % characters embedded in the string. Wherever it sees a %, the printf( ) method will substitute the corresponding argument after the format string argument. In this example, the first % would be substituted by the first additional argument to the method:

printf("Addition: % plus % equals %\n", 1, 1, 2);


Substitution continues until the all of the arguments are used. Once completed, this example would produce the following output:

Addition: 1 plus 1 equals 2


The problem with the printf( ) method is that the writer of the method never knows how many arguments you will send him. In this example, four arguments were sent: the format string and the three values to use. You could have also called the method in the following way:

printf("Numbers: %, %, %, % \n", 6, 7, 8, 3);


Varargs allow you to declare a method that functions with this syntax. Using varargs, you can build a Java version of the printf( ) method. In the Test class that comes with the implementation of the prototype, there is an implementation of this method that you can use:

class Test {
 // . . . other code
 // varargs
 public static void printf(String fmt, Object... args) {
 int i = 0;
 // foreach on primitive array
 for (char c : fmt.toCharArray( )) {
 if (c == '%')
 System.out.print(args[i++]);
 else if (c == '\n')
 System.out.println( );
 else
 System.out.print(c);
 }
 }
 public static void main(String[] args) {
 // . . . other code
 // varargs and boxing
 printf("Addition: % plus % equals %\n", 1, 1, 2);
 // . . . other code
 }
}


In the declaration of the printf( ) method, a new syntax declared the variable arguments. When you use the ... operator after an argument, you are indicating that the number of arguments is undefined. In this case, you indicate that the number of object arguments is undefined. Once the arguments have been passed to the method, they can be used exactly like an array of objects:

System.out.print(args[i++]);


When you call the method, you not only use varargs, but also the autoboxing functionality in Tiger:

printf("Addition: % plus % equals %\n", 1, 1, 2);


In this call to the printf( ) method, the second, third, and fourth arguments are autoboxed into Integer objects. You can also use varargs with any other type, as in the following signature:

public static void printf(String fmt, int... args) {
}


This indicates that the variable arguments are all integers. If a user tries to pass anything other than an integer as the second argument or higher, the compiler will give her an error. The varargs syntax is used merely as a convenience. Using the functionality in JDK 1.4, you could have written the method with the following signature:

public static void printf(String fmt, Object[] args) {
}


However, if you had used this signature, you would have had to call the method using the following:

printf("Addition: % plus % equals %\n",
 new Object[] { new Integer(1), new Integer(1), new Integer(2)});


This call would accomplish the same task as using varargs, but it is much more verbose and difficult to read.

      
Comments