Sun Java Solaris Communities My SDN Account Join SDN
 
Core Java Technologies Tech Tips

Listeners vs Adapters and BigDecimal

In This Issue

Welcome to the Core Java Technologies Tech Tips for July 2007. Core Java Technologies Tech Tips provides tips and hints for using core Java technologies and APIs in the Java Platform, Standard Edition 6 (Java SE 6)

In this issue provides tips for the following:

» Listeners vs Adapters
» The Need for BigDecimal

These tips were developed using Java SE 6. You can download the Java Platform, Standard Edition 6 Development Kit (JDK 6), to use these and future tech tips, from the Java SE Downloads page

The author of this month's tips is John Zukowski, president and principal consultant of JZ Ventures, Inc.

Listeners vs Adapters

The JavaBeans component model (and thus the Swing component set) is built upon properties and events. Properties have setter and getter methods for working with their values. Events require you to use listeners and to implement interfaces in order to receive notification of their occurrence. Although working with properties is simple, listener objects require a little extra discussion to understand how they work, typically in the graphical user interface (GUI) world. Specifically, this tip describes the AWT and Swing event-related classes that offer both a listener interface and an adapter implementation.

The following classes show examples of listener and adapter pairs:

package java.awt.event
- ComputerListener/ComputerAdapter
- ContainerListener/ContainerAdapter
- FocusListener/FocusAdapter
- HierarchyBoundsListener/HierarchyBoundsAdapter
- KeyListener/KeyAdapter
- MouseListener/MouseAdapter
- MouseMotionListener/MouseMotionAdapter
- WindowListener/WindowAdapter

package java.awt.dnd
- DragSourceListener/DragSourceAdapter
- DragTargetListener/DragTargetAdapter

package javax.swing.event
- InternalFrameListener/InternalFrameAdapter
- MouseInputListener/MouseInputAdapter

 

These class pairs offer two ways to do the same thing. First, consider a simple example that doesn't offer an adapter class. The ActionListener class has a single actionPerformed method. Using an anonymous inner class, you will typically use an ActionListener class in the following manner:

ActionListener listener = new ActionListener() {
    public void actionPerformed(ActionEvent actionEvent) {
      System.out.println("Event happened");
    }
};
 

You can also use the actionPerformed method by implementing the ActionListener interface in a high-level class:

public class MyClass extends JFrame implements ActionListener {
    ...
    public void actionPerformed(ActionEvent actionEvent) {
      System.out.println("Event happened");
    }
}
 

The ActionListener interface has a single method, and implementers of the interface must provide an implementation of that single method for it to do much of anything.

Other listener interfaces aren't quite so simplistic. For example, the MouseMotionListener interface has two methods: mouseDragged and mouseMoved. When you implement an interface, you must implement all the methods defined by the interface:

MouseMotionListener listener = new MouseMotionListener() {
    public void mouseDragged(MouseEvent mouseEvent) {
      System.out.println("I'm dragging: " + mouseEvent);
    }
    public void mouseMoved(MouseEvent mouseEvent) {
      System.out.println("I'm moving: " + mouseEvent);
    }
};
 

There are situations when your application doesn't need to track all events for a particular listener interface. Maybe your code only needs to respond to one or two of the methods in a listener interface. For instance, do you really want to know when a mouse moves, or only when it moves with a mouse button depressed? You cannot implement just one of the MouseMotionListener methods and leave the others out:

MouseMotionListener badListener = new MouseMotionListener() {
    public void mouseDragged(MouseEvent mouseEvent) {
      System.out.println("I'm dragging: " + mouseEvent);
    }
};
 

This listener implementation will result in a compile-time error since an interface isn't fully implemented. With an interface like MouseMotionListener, that isn't too much of a problem, you just have to provide a stub for the method you aren't interested in:

MouseMotionListener listener = new MouseMotionListener() {
    public void mouseDragged(MouseEvent mouseEvent) {
      System.out.println("I'm dragging: " + mouseEvent);
    }
    public void mouseMoved(MouseEvent mouseEvent) {
      // Do nothing
    }
};
 

Not all listener interfaces are so small. Although MouseMotionListener has only two methods, the MouseListener interface has five:

  •  void mouseClicked(MouseEvent mouseEvent)
  •  void mouseEntered(MouseEvent mouseEvent)
  •  void mouseExited(MouseEvent mouseEvent)
  •  void mousePressed(MouseEvent mouseEvent)
  •  void mouseReleased(MouseEvent mouseEvent)
  •  

    If you want to add a MouseListener to a component, your interface implementation must have five methods in it:

    MouseListener mouseListener = new MouseListener() {
      public void mouseClicked(MouseEvent mouseEvent) {
          System.out.println("I'm clicked: " + mouseEvent);
      }
      public void mouseEntered(MouseEvent mouseEvent) {
          System.out.println("I'm entered: " + mouseEvent);
      }
      public void mouseExited(MouseEvent mouseEvent) {
          System.out.println("I'm exited: " + mouseEvent);
      }
      public void mousePressed(MouseEvent mouseEvent) {
          System.out.println("I'm pressed: " + mouseEvent);
      }
      public void mouseReleased(MouseEvent mouseEvent)  {
          System.out.println("I'm released: " + mouseEvent);
      }
    };
     

    If your application only needs to know whether the mouse is pressed or released over a component, the other three methods will be empty and ignored. Those methods are unnecessary code. The adapter classes can help reduce the amount of code you must write when your application needs only a small subset of all interface methods. Each adapter class fully implements its associated interface (or interfaces). Then, if you want a listener for a subset of associated methods, you just have to provide that subset. No empty stubs required. Here is just such an adapter for the required MouseListener previously described.

    MouseListener mouseListener = new MouseAdapter() {
      public void mousePressed(MouseEvent mouseEvent) {
          System.out.println("I'm pressed: " + mouseEvent);
      }
      public void mouseReleased(MouseEvent mouseEvent)  {
          System.out.println("I'm released: " + mouseEvent);
      }
    };
     

    This code still creates a MouseListener. However, instead of implementing all the interface methods that you don't care about, with the help of MouseAdapter, you only have to implement those MouseListener methods you are truly interested in.

    Not every multi-method listener has an adapter. You can certainly create your own if you constantly find your self stubbing out most of an interface. Of the built-in classes, only the listeners listed at the top of this tip offer them. Also, the adapters are true classes, not interfaces. If you want your custom JButton subclass to also implement MouseListener, you cannot have that class subclass MouseAdapter, as only single inheritance is allowed. For example, the following code causes a compilation-time error because it attempts to subclass both JButton and MouseAdapter:


    public class BadJButtonSubclass extends JButton, MouseAdapter {
       ...
      public void mousePressed(MouseEvent mouseEvent) {
          System.out.println("I'm pressed: " + mouseEvent);
      }
    }
     

    If you truly wanted this JButton subclass to be a MouseListener, you must explicitly say so, AND make sure all the methods of the interface are implemented:

    public class GoodJButtonSubclass extends JButton implements MouseListener {
      ...
      public void mouseClicked(MouseEvent mouseEvent) {
        // Do nothing
      }
      public void mouseEntered(MouseEvent mouseEvent) {
        // Do nothing
      }
      public void mouseExited(MouseEvent mouseEvent) {
        // Do nothing
      }
      public void mousePressed(MouseEvent mouseEvent) {
          System.out.println("I'm pressed: " + mouseEvent);
      }
      public void mouseReleased(MouseEvent mouseEvent)  {
        // Do nothing
      }
      ...
         addMouseListener(this);
      ...
    }
     

    Of course, you don't have to have your high-level class implement the interface itself. This may be a good example of when you should create the listener as an inner or anonymous class instead.

    If you use an integrated development environment (IDE) to create your user interface, the IDE will often generate the interface framework for you. You will need to code the business logic inside the necessary interface methods. An IDE can simplify the implementation of a large interface.

    For more information about this topic, read the How to Write a Mouse Listener lesson of The Java Tutorial.

    Adapters aren't limited to mouse listening. However, the MouseAdapter is a frequent example because the MouseListener interface has so many methods. The WindowListener interface is also another large interface, and it has an associated WindowAdapter class.

    The Need for BigDecimal

    Working with floating point numbers can be fun. Typically, when working with amounts, you automatically think of using a double type, unless the value is a whole number, then an int type is typically sufficient. A float or long can also work out, depending upon the size of a value. When dealing with money, though, these types are absolutely the worst thing you can use as they don't necessarily give you the right value, only the value that can be stored in a binary number format. Here is a short example that shows the perils of using a double for calculating a total, taking into account a discount, and adding in sales tax.

    The Calc program starts with an amount of $100.05, then gives the user a 10% discount before adding back 5% sale tax. Your sales tax percentage may vary, but this example will use 5%. To see the results, the class uses the NumberFormat class to format the results for what should be displayed as currency.

    import java.text.NumberFormat;

    public class Calc { 
      public static void main(String args[]) { 
        double amount = 100.05; 
        double discount = amount * 0.10; 
        double total = amount - discount; 
        double tax = total * 0.05; 
        double taxedTotal = tax + total; 
        NumberFormat money = NumberFormat.getCurrencyInstance(); 
        System.out.println("Subtotal : "+ money.format(amount)); 
        System.out.println("Discount : " + money.format(discount)); 
        System.out.println("Total : " + money.format(total)); 
        System.out.println("Tax : " + money.format(tax)); 
        System.out.println("Tax+Total: " + money.format(taxedTotal)); 
      } 
    }
     

    Using a double type for all the internal calculations produces the following results:

    Subtotal : $100.05 
    Discount : $10.00 
    Total : $90.04 
    Tax : $4.50
    Tax+Total: $94.55
     

    The Total value in the middle is what you might expect, but that Tax+Total value at the end is off. That discount should be $10.01 to give you that $90.04 amount. Add in the proper sales tax and the final total goes up a penny. The tax office won't appreciate that. The problem is rounding error. Calculations build on those rounding errors. Here are the unformatted values:

    Subtotal : 100.05 
    Discount : 10.005 
    Total : 90.045 
    Tax : 4.50225
    Tax+Total: 94.54725
     

    Looking at the unformatted values, the first question you might ask is why does 90.045 round down to 90.04 and not up to 90.05 as you might expect? (or why does 10.005 round to 10.00?) This is controlled by what is called the RoundingMode, an enumeration introduced in Java SE 1.6 that you had no control over in prior releases. The acquired NumberFormat for currencies has a default rounding mode of HALF_EVEN. This means that when the remaining value is equidistant to the edges, to round towards the even side. According to the Java platform documentation for the enumeration, this will statistically minimize cumulative errors after multiple calculations.

    The other available modes in the RoundingMode enumeration are:

    • CEILING which always rounds towards positive infinity
    • DOWN which always rounds towards zero
    • FLOOR which always rounds towards negative
    • UP which always rounds away from zero
    • HALF_DOWN which always rounds towards nearest neighbor, unless both neighbors are equidistant, in which case it rounds down
    • HALF_UP which always rounds towards nearest neighbor, unless both neighbors are equidistant, in which case it rounds up
    • UNNECESSARY which asserts exact result, with no rounding necessary

    Before looking into how to correct the problem, let us look at a slightly modified result, starting with a value of 70 cents, and offering no discount.

    Total : $0.70 
    Tax : $0.03 
    Tax+Total: $0.74
     

    In the case of the 70 cent transaction, it isn't just a rounding problem. Looking at the values without formatting, here's the output:

    Total : 0.7 
    Tax : 0.034999999999999996 
    Tax+Total: 0.735
     

    For the sales tax the value 0.035 just can't be stored as a double. It just isn't representable in binary form as a double.

    The BigDecimal class helps solve some problems with doing floating-point operations with float and double. The BigDecimal class stores floating-point numbers with practically unlimited precision. To manipulate the data, you call the add(value), subtract(value), multiply(value), or divide(value, scale, roundingMode) methods.

    To output BigDecimal values, set the scale and rounding mode with setScale(scale, roundingMode), or use either the toString() or toPlainString() methods. The toString() method may use scientific notation while toPlainString() never will.

    Before converting the program to use BigDecimal, it is important to point out how to create one. There are 16 constructors for the class. Since you can't necessarily store the value of a BigDecimal in a primitive object like a double, it is best to create your BigDecimal objects from a String. To demonstrate this error, here's a simple example:

      double dd = .35; 
      BigDecimal d = new BigDecimal(dd);
      System.out.println(".35 = " + d);
     

    The output is not what you might have expected:

      .35 = 0.34999999999999997779553950749686919152736663818359375
     

    Instead, what you should do is create the BigDecimal directly with the string ".35" as shown here:

      BigDecimal d = new BigDecimal(".35");
     

    Resulting in the following output:

      .35 = 0.35 
     

    After creating the value, you can explicitly set the scale of the number and its rounding mode with setScale(). Like other Number subclasses in the Java platform, BigDecimal is immutable, so if you call setScale(), you must "save" the return value:

      d = d.setScale(2, RoundingMode.HALF_UP);
     

    The modified program using BigDecimal is shown here. Each calculation requires working with another BigDecimal and setting its scale to ensure the math operations work for dollars and cents. If you want to deal with partial pennies, you can certainly go to three decimal places in the scale but it isn't necessarily.

    import java.math.BigDecimal; 
    import java.math.RoundingMode;

    public class Calc2 { 
      public static void main(String args[]) {
        BigDecimal amount = new BigDecimal("100.05"); 
        BigDecimal discountPercent = new BigDecimal("0.10"); 
        BigDecimal discount = amount.multiply(discountPercent); 
        discount = discount.setScale(2, RoundingMode.HALF_UP); 
        BigDecimal total = amount.subtract(discount);
        total = total.setScale(2, RoundingMode.HALF_UP); 
        BigDecimal taxPercent = new BigDecimal("0.05"); 
        BigDecimal tax = total.multiply(taxPercent); 
        tax = tax.setScale(2, RoundingMode.HALF_UP); 
        BigDecimal taxedTotal = total.add(tax);
        taxedTotal = taxedTotal.setScale(2, RoundingMode.HALF_UP);
        System.out.println("Subtotal : " + amount);
        System.out.println("Discount : " + discount);
        System.out.println("Total : " + total); 
        System.out.println("Tax : " + tax); 
        System.out.println("Tax+Total: " + taxedTotal); 
      } 
    }
     

    Notice that NumberFormat isn't used here, though you can add it back if you'd like to show the currency symbol.

    Now, when you run the program, the calculations look a whole lot better:

    Subtotal : 100.05 
    Discount : 10.01 
    Total : 90.04 
    Tax : 4.50 
    Tax+Total: 94.54
     

    BigDecimal offers more functionality than what these examples show. There is also a BigInteger class for when you need unlimited precision using whole numbers. The Java platform documentation for the two classes offers more details for the two classes, including more details on scales, the MathContext class, sorting, and equality.

    Developer Assistance

    Need programming advice on Java SE? Try Developer Expert Assistance.
     

    Rate and Review
    Tell us what you think of the content of this page.
    Excellent   Good   Fair   Poor  
    Comments:
    Your email address (no reply is possible without an address):
    Sun Privacy Policy

    Note: We are not able to respond to all submitted comments.

    Oracle is reviewing the Sun product roadmap and will provide guidance to customers in accordance with Oracle's standard product communication policies. Any resulting features and timing of release of such features as determined by Oracle's review of roadmaps, are at the sole discretion of Oracle. All product roadmap information, whether communicated by Sun Microsystems or by Oracle, does not represent a commitment to deliver any material, code, or functionality, and should not be relied upon in making purchasing decisions. It is intended for information purposes only, and may not be incorporated into any contract.