Java Foundation - Not Magical Generics

Preface

Previously, some points related to Java basics were trained for company newcomers, and generic related points of knowledge were systematically sorted out.Share it.I hope that some students who are not familiar with generics will have a complete knowledge of Java generics.

Before you start, give you a test.

List<String> strList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();
        
System.out.println(strList.getClass() == integerList.getClass());

What is the final output of the above code?Students who are familiar with generics should be able to answer, but those who are not familiar with generics may be wrong.

content

  • An overview of generics

    • why generic
    • The role of generics
  • Definition and use of generics

    • generic class
    • generic method
    • generic interface
  • Wildcards?

    • Unbounded wildcards
    • Upper limit wildcard
    • lower bound
  • Type Erasure

With questions

  1. What are generics in Java and what are the benefits of using them?
  2. What are qualified and unbounded wildcards in generics?
  3. Can you pass List<String>to a method that accepts the List<Object>parameter?
  4. How do Java generics work and what is type erasing?

1. Overview of generics

The earliest concept of "generic programming" originated from the C++ Template, which Java borrowed from, but in different ways.C++ generates different classes based on template classes, and Java uses type erasing.

1.1 why generic?

Generic has been added to the Java 1.5 release.

There are many reasons for the emergence of generics, and one of the most striking is the creation of container classes.

-- "Java Programming Ideas"

Containers are the places where you want to store the objects you want to use.The same is true for arrays, but container classes are more flexible and functional than they are.All programs require you to hold a large number of objects at run time, so container classes are one of the most reusable libraries you need.

Look at this example below.

public class AutoMobile {
}

/**
 * Poorly reusable container classes
 */
public class Holder1 {

    private AutoMobile a;

    public Holder1(AutoMobile a) {
        this.a = a;
    }
    //~~
}

/**
 * Container classes that want to be reusable before java5
 * @author Richard_yyf
 * @version 1.0 2019/8/29
 */
public class Holder2 {

    private Object a;

    public Holder2(Object a) {
        this.a = a;
    }

    public Object getA() {
        return a;
    }

    public void setA(Object a) {
        this.a = a;
    }

    public static void main(String[] args) {
        Holder2 h2 = new Holder2(new AutoMobile());
        AutoMobile a = (AutoMobile) h2.getA();
        h2.setA("Not an AutoMobile");
        String s = (String) h2.getA();
        h2.setA(1);
        Integer x = (Integer) h2.getA();
    }
}



/**
 * Reusability through generics
 * The primary purpose of generics is to specify what type of objects the container will hold
 * And it's the compiler that guarantees the correctness of the type
 *
 * @author Richard_yyf
 * @version 1.0 2019/8/29
 */
public class Holder3WithGeneric<T> {

    private T a;

    public Holder3WithGeneric(T a) {
        this.a = a;
    }

    public T getA() {
        return a;
    }

    public void setA(T a) {
        this.a = a;
    }

    public static void main(String[] args) {
        Holder3WithGeneric<AutoMobile> h3 = new Holder3WithGeneric<>(new AutoMobile());
        // No class cast needed
        AutoMobile a = h3.getA();
    }
}

By comparing the above, we should be able to understand what type parameterization actually means.

Every object read from a collection needs to be converted before there is a generic.If someone accidentally inserts an object of the wrong type, conversion processing at run time will fail.This is obviously intolerable.

The emergence of generics has brought a different programming experience to Java.

The role of 1.2 generics

  1. The parameterized type.Simple and rude as replacing all types with ordinary Object s, generics allow categories of data to be passed in externally, like parameters.It provides an extensibility.It is more in line with the purpose of software programming for abstract development.
  2. Type detection.Once a specific type is determined, generics provide a mechanism for type detection, where only matching data can be assigned properly, otherwise the compiler will fail.So, it is a type security detection mechanism, which improves the security of software to some extent and prevents low-level errors.
  3. Improve code readability.You don't have to wait until it runs to force a conversion, because the effect of Holder<AutoMobile>type visualization allows programmers to guess at a glance what type of data this container class holds.
  4. Code reuse.Generics combine processing code for objects of the same type, making code more reusable.

2. Definition and Use of Generics

Generics can be divided into three types according to their use.

  1. generic class
  2. generic method
  3. generic interface

2.1 Generic Class

  • Overview: Define generics on classes
  • Define format:

    public class class name <generic type 1,...> {
        ...
    }
  • Note: Generic types must be reference types (non-basic data types)

Type parameter specification (convention common name)

The letters in angle brackets <> are called type parameters and are used to refer to any type.We often see the <T> notation. In fact, T is just a habitual notation, if you like.You can write like this.

public class Test<Hello> {
    Hello field1;
}

However, for specifications and readability purposes, Java recommends that we use a single capital letter to represent the type parameter.Common examples are:

  • T stands for any class in general.
  • E stands for Element or Exception exception.
  • K stands for Key.
  • V stands for Value and is often used in conjunction with K.
  • S stands for Subtype

2.2 Generic Methods

  • Overview: Defining generics in methods
  • Define format:

    Public <generic type>return type method name (generic type variable name) {
        ...
    }
  • Matters needing attention:

    • Here, T in <T> is called a type parameter, while T in a method is called a parameterized type and is not a real runtime parameter.
    • Parameters defined in method declarations can only be used in this method, while type parameters defined in interface and class declarations can be used in the entire interface and class.

The coexistence of generic classes and methods

/**
 * Coexistence of generic classes and methods
 * @author Richard_yyf
 * @version 1.0 2019/8/29
 */
public class GenericDemo2<T> {

    public  void testMethod(T t){
        System.out.println(t.getClass().getName());
    }
    public  <T> T testMethod1(T t){
        return t;
    }

    public static void main(String[] args) {
        GenericDemo2<String> t = new GenericDemo2<>();
        t.testMethod("generic");
        Integer integer = 1;
        Integer i = t.testMethod1(integer);

    }
}

Generic methods always take their own type parameters as they are defined

Of course, in real-world situations, never write code that is so difficult to read.

2.3 Generic Interface

Generic interfaces are similar to generic classes.

  • Overview of generic interfaces: Defining generics in interfaces
  • Define format:

    public interface interface name < generic type > {
        ...
    }

Demo

public interface GenericInterface<T> {

    void show(T t);
}

public class GenericInterfaceImpl<String> implements GenericInterface<String>{

    @Override
    public void show(String o) {

    }
}

3. Wildcards?

In addition to using <T>for generics, there are <?>forms.They are called wildcards.

Why introduce this concept?Let's start with Demo below.

public class GenericDemo2 {

    class Base{}

    class Sub extends Base{}

    public void test() {
        // Inheritance relationship
        Sub sub = new Sub();
        Base base = sub;
        List<Sub> lsub = new ArrayList<>();
        // The compiler will not let this line of code pass.
        // Because Subis a subclass of Base, it does not mean that List <Sub>and List <Base>have an inheritance relationship.
        List<Base> lbase = lsub;
    }
}

In real-world coding, there is a real need for generics to handle a range of data types, such as a class and its subclasses. The concept of wildcards was introduced to Java.

Therefore, wildcards appear to specify a range of types in a generic.

Wildcards come in three forms.

  1. <?>Called an infinite wildcard
  2. <? Extends T>is called a wildcard with an upper limit
  3. <? Super T>is called a wildcard with a lower limit

3.1 Unbounded wildcard <?>

Infinite wildcards are often used in conjunction with container classes, where? Actually represents an unknown type, so when? Is involved, the operation must be independent of the specific type.

// Collection.java
public interface Collection<E> extends Iterable<E> {
   
    boolean add(E e);
}

public class GenericDemo3 {
    /**
     * Test unlimited wildcards <?>
     * @param collection c
     */
    public void testUnBoundedGeneric(Collection<?> collection) {
        collection.add(123);
        collection.add("123");
        collection.add(new Object());

        // You can only call type-independent methods in the Collection
        collection.iterator().next();
        collection.size();
    }
}

There is no need to focus on the real type in the Collection because it is unknown.Therefore, you can only call type-independent methods in a Collection.

Some classmates might think, <?>Why quote it since its role is so insignificant?_

Personally, I think that by improving the readability of the code, programmers can quickly create a very concise impression of it and quickly infer the intentions of the source author when they see it.

(rarely used, but to understand)

For the convenience of the next instructions, first define a few classes.

    class Food {}

    class Fruit extends Food {}

    class Apple extends Fruit {}

    class Banana extends Fruit {}

    // Container Class
    class Plate<T> {
        private T item;

        public Plate(T item) {
            this.item = item;
        }

        public T getItem() {
            return item;
        }

        public void setItem(T item) {
            this.item = item;
        }
    }

3.2 Upper limit wildcard <? Extends T>

<?>means that the type is unknown, but we do need to be a little more precise in describing the type. We want to identify the category within a range, such as type T and its subclasses, which can be placed in this container.

What is the upper bound

In this system, the upper limit wildcard Plate<?Extds Fruit > covers the blue area in the image below.

Side effect

Boundaries make it easier to convert between different Java generics.But don't forget that such a conversion also has some side effects.That is, some functions of the container may fail.

 public void testUpperBoundedBoundedGeneric() {
       Plate<? extends Fruit> p = new Plate<>(new Apple());

       // No element can be saved
        p.setItem(new Fruit()); // error
        p.setItem(new Apple()); // error

        // The read element needs to be a Fruit or Fruit base class
        Fruit fruit = p.getItem();
        Food food = p.getItem();
//        Apple apple = p.getItem();
    }

<? Extends Fruit>invalidates the set() method of placing things on a plate.But the get() method is still valid.For example, in the following example, two set () methods failed to insert Apple and Fruit.

The reason is that the compiler only knows if Fruit is inside the container or if it derives from it, but it does not know what type it is.Maybe Fruit?Maybe Apple?Or Banana, RedApple, GreenApple?

If you need a read-only container to produce T, use <? Extends T>.

3.3 Lower bound wildcard <? Super T>

Correspondingly, there is a lower limit wildcard <? Super T>

What is the lower bound

For that example, Plate<?Sup Fruit>Covers the red area in the image below.

Side effect

 public void testLowerBoundedBoundedGeneric() {
//        Plate<? super Fruit> p = new Plate<>(new Food());
        Plate<? super Fruit> p = new Plate<>(new Fruit());

        // Save Element Normal
        p.setItem(new Fruit());
        p.setItem(new Apple());

        // What you read can only be placed in Object
        Apple apple = p.getItem(); // error
        Object o = p.getItem();
    }

Because the lower bound specifies the minimum granularity of the element, it actually relaxes the type control of the container element.Since elements are the base class of Fruit, it is possible to store classes smaller in size than Fruit.But reading out takes a lot of work, and only the base class Object s of all classes can be loaded.But then element type information is lost.

3.4 PECS Principles

PECS - Producer Extends Consumer Super

  • "Producer Extends" - If you need a read-only container to produce T, use <? Extends T>.
  • "Consumer Super" - If you need a write-only container to consume T, use <? Super T>.
  • If we need to read and write at the same time, then we can't use wildcards.

4. Type Erase

Generics were introduced in Java version 1.5 and were not previously a concept of generics, but it is clear that generic code is well compatible with previous versions.

This is because generic information only exists during the code compilation phase and is erased before entering the JVM

The technical term is type erase.

List<String> strList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();
        
System.out.println(strList.getClass() == integerList.getClass());
==== output =====
true
=================

The result of printing is true because both List <String>and List <Integer>Classes in the jvm are List.class.

Generic information was erased.

/**
 * Type Erase Related Classes
 *
 * @author Richard_yyf
 * @version 1.0 2019/8/29
 */
public class EraseHolder<T> {

    T data;

    public EraseHolder(T data) {
        this.data = data;
    }

    public static void main(String[] args) {
        EraseHolder<String> holder = new EraseHolder<>("hello");
        Class clazz = holder.getClass();
        System.out.println("erasure class is:" + clazz.getName());

        Field[] fs = clazz.getDeclaredFields();
        for ( Field f:fs) {
            // Can we say that once a generic class is erased by a type, the corresponding type is replaced by an Object type?
            System.out.println("Field name "+f.getName()+" type:"+f.getType().getName());
        }

        EraseHolder2<String> holder2 = new EraseHolder2<>("hello");
        clazz = holder2.getClass();
        fs = clazz.getDeclaredFields();
        for ( Field f:fs) {
            System.out.println("Field name "+f.getName()+" type:"+f.getType().getName());
        }
    }

    static class EraseHolder2<T extends String> {
        T data;

        public EraseHolder2(T data) {
            this.data = data;
        }
    }
}

Limitations

Using the principle of type erasing, reflection bypasses operating limitations that are not allowed by the compiler in normal development.

public class EraseReflectDemo {

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(23);
        // can't add here
        // Boolean add (E) because of the restriction of generics;
        list.add("123"); // error

        // Reflection allows you to bypass the compiler and call the add method
        // Because Boolean add (E) is the same as boolean add(Object e) when type is erased;

        try {
            Method method = list.getClass().getDeclaredMethod("add", Object.class);

            method.invoke(list, "test");
            method.invoke(list, 42.9f);
        } catch (Exception e) {
            e.printStackTrace();
        }

        for (Object o : list) {
            System.out.println(o);
        }


    }
}
==== output =====
23
test
42.9
=================

Keywords: Java Programming jvm

Added by Aretai on Fri, 30 Aug 2019 05:24:14 +0300