Return-Path: Received: from pacific-carrier-annex.mit.edu by po10.mit.edu (8.9.2/4.7) id OAA01079; Wed, 9 Oct 2002 14:49:08 -0400 (EDT) Received: from hermes.sun.com (hermes.sun.com [64.124.140.169]) by pacific-carrier-annex.mit.edu (8.9.2/8.9.2) with SMTP id OAA11602 for ; Wed, 9 Oct 2002 14:49:07 -0400 (EDT) Date: 9 Oct 2002 09:37:20 -0800 From: "JDC Tech Tips" To: alexp@mit.edu Message-Id: <23407496-686162560@hermes.sun.com> Subject: Core Java Technologies Tech Tips, Oct. 8, 2002 (Regular Expression Groups, Anonymous Classes) Mime-Version: 1.0 Content-Type: text/html; charset=us-ascii Content-Transfer-Encoding: 7bit X-Mailer: SunMail 1.0 Core Java Technologies Technical Tips
image
image
Core Java Technologies Technical Tips
image
   View this issue as simple text October 8, 2002    

In this Issue

Welcome to the Core JavaTM Technologies Tech Tips, October 8, 2002. Here you'll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SETM).

This issue covers:

Using Regular Expression Groups
Anonymous Classes

These tips were developed using Java 2 SDK, Standard Edition, v 1.4.

This issue of the JDC Tech Tips is written by Glen McCluskey.

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.

Pixel

USING REGULAR EXPRESSION GROUPS

Regular expressions are a new feature of the Java 2 Platform, Standard Edition v 1.4. A regular expression is a string pattern that you can use to perform sophisticated string searching and replacement. For example, the regular expression:

    \d+

means "a sequence of one or more consecutive digit characters". A regular expression is represented by (compiled into) an instance of the java.util.regex.Pattern class. Then the related Matcher class is used to match input character sequences against the pattern.

Sometimes when matching is done, all you care about is whether the pattern was found somewhere in the input sequence. A common application of this is a utility that searches a text file and prints lines that match a specified pattern.

But sometimes you might want to get more information about a match than simply an indication of whether the pattern was found in the input. For example, if you're searching for a number, using the regular expression illustrated above, you might want to retrieve the actual text of the number that was found.

This is one situation where regular expression groups are useful. A group is a numbered part of a regular expression. For example, in the following expression:

    (\d+)zzz

there are two groups. Group 0 always refers to the whole expression, and group 1 to the subexpression that starts with the open parenthesis "(" and ends with the corresponding close parenthesis ")". The text of matched groups is saved by the regular expression matcher, and can be retrieved or referenced later in the regular expression.

Let's illustrate these ideas with an example:

    import java.util.regex.*;
    
    public class GroupDemo1 {
        static final String stringlist[] = {
            "abc 123 def",
            "456 ghi",
            "jkl789mno"
        };
    
        public static void main(String args[]) {
    
            // compile regular expression pattern for a
            // number consisting of one or more digits
    
            Pattern patt = Pattern.compile("(\\d+)");
    
            for (
              int i = 0; i < stringlist.length; i++) {
                String currstr = stringlist[i];
    
                // see if the current string has a match
    
                Matcher match = patt.matcher(currstr);
    
                // if a match, print the string text
                // for the matching group (group 1)
    
                if (match.find()) {
                    System.out.println("For \"" +
                        currstr + "\" match is: " +
                        match.group(1));
                }
            }
        }
    }

In this demonstration, GroupDemo1, there are some strings with embedded numbers in them. The program uses regular expression features to search for the numbers, and display the string text for each number that it finds. The program creates a Matcher object for each input string. Then the program calls the find method to see if there is a match against the regular expression pattern. If there is a match, the program gets the matching text for group 1 from the Matcher and displays it. In this example, the regular expression is:

    (\d+)

and group 1 is the text matched by "\d+". When you run the program, you should see the result:

    For "abc 123 def" match is: 123
    For "456 ghi" match is: 456
    For "jkl789mno" match is: 789

Let's look at another example similar to the first one:

    import java.util.regex.*;
    
    public class GroupDemo2 {
        static final String stringlist[] = {
            "abc 123 def 123",
            "456 ghi",
            "jkl789mno    789pqr",
            "123 456"
        };
    
        public static void main(String args[]) {
    
            // compile regular expression pattern for 
            // a number consisting of one or more 
            // digits followed by the same number later 
            // in the string
    
            Pattern patt = 
                        Pattern.compile("(\\d+).*\\1");
    
            for (
               int i = 0; i < stringlist.length; i++) {
                String currstr = stringlist[i];
    
                // see if the current string 
                // has a match
    
                Matcher match = patt.matcher(currstr);
    
                // if a match, print the string text
                // for the matching group (group 1)
    
                if (match.find()) {
                    System.out.println("For \"" +
                        currstr + "\" match is: " +
                        match.group(1));
                }
            }
        }
    }

The GroupDemo2 demo is almost the same as GroupDemo1, but with a different regular expression:

    (\d+).*\1

For an input sequence to match this expression, it must contain a number followed by any characters followed by the original number. The "\1" is a back reference to the first group in the expression. So input of:

    123 abc 123

will match, but:

    123 abc 456

will not.

When you run the GroupDemo2 program, you should see the result:

    For "abc 123 def 123" match is: 123
    For "jkl789mno    789pqr" match is: 789

Another way that you can use groups is to do string editing, for example, replacing the text of a matching group with other text. Here's an example:

    import java.util.regex.*;
    
    public class GroupDemo3 {
        static final String stringlist[] = {
            "abc 123 def   123",
            "456 ghi",
            "no match",
            "jkl789mno   789",
            "",
            "123.123",
            "1,2,3,4,5,6,7,8,9,10"
        };
    
        public static void main(String args[]) {
    
            // compile regular expression pattern for 
            // a number consisting of one or more 
            // digits
    
            Pattern patt = Pattern.compile("(\\d+)");
    
            for (
              int i = 0; i < stringlist.length; i++) {
                String currstr = stringlist[i];
    
                String outstr;
    
                // see if the current string has 
                // a match
    
                Matcher match = patt.matcher(currstr);
                boolean result = match.find();
    
                // if found a match, then go through 
                // string and replace all matches with 
                // "[matchstring]"
    
                if (result) {
                    StringBuffer strbuf = 
                                    new StringBuffer();
                    do {
                        match.appendReplacement(
                                       strbuf, "[$1]");
                        result = match.find();
                    } while (result);
                    match.appendTail(strbuf);
                    outstr = strbuf.toString();
                }
    
                // if no match, just point output 
                // at input
    
                else {
                    outstr = currstr;
                }
    
                // display the result
    
                System.out.println(outstr);
            }
        }
    }

The GroupDemo3 program finds numbers in the input, as before. When a number is found, it takes the text of the number and puts "[]" around it. Ultimately, the program produces an output string that consists of the input with this editing operation performed on it.

The appendReplacement and appendTail methods are the key to performing this operation. The appendReplacement method is used to replace matched text in the input with a new string that you specify. The string can contain references to groups. For example, the GroupDemo3 program specifies the replacement string as "[$1]". This means to replace matched instances of group 1 with the group 1 text surrounded by "[]", and append the result to an output string buffer. When the matching process is exhausted, appendTail is used to append the rest of the input to the output.

The output of the GroupDemo3 program is:

    abc [123] def   [123]
    [456] ghi
    no match
    jkl[789]mno   [789]

    [123].[123]
    [1],[2],[3],[4],[5],[6],[7],[8],[9],[10]

The logic in this demo is very similar to the Matcher.replaceAll method.

For further information about regular expressions, see the article "Regular Expressions and the Java Programming Language" by Dana Nourie and Mike McCloskey.

Pixel

ANONYMOUS CLASSES

If you've done much Java programming, you might have realized that it's possible to declare classes that are nested within other classes. This tip is going to look at one particular kind of nesting, which goes by the name "anonymous class". To get started on the discussion, let's look at a simple example:

    class Base {
        void method1() {}
        void method2() {}
    }
    
    class A { // normal class
    
        static class B {} // static nested class
    
        class C {} // inner class
    
        void f() {
            class D {} // local inner class
        }
    
        void g() {
            // anonymous class
            Base bref = new Base() { 
                void method1() {}
            };
        }
    }

The example illustrates various kinds of nested and inner classes. A nested class that is not declared static is called an inner class. In the example code, B is a nested class, while C is a nested class and an inner class.

The main focus of this tip is anonymous classes. You can glean a few insights about anonymous classes by studying the example above. One key idea is that an anonymous class has no name. An anonymous class is a subclass of an existing class (Base in this example) or an implementation of an interface.

Because an anonymous class has no name, it cannot have an explicit constructor. Neither can an anonymous class be referred to outside its declaring expression, except indirectly through a superclass or interface object reference. Anonymous classes are never static, never abstract, and always final. Also, each declaration of an anonymous class is unique. For example, the following code declares two distinct anonymous classes:

    Base bref1 = new Base() {
        void method1() {}
    };

    Base bref2 = new Base() {
        void method1() {}
    };

Each anonymous class is declared within an expression.

Let's look at a common situation where you would use an anonymous class:

    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    
    public class AnonDemo2 {
        public static void main(String args[]) {
    
            // create a JFrame and add a listener
            // to it to handle window closing
    
            JFrame frame = new JFrame("AnonDemo2");
            frame.addWindowListener(
                                  new WindowAdapter() {
                public void windowClosing(
                                       WindowEvent e) {
                    System.exit(0);
                }
            });
    
            // create a JPanel and add it to the frame
    
            JPanel panel = new JPanel();
            panel.setPreferredSize(
                              new Dimension(300, 300));
            frame.getContentPane().add(panel);
    
            // display the frame
    
            frame.pack();
            frame.setVisible(true);
        }
    }

This example displays a JPanel on the screen. The demo adds a window listener to the JFrame object such that when the user closes the window, the application terminates.

WindowListener is an interface. An implementing class must define all the methods specified in the interface. WindowAdapter implements the interface using dummy methods, like this:

    public abstract class WindowAdapter
        implements WindowListener, WindowStateListener,
        WindowFocusListener
    {
        public void windowOpened(WindowEvent e) {}
        public void windowClosing(WindowEvent e) {}
        public void windowClosed(WindowEvent e) {}
        public void windowIconified(WindowEvent e) {}
        public void windowDeiconified(WindowEvent e) {}
        public void windowActivated(WindowEvent e) {}
        public void windowDeactivated(WindowEvent e) {}
        public void windowStateChanged(WindowEvent e) {}
        public void windowGainedFocus(WindowEvent e) {}
        public void windowLostFocus(WindowEvent e) {}
    }

The demo application, AnonDemo2, needs to override just one of these methods, the windowClosing one. So it subclasses the adapter class and overrides the single method. The subclass is used only once within the application, and contains very simple logic. That's why an anonymous class is a good choice in this situation. The anonymous class extends the WindowAdapter class to override a single method. WindowAdapter, in turn, implements the WindowListener class by use of dummy methods that do nothing.

Let's look at another example. Suppose that you have a List of Integer objects, and you want to sort the list, both in ascending order (the default) and in descending order. Here's some code to do this:

import java.util.*;

public class AnonDemo3 {
    public static void main(String args[]) {

        // create an ArrayList and add
        // some Integer objects to it

        List list = new ArrayList();
        list.add(new Integer(37));
        list.add(new Integer(-59));
        list.add(new Integer(83));

        // sort the list in the usual (ascending order)

        Collections.sort(list);
        System.out.println(list);

        // sort the list in descending order
        // using a function object implemented
        // via an anonymous class

        Collections.sort(list, new Comparator() {
            public int compare(Object o1, Object o2) {
                int a = ((Integer)o1).intValue();
                int b = ((Integer)o2).intValue();
                return a < b ? 1 : a == b ? 0 : -1;
            }
        });
        System.out.println(list);
    }
}

The program does the first sort in the obvious fashion. Then, to sort in descending order, the program must specify a Comparator function object. This object implements comparison logic to sort Integer objects in descending order.

This demo uses an anonymous class, one that implements the java.util.Comparator interface. If this kind of sorting is done in only one place in an application, an anonymous class makes sense, but if it's done many places, it might make more sense to introduce a top-level or static nested class:

    class MyComparator implements Comparator {
        ...
    }

and implement the sort logic only once.

The output of the program is:

    [-59, 37, 83]
    [83, 37, -59]

Let's look at a final example, one that illustrates a couple of issues related to the use of anonymous classes:

    class A {
        int afield;
    
        // set value of afield
    
        A(int afield) {
            this.afield = afield;
        }
    
        // get value of afield
    
        int getValue() {
            return afield;
        }
    }
    
    public class AnonDemo4 {
        static A createAnon() {
            final int dlocal = 40;
    
            // return from the f() method an instance
            // of the anonymous class derived from A
    
            // invoke superclass constructor
            return new A(10) {             
                int bfield = 20;
                int cfield;
    
                {
                    cfield = 30;
                }
    
                int getValue() {
                    return afield + bfield + cfield 
                    + dlocal;
                }
            };
        }
    
        public static void main(String args[]) {
            A anonref = createAnon();
    
            System.out.println(anonref.getValue());
        }
    }

In this example, the createAnon method declares an anonymous class and returns a superclass (A) reference to an instance of the anonymous class. This means that the anonymous class instance can be used outside the declaring context (createAnon). Then the getValue method is called on the anonymous class object reference.

Recall that anonymous classes do not have names, and so, they cannot have explicit constructors. But there are several ways to get around this limitation. When an instance of an anonymous class is created, by saying:

    new A(10) {...}

the superclass constructor, A(int), is automatically called.

Instance initialization for the anonymous class instance is handled in the normal way, so that

    int bfield = 20;

and

    {
        cfield = 30;
    }

work as usual. These mechanisms can be used to do part of the work that might normally be done in a constructor.

There's one additional unusual feature of the AnonDemo4 example. The dlocal variable is declared as final. If the final keyword is left off the declaration, this code will evoke a compiler error. Why? Because it's possible, as this example illustrates, to refer to an anonymous class object outside the context where the class was declared. If such a reference is made, what value would dlocal have, given that it's a local variable declared in the createAnon method? This is a classic programming issue that occurs when an invalid stack frame is referenced.

To get around this problem, the local variable must be made final, that is, bound to a particular value which can be used in place of the variable (dlocal) itself. So instead of using "dlocal", the value "40" is used.

Anonymous classes are very useful tools, but they should not be overemphasized. Some other kind of class might be a better choice if more than one anonymous class in your application uses the same logic, or if the logic in such a class is complicated, or if you have deep class nesting. Also, anonymous class declarations can be hard to read, so you should keep them simple.

For further information about anonymous classes, see section 5.4 Anonymous Inner Classes, in "The JavaTM Programming Language Third Edition" by Arnold, Gosling, and Holmes. Also see item 18 "Favor static member classes over nonstatic" in Effective Java Programming Language Guide" by Joshua Bloch.

Pixel

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


Comments? Send your feedback on the Core JavaTM Technologies Tech Tips to: jdc-webmaster@sun.com

Subscribe to the following newsletters for the latest information about technologies and products in other Java platforms:

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

To subscribe to these and other JDC publications:
- Go to the JDC Newsletters and Publications page, choose the newsletters you want to subscribe to and click "Update".
- To unsubscribe, go to the subscriptions page, uncheck the appropriate checkbox, and click "Update".


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


Copyright 2002 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.


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


Core Java Technologies Tech Tips
October 8, 2002


Sun, Sun Microsystems, Java, Java Developer Connection, J2SE, J2EE, and J2ME are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.

Sun Microsystems, Inc.
Please send me newsletters in text.
Please unsubscribe me from this newsletter.