Take Care when Calling Legacy Code






Take Care when Calling Legacy Code

As we have seen, generic types are checked at compile time, not run time. Usually, this is just what we want, since checking at compile time reports errors earlier and incurs no run-time overhead. However, sometimes this may not be appropriate, either because we can't be sure that compile-time checks are adequate (say, because we are passing an instance of a parameterized type to a legacy client or to a client we don't trust), or because we need information about types at run time (say, because we want a reifiable type for use as an array component). A checked collection will often do the trick, and when that will not do, we can create a specialized class. We consider checked collections in this section, security issues in the next section, and specialized classes in the section after that.

Consider a legacy library that contains methods to add items to a given list and to return a new list containing given items:

class LegacyLibrary {
  public static void addItems(List list) {
    list.add(new Integer(1));  list.add("two");
  }
  public static List getItems() {
    List list = new ArrayList();
    list.add(new Integer(3));  list.add("four");
    return list;
  }
}

Now consider a client that uses this legacy library, having been told (incorrectly) that the items are always integers:

class NaiveClient {
  public static void processItems() {
    List<Integer> list = new ArrayList<Integer>();
    LegacyLibrary.addItems(list);
    List<Integer> list2 = LegacyLibrary.getItems(); // unchecked
    // sometime later ...
    int s = 0;
    for (int i : list) s += i; // class cast 
 
 exception
 
 
 

    for (int i : list2) s += i; // class cast exception
  }
}

There is no warning when passing the integer list to the method addItems, because the parameterized type List<Integer> is considered a subtype of List. The conversion from List to List<Integer> of the list returned by getItems does issue an unchecked warning. At run-time, a class cast exception will be raised when attempting to extract data from these lists, since the cast to type Integer implicitly inserted by erasure will fail. (The failure of these casts does not constitute a violation of the cast-iron guarantee, because this guarantee doesn't hold in the presence of legacy code or unchecked warnings.) Because the exception is raised far from the place where the strings are added to the lists, the bug may be hard to pinpoint.

If the legacy library has been generified by applying the minimal changes or stubs techniques (see Sections 5.4.1 and 5.4.2), then these problems cannot arise as long as generic types have been assigned correctly.

A less-naïve client may design code that catches the error earlier and is easier to debug.

class WaryClient {
  public static void processItems() {
    List<Integer> list = new ArrayList<Integer>();
    List<Integer> view = Collections.checkedList(list, Integer.class);
    LegacyLibrary.addItems(view);  // class cast exception
    List<Integer> list2 = LegacyLibrary.getItems(); // unchecked
    for (int i : list2) {}  // class cast exception
    // sometime later ...
    int s = 0;
    for (int i : list) s += i;
    for (int i : list2) s += i;
  }
}

The method checkedList in the convenience class Collections takes a list and a class token and returns a checked view of the list; whenever an attempt is made to add an element to the checked view, reflection is used to check that the element belongs to the specified class before adding it to the underlying list (see Section 17.3.3). Using a checked list view will cause a class cast exception to be raised inside the method addItems when it attempts to add a string to the list. Since the method getItems creates its own list, the client cannot use a wrapper in the same way. However, adding an empty loop at the point where the list is returned can guarantee that the error is caught close to the offending method call.

Checked lists provide useful guarantees only when the list elements are of a reifiable type. If you want to apply these techniques when the list is not of a reifiable type, you might want to consider applying the specialization technique of Section 8.3.



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows