Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 150 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
150
Dung lượng
1,94 MB
Nội dung
comparing objects. Making the object type implement an interface that declares a method that compares objects is the way to do this. A binary tree implementation also provides an example of a situation where you can apply the power of recursion to very good effect. Defining the Generic Type You can come to some general conclusions about what the characteristics of your BinaryTree<> class are going to be by considering how it will work. Objects of type Node are going to be a fundamental part of a binary tree. The Node objects in the tree are really part of the inner workings of the container so they don’t need to be known about or accessible externally. It is therefore appropriate if you define Node objects by a private inner class to the BinaryTree<> class. All nodes in a binary tree must be different, but you can allow duplicate data items to be added to a tree by providing for a count of the number of identical objects to be recorded in a Node object. Obviously, as a minimum, a BinaryTree<> object will have to store the Node object that is the root node for the tree and provide a method for adding new nodes. It’ll also need a method for returning all the data that was stored in the tree in sequence, so you need some facility for packaging this. The generic LinkedList<> type from the previous example pro- vides a convenient facility for this. The type for objects that can be added to the tree must have a method for comparing them. The Comparable<> interface that is defined in the java.lang package declares a single method, the compareTo() method, that will fit the bill. The compareTo() method returns a negative integer if the object for which it is called is less than the argument to the method, 0 if it equals the argument, and a positive integer if it is greater, so it does precisely what you need for placing new values in a BinaryTree<> class object. If you specify the Comparable<> interface as a constraint on the type parameter for the BinaryTree<> class, it ensures that all objects added to a BinaryTree<> object implement the compareTo() method. Because the Comparable<> interface is defined as a parameter- ized type, it fits exactly with what you want here. Here’s a first stab at outlining the BinaryTree<> generic type: public class BinaryTree<T extends Comparable<T>> { // Add a value to the tree public void add(T value) { // Add a value to the tree } // Create a list containing the values from the tree in sequence public LinkedList<T> sort() { // Code to extract object from the tree in sequence // and insert then in a LinkedList object and return that } LinkedList<T> values; // Stores sorted values private Node root; // The root node // Private inner class defining nodes private class Node { Node(T value) { obj = value; count = 1; } 571 Generic Class Types 16_568744 ch13.qxd 11/23/04 9:32 PM Page 571 T obj; // Object stored in the node int count; // Count of identical nodes Node left; // The left child node Node right; // The right child node } } No BinaryTree<> constructor is defined because the default constructor suffices. The default no-arg constructor creates an object with the root node as null. Thus, all objects are added to the tree by calling the add() method. The sort() method returns a LinkedList<> object that it creates, containing the objects that were stored in the tree in ascending sequence. The inner Node class has four fields that store the value, the count of the number of values identical to this, and references to its left and right child nodes. The constructor just initializes the obj and count fields in the Node object that is created, leaving left and right with their default values of null. Of course, when a Node object is first created, it won’t have any child nodes, and the count of identical objects in the tree will be 1. Let’s look at how objects will be inserted into a tree. Inserting Objects in a Binary Tree It’s easy to see how adding an object to the tree can be a recursive operation in general. The process is illustrated in Figure 13-3. Figure 13-3 New Node to be inserted: Check the root node(546): It's less so the left child fits Check the node(244): It's less so the left child fits Check the node(54): It's greater so the right child fits Check the node(59): It's less so the left child fits No child node so insert the new code: 546 244 622 54 379 37 57 59 57 630 420 Root Node Success! 572 Chapter 13 16_568744 ch13.qxd 11/23/04 9:32 PM Page 572 The shaded nodes in Figure 13-3 are the ones that have to be considered in the process of inserting the value 57 in the tree. To find where the new node for an object should be placed in relation to the existing nodes, you’ll start with the root node to see which of its child nodes represents a potential position for the new node. If the candidate child node that you choose already exists, then you must repeat the pro- cess you’ve just gone through with the root node with the chosen child node. Of course, this child node may itself have child nodes so the process may need to be repeated again. You should be able to visual- ize how this can continue until either you find a child node that contains an object that is identical to the one contained in the new node or you find a vacant child node position where the new node fits. You can implement the add() method in the BinaryTree<> generic type definition like this: public void add(T value) { if(root == null) { // If there’s no root node root = new Node(value); // store it in the root } else { // Otherwise add(value, root); // add it recursively } } If the root node is null, the add() method creates a new root node containing the value to be inserted. If root is not null, then the node where it fits in the tree must be found, and this is the function per- formed by another version of the add() method that accepts two arguments specifying the value to be inserted into the tree and the node where it might be inserted. The second argument allows the method to be called recursively. This method can be private as it does not need to be accessed externally. You could implement it like this: private void add(T value, Node node) { int comparison = node.obj.compareTo(value); if(comparison == 0) { // If it is equal to the current node ++node.count; // just increment the count return; } if(comparison > 0) { // If it’s less than the current node if(node.left == null) { // and the left child node is not null node.left = new Node(value); // Store it as the left child node } else { // Otherwise add(value, node.left); // add it to the left node } } else { // It must be greater than the current node if(node.right == null) { // so it must go to the right node.right = new Node(value); } else { add(value, node.right); } } } This method is called only with a non-null second argument. The first step is to compare the object to be inserted, which is given by the first argument, value, with the object stored in the current node, spec- ified by the second argument. If the new object equals the one stored in the current node, you need to update the count only for the current node and you are done. 573 Generic Class Types 16_568744 ch13.qxd 11/23/04 9:32 PM Page 573 If the new object is not equal to that stored in the current node, you first check whether it’s less. Remember: The compareTo() method returns a positive integer when the object for which it is called is greater than the argument, so the value of comparison being positive means that the new object is less than that in the current node. That makes it a candidate for the left child node of the current node, but only if the left child node is null. If the left child node is not null, you call the add() method recursively to add the object relative to the left node. You’ve tested for zero and positive values of comparison, so the only other possibility is that the comparison value is negative. In this case you repeat the same procedure, but with the right child node. This process finds the place for the new node containing the inserted object so that each node has only a left child that is less than the current node and a right child that is greater. In fact, for any node, the values stored in the whole left subtree will be less than the current node, and the values in the whole right subtree will be greater. Now that you’ve got them in, you have to figure out how you’re going to get them out again. Extracting Objects from the Binary Tree Calling the sort() method for a BinaryTree<> object will return a LinkedList<> object containing the objects from the tree in ascending sequence. The process for selecting the objects to be inserted into the linked list is also recursive. You can define the sort() method like this: public LinkedList<T> sort() { values = new LinkedList<T>(); // Create a linked list treeSort(root); // Sort the objects into the list return values; } The LinkedList<> object is a field in the BinaryTree<> object and the sort() method eventually returns a reference to it. You create a new LinkedList<> object each time to hold the sorted values of type T from the tree. The sort() method could be called several times for a BinaryTree<> object, with the contents of the binary tree being changed in the intervening period, so you must be sure you create the linked list from scratch each time. The real work of inserting the objects from the tree into the linked list values is going to be done by the recursive treeSort() method. You can get an inkling of how this will work if you recall that the left child node object of every node will be less than the current node, which will be less than the right child node. Therefore, you want to access the objects in the sequence: left child node - node - right child node Of course, the child nodes may themselves have child nodes, but the same applies to them. Take the left child node, for example. The objects here should be accessed in the sequence: left child of left child node - left child node - right child of left child node The same goes for the right child node and its children. All you have to do is express this as code, and you can do that like this: private void treeSort(Node node) { if(node != null) { // If the node isn’t null treeSort(node.left); // process its left child // List the duplicate objects for the current node for(int i = 0 ; i<node.count ; i++) { values.addItem(node.obj); } 574 Chapter 13 16_568744 ch13.qxd 11/23/04 9:32 PM Page 574 treeSort(node.right); // Now process the right child } } If the node that is passed to the treeSort() method is null, nothing further is left to do so the method returns. If the argument is not null, you process the left child node, then the node that was passed as the argument, then the right child node—just as you saw earlier. That does it all. The actual insertion of an object into the linked list always occurs in the for loop. This loop typically executes one iteration because most of the time, no duplicate objects are in the tree. The value of the left child node, if it exists, is always added to the linked list before the value of the current node because you don’t add the value from the current node until the treeSort() method call for the left child returns. Similarly, the value from the right child node will always be added to the linked list after that of the current node. You’re ready to give the BinaryTree<> generic type a whirl. Try It Out Sorting Using a Binary Tree You’ll need to create a directory to hold the three source files for this program. When you’ve set that up, copy the LinkedList.java source file from the previous example to the new directory. You can then add the BinaryTree.java source file containing the following code to the directory: public class BinaryTree<T extends Comparable<T>> { // Add a value to the tree public void add(T value) { if(root == null) { // If there’s no root node root = new Node(value); // store it in the root } else { // Otherwise add(value, root); // add it recursively } } // Recursive insertion of an object private void add(T value, Node node) { int comparison = node.obj.compareTo(value); if(comparison == 0) { // If it is equal to the current node ++node.count; // just increment the count return; } if(comparison > 0) { // If it’s less than the current node if(node.left == null) { // and the left child node is not null node.left = new Node(value); // Store it as the left child node } else { // Otherwise add(value, node.left); // add it to the left node } } else { // It must be greater than the current node if(node.right == null) { // so it must go to the right node.right = new Node(value); } else { add(value, node.right); } } } 575 Generic Class Types 16_568744 ch13.qxd 11/23/04 9:32 PM Page 575 // Create a list containing the values from the tree in sequence public LinkedList<T> sort() { values = new LinkedList<T>(); // Create a linked list treeSort(root); // Sort the objects into the list return values; } // Extract the tree nodes in sequence private void treeSort(Node node) { if(node != null) { // If the node isn’t null treeSort(node.left); // process its left child // List the duplicate objects for the current node for(int i = 0 ; i<node.count ; i++) { values.addItem(node.obj); } treeSort(node.right); // Now process the right child } } LinkedList<T> values; // Stores sorted values private Node root; // The root node // Private inner class defining nodes private class Node { Node(T value) { obj = value; count = 1; } T obj; // Object stored in the node int count; // Count of identical nodes Node left; // The left child node Node right; // The right child node } } You can try out sorting integers and strings using BinaryTree<> objects with the following code: public class TryBinaryTree { public static void main(String[] args) { int[] numbers = new int[30]; for(int i = 0 ; i<numbers.length ; i++) { numbers[i] = (int)(1000.0*Math.random()); // Random integers 0 to 999 } // List starting integer values int count = 0; System.out.println(“Original values are:”); for(int number : numbers) { System.out.printf(“%6d”, number); if(++count%6 == 0) { System.out.println(); 576 Chapter 13 16_568744 ch13.qxd 11/23/04 9:32 PM Page 576 } } // Create the tree and add the integers to it BinaryTree<Integer> tree = new BinaryTree<Integer>(); for(int number:numbers) { tree.add(number); } // Get sorted values LinkedList<Integer> values = tree.sort(); count = 0; System.out.println(“\nSorted values are:”); for(Integer value : values) { System.out.printf(“%6d”, value); if(++count%6 == 0) { System.out.println(); } } // Create an array of words to be sorted String[] words = {“vacillate”, “procrastinate”, “arboreal”, “syzygy”, “xenocracy”, “zygote” , “mephitic”, “soporific”, “grisly” , “gristly” }; // List the words System.out.println(“\nOriginal word sequence:”); for(String word : words) { System.out.printf(“%-15s”, word); if(++count%5 == 0) { System.out.println(); } } // Create the tree and insert the words BinaryTree<String> cache = new BinaryTree<String>(); for(String word : words) { cache.add(word); } // Sort the words LinkedList<String> sortedWords = cache.sort(); // List the sorted words System.out.println(“\nSorted word sequence:”); count = 0; for(String word : sortedWords) { System.out.printf(“%-15s”, word); if(++count%5 == 0) { System.out.println(); } } } } 577 Generic Class Types 16_568744 ch13.qxd 11/23/04 9:32 PM Page 577 The output should be along the lines of the following: Original values are: 110 136 572 589 605 832 565 765 514 616 347 724 152 527 124 324 42 508 621 653 480 236 1 793 324 31 127 170 724 546 Sorted values are: 1 31 42 110 124 127 136 152 170 236 324 324 347 480 508 514 527 546 565 572 589 605 616 621 653 724 724 765 793 832 Original word sequence: vacillate procrastinate arboreal syzygy xenocracy zygote mephitic soporific grisly gristly Sorted word sequence: arboreal grisly gristly mephitic procrastinate soporific syzygy vacillate xenocracy zygote How It Works You have defined BinaryTree<> as a parameterized type with a type parameter that is constrained to implement the parameterized interface type Comparable<>. Thus, a type argument that you use with the BinaryTree<> generic type must implement the Comparable<> interface. If it doesn’t, the code won’t compile. This ensures that all objects added to a BinaryTree<> object will have the compareTo() function available. The definition for BinaryTree<> also demonstrates that a generic type definition can include a field of another generic type — LinkedList<> in this instance. The LinkedList<> field type is determined by the type argument supplied to the BinaryTree<> generic type. After creating and displaying an array of 30 random integer values, you define a BinaryTree<Integer> object that will store objects of type Integer. The following statement does this: BinaryTree<Integer> tree = new BinaryTree<Integer>(); You then insert the integers into the binary tree in a loop: for(int number:numbers) { tree.add(number); } The parameter type for the add() method will be type Integer, but autoboxing automatically takes care of converting your arguments of type int to objects of type Integer. Calling the sort() method for the BinaryTree object, values, returns the objects from the tree con- tained in a LinkedList object: LinkedList<Integer> values = tree.sort(); 578 Chapter 13 16_568744 ch13.qxd 11/23/04 9:32 PM Page 578 The Integer objects in the linked list container are ordered in ascending sequence. You list these in a for loop: for(Integer value : values) { System.out.printf(“%6d”, value); if(++count%6 == 0) { System.out.println(); } } You are able to use the collection-based for loop here because the LinkedList<> type implements the Iterable<> interface; this is the sole prerequisite on a container for it to allow you to apply this for loop to access the elements. Just to demonstrate that BinaryTree<> works with more types than just Integer, you create an object of type BinaryTree<String> that you use to store a series of String objects that are words. You use essentially the same process as you used with the integers to obtain the words sorted in ascending sequence. Note the use of the ‘-’ flag in the format specifier for the strings in the first argument to the printf() method. This outputs the string left-justified in the output field, which makes the output of the strings look tidier. Hidden Constraints in the BinaryTree<> Type So the BinaryTree<> class works well then? Well, not as well as it might. The parameterized type has a built-in constraint that was not exposed by the examples storing String and Integer objects. Suppose you define a Person class like this: public class Person implements Comparable<Person> { public Person(String name) { this.name = name; } public int compareTo(Person person) { if( person == this) { return 0; } return this.name.compareTo(person.name); } public String toString() { return name; } protected String name; } This is a simple class representing a person. It implements the Comparable<> interface so you can use a BinaryTree<Person> object to store and sort objects of type Person. This will work just as well as the BinaryTree<String> and BinaryTree<Integer> examples. 579 Generic Class Types 16_568744 ch13.qxd 11/23/04 9:32 PM Page 579 However, you might possibly subclass the Person type like this: public class Manager extends Person { public Manager(String name, int level) { super(name); this.level = level; } public String toString() { return “Manager “+ super.toString() + “ level: “ + level; } protected int level; } This class defines a special kind of Person —a manager no less! You have just one extra field specifying the level that reflects where the manager sits in the corporate pecking order. You also have a version of the toString() method that presents the Person as a manager with his or her level. The class inherits the implementation of the Comparable<> interface from the base class. If that’s sufficient for differenti- ating two persons, it should be okay for separating two managers. However, it’s not good enough for the BinaryTree<> type. You could try adding Manager objects to a binary tree like this: BinaryTree<Manager> people = new BinaryTree<Manager>(); Manager[] managers = { new Manager(“Jane”,1), new Manager(“Joe”,3), new Manager(“Freda”,3)}; for(Manager manager: managers){ people.add(manager); } However, it doesn’t work. If you insert this fragment at the end of main() in the previous example, you’ll get a compiler error message relating to the statement that creates the BinaryTree<Manager> object; it’ll say something along the lines of “type parameter Manager is not within its bound.” The problem is that your BinaryTree<> class requires that the Manager class itself should implement the Comparable<Manager> interface. The inherited implementation of Comparable<Person> is not acceptable. Obviously, this is a serious constraint. You don’t want the binary tree implementation to be as rigid as that. As long as there’s an implementation of Comparable<> in a class that allows objects to be compared, that should suffice. What you really want is for your BinaryTree<> generic type to accept any type argument that is of a type that implements Comparable<> for the type itself or for any super- class of the type. You don’t have the tools to deal with this at this point, but I’ll return to the solution of this problem a little later in this chapter. Variables of a Raw Type You have seen that the run-time type of all instances of a generic type is the same and is just the generic type name without any parameters. You can use the generic type name by itself to define variables. For example: LinkedList list = null; 580 Chapter 13 16_568744 ch13.qxd 11/23/04 9:32 PM Page 580 [...]... BinaryTree .java and LinkedList .java source files to the directory containing TryWildcardArray .java When you compile this program, you will get two warnings from the compiler from the statements that involve explicit casts The output will be similar to that from the previous example The sorted lists of values will be output one value per line because that’s how the listAll() method displays them 59 0 Generic... type such as Person or java. lang.String in your application is produced by supplying the type as the argument for the generic type parameter, so of type Class and Class in these two instances Because these class types are produced from a generic type, many of the methods have parameters or return types that are specifically the type argument — Person or java. lang.String in the... words System.out.println(“\nOriginal word sequence:”); for(String word : words) { System.out.printf(“%-15s”, word); if(++count %5 == 0) { System.out.println(); } } // Create the tree and insert the words BinaryTree cache = new BinaryTree(); for(String word : words) { cache.add(word); } 58 3 Chapter 13 // Sort the words LinkedList sortedWords = cache.sort(); // List the sorted words... definition of the no-arg constructor, which is not supplied by the compiler when you explicitly define a constructor of your own Put this source file in a new directory and copy the LinkedList .java, Person .java, and Manager .java files from the previous example to this directory You can add the following source file to try out the parameterized constructor: public class TryParameterizedConstructor { public... accessing each of the elements in a collection It is worth noting at this point that Java also provides something called an enumerator that is defined by any class that implements the java. util.Enumeration generic interface type An enumerator provides essentially the same capability as an iterator, but it is recommended in the Java documentation that you use an iterator in preference to an enumerator for... documentation relating to the Class generic type that comes with the JDK Arrays and Parameterized Types Arrays of elements that are of a specific type produced from a generic type are not allowed For example, although it looks quite reasonable, the following statement is not permitted and will result in a compiler error message: 58 8 Chapter 13 System.out.printf(“%6d”, number); if(++count%6 == 0) {... “syzygy”, “xenocracy”, “zygote”, “mephitic”, “soporific”, “grisly”, “gristly” }; // List the words System.out.println(“\nOriginal word sequence:”); for(String word : words) { System.out.printf(“%-15s”, word); if(++count %5 == 0) { System.out.println(); } } // Insert the words into second tree for(String word : words) { ((BinaryTree)trees[1]).add(word); } // Sort the values in both trees for(int i =... a static method to do this using a constraint on the wildcard type specification: public static void saveAll(LinkedList . 136 57 2 58 9 6 05 832 56 5 7 65 514 616 347 724 152 52 7 124 324 42 50 8 621 653 480 236 1 793 324 31 127 170 724 54 6 Sorted values are: 1 31 42 110 124 127 136 152 170 236 324 324 347 480 50 8 51 4 52 7. fits No child node so insert the new code: 54 6 244 622 54 379 37 57 59 57 630 420 Root Node Success! 57 2 Chapter 13 16 _56 8744 ch13.qxd 11/23/04 9:32 PM Page 57 2 The shaded nodes in Figure 13-3 are. values are: 1 31 42 110 124 127 136 152 170 236 324 324 347 480 50 8 51 4 52 7 54 6 56 5 57 2 58 9 6 05 616 621 653 724 724 7 65 793 832 Original word sequence: vacillate procrastinate arboreal syzygy xenocracy