Generics in java(1)

Generics in java(1)

 This blog refers to << thinking in java >> Chapter 15 Generics, records and organizes my study notes, combining my own thinking and understanding.

Generics use

  1. Use type parameters, enclosed in angle brackets, after the class name/interface. If there are multiple parameters, just separate them with commas.
public class Holder<T>{
    private T a;
    public void set(T a){ this.a = a;}
}
  1. Basic types cannot be used as type parameters, but java has the function of automatic packaging and automatic unpacking, which can easily convert between basic types and their corresponding wrapper types.
  2. Generic methods: Just put the generic parameter list before the return value. For example public <T> void f(T x){...}, whether you have a generic method or not has nothing to do with whether the class is generic or not. When using a generic class, you must specify it when you create the object The value of the type parameter, and when using a generic method, it is usually not necessary to specify the parameter type, the compiler will find out the specific type. If the basic type is passed in when calling a generic parameter, then the automatic packaging mechanism will intervene.
  3. Explicit type specification for generic methods: insert angle brackets between the dot operator and the method name, and then put the type in the angle brackets. If it is defined inside the class of the method, you must use this before the dot operator The keyword, if it is a static method, must add the class name before the dot operator. But this syntax is generally used when writing non-assignment statements.
  4. Generics can also be used for inner classes and anonymous inner classes. Assuming there is a class that uses generics Generator<T>, consider a method that returns an Generator<Customer>object:
    public static Generator<Customer> G
    generator(){
        return new Generator<Customer>(){
            public Customer next(){
                return new Customer();
            }
        }

erase

  1. Doubt introduction: Consider the code:
    Class c1 = new ArrayList<String>().getClass();
    Class c2 = new ArrayList<Integer>().getClass();
    System.out.println(c1 == c2); // output : true

It turns out that the output is true! It turns out: inside the generic code, there is no way to get any information about the type of the generic parameter. Java generics are implemented with erasure, which means that when you use generics, any specific The type information is erased, and the only thing you know is that you are using an object. So both types above are erased as "native" types.

  1. Due to erasure, the following ObjectOne.java code fails to compile:
    ObjectOne.java
    public class ObjectOne<T> {
    private T obj;
    ObjectOne(T obj){
        this.obj = obj;
    }
    void test(){
        obj.f(); // can't resolve method f()
    }

    public static void main(String[] args) {
        ObjectTwo t = new ObjectTwo();
        ObjectOne<ObjectTwo> o = new ObjectOne<>(t);
    }
}

    ObjectTwo.java
    public class ObjectTwo {
    void f(){

    }
}

java cannot map the need in test() to be able to call f() on obj to the fact that ObjectTwo has f(), in order to call f() we have to assist the generic class, given the bounds of the generic class, We need to use the extends keyword, and ObjectOne<T>replace ObjectOne<T extends ObjectTwo>it with , which indicates that T must have type ObjectTwo or a type derived from ObjectTwo (similarly, if you use properties in a generic class, for example T.attr, then this must also be specified <T extends X>, where The X class contains the attribute x, and this X is also equivalent to assisting the generic class). But you can see that this does not use the benefits of generics. If this is the case, why simply replace the T in the class with ObjectTwo's What about the type? Of course, if the class is to return a T type, then generics can come in handy, because it can return an exact type of T instead of a derived type of T.
3. Generic types are only available in static Appears only during type checking, after which all generic types in the program are erased and replaced with their non-generic upper bounds, for example LIst is erased to List, while ordinary type variables have no specified bounds will be erased to Object.

Erasure compensation (some workarounds for the limitations of generics in java)

  1. Erasure loses the ability to do something in generic code, any operation that needs to know the exact type information at runtime will not work:
    public class ObjectOne<T> {
        private final int SIZE = 100;
        public void f(Object obj){
            if (obj instanceof T){ } // error
            T var = new T(); // error
            T[] array = new T[SIZE]; // error
            T[] array2 = (T[]) new Object[SIZE]; // unchecked case
        }

    }

The attempt of instanceof in the above example fails because its type information has been erased. If a type label is introduced, the isInstance() method can be used dynamically. (That is, you need to replace T Class<T> objwith and introduce an obj object)

    public class ClassTypeCapture<T>{
        Class<T> kind;
        public ClassTypeCapture(Class<T> kind){
            this.kind = kind;
        }
        public boolean f(Object arg){
            return kind.isInstance(arg);
        }
  1. You can see from the above that new T() will not be implemented due to erasure, and the compiler cannot verify that T has a default (no-argument) constructor , although this operation is natural and safe in C++. Solution: pass A factory object of type Class, use the getConstrutor().newInstance() method to create instances.
    //c++ version : work perfectly
    template<class T> class Foo{
        T x;
        T* y;
        public:
            Foo(){
                y = new T();
            }
    };

    class Bar(){};

    int main(){
        Foo<Bar> fb; // ok!
        Foo<int> fi; // 即使是基本类型也没毛病

    // java version 
    public class ClassAsFactory<T>{
        T x;
        public ClassAsFactory(Class<T> kind){
            try{
                x = kind.getConstructor().newInstance();
            } catch (Exception e){
                throw new RuntimeException();
            }
        }
    }

However, there are still problems with the above java code. If called new ClassAsFactory<Integer>(), it will fail because Integer does not have a default constructor, but this error is not caught by the compiler. Sun recommends using an explicit factory and restricting its type so that only the The class that implements this factory. ( My understanding is that you can't throw the Class object of this object when you want to produce any type of object. It is possible that this object does not have a default parameterless constructor like Integer, so this factory only It can be the existence of an interface. Any object you need to produce must implement the factory interface and define your own generation method, so you can find that your object has actually been created in the factory you defined ) Solution 2:

    public interface Factory<T> {
        T create();
    }

    public class Foo2<T> {
        private T x;
        public <F extends Factory<T>> Foo2(F factory){
            x = factory.create();
        }
    }

    public class IntegerFactory implements Factory<Integer> {
        @Override
        public Integer create() {
            return new Integer(0);
        }
    }

    public class Widget {
        public static class Factory implements general.Factory<Widget>{
            @Override
            public Widget create() {
                return new Widget();
            }
        }
    }

So if you want to generate your object, you only need to write a factory class that implements the Factory interface, implement the create method of your class, and then pass the factory class to the Foo2 class, which contains the object you want to generate. (At this moment, I'm already stunned, and it is so troublesome to do a few lines of C++ under Java). Another solution is to use the template method in the design pattern:

    abstract class GenericWithCreate<T> {
        final T element;
        GenericWithCreate(){
            element = create();
        }
        abstract T create();
    }

    class X{}

    public class Creator extends GenericWithCreate<X> {
        @Override
        X create() {
            return new X();
        }
    }

This method separates the create step and uses a Creator of the specified type to generate the object.

Generic array

  1. As you can see above, generic arrays cannot be created. The general workaround is to use ArrayList.
    public class ListOfGenerics<T>{
        private List<T> array = new ArrayList<T>;
        public void add(T item){ array.add(item);}
        public T get(int index){ array.get(index);}
    }
  1. Consider the following code first:
    public class ArrayOfGeneric {

    static final int SIZE = 100;
    static Generic<Integer>[] gia;

    public static void main(String[] args) {
        // gia = (Generic<Integer>[]) new Object[SIZE]; // uncheck cast. Produce classCastException.
        // gia = new Generic<Integer>[SIZE]; error
        gia = (Generic<Integer>[]) new Generic[SIZE]; // uncheck cast
        System.out.println(gia.getClass().getSimpleName());
        gia[0] = new Generic<>();
    }
}

One thing that is confusing is that arrays have the same structure (array slot size and array layout) regardless of their holding type, it seems you can do this by creating an Object array and casting it to the desired array type , in fact this compiles but doesn't run, a classCastException occurs. The problem is that arrays will keep track of their actual type, which is determined at array creation time, and at runtime, they are still Object arrays , and this will cause problems. The only way to successfully create a generic array is to create a new array of the erased type, and then cast it, asgia = (Generic<Integer>[]) new Generic[SIZE]这一句
there is also a way to do it, using a generic array wrapper, and preferably inside a collection Object[]

    public class GenericArray<T>{
        private Object[] array;
        public GenericArray(int sz){
            array = new Object[sz];
        }
        public void put(int index, T item){
            array[index] = item;
        }
        public T get(int index){ return (T)array[index];}
        public T[] rep(){ return (T[]) array;} // warning : unchecked cast

Now that the internal representation is Object[] instead of T[], when get() is called, it casts the object to T, which is actually the correct type. But if you call rep(), it still tries to cast Object[] Casting to T[] , which is still incorrect, will cause a warning at the compiler and an exception at runtime. Therefore, there is no way to override the underlying array type, it can only be Object[]
For new code, it should be Pass a type tag:

    public class GenericArrayWithToken <T>{
    private T[] array;
    public GenericArrayWithToken(Class<T> type, int sz){
        array = (T[]) Array.newInstance(type, sz);
    }

    public void put(int index, T item){
        array[index] = item;
    }

    public T get(int index){
        return array[index];
    }

    public T[] rep(){
        return array;
    }

    public static void main(String[] args) {
        GenericArrayWithToken<Integer> gai = new GenericArrayWithToken<>(Integer.class, 10);
        Integer[] ia = gai.rep();
        ia[0] = 1;
        System.out.println(ia[0]);
    }
}

The type marker Class is passed to the constructor to recover from erasure.

boundary

  1. Bounds allow you to set constraints on parameter types that you can use with generics. Since erasure removes type information, all methods called with unbounded generic parameters are only those that can be called with Object, but if you can set this parameter restricted to a subset of types, then you can call methods with those subsets of types.
  2. Using the extends and super keywords to limit the parameter type <T extends ClassName>means that the type is the type of ClassName or its derived type, and the meaning of super is also easy to get. If you need to extend/super multiple ClassNames, use &symbols to connect.
  3. Wildcards:
    First show a special behavior of arrays: you can assign array references of base types to arrays of derived types:
    //definition
    class Fruit{}
    class Apple{}
    class Joanthan extends Apple{}
    class Orange extends Fruit{}

Consider the statement:

    Fruit[] f = new Apple[10];
    f[0] = new Apple();
    f[1] = new Joanthan();
    try {
        f[0] = new Fruit();
    } catch (Exception e} {System.out.println(e);}
    try {
        f[1] = new Orange()'
    } catch (Exception e){System.out.println(e);}

It turns out that it compiles fine, but runs both try blocks throwing exceptions. The reason is simple: the actual array type is Apple[], and you should only be able to put Apple or subclasses of Apple in it, which is important to the compiler Or run-time is no problem. But the compiler also allows you to put Fruit or its derived class (such as Orange) into this array, since it is a reference to Fruit. But the runtime's array mechanism knows that it handles is actually Apple[], so an exception will be thrown when placing heterogeneous types in the array.
Then, when we try to use a generic container instead of a generic array List<Fruit> f = new ArrayList<Apple>(), the compiler will report an error, Apple's list is not a Fruit's at all list, even though Apple is a subclass of Fruit.
So we want to introduce the wildcard , List<? extends Fruit> f = new ArrayList<Apple>, but note that this doesn't actually mean that any type of Fruit can be held, the wildcard refers to a specific type, but now you don't know it The actual type of , in this case, you can't safely add objects to it, so now you can't even add Apple, Fruit. But if you create one Holder<Apple>, although you can't upcast to it Holder<Fruit>, you can upcast to it Holder<? extends Fruit?. If Call get() and it will return a Fruit.

    public class Holder<T>{
        private T value;
        public Holder(){}
        public Holder(T val){ value = val;}
        public void set(T val){ value = val;}
        public T get() { return value;}
        public boolean equals(Object obj){ return value.equals(obj);}
        public static void main(String[] args){
            Holder<Apple> apple = new Holder<Apple>(new Apple());
            Holder<? extends Fruit> fruit = apple; // ok
            Fruit p = fruit.get();
            Apple d = (Apple) fruit.get();
        }
    }
  1. Using the supertype wildcard super, you can declare that the wildcard is bounded by any base class of a particular class, by specifying <? extends MyClass>.
        List<? super Apple> a = new ArrayList<>();
        a.add(new Apple());
        a.add(new LittleApple()); // littleApple为Apple的子类
        //a.add(new Fruit()); error

a is a list of some base type of Apple, then you can know that it is safe to add Apple or LittleApple to it, but it is not safe to add Fruit, because you cannot be sure that Fruit is the base class or the derived class of this base class.
5 . First look at the code in the book:


public class GeneralReading {
    static <T> T readExact(List<T> list){
        return list.get(0);
    }
    static List<Apple> apples = Arrays.asList(new Apple());
    static List<Fruit> fruits = Arrays.asList(new Fruit());
    static void f1(){
        Apple a = readExact(apples);
        Fruit f = readExact(fruits);
        f = readExact(apples);
    }

    static class Reader<T>{
        T readExact(List<T> list){
            return list.get(0);
        }
    }

    static void f2(){
        Reader<Fruit> fruitReader = new Reader<>();
        Fruit f = fruitReader.readExact(fruits);
        //Fruit a = fruitReader.readExact(apples); error
    }

    static class CovariantReader<T>{
        T readCovariant(List<? extends T> list){
            return list.get(0);
        }
    }

    static void f3(){
        CovariantReader<Fruit> fruitCovariantReader = new CovariantReader<>();
        Fruit f = fruitCovariantReader.readCovariant(fruits);
        Fruit a = fruitCovariantReader.readCovariant(apples);
    }
}

It can be seen that: ① f1() is a static generic method, and its return value can be effectively adapted to each method call. ② Reader is a generic class. When creating an instance of this class, parameters must be determined for this class. In f2() The parameter determined at the time of creation is Fruit. Although the incoming List<Apple>can generate Fruit, FruitReader does not allow it. ③The method of the CovariantReader class will accept List<? extends T>it, so it is safe to read a T in the list.
I will first summarize according to my understanding .
6. (add) : List,List<?>,List<? extends Object>① First of all, as mentioned before, '?' does not mean that the container can hold any type, it is definite, but we don't know its exact type (or we don't know the container The lower bound). ②I have found out from practice that the three of them can be assigned to each other, and their get methods are running normally. ③However, as mentioned earlier, since we don't know about '?' The specific type information (or its lower bound), so it List<?>,List<? extends Object>can't add any object, but List is equivalent List<Object>, it can add any object. (On the contrary, since List<? super ClassType>we know that its lower bound is ClassType, then it can at least add ClassType type or a subtype of ClassType)
7. (get) For List,List<?>,List<? super ClassType>, we don't know what type of upper bound of the container is, so calling the get method returns the value of Object; and for List<ClassType>,List<? extends ClassType>, we all know the upper bound of the container, so call The return value of the get method is ClassType.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324698621&siteId=291194637