.
.
java.sun.com developers.sun.com
.
   View this issue as simple text December 1, 2004    

In this Issue

Welcome to the Core Java Technologies Tech Tips for December 1, 2004. Here you'll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SE).

This issue covers:

-Scanning Text With java.util.Scanner
-Covariant Return Types

These tips were developed using the Java 2 Platform Standard Edition Development Kit 5.0 (JDK 5.0). You can download JDK 5.0 at http://java.sun.com/j2se/1.5.0/download.jsp.

This issue of the Core Java Technologies Tech Tips is written by Daniel H. Steinberg, Director of Java Offerings for Dim Sum Thinking, Inc, and editor-in-chief for java.net (http://java.net).

See the Subscribe/Unsubscribe note at the end of this newsletter to subscribe to Tech Tips that focus on technologies and products in other Java platforms.

For more Java technology content, visit these sites:

java.sun.com - The Java technology source for developers. Get the latest Java platform releases, tutorials, newsletters and more.

java.net - A web forum where enthusiasts of Java technology can collaborate and build solutions together.

java.com - The ultimate marketplace promoting Java technology, applications and services.

.
.

SCANNING TEXT WITH JAVA.UTIL.SCANNER

J2SE 5.0 adds classes and methods that can make every day tasks easier to perform. In this tip you will see how the newly added java.util.Scanner class makes it easier to read and parse strings and primitive types using regular expressions.

Before the J2SE 5.0 release, you probably wrote code such as the following TextReader class to read text from a file:

   import java.io.BufferedReader;
   import java.io.FileReader;
   import java.io.IOException;
   import java.io.File;

   public class TextReader {
     private static void readFile(String fileName) {
       try {
         File file = new File(fileName);
         FileReader reader = new FileReader(file);
         BufferedReader in = new BufferedReader(reader);
         String string;
         while ((string = in.readLine()) != null) {
           System.out.println(string);
         }
         in.close();
       } catch (IOException e) {
         e.printStackTrace();
       }
     }

     public static void main(String[] args) {
       if (args.length != 1) {
         System.err.println("usage: java TextReader "
           + "file location");
         System.exit(0);
       }
       readFile(args[0]);
     }
   }

The basic approach in classes like this is to create a File object that corresponds to the actual file on the hard drive. The class then creates a FileReader associated with the file and then a BufferedReader from the FileReader. It then uses the BufferedFile reader to read the file one line at a time.

To view the TextReader class in action, you need to create a document for the class to read and parse. To create the document, save the following two lines of text in a file named TextSample.txt in the same directory as TextReader:

   Here is a small text file that you will
   use to test java.util.scanner.

Compile TextReader. Then run it by entering the following:

   java TextReader TextSample.txt

You should see the original file echoed back to you in standard output.

You can simplify the code in TextReader by using java.util.Scanner, a class that parses primitive types and strings:

   import java.io.File;
   import java.io.FileNotFoundException;
   import java.util.Scanner;

   public class TextScanner {

     private static void readFile(String fileName) {
       try {
         File file = new File(fileName);
         Scanner scanner = new Scanner(file);
         while (scanner.hasNext()) {
           System.out.println(scanner.next());
         }
         scanner.close();
       } catch (FileNotFoundException e) {
         e.printStackTrace();
       }
     }

     public static void main(String[] args) {
       if (args.length != 1) {
         System.err.println("usage: java TextScanner1"
           + "file location");
         System.exit(0);
       }
       readFile(args[0]);
     }
   }

Compile TextScanner. Then run it as follows:

   java TextScanner TextSample.txt

You should get the following output:

   Here
   is
   a
   small
   text
   file
   that
   you
   will
   use
   to
   test
   java.util.scanner.

TextScanner creates a Scanner object from the File. The Scanner breaks the contents of the File into tokens using a delimiter pattern, By default the delimiter pattern is whitespace. TextScanner then calls the hasNext() method in Scanner. This method returns true if another token exists in the Scanner's input, which is the case until it reaches the end of the file. The next() method returns a String that represents the next token. So until it reaches the end of the file, TextScanner prints the String returned by next() on a separate line.

You can change the delimeter that is used to tokenize the input, through the useDelimiter method of Scanner. You can pass in a String or a java.util.regex.Pattern to the method. See the JavaDocs page for Pattern for information on what patterns are appropriate. For example, you can read the input one line at a time by using the newline character (\n) as a delimiter. Here is the revised readFile() method for TextScanner that uses a newline character as the delimiter:

   private static void readFile(String fileName) {
     try {
       Scanner scanner = new Scanner(new File(fileName));
       scanner.useDelimiter
         (System.getProperty("line.separator")); 
       while (scanner.hasNext()) {
         System.out.println(scanner.next());
       scanner.close();
     } catch (FileNotFoundException e) {
       e.printStackTrace();
     }
   } 

Note that there are other options for detecting the end of a line. You could, for example, test for lines that end with a newline character or that end with a carriage return and a newline character. You can do that using the regular expression "\r\n|\n". The JavaDocs for java.util.regex.Pattern shows other possible line terminators, so a more complete check would use the expression "\r\n|[\r\n\u2028\u2029\u0085]". You can also use the hasNextLine() and nextLine() methods from the Scanner class. In any case, with the revised TextScanner, the output should match the contents and layout of TextSample.txt. In other words, you should see the following:

   Here is a small text file that you will
   use to test java.util.scanner.

A simple change of the pattern in the delimiter used by the Scanner gives you a great deal of power and flexibility. For example, if you specify the following delimiter:

   scanner.useDelimiter("\\z");

it reads in the entire file at once. This is similar to the trick suggested by Pat Niemeyer in his java.net blog. You can read in the entire contents of a web page without creating several intermediate objects. The code for the following class, WebPageScanner, reads in the current contents of the java.net homepage:

   import java.net.URL;
   import java.net.URLConnection;
   import java.io.IOException;
   import java.util.Scanner;

   public class WebPageScanner {
     public static void main(String[] args) {
       try {
         URLConnection connection =
           new URL("http://java.net").openConnection();
         String text = new Scanner(
           connection.getInputStream()).
           useDelimiter("\\Z").next();
       } catch (IOException e) {
         e.printStackTrace();
       }
     }
   }

You can handle more than Strings with the Scanner class. You can also use Scanner to parse data that consists of primitives. To illustrate this, save the following three lines in a file named Employee.data (in the same directory as TextSample):

   Joe, 38, true
   Kay, 27, true
   Lou, 33, false

You could still treat this as one large String and perform the conversions after parsing the String. Instead, you can parse this file in two steps. This is illustrated in the following class, DataScanner:

   import java.util.Scanner;
   import java.io.File;
   import java.io.FileNotFoundException;

   public class DataScanner {

     private static void readFile(String fileName) {
       try {
         Scanner scanner =
           new Scanner(new File(fileName));
         scanner.useDelimiter
           (System.getProperty("line.separator")); 
         while (scanner.hasNext()) {
           parseLine(scanner.next());
         }
         scanner.close();
       } catch (FileNotFoundException e) {
         e.printStackTrace();
       }
     }

     private static void parseLine(String line) {
       Scanner lineScanner = new Scanner(line);
      lineScanner.useDelimiter("\\s*,\\s*");
       String name = lineScanner.next();
       int age = lineScanner.nextInt();
       boolean isCertified = lineScanner.nextBoolean();
       System.out.println("It is " + isCertified +
         " that " + name + ", age "
         + age + ", is certified.");
     }

     public static void main(String[] args) {
       if (args.length != 1) {
         System.err.println("usage: java TextScanner2"
           + "file location");
         System.exit(0);
       }
       readFile(args[0]);
     }
   }

The outer Scanner object in DataScanner reads a file, one line at a time. The readFile() method passes each line to a second scanner. The second scanner parses the comma delimited data and discards the whitespace on either side of the comma. There are variants of the hasNext() and next() methods which enable you to test whether or not the next token is of a specified type and to attempt to treat the next token as an instance of that type. For example, nextBoolean() attempts to treat the next token as a boolean and tries to match it to either the String "true" or the String "false". If the match cannot be made, a java.util.InputMismatchException is thrown. The parseLine() method of DataScanner shows how each line is parsed into a String, an int, and a boolean.

Compile DataScanner. Then run it as follows:

   java DataScanner Employee.data

You should get the following output:

   It is true that Joe, age 38, is certified.
   It is true that Kay, age 27, is certified.
   It is false that Lou, age 33, is certified.

You might be tempted to use just the comma as a delimiter. In other words you might try this:

   lineScanner.useDelimiter(",");

This will result in an InputMismatchException. That's because an extra space will be included in the token that you are trying to convert to a boolean, and the space does not match either "true" or "false". As is the case with all applications of regular expressions, the underlying power requires that you take extra care in constructing your patterns.

For more information on Scanner, see the formal documentation.

.
.

COVARIANT RETURN TYPES

You cannot have two methods in the same class with signatures that only differ by return type. Until the J2SE 5.0 release, it was also true that a class could not override the return type of the methods it inherits from a superclass. In this tip you will learn about a new feature in J2SE 5.0 that allows covariant return types. What this means is that a method in a subclass may return an object whose type is a subclass of the type returned by the method with the same signature in the superclass. This feature removes the need for excessive type checking and casting.

Let's start with the following class, ConfusedClass. The class tries to declare two methods with the same signature. One of the methods returns a JTextField, and the other returns a JPasswordField.

   import javax.swing.JTextField;
   import javax.swing.JPasswordField;

   public class ConfusedClass {

     public JTextField getTextField(){
       return new JTextField();
     }
  
     public JPasswordField getTextField(){
       return new JPasswordField();
     }
   }

If you try to compile ConfusedClass, you get the following compile error:

   ConfusedClass.java:10: getTextField() is already defined in 
   ConfusedClass
     public JPasswordField getTextField(){
                                  ^
   1 error

Looking at this situation from the perspective of a class calling getTextField(), you can see the reason for the compile time error. How would you indicate which of the two methods you are targeting? Consider, for example, this snippet:

   ConfusedClass cc = new ConfusedClass();
   JTextField field = cc.getTextField();

Because a JPasswordField extends JTextField, either version of the method could correctly be called.

Next, create two classes, each of which having a different version of the getTextField() methods. The two methods differ by implementation and return type. Start with the following base:

   import javax.swing.JTextField;

   public class ConfusedSuperClass {

     public JTextField getTextField(){
       System.out.println("Called in " + this.getClass());
       return new JTextField();
     }
  
   }

Compile ConfusedSuperClass. You'll see that it compiles without error. Now create a derived class, one that extends ConfusedSuperClass. The derived class attempts to return an instance of JPasswordField instead of the JTextField returned by the getTextField() method in ConfusedSuperClass.

   import javax.swing.JPasswordField;

   public class ConfusedSubClass extends ConfusedSuperClass {

     public JPasswordField getTextField(){
       System.out.println("Called in " + this.getClass());
       return new JPasswordField();
     }
  
   }

If you use a version of the JDK prior to J2SE 5.0, ConfusedSubClass will not compile. You will see an error like this.

   ConfusedSubClass.java:5: getTextField() in ConfusedSubClass 
   cannot override getTextField() in ConfusedSuperClass;
   attempting to use incompatible return type
   found   : javax.swing.JPasswordField
   required: javax.swing.JTextField
     public JPasswordField getTextField(){
                                  ^
   1 error

The error reported is that you are attempting to use an incompatible return type. In fact, the JPasswordField you are attempting to return is a subtype of JTextField. This same code compiles correctly under J2SE 5.0. You are now allowed to override the return type of a method with a subtype of the original type. In the current example, the getTextField() method in ConfusedSuperClass returns an instance of type JTextField. The getTextField() method in the ConfusedSubClass returns an instance of type JPasswordField.

You can exercise these two classes with the following NotConfusedClient class. This class creates an instance of type ConfusedSuperClass and of type ConfusedSubClass. It then calls getTextField() on each instance, and displays the type corresponding to the object returned by the method.

   import javax.swing.JTextField;

   public class NotConfusedClient {
     static JTextField jTextField;

     public static void main(String[] args) {
       System.out.println("===== Super Class =====");
       jTextField = new ConfusedSuperClass().getTextField();
       System.out.println("Got back an instance of "
         + jTextField.getClass());
       System.out.println("===== Sub Class =====");
       jTextField = new ConfusedSubClass().getTextField();
       System.out.println("Got back an instance of  "
         + jTextField.getClass());
     }
   }

Compile and run NotConfusedClient. When you run it, you should see the following output:

   ===== Super Class =====
   Called in class ConfusedSuperClass
   Got back an instance of class javax.swing.JTextField
   ===== Sub Class =====
   Called in class ConfusedSubClass
   Got back an instance of  class javax.swing.JPasswordField

In fact, you will get the same output if you change the return type of getTextField() to JTextField in ConfusedSubClass. The payoff comes when you use the object that is returned by the call to getTextField(). Before J2SE 5.0, you needed to downcast to take advantage of methods that are present in the derived class but not in the base class. As you've seen, in J2SE 5.0 ConfusedSubClass compiles with a different return type specified for getTextField() than is present in the superclass. You can now use your covariant return type to call a method that is only available in the subtype. First, recast the supertype like this:

   public class SuperClass {

     public SuperClass getAnObject(){
       return this;
     }

   }

Add the exclusive method to the corresponding subclass, and change the return type of the getAnObject() method:

   public class SubClass extends SuperClass {

     public SubClass getAnObject(){
       return this;
     }

     public void exclusiveMethod(){
       System.out.println("Exclusive in Subclass.");
     }
  
     public static void main(String[] args) {
         System.out.println("===== Call Exclusive method =====");
         new SubClass().getAnObject().exclusiveMethod();
     } 

   }

Compile SuperClass and SubClass, then run SubClass. You should see the following output:

   ===== Call Exclusive method =====
   Exclusive in Subclass.

The main() method creates an instance of the subclass. It then calls getAnObject(). It follows this with a call to exclusiveMethod() on the returned object of type SubClass. If the return type of getAnObject() was SuperClass in SubClass.java, the code would not have compiled.

.
.
Rate and Review
Tell us what you think of the content of this page.
Excellent   Good   Fair   Poor  
Comments:
If you would like a reply to your comment, please submit your email address:
Note: We may not respond to all submitted comments.
.
.

IMPORTANT: Please read our Licensing, Terms of Use, and Privacy policies:
http://developer.java.sun.com/berkeley_license.html
http://www.sun.com/share/text/termsofuse.html

Privacy Statement: Sun respects your online time and privacy (http://sun.com/privacy). You have received this based on your email preferences. If you would prefer not to receive this information, please follow the steps at the bottom of this message to unsubscribe.

Comments? Send your feedback on the Core Java Technologies Tech Tips to: http://developers.sun.com/contact/feedback.jsp?category=newslet

Subscribe to other Java developer Tech Tips:

- Enterprise Java Technologies Tech Tips. Get tips on using enterprise Java technologies and APIs, such as those in the Java 2 Platform, Enterprise Edition (J2EE).
- Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2ME).

To subscribe to these and other Sun Developer Network publications:
- Go to the Sun Developer Network Subscriptions page, choose the newsletters you want to subscribe to and click "Submit".
- To unsubscribe, go to the subscriptions page, uncheck the appropriate checkbox, and click "Submit".


ARCHIVES: You'll find the Core Java Technologies Tech Tips archives at:
http://java.sun.com/developer/JDCTechTips/index.html


Copyright 2004 Sun Microsystems, Inc. All rights reserved.
4150 Network Circle, Santa Clara, CA 95054 USA.


This document is protected by copyright. For more information, see:
http://java.sun.com/developer/copyright.html


Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks or registered trademarks (http://www.sun.com/suntrademarks/) of Sun Microsystems, Inc. in the United States and other countries.

Sun Microsystems,
Inc.
.
.