JAVA programming ideology-set types
12. JAVA programming ideology-set types
The standard Java 1.0 and 1.1 libraries provide a series of collection classes. But they are basically competent for most of their programming requirements. Java 1.2 provides a redesigned set of large collection libraries.
1 Vector
Vector is easy to use. In most cases, you only need to use addElement () to insert an object, use elementAt () to extract an object at a time, and use elements () to obtain an "enumeration" of the sequence ". However, there are still a series of other methods that are very useful.
Java standard sets contain the toString () method, so they can generate their own String expressions, including the objects they hold.
In the Vector, toString () will step and traverse each element of the Vector, and call toString () for each element (). Suppose you want to print the address of your class. It seems that you can simply reference this (especially C ++ programmers tend to do this ):
1.1 The Code is as follows:
import java.util.*;publicclass CrashJava {public String toString() {return"CrashJava address: " + this +"\n";}publicstaticvoidmain(String[]args){Vector v = new Vector();for (inti = 0; i < 10;i++)v.addElement(new CrashJava());System.out.println(v);}}
1.2 output
Exceptionin thread "main" java.lang.StackOverflowErrorat java.lang.StringBuilder.append(UnknownSource)atjava.lang.StringBuilder.(Unknown Source)at CrashJava.toString(CrashJava.java:5)at java.lang.String.valueOf(Unknown Source)at java.lang.StringBuilder.append(UnknownSource)at CrashJava.toString(CrashJava.java:5)at java.lang.String.valueOf(Unknown Source)at java.lang.StringBuilder.append(UnknownSource)at CrashJava.toString(CrashJava.java:5)at java.lang.String.valueOf(Unknown Source)at java.lang.StringBuilder.append(UnknownSource)at CrashJava.toString(CrashJava.java:5)at java.lang.String.valueOf(Unknown Source)at java.lang.StringBuilder.append(UnknownSource)at CrashJava.toString(CrashJava.java:5)at java.lang.String.valueOf(Unknown Source)at java.lang.StringBuilder.append(UnknownSource)at CrashJava.toString(CrashJava.java:5)at java.lang.String.valueOf(Unknown Source)at java.lang.StringBuilder.append(UnknownSource)at CrashJava.toString(CrashJava.java:5)at java.lang.String.valueOf(Unknown Source)at java.lang.StringBuilder.append(UnknownSource)at CrashJava.toString(CrashJava.java:5)at java.lang.String.valueOf(Unknown Source)at java.lang.StringBuilder.append(UnknownSource)at CrashJava.toString(CrashJava.java:5)at java.lang.String.valueOf(Unknown Source)at java.lang.StringBuilder.append(UnknownSource)at CrashJava.toString(CrashJava.java:5)at java.lang.String.valueOf(Unknown Source)at java.lang.StringBuilder.append(UnknownSource)at CrashJava.toString(CrashJava.java:5)at java.lang.String.valueOf(Unknown Source)at java.lang.StringBuilder.append(UnknownSource)at CrashJava.toString(CrashJava.java:5)at java.lang.String.valueOf(Unknown Source)at java.lang.StringBuilder.append(UnknownSource)at CrashJava.toString(CrashJava.java:5)at java.lang.String.valueOf(Unknown Source)at java.lang.StringBuilder.append(UnknownSource)at CrashJava.toString(CrashJava.java:5)at java.lang.String.valueOf(Unknown Source)at java.lang.StringBuilder.append(UnknownSource)at CrashJava.toString(CrashJava.java:5)at java.lang.String.valueOf(Unknown Source)at java.lang.StringBuilder.append(UnknownSource)at CrashJava.toString(CrashJava.java:5)at java.lang.String.valueOf(Unknown Source)at java.lang.StringBuilder.append(UnknownSource)at CrashJava.toString(CrashJava.java:5)at java.lang.String.valueOf(Unknown Source)at java.lang.StringBuilder.append(UnknownSource)at CrashJava.toString(CrashJava.java:5)at java.lang.String.valueOf(Unknown Source)at java.lang.StringBuilder.append(UnknownSource)at CrashJava.toString(CrashJava.java:5)at java.lang.String.valueOf(Unknown Source)at java.lang.StringBuilder.append(UnknownSource)at CrashJava.toString(CrashJava.java:5)at java.lang.String.valueOf(Unknown Source)at java.lang.StringBuilder.append(Unknown Source)
If you simply create a CrashJava object and print it out, you will get an endless series of violation errors. However, if we put the CrashJava object into a Vector and print the Vector as demonstrated here, there will be no error messages, or even a violation. In this case, Java just crashes (but at least it does not crash my operating system ).
When we use the following statement:
"CrashJava address:" + this
The compiler finds a "+" behind a string and something that does not seem to be a string, so it will try to convert this into a string. ToString () is called during conversion, and the latter generates a recursive call. If this happens in a Vector, it seems that the stack will overflow, and the violation control mechanism has no chance to respond.
If you want to print the Object address in this case, the solution is to call the toString method of the Object. In this case, you do not need to add this. You only need to use super. toString (). Of course, there is also a premise to take this approach: we must directly inherit from the Object, or no parent class overwrites the toString method.
2 B I t S e t
BitSet is actually a Vector composed of binary bits. If you want to efficiently store a large amount of "On-Off" information, you should use BitSet. It makes sense only from the perspective of size; if you want efficient access, it will be slower than using some inherent types of arrays.
In addition, the minimum length of a BitSet is the length of a Long integer (Long): 64 bits. This means that if we want to save data smaller than this, such as 8-bit data, BitSet will be wasted. Therefore, it is best to create your own class and use it to hold your own flag space.
In a normal Vector, as we add more and more elements, the set will expand itself. To some extent, BitSet is no exception. Sometimes it will expand itself, sometimes it will not. In addition, Java 1.0 seems to have done the worst in this regard, and its BitSet performance is very unsatisfactory (Java1.1 has corrected this problem ).
2.1 The Code is as follows:
import java.util.*;publicclass Bits {publicstaticvoidmain(String[]args){Random rand =new Random();// Take the LSB of nextInt():bytebt = (byte)rand.nextInt();BitSet bb =new BitSet();for (inti = 7; i >= 0;i--)if (((1 <bb.set(i);elsebb.clear(i);System.out.println("byte value: " +bt);printBitSet(bb);shortst = (short)rand.nextInt();BitSet bs =new BitSet();for (inti = 15; i >= 0;i--)if (((1 <bs.set(i);elsebs.clear(i);System.out.println("short value: " +st);printBitSet(bs);intit = rand.nextInt();BitSet bi =new BitSet();for (inti = 31; i >= 0;i--)if (((1 <bi.set(i);elsebi.clear(i);System.out.println("int value: " +it);printBitSet(bi);// Test bitsets >= 64 bits:BitSet b127 =new BitSet();b127.set(127);System.out.println("set bit 127: " +b127);BitSet b255 =new BitSet(65);b255.set(255);System.out.println("set bit 255: " +b255);BitSet b1023 =new BitSet(512);// Without the following, an exception is thrown// in the Java 1.0 implementation of BitSet:// b1023.set(1023);b1023.set(1024);System.out.println("set bit 1023: " +b1023);} staticvoidprintBitSet(BitSet b){System.out.println("bits: " +b);String bbits =new String();for (intj = 0; j < b.size(); j++)bbits += (b.get(j) ?"1" : "0");System.out.println("bit pattern: " +bbits);}} /// :~
2.2 output:
bytevalue: 96bits:{5, 6}bitpattern: 0000011000000000000000000000000000000000000000000000000000000000shortvalue: 28482bits:{1, 6, 8, 9, 10, 11, 13, 14}bitpattern: 0100001011110110000000000000000000000000000000000000000000000000intvalue: -1121710154bits:{1, 2, 4, 5, 7, 8, 9, 10, 11, 18, 21, 24, 26, 27, 28, 29, 31}bitpattern: 0110110111110000001001001011110100000000000000000000000000000000setbit 127: {127}setbit 255: {255}set bit 1023: {1024}
The random number generator is used to create a random byte, short, and int. Each one is converted into a BitSet BIT model. At this time, everything is normal, because BitSet is 64-bit, so they do not cause the final size increase. However, in Java 1.0, once BitSet is greater than 64 bits, some confusing behaviors may occur. If we set a single bit larger than the one currently allocated by the BitSet storage space, it can be expanded normally. However, once you try to set a bit at a higher position without first touching the boundary, you will get an annoying violation. This is because BitSet cannot be correctly extended in Java 1.0. In this example, a BitSet with 512 bits is created. The storage space allocated by the builder is twice the number of digits. Therefore, if you set a bit of 1024 or higher without setting a bit of 1023 first, an violation will be obtained in Java. Fortunately, this problem has been corrected in Java 1.1. If you are writing code for Java 1.0, avoid using BitSet whenever possible.
3 S t a c k
Stack can also be called a "post-in-first-out" (LIFO) set. In other words, the last "push" in the stack will be the first "pop-up" in the future. Like all other Java collections, the push and pop-up operations are "objects", so you must "shape" the pop-up items ". A rare practice is to reject the use of Vector as the basic component element of a Stack, but to "inherit" a Stack from the Vector. In this way, it has all the features and behaviors of a Vector, and some additional Stack behaviors. It is difficult to determine whether the designer wants to do so or is an inherent design.
3.1 The Code is as follows:
import java.util.*; publicclass Stacks {static String[]months = { "January", "February", "March","April","May","June","July","August","September","October","November","December" };publicstaticvoidmain(String[]args){Stack stk = new Stack();for (inti = 0; i < months.length;i++)stk.push(months[i] +" ");System.out.println("stk = " +stk);// Treating a stack as a Vector:stk.addElement("The last line");System.out.println("element 5 = " +stk.elementAt(5));System.out.println("popping elements:");while (!stk.empty())System.out.println(stk.pop());}}
3.2 output:
stk= [January , February , March , April , May , June , July , August , September, October , November , December ]element5 = Junepoppingelements:Thelast lineDecemberNovemberOctoberSeptemberAugustJulyJuneMayAprilMarchFebruaryJanuary
Each row of the months array is inherited into the stack through push (), and is removed from the top of the stack using pop () later. One point to declare is that the Vector operation can also be performed on Stack objects. This may be determined by the inherited feature-Stack "belongs" to a Vector. Therefore, operations that can be performed on the Vector can also be performed on the Stack, such as the elementAt () method.
4 H a s h t a B l e
Vector allows us to use a number to select from a series of objects, so it actually associates numbers with objects. But what if we want to select a series of objects based on other criteria? Stack is such an example: its selection criteria are "Last thing pushed into the stack ".
This"Select from a series of objectsThe concept can also be called"Ing","Dictionary"Or"Join Array". In concept, it looks like a Vector, but instead of looking for objects through numbers, it uses another object to look for them! This is usually an important process in a program.
In Java, this concept is embodied in the abstract class Dictionary. The interface of this class is very intuitive. size () tells us how many elements are contained in it; isEmpty () determines whether it contains elements (true if yes); put (Object key, object value) Add a value (what we want) and associate it with the same key (what we want to search for); get (Object key) get the value corresponding to a key, and remove (ObjectKey) is used to delete the "key-value" pair from the list. You can also use Enumeration technology: keys () generates an Enumeration for the key, while elements () generates an Enumeration for all values. This is the whole of a Dict ionary (dictionary.
The implementation process of Dictionary is not troublesome. A simple method is listed below. It uses two vectors, one for holding the key and the other for holding the value:
4.1 code 1 is as follows:
import java.util.*;publicclass AssocArray extendsDictionary{privateVector keys = newVector();privateVector values = newVector(); publicintsize() {returnkeys.size();} publicbooleanisEmpty() {returnkeys.isEmpty();} public Object put(Objectkey, Object value){keys.addElement(key);values.addElement(value);returnkey;} public Object get(Objectkey) {intindex = keys.indexOf(key);// indexOf() Returns -1 if key not found:if (index == -1)returnnull;returnvalues.elementAt(index);} public Object remove(Objectkey) {intindex = keys.indexOf(key);if (index == -1)returnnull;keys.removeElementAt(index);Object returnval =values.elementAt(index);values.removeElementAt(index);returnreturnval;} publicEnumeration keys() {returnkeys.elements();} publicEnumeration elements() {returnvalues.elements();} // Test it:publicstaticvoidmain(String[]args){AssocArray aa =new AssocArray();for (charc = 'a';c <='z'; c++)aa.put(String.valueOf(c), String.valueOf(c).toUpperCase());char[]ca = { 'a','e','i','o','u'};for (inti = 0; i < ca.length; i++)System.out.println("Uppercase: "+aa.get(String.valueOf(ca[i])));}} /// :~
4.2 output 1 is as follows:
Uppercase:AUppercase:EUppercase:IUppercase:OUppercase:U
In the definition of AssocArray, it "extends" the dictionary. AssocArray is a type of Dictionary, so it can send the same request as Dictionary. If you want to generate your own Dictionary and do it here, all you need to do is fill in all the methods in the Dictionary (and all methods must be overwritten, because they -- except the Builder -- are abstract ).
The Vector key and value are linked by a standard index number. That is to say, if you call put () with a key of "roof" and a value of "blue" -- assume that we are going to associate the parts of a house with their paint colors, in addition, AssocArray has 100 elements, so "roof" has 101 key elements, and "blue" has 101 value elements. And pay attention to get (). If we pass "roof" as the key, it will generate. index. of () index number, and then use that index number to generate the value in the relevant value vector.
The test in main () is very simple. It converts lowercase characters into uppercase and lowercase characters, which obviously can be performed in a more effective way. It reveals the powerful functions of AssocArray.
The standard Java library contains only one variant of Dictionary, named Hashtable (hash ). Java Hash has the same interface as AssocArray (because both are inherited from Dictionary ). However, one aspect reflects the difference: execution efficiency. If you think about what you have to do for a get (), you will find that the search key in a Vector is much slower. However, the use of the hash can speed up a lot. You do not need to use lengthy linear search techniques to find a key, but use a special value named "hash code ". Hash code can obtain information in an object and convert it to an integer (int) that is "relatively unique ). All objects have a hash code, and hashCode () is a method of the root class Object. Hashtable gets the hashCode () of the object, and then uses it to quickly find the key. This greatly improves the performance.
Now you only need to know that the hash is a fast "Dictionary", and the Dictionary is a very useful tool.
Code 2, 4.3
An example of an application hash can be used to check the randomness of Java's Math. random () method. Ideally, it should generate a series of perfect random distribution numbers. To verify this, we need to generate a large number of random numbers and then calculate the numbers falling in different ranges. A hash can greatly simplify this task because it can associate objects with objects.
importjava.util.*;classCounter {inti = 1; publicString toString() {returnInteger.toString(i);}} classStatistics {publicstaticvoidmain(String[]args) {Hashtable ht =newHashtable();for (inti = 0; i < 10000;i++) {// Produce anumber between 0 and 20:Integerr =new Integer((int) (Math.random()* 20));if (ht.containsKey(r))((Counter)ht.get(r)).i++;elseht.put(r,newCounter());}System.out.println(ht);}} /// :~
4.4 output:
{19=489,18=510, 17=478, 16=509, 15=515, 14=477, 13=479, 12=523, 11=504, 10=501, 9=522,8=489, 7=508, 6=481, 5=505, 4=502, 3=542, 2=481, 1=528, 0=457}
In main (), each time a random number is generated, it is encapsulated into an Integer object so that the handle can be used along with the hash (basic data types cannot be used for a collection, only object handles can be used ). The containKey () method checks whether the key is already in the Set (that is, has the number been found before ?) If it is already in the Set, the get () method gets the value associated with that key, which is a Counter (Counter) object. The value I in the counter will increase by 1, indicating that this specific random number appears again.
If the key has not been found before, the put () method will still include a new "key-value" pair in the hash table. At the beginning of creation, Counter automatically initializes its variable I to 1, marking the first appearance of this random number.
To display the scattered list, simply print it out. The Hashtable toString () method can traverse all key-value pairs and call toString () for each pair (). Integer toString () is defined in advance. You can see the toString used by the counter.
You may wonder whether the Counter class is necessary. It seems that there is no function to encapsulate class Integer at all. Why not use int or Integer? In fact, since all sets can accommodate only object handles, integers are not allowed at all. After learning the set, the concept of encapsulation class may be easier for everyone to understand, because it is impossible to put any basic data type into the set. However, the only thing we can do with the Java package is to initialize it into a specific value and then read that value. That is to say, once the encapsulated object has been created, there is no way to change a value. This makes the Integer package meaningless to solve our problem, so we have to create a new class to meet our requirements.
4.5 Code 3 create key classes
Use the class (Integer) of a standard library as a key of Hashtable. As a key, it works well because it has all the conditions for proper operation. However, when using the hashed list, once we create our own class as the key
You will encounter a very common problem. For example, assume that a weather forecast system matches the Groundhog object to the Prediction object ). This looks very intuitive: We create two classes and use Groundhog as the key while using Prediction as the value. As follows:
importjava.util.*;classGroundhog {intghNumber;Groundhog(intn) {ghNumber =n;}}classPrediction {booleanshadow = Math.random() > 0.5;publicString toString() {if (shadow)return"Six more weeks of Winter!";elsereturn"Early Spring!";}}publicclass SpringDetector {publicstaticvoidmain(String[]args) {Hashtable ht =newHashtable();for (inti = 0; i < 10;i++)ht.put(new Groundhog(i),new Prediction());System.out.println("ht = " + ht +"\n");System.out.println("Looking up prediction for groundhog #3:");Groundhoggh =new Groundhog(3);if (ht.containsKey(gh))System.out.println((Prediction)ht.get(gh));}}
4.6 output 3
Ht = {Groundhog @ 7852e922 = Early Spring !, Groundhog @ 5c647e05 = Early Spring !, Groundhog @ 75b84c92 = Early Spring !, Groundhog @ 33909752 = Early Spring !, Groundhog @ 42a57993 = Six more weeks of Winter !, Groundhog @ 55f96302 = Six more weeksof Winter !, Groundhog @ 4e25154f = Six more weeks of Winter !, Groundhog @ 70dea4e = Early Spring !, Groundhog @ 3d4eac69 = Early Spring !, Groundhog @ 6d06d69c = Six more weeks of Winter !}
Lookingup prediction for groundhog #3:
Each Groundhog has an ID number. Therefore, if you are looking for a Prediction in the hash, you only need to instruct it to "tell me the Prediction associated with Groundhog number 3 ". The Prediction class contains a Boolean value, which is initialized using Math. random () and a toString () to explain the results. In main (), fill in a hash list with Groundhog and their related Prediction. The hash is printed so that we can see that they are indeed filled. Then, use a Groundhog marked with 3 to find the forecast corresponding to Groundhog #3.
It seems very simple, but it is actually not feasible. The problem is that Groundhog inherits from the Common Object root class (if no base class is specified at the beginning, all classes will eventually inherit from the Object ). In fact, the hashCode () method of the Object is used to generate the hash code for each Object. By default, only the address of its Object is used. Therefore, the first instance of Groundhog (3) does not generate a hash code equal to the second instance of Groundhog (3.
Use code 4 for retrieval. Besides overwriting hashCode () correctly (). Do another thing: overwrite the equals () that is also part of the Object (). This method is used when the hash table tries to determine whether our key is equal to a key in the table. Similarly, the default Object. equals () simply compares the Object address, so one Groundhog (3) is not equal to the other Groundhog (3 ).
Therefore, to use your classes as keys in the hash list, you must overwrite both hashCode () and equals (), as shown below:
Code 4 4.7
import java.util.*; class Groundhog2 {intghNumber; Groundhog2(intn) {ghNumber =n;} publicinthashCode() {returnghNumber;} publicbooleanequals(Object o) {return (oinstanceof Groundhog2) && (ghNumber == ((Groundhog2)o).ghNumber);}} publicclass SpringDetector2 {publicstaticvoidmain(String[]args){Hashtable ht = new Hashtable();for (inti = 0; i < 10;i++)ht.put(new Groundhog2(i),new Prediction());System.out.println("ht = " +ht + "\n");System.out.println("Looking up prediction for groundhog #3:");Groundhog2 gh =new Groundhog2(3);if (ht.containsKey(gh))System.out.println((Prediction)ht.get(gh));}}
4.8 output 4
ht= {Groundhog2@9=Six more weeks of Winter!, Groundhog2@8=Six more weeks ofWinter!, Groundhog2@7=Six more weeks of Winter!, Groundhog2@6=Six more weeks ofWinter!, Groundhog2@5=Early Spring!, Groundhog2@4=Six more weeks of Winter!,Groundhog2@3=Six more weeks of Winter!, Groundhog2@2=Six more weeks of Winter!,Groundhog2@1=Six more weeks of Winter!, Groundhog2@0=Early Spring!} Lookingup prediction for groundhog #3:Sixmore weeks of Winter!
This Code uses the Prediction from the previous example. Therefore, SpringDetector. java must be compiled first. Otherwise, a compilation error will be generated when trying to compile SpringDetector2.java.
Groundhog2.hashCode () returns the egg number as an identifier (in this example, the programmer must ensure that no two egg rats coexist with the same ID number ). To return a unique identifier, hashCode () is not required. The equals () method must be able to strictly determine whether two objects are equal.
The equals () method requires two checks: Check whether the object is null; if not, continue to check whether it is an instance of Groundhog2 (the instanceof keyword is used ). Even if you want to continue executing equals (), it should be a Groundhog2. This comparison is based on the actual ghNumber. Once we run the program
It will be seen that it finally produces the correct output (many Java library classes cover the hashcode () and equals () Methods to adapt to their own content ).
In previous notes, we used a Hashtable type named Properties. In that example, the following program lines:
Properties p = System. getProperties ();
P. list (System. out );
A static method named getProperties () is called to obtain a special Properties object and describe some features of the system. List () is a method of Properties. You can send the content to any streaming output we select. There is also a save () method that can be used to write the attribute list to a file for later reading using the load () method. Although the Properties class is inherited from Hashtable, it also contains a hash to accommodate the "default" attribute list. Therefore, if no attribute is found in the primary column, the default attribute is automatically searched.
The Properties class can be found in the user documentation of the Java library for more details.
5. Discuss the enumerator
Demonstrate the real power of Enumeration (Enumeration): separates operations that traverse a sequence from the basic structure of that sequence. The PrintData class uses an Enumeration to move in a sequence and calls the toString () method for each object. At this time, two sets of different types are created: a Vector and a Hashtable. And fill in the Mouse and Hamster objects in them respectively (first compile HamsterMaze. java and WorksAnyway. java, otherwise the following program cannot be compiled ). Because Enumeration hides the structure of the base-level set, PrintData does not know or care about the type of set that Enumeration comes from:
Code 5.1
WorksAnyway.java:importjava.util.*; classMouse {privateintmouseNumber; Mouse(inti) {mouseNumber =i;} // Magic method:publicString toString() {return"This is Mouse #" +mouseNumber;} voidprint(Stringmsg) {if (msg !=null)System.out.println(msg);System.out.println("Mouse number " + mouseNumber);}} classMouseTrap {staticvoid caughtYa(Objectm) {Mousemouse =(Mouse)m;// Cast fromObjectmouse.print("Caught one!");}} publicclass WorksAnyway {publicstaticvoidmain(String[]args) {Vector mice =newVector();for (inti = 0; i < 3;i++)mice.addElement(new Mouse(i));for (inti = 0; i // No castnecessary, automatic call// toObject.toString():System.out.println("Free mouse: " + mice.elementAt(i));MouseTrap.caughtYa(mice.elementAt(i));}}}Enumerators2.javaimport java.util.*;class PrintData {staticvoidprint(Enumeration e) {while (e.hasMoreElements())System.out.println(e.nextElement().toString());}} class Enumerators2 {publicstaticvoidmain(String[]args){Vector v = new Vector();for (inti = 0; i < 5;i++)v.addElement(new Mouse(i));Hashtable h = new Hashtable();for (inti = 0; i < 5;i++)h.put(new Integer(i),new Hamster(i));System.out.println("Vector");PrintData.print(v.elements());System.out.println("Hashtable");PrintData.print(h.elements());}} /// :~
5.2 output
VectorThisis Mouse #0Thisis Mouse #1Thisis Mouse #2Thisis Mouse #3Thisis Mouse #4HashtableThisis Hamster #4Thisis Hamster #3Thisis Hamster #2Thisis Hamster #1Thisis Hamster #0
Note that PrintData. print () uses the fact that the objects in these sets belong to the Object class, so it calls toString (). However, when solving your own problems, you must always ensure that your Enumeration traverses a specific type of set. For example, it may require that all elements in the Set be a Shape (geometric Shape) and contain the draw () method. In this case, the objects returned by Enumeration. nextElement () must be traced down to generate a Shape.