12. Collection (1)

Chapter Summary

  • Generics and type-safe collections
  • basic concept

If a program contains only a fixed number of objects whose lifetimes are known, it is a very simple program.

Typically, programs always create new objects based on certain conditions that are known only at runtime. Until then, there is no way to know the number or even the exact types of objects required. To solve this common programming problem, any number of objects need to be created at any time and in any location. Therefore, you cannot rely on creating named references to hold each object:

MyType aReference;

Because one never knows how many such references are actually needed.

Most programming languages ​​provide some way to solve this basic problem. Java has many ways to save objects (to be precise, references to objects). For example, the array we learned earlier is a type supported by the compiler. Arrays are the most efficient way to save a set of objects. If you want to save a set of basic types of data, it is also recommended to use arrays. But arrays have a fixed size, and in more general cases, when writing a program, you don't know how many objects you will need, or whether you need a more complex way to store objects, so the fixed size limit of the array appears. Too restrictive.

The java.util library provides a fairly complete set of collection classes to solve this problem. The basic types are List , Set , Queue and Map . These types are also called container classes, but I'll use the terminology used by the Java class library. Collections provide a sophisticated way to save objects, and you can use these tools to solve a large number of problems.

Collections also have some other properties. For example, Set holds only one object for each value, and Map is an associative array that allows some objects to be associated with other objects. Java collection classes can automatically adjust their size. Therefore, unlike arrays, when programming, you can place any number of objects in a collection without caring about how big the collection should be.

Although there is no direct keyword support in Java, collection classes are still an essential tool that can significantly enhance your programming capabilities. In this chapter, we will introduce the basic knowledge of Java collection class library and focus on some typical usage. Here we will focus on collections used in everyday programming. Later, in the Appendix: Collections topic, you will also learn about the remaining collections and related functions, as well as more details on how to use them.

Generics and type-safe collections

One major problem with using pre-Java 5 collections is that the compiler allows you to insert incorrect types into the collection. For example, consider a collection of Apple objects, using the most basic and reliable ArrayList . Now, you can think of ArrayList as an "array that can automatically expand its size". Using an ArrayList is fairly simple: create an instance, add()insert objects get()using ArrayList also has a size()method to indicate how many elements are contained in the collection, so you will not accidentally cause an error due to an array out of bounds.

In this example, both Apple and Orange are put into the set, and then they are taken out. Normally, the Java compiler will give a warning because this example does not use generics. Here, specific annotations are used to suppress warning messages. Annotations start with the "@" symbol and can take parameters. The annotation and its parameters here @SuppressWarningindicate that only "unchecked" type warnings are suppressed:

import java.util.*;

class Apple {
    
    
    private static long counter;
    private final long id = counter++;

    public long id() {
    
    
        return id;
    }
}

class Orange {
    
    
}

public class ApplesAndOrangesWithoutGenerics {
    
    
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
    
    
        ArrayList apples = new ArrayList();
        for (int i = 0; i < 3; i++) {
    
    
            apples.add(new Apple());
        }
        // No problem adding an Orange to apples:
        apples.add(new Orange());
        for (Object apple : apples) {
    
    
            ((Apple) apple).id();
            // Orange is detected only at run time
        }
    }
}

Insert image description here

Apple and Orange are completely different. They have nothing in common except that they are both Object (if a class does not explicitly declare which class it inherits from, then it automatically inherits from Object ). Because ArrayList saves Object , not only can Apple objects be put into this collection through the ArrayList method , but also Orange objects can be put into it . This will not cause any problems at compile time or run time. When you use the ArrayList method to take out the object you think is Apple , you get only an Object reference, which must be converted to Apple . The entire expression then needs to be enclosed in parentheses to force the transformation before calling Apple 's method. Otherwise, a syntax error will result.add()get()id()

At runtime, when trying to convert the Orange object to Apple , I get the error shown in the output.

In the Generics chapter, you will learn that creating classes using Java generics can be complex. However, using predefined generic classes is quite simple. For example, to define an ArrayList for holding Apple objects , just use ArrayList instead of ArrayList . Enclosed in angle brackets are _type parameters_ (there may be more than one), which specifies the type that this collection instance can hold.

By using generics, you can prevent objects of the wrong type from being placed into a collection at compile time. Here's the same example, but using generics:

// collections/ApplesAndOrangesWithGenerics.java
import java.util.*;

public class ApplesAndOrangesWithGenerics {
    
    
    public static void main(String[] args) {
    
    
        ArrayList<Apple> apples = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
    
    
            apples.add(new Apple());
        }
        // Compile-time error:
        // apples.add(new Orange());
        for (Apple apple : apples) {
    
    
            System.out.println(apple.id());
        }
    }
}
0
1
2

On the right side of the apples definition, you can see new ArrayList<>(). This is sometimes called "diamond syntax". Before Java 7, type declarations were required at both ends, as follows:

ArrayList<Apple> apples = new ArrayList<Apple>();

As types become more complex, the code produced by this repetition is very confusing and difficult to read. The programmer finds that all type information is available from the left side, so the compiler has no reason to force the right side to repeat it. Although type inference was a small request, the Java language team embraced it and improved it.

With the type specification in the ArrayList declaration, the compiler prevents Orange from being put into apples , so this becomes a compile-time error rather than a run-time error.

Using generics, no cast is required to get elements from a List . Because List knows what type it holds, get()it performs the cast for you when you call . So, with generics, you not only know that the compiler will check the type of objects you put into the collection, but you also get a cleaner syntax when working with objects in the collection.

When you specify a type as a generic parameter, you are not limited to placing objects of the exact type into the collection. Upcasting also works on generics just like it does on other types:

// collections/GenericsAndUpcasting.java
import java.util.*;

class GrannySmith extends Apple {
    
    
}

class Gala extends Apple {
    
    
}

class Fuji extends Apple {
    
    
}

class Braeburn extends Apple {
    
    
}

public class GenericsAndUpcasting {
    
    
    public static void main(String[] args) {
    
    
        ArrayList<Apple> apples = new ArrayList<>();
        apples.add(new GrannySmith());
        apples.add(new Gala());
        apples.add(new Fuji());
        apples.add(new Braeburn());
        for (Apple apple : apples) {
    
    
            System.out.println(apple);
        }
    }
}

Insert image description here

Therefore, a subtype of Apple can be added to a collection designated to hold Apple objects.

The program's output is produced from Object 's default method, which prints the class name followed by the unsigned hexadecimal representation of the object's hash code (this hash code is generated by the method).toString()hashCode()

basic concept

The Java collection class library adopts the idea of ​​"holding objects" and divides it into two different concepts, expressed as the basic interface of the class library:

  1. Collection : A sequence of independent elements that obey one or more rules. List must save elements in the order of insertion, Set cannot contain duplicate elements, and Queue follows queuing rules to determine the order in which objects are generated (usually the same order in which they are inserted).
  2. Map (Map) : A set of "key-value pair" objects that allow keys to be used to look up values. ArrayList uses numbers to look up objects, so in a sense it is associating numbers and objects. map allows us to use one object to find another object. It is also called an associative array because it associates objects with other objects; or a dictionary because it can be used A key object to look up value objects, just like you would use a word to look up a definition in a dictionary. Maps are powerful programming tools.

Although it's not always possible, ideally most of the code you write will be dealing with these interfaces, and the only place you need to specify the precise type used is at creation time. Therefore, a List can be created like this :

List<Apple> apples = new ArrayList<>();

Note that ArrayList has been upcast to List , which is the opposite of how it was handled in the previous example. The purpose of using the interface is that if you want to change the specific implementation, you only need to modify it at creation time, like this:

List<Apple> apples = new LinkedList<>();

Therefore, you should create an object of a concrete class, upcast it to the corresponding interface, and then use this interface in the rest of the code.

This approach does not always work because some concrete classes have additional functionality. For example, LinkedList has extra methods that are not included in the List interface, and TreeMap also has methods that are not included in the Map interface. If you need to use these methods, you cannot upcast them to a more general interface.

The Collection interface generalizes the concept of a sequence - a way of storing a group of objects. Here is a simple example that fills a Collection (here represented by an ArrayList ) with Integer objects , and then prints each element in the collection:

// collections/SimpleCollection.java
import java.util.*;

public class SimpleCollection {
    
    
    public static void main(String[] args) {
    
    
        Collection<Integer> c = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
    
    
            c.add(i); // Autoboxing
        }
        for (Integer i : c) {
    
    
            System.out.print(i + ", ");
        }
    }
}
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,

This example only uses methods from Collection (ie), so using objects from add()any class that inherits from Collection will work fine. But ArrayList is the most basic sequence type.

add()The name of the method indicates that it adds a new element to the Collection . However, the documentation states in great detail that add()"Ensure that this Collection contains the specified element." This is because the meaning of Set is taken into account , because in Set , elements are added only when the elements do not exist. When working with an ArrayList , or any other type of List , add()always means "put it in" because the List doesn't care if there are duplicate elements.

You can use the for-in syntax to iterate over all Collections , as shown here. Later in this chapter, you'll also learn about a more flexible concept, iterators .

Guess you like

Origin blog.csdn.net/GXL_1012/article/details/132545330