Return-Path: Received: from po10.mit.edu ([unix socket]) by po10.mit.edu (Cyrus v2.1.5) with LMTP; Thu, 20 Feb 2003 19:30:29 -0500 X-Sieve: CMU Sieve 2.2 Received: from fort-point-station.mit.edu by po10.mit.edu (8.12.4/4.7) id h1L0USdX027580; Thu, 20 Feb 2003 19:30:28 -0500 (EST) Received: from hermes.sun.com (hermes.sun.com [64.124.140.169]) by fort-point-station.mit.edu (8.9.2/8.9.2) with SMTP id TAA07930 for ; Thu, 20 Feb 2003 19:30:27 -0500 (EST) Date: 20 Feb 2003 16:24:57 -0800 From: "JDC Tech Tips" To: alexp@mit.edu Message-Id: <305717631455463446@hermes.sun.com> Subject: Core Java Technologies Tech Tips (Choosing a Collections Framework Implementation, Providing a Scalable Image Icon) Mime-Version: 1.0 Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: 7bit X-Mailer: SunMail 1.0 X-Spam-Score: 2.2 X-Spam-Level: ** (2.2) X-Spam-Flag: NO X-Scanned-By: MIMEDefang 2.28 (www . roaringpenguin . com / mimedefang) Core Java Technologies Technical Tips
.
.
Core Java Technologies Technical Tips
.
   View this issue as simple text February 20, 2003    

In this Issue

Welcome to the Core JavaTM Technologies Tech Tips, February 20, 2003. 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:

.Choosing A Collections Framework Implementation
.Providing a Scalable Image Icon
.An Addition to Last Month's Tip on Reading Files From JARs

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

This issue of the Core Java Technologies Tech Tips is written by John Zukowski, president of JZ Ventures, Inc.

.
.

CHOOSING A COLLECTIONS FRAMEWORK IMPLEMENTATION

The Collections Framework has been a part of the Java 2 Platform, Standard Edition (J2SE) since version 1.2. The framework offers a series of classes, interfaces, and implementations for working with collections of data. You can produce different behaviors by choosing different implementations of the core interfaces. This tip examines some considerations in choosing the right implementation for your needs.

First, understand that to minimize the programming changes required when changing to a different interface implementation, your programs should typically access the interface, not the specific implementations. In other words, typically use:

   List list = new ArrayList();
   Set set = new HashSet();
   Map map = new TreeMap();

Don't use:

   ArrayList list = new ArrayList();
   HashSet set = new HashSet();
   TreeMap map = new TreeMap();

This allows you to change the collection implementation without affecting the rest of your code.

The key three interfaces of the framework are Set, List, and Map. A Set offers a collection of unique elements. A List provides ordered access (by index), but it doesn't guarantee uniqueness. The Map interface is different than the other two. Instead of just offering a collection of single elements, a Map provides a collection of key-value pairs. Based on a key, you find its value through a lookup operation. In other words, it's similar to checking for the phone number of a friend.

There are three concrete Set implementations that are part of the Collection Framework: HashSet, TreeSet, and LinkedHashSet. All three implementations provide a collection of unique elements. Typically, you use HashSet, which maintains its collection in an unordered manner. If this doesn't suit your needs, you can use TreeSet. A TreeSet keeps the elements in the collection in sorted order. For example, if a set of names (Mary, John, and Sam) are in a Set, a TreeSet iterates through the elements in an ordered fashion: John, Mary, and Sam. By comparison, the order of elements in a HashSet is undefined. Finally, there is LinkedHashSet. While HashSet has an undefined order for its elements, LinkedHashSet supports iterating through its elements in the order they were inserted. It maintains a secondary linked list to! manage insertion order. Understand that the additional features provided by TreeSet and LinkedHashSet add to the runtime costs.

The List interface offers two such concrete implementations: ArrayList and LinkedList. There is a third implementation, Vector, but it is part of the historical collection classes. (These are classes from an earlier release. They're provided as a retrofit into the collections framework, and are typically avoided with newer programs.) Of the newer classes, the ArrayList provides a collection backed by an array. It provides quick indexed access to its elements, and works best when elements are only added and removed at the end. To make this happen, ArrayList performs an internal move operation when an element is added or removed. By comparison, LinkedList is best when add and remove operations happen anywhere, not only at the end. LinkList doesn't do an internal move operation for an element insert or remove, it just manipulates reference pointers. But LinkedList'! s added flexibility comes at an added cost -- it results in much slower indexed operations. So if you frequently access random elements, for example, you often make requests like "give me the value of element N", using an ArrayList is better. But if you frequently add and remove elements from positions other than the end, LinkedList is better.

The final core interface is Map. There are five implementations of Map provided as part of the framework: HashMap, TreeMap, LinkedHashMap, IdentityHashMap, and WeakHashMap. There is also one historical implementation: Hashtable. While there are more implementations here, it's actually easier to choose the right implementation for your needs:

  • By default, choose HashMap. Typically, it serves the most needs.

  • Choose the TreeMap implementation if you need to maintain the keys of the map in a sorted order. However, if you only need the keys sorted when you are done with the Map or need to generate a report, sometimes it's better to simply keep everything in a HashMap while adding, and create a TreeMap at the end:

    Map map = new HashMap();
    // Add and remove elements from unsorted map
    map.put("Foo", "Bar");
    map.put("Bar", "Foo");
    map.remove("Foo");
    map.put("Foo", "Baz");
    // Then sort before displaying elements
    // in sorted order
    map = new TreeMap(map);
    This avoids the overhead of maintaining the map sort, and leaves the burden of sorting to the end.

  • LinkedHashMap works like the LinkedHashSet. It maintains the ability to traverse the map elements in insertion order. Typically, maps use the equals method to check if an element is in the Map.

  • IndentityHashMap uses == to check for equality of elements. Use IndentityHashMap in those (probably rare) cases where you need strict reference equality.

  • WeakHashMap is rarely used. It keeps its keys as weak references. A weak reference allows a program to maintain a reference to an object, but it doesn't prevent the object from being considered for reclamation by the garbage collector. If the only access to the key is through the weak reference, the garbage collector can get rid of the key-value entry. If you don't need weak references, avoid the WeakHashMap.

As is the case with Vector, the Hashtable is typically avoided in newer programs in favor of HashMap.

The pointers offered in this tip should help you choose a Collections Framework interface implementation. Although sometimes the best way to choose between two implementations is to run them both and compare the results. If you're unsure which of two implementations to choose, write a quick test program that manipulates each interface 10 or 20 thousand times. Then see which one works better for your specific requirements.

For more information about the Collections Framework, see "The Collections Framework."

.
.

PROVIDING A SCALABLE IMAGE ICON

The Icon interface in JFC/Swing provides a flexible way to include images with GUI components such as buttons and labels. By simply implementing the three methods in the interface, you can make anything be the image for a component.

  public interface Icon {
    public void paintIcon(Component c,
                          Graphics g,
                          int x,
                          int y);
    public int getIconWidth();
    public int getIconHeight();
  }

However there's a scalability problem here -- the width and height of the image must be fixed. The ImageIcon class provides an Icon implementation that works with Image files. If the size of the Image in the file isn't the size of the Icon you want, you must manually scale an Image before making the Icon.

This tip presents an Icon implementation that does the scaling for you. The implementation lets the image scale itself when it is time to paint the icon. That way, you can adjust what the scaling factors are without creating multiple Image or Icon objects.

There are two ways to scale an Image object. The Image class offers a getScaledInstance that lets you create a cached version of the Image for drawing:

  getScaledInstance(int width, int height, int hints)

Or you can scale the image "on the fly" at drawing time with the drawImage methods:

  drawImage(Image image, int x, int y, int width, 
      int height, ImageObserver observer) 
  drawImage(Image image, int x, int y, int width, 
      int height, Color bgcolor, ImageObserver observer) 

Let's first look at getScaledInstance. The getScaledInstance method works by taking a new size and a hint about how to do the scaling. There are five constants in the Image class that define how to do the scaling:

  • SCALE_DEFAULT to use the default algorithm
  • SCALE_FAST to favor speed over smoothness
  • SCALE_SMOOTH to favor smoothness over speed
  • SCALE_REPLICATE to use the ReplicateScaleFilter for scaling
  • SCALE_AREA_AVERAGING to use AreaAveragingScaleFilter for scaling

While there are five constants here, there are really only two settings. The SCALE_FAST constant offers the same behavior as SCALE_REPLICATE. The SCALE_SMOOTH constant offers the same behavior as SCALE_AREA_AVERAGING. And with Image being an abstract class, SCALE_DEFAULT provides the default of the two scaling mechanisms for a specific Image implementation.

What do the scaling operations do? SCALE_REPLICATE copies lines when scaling up, and removes lines when scaling down. SCALE_AREA_AVERAGING looks at neighboring pixels to get a smoother look when scaling in either direction.

It might seem sensible to scale an Image once with getScaledInstance, and repeatedly draw the scaled Image with a version of drawImage that doesn't do scaling. However, there are several factors that should be considered that actually favor doing on-the-fly scaling with the drawImage versions that scale. The technology behind on-the-fly scaling is continually improving, and as hardware acceleration through D3D and OpenGL becomes available, the performance difference approaches zero. The key factor is the extra memory required to cache scaled instances. With on-the-fly scaling, there is no caching. For a large image, multiple megabytes can be involved here. If you cache multiple images, memory usage adds up quickly. A third difference has to do with some implementation deficiencies in the Toolkit image code, but that isn't obvious from the sample application without "digging under the covers".

Here's a demonstration of an Icon that scales. The following ImageIconScalable class subclasses ImageIcon and adds a setScaledSize method that lets you change the size of the drawn Image. Use -1 for a width and height to reset the dimensions back to the original size. The bulk of the code offers constructors to match with those of ImageIcon.

import java.awt.*;
import javax.swing.*;
import java.net.*;

public class ImageIconScalable extends ImageIcon {

  int width = -1;
  int height = -1;

  public ImageIconScalable() {
    super();
  }

  public ImageIconScalable(byte imageData[]) {
    super(imageData);
  }
  
  public ImageIconScalable(byte imageData[],
                 String description) {
    super(imageData, description);
  }
  
  public ImageIconScalable(Image image) {
    super(image);
  }
  
  public ImageIconScalable(Image image,
                 String description) {
    super(image, description);
  }
  
  public ImageIconScalable(String filename) {
    super(filename);
  }
  
  public ImageIconScalable(String filename,
                 String description) {
    super(filename, description);
  }
               
  public ImageIconScalable(URL location) {
    super(location);
  }
  
  public ImageIconScalable(URL location,
                 String description) {
    super(location, description);
  }

  public int getIconHeight() {
    int returnValue;
    if (height == -1) {
      returnValue = super.getIconHeight();
    } else {
      returnValue = height;
    }
    return returnValue;
  }

  public int getIconWidth() {
    int returnValue;
    if (width == -1) {
      returnValue = super.getIconWidth();
    } else {
      returnValue = width;
    }
    return returnValue;
  }

  public void paintIcon(Component c,
                        Graphics g,
                        int x,
                        int y) {
    if ((width == -1) && (height == -1)) {
      g.drawImage(getImage(), x, y, c);
    } else {
      g.drawImage(getImage(), x, y, width, height, c);
    }
  }

  public void setScaledSize(int width,
                            int height) {
    this.width = width;
    this.height = height;
  }
}

To test the new scaling ability, the following test program takes an image file name from the command line and places it in a button. The display gives you the opportunity to make the size of the image larger or smaller.

IISTest

Select different size multipliers. Notice that the icon size changes without reloading the Image icon. One line worth mentioning specifically is the call to revalidate the button that contains the scalable ImageIcon:

    jbImage.revalidate();

Because the added setScaledSize method doesn't change any properties of the component associated with the icon, the program needs to manually notify the component that its state changed. This both redraws the component and revalidates the display. This affects the button size, because the width and height changed.

import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;

public class IISTest extends JFrame 
                    implements ActionListener {

  ImageIconScalable imageIcon;

  JButton    jbDiv2,
             jbDiv4,
             jbMult2,
             jbOrig,
             jbImage;

  public IISTest(String filename) {
    setTitle("IISTest");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    imageIcon = new ImageIconScalable( 
      getClass().getResource(filename));
    jbDiv2 = new JButton("Div2");
    jbDiv4 = new JButton("Div4");
    jbMult2 = new JButton("Mult2");
    jbOrig = new JButton("Original");
    jbImage = new JButton(imageIcon);

    jbDiv2.addActionListener(this);
    jbDiv4.addActionListener(this);
    jbMult2.addActionListener(this);
    jbOrig.addActionListener(this);
    JPanel jpNorth = new JPanel();
    jpNorth.add(jbDiv2);
    jpNorth.add(jbDiv4);
    jpNorth.add(jbMult2);
    jpNorth.add(jbOrig);

    JPanel jpCenter = new JPanel();
    jpCenter.add(jbImage);

    Container cp = getContentPane();
    cp.add(jpNorth, BorderLayout.NORTH);
    cp.add(jpCenter, BorderLayout.CENTER);
    setSize(400, 400);
    show();
  }

  public void doChange(
      int proportion, boolean multiply) {
    int width = imageIcon.getIconWidth();
    int height = imageIcon.getIconHeight();
    if (multiply) {
      imageIcon.setScaledSize(
          width * proportion, 
          height * proportion); 
    } else {
      imageIcon.setScaledSize(
          width / proportion, 
          height / proportion); 
    }
  }
    
  public void actionPerformed(ActionEvent ae) {
    JButton source = (JButton)ae.getSource();
    if (source.equals(jbDiv2)) {
      doChange(2, false);
    } else if(source.equals(jbDiv4)) {
      doChange(4, false);
    } else if(source.equals(jbMult2)) {
      doChange(2, true);
    } else if(source.equals(jbOrig)) {
      imageIcon.setScaledSize(-1, -1);
    }
    // Changing "icon" without changing icon
    // So, need to revalidate associated component(s)
    jbImage.revalidate();
  }

  public static void main(String args[]) {
    if (args.length != 1) {
      System.err.println(
         "Please provide an image file name to load");
    } else {
      new IISTest(args[0]);
    }
  }
}
.
.

AN ADDITION TO LAST MONTH's TIP ON READING FILES FROM JARS

The January 22, 2003 tip "Reading files from Java Archives (JARs)" discussed the use of the JarFile class to read files from within a JAR file. You should note that the JarFile class is useful for reading files from a JAR when the files are not in the class path. The JarFile class is also useful for reading files from a JAR when you want to specify the target JAR file. However, if the JAR file is in the class path, there is a simpler way of reading from it. You can use:

  URL url = ClassLoader.getSystemResource(name);

or

  InputStream stream = 
     ClassLoader.getSystemResourceAsStream(name);

These techniques allow you to read a file out of a JAR file that is located in your class path. You don't need to specify the JAR filename.

.
.
.

Reader Feedback

  Very worth reading    Worth reading    Not worth reading 

If you have other comments or ideas for future technical tips, please type them here:

 

Have a question about JavaTM programming? Use Java Online Support.

.
.

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 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 (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 2003 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/jdc/copyright.html


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 unsubscribe me from this newsletter.