Esas cosas sobre los genéricos de Java

1. Descripción general de los genéricos

1.1 Por qué usar genéricos

Sin genéricos, solo puede usar tipos específicos o tipos de objetos al escribir código, y no puede hacer lo que el usuario quiere usar . Por ejemplo, cuando se crea un método, los parámetros formales deben especificar el tipo de datos que se utilizará.Al comienzo de la creación del método, se ha determinado el tipo de datos que el método puede manejar, lo que limita en gran medida la flexibilidad de la programación. Debido a esto, existe una programación genérica donde tú decides qué tipo específico es cuando lo usas .

1.2 ¿Qué son los genéricos?

Genérico: cosas amplias, comunes, no específicas. Los genéricos son el uso de símbolos para representar tipos no específicos al comienzo de la definición, y los tipos específicos se especifican dinámicamente cuando se usan. Debe comprender este tipo de idea de diseño de programación genérica. Los beneficios de usar genéricos son que el código es más conciso, más flexible y el programa es más robusto (no hay advertencia en el momento de la compilación y no habrá una excepción de conversión de clase). - ClassCastException en tiempo de ejecución).

2. Interfaz genérica, clase, método

Los genéricos permiten su uso al definir interfaces, clases y métodos , y se especificarán dinámicamente al declarar variables, crear objetos y llamar a métodos.

2.1 Interfaz genérica

Defina una interfaz genérica: como la interfaz de lista en una colección

// 定义接口时指定泛型:E,E类型在接口中就可以作为类型使用
public interface List<E> extends Collection<E>{
    ……
    boolean add(E e);
    Iterator<E> iterator();
    ……
}
// 定义接口时指定泛型:K 和 V,K和V类型在接口中就可以作为类型使用
public interface Map<K,V>{
    ……
    Set<K> keySet();
    Collection<V> values();
    Set<Map.Entry<K, V>> entrySet();
    ……
}

Usar interfaz genérica: El tipo genérico E de la interfaz Lista se especifica como el tipo concreto Cadena cuando se usa

public static void main(String[] args) {
    List<String> list = new ArrayList<>();// 指定泛型类型E=String
    list.add("我只认识字符串");//boolean add(E e); 等价于boolean add(String e);
    Iterator<String> iterator = list.iterator();//Iterator<E> iterator();

    while (iterator.hasNext()){
        String next = iterator.next();//不需要强转为String
        System.out.println(next);
    }
}

En cuanto a cómo usar la colección Map<K,V> de interfaz genérica, puede escribirlo usted mismo y sentirlo.

2.2 Clases genéricas

clase genérica simple

definir clase genérica

public class DemoFx<D> {
    private D dd;
    public D getDd(){
        return this.dd;
    }
    public void setDd(D dd){
        this.dd = dd;
    }
}

Usar clases genéricas

public static void main(String[] args) {
    DemoFx<String> stringDemoFx = new DemoFx<>();
    stringDemoFx.setDd("我是字符串类型");
    System.out.println(stringDemoFx.getDd());
}

Herencia e implementación de clases genéricas

Defina una clase genérica: tome la clase ArrayList como ejemplo, herede la clase abstracta genérica e implemente la interfaz genérica:

public class ArrayList<E> extends AbstractList<E> 
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
    ……
    public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }
    ……
}

Usar clases genéricas

public static void main(String[] args) {
    List<String> list = new ArrayList<String>();// 指定泛型类型E=String
    list.add("我只认识字符串");
    String s = list.get(0);// 返回值为String
}

2.3 Métodos genéricos

Definición de métodos genéricos: o el caso de ArrayList

public class ArrayList<E> extends AbstractList<E> 
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
    ……
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of as runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }
    ……
}

Use métodos genéricos: public <T> T[] toArray(T[] a)

public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
    list.add("s1");
    list.add("s2");
    list.add("sn");
    // public <T> T[] toArray(T[] a) 
    String[] strings = list.toArray(new String[list.size()]);
    System.out.println(Arrays.asList(strings));
}

3. Escriba comodines

3.1 Uso de comodines de tipo

El símbolo de representación de comodín es el signo de interrogación < ? >, que es un tipo desconocido y puede coincidir con cualquier tipo, también conocido como comodín ilimitado .

Comparando los métodos creados por "comodín" y "genérico"

// 通配符定义
public void foreach(List<?> list){
    for (int i =0 ;i<list.size();i++) {
        Object o = list.get(i);
        System.out.println(o.toString());
    }
}
// 泛型定义
public <T> void foreach2(List<T> list){
    for(T t : list){
        System.out.println(t.toString());
    }
}
// 使用
public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
    list.add("s1");
    list.add("s2");
    list.add("sn");
    Demo demo = new Demo();
    demo.foreach(list); // 通配符
    demo.foreach2(list); // 泛型
}

Tanto los comodines como los genéricos pueden lograr el mismo efecto, y el método genérico también puede usar el tipo genérico definido por sí mismo, y el "?" del comodín no se puede usar como un tipo de datos, por lo que solo se puede usar Object en casos de métodos comodín. Recibe los elementos de una lista, lo que también tiene la desventaja de los comodines: no hay forma de determinar cuál es el tipo desconocido.

Entonces, ¿cuál es el uso de los comodines?

Los comodines son un caso especial de genéricos que se pueden usar en parámetros formales sin definir un tipo desconocido.

Diferencia entre genéricos y comodines

  • El compilador de Java infiere el tipo genérico [T] como un tipo T y permite variables de tipo T en el bloque de código, mientras que el comodín [?] se infiere como un tipo desconocido y no hay una variable de tipo ?;

  • Class<T> debe depender de T, y <T> debe especificarse cuando se declara el método, mientras que Class<?> no es necesario;

Esto puede ser una mejor comprensión de los genéricos y los comodines: los genéricos enfatizan los tipos y los comodines enfatizan los símbolos .

Class<?> representa cualquier tipo, pero no es equivalente a Class<Object>. El primero solo puede insertar nulo si el tipo no coincide , pero el segundo puede insertar Object o cualquier subclase de Object object.

Por ejemplo: no puede agregar objetos de ningún tipo a la lista List<?>, excepto nulo

3.2 Tipo límite superior

Límite superior del comodín: <? extends Demo>, el límite superior del comodín [?] es de tipo Demo, es decir, el alcance de <? extends Demo> es Demo o su tipo de subclase.

Límite superior de genéricos: <T extiende Demo>, igual que la comprensión de comodines. El límite superior del tipo se muestra en la figura.

 

Caso :

Cree tres clases DemoFather, Demo, DemoChildren, la relación es como se muestra arriba

public class DemoTest {

    public static void main(String[] args) {
        List<DemoChildren> demoChildrens = new ArrayList<>();
        demoChildrens.add(new DemoChildren());
        demoChildrens.add(new DemoChildren());

        DemoTest test = new DemoTest();
        test.testDemo(demoChildrens); // 通配符
        test.testDemo2(demoChildrens);// 泛型

    }

    // 通配符上限:控制list 集合允许的类型范围为Demo或其子类
    public void testDemo(List<? extends Demo> list){
        // 若无上限,这里只能用Object类型代替Demo类型
        for (Demo demo : list){
            System.out.println(demo.toString());
        }
    }

    // 泛型上限:控制list 集合允许的类型范围为Demo或其子类
    public <T extends Demo> void testDemo2(List<T> list){
        for (T t : list){
            System.out.println(t.toString());
        }
        // or
        for(Demo demo:list){
            System.out.println(demo.toString());
        }
    }

}

El límite superior de los genéricos se determina en el momento de la definición <T extends Demo>, los comodines determinan directamente el límite superior de los parámetros formales <? extends Demo>. De hecho, se entiende bien.El límite superior del tipo es agregar un rango "límite superior" basado en el método de escritura general, que es ambos extiende xxx.

Algunos ejemplos de código fuente

// 接口泛型上限
public interface ObservableArray<T extends ObservableArray<T>> extends Observable {……}
// 抽象类泛型上限
public abstract class ArrayListenerHelper<T extends ObservableArray<T>> extends ExpressionHelperBase {……}
public abstract class CellBehaviorBase<T extends Cell> extends BehaviorBase<T> {……}
// 方法泛型上限
public static <T extends Number> ReadOnlyLongProperty readOnlyLongProperty(final ReadOnlyProperty<T> property) {……}
// 通配符上限
void putAll(Map<? extends K, ? extends V> m);

3.3 Tipo límite inferior

El límite inferior del comodín: <? super Demo>, el límite inferior del comodín [?] es el tipo Demo, es decir, el alcance de <? super Demo> es el tipo principal de Demo.

Límite inferior genérico: Ninguno . Principalmente porque los límites inferiores del tipo pueden ser confusos y no particularmente útiles. Alguna explicación de por qué no hay un límite inferior para los parámetros de tipo: http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ107

public static void main(String[] args) {
    Demo demo = new Demo();
    List<Demo> demos = new ArrayList<>();
    demos.add(demo);
    DemoTest test = new DemoTest();
    test.testSuper(demos);

    DemoFather demoFather = new DemoFather();
    List<DemoFather> demoFathers = new ArrayList<>();
    demoFathers.add(demoFather);
    DemoTest test2 = new DemoTest();
    test2.testSuper(demoFathers);
}

public void testSuper(List<? super Demo> list){
    // 虽然有下限,但无法直接使用Demo类型接收参数
    for (Object obj : list){
        System.out.println(obj.toString());
    }
}

Aunque existe un límite inferior, no es posible utilizar directamente el tipo Demo para recibir parámetros. Es como "upcasting" y "downcasting", upcasting es automático, downcasting requiere conversión forzada; los límites superiores de tipo pueden usar el tipo más grande (clase principal) para recibir un tipo más pequeño (subclase) de lo que es, y el límite inferior de tipo es no es posible usar el tipo más pequeño (subclase) para aceptar un tipo potencialmente más grande (superclase).

Algunos ejemplos de código fuente

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    ……
    public void sort(Comparator<? super E> c) {
        final int expectedModCount = modCount;
        Arrays.sort((E[]) elementData, 0, size, c);
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }
}

public class Arrays {
    public static <T> void sort(T[] a, int fromIndex, int toIndex,
                                Comparator<? super T> c) {
        if (c == null) {
            sort(a, fromIndex, toIndex);
        } else {
            rangeCheck(a.length, fromIndex, toIndex);
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, fromIndex, toIndex, c);
            else
                TimSort.sort(a, fromIndex, toIndex, c, null, 0, 0);
        }
    }
}

¿Qué pasa con las letras genéricas E, K, V, T?

  • E significa Elemento,

  • K representa clave,

  • V representa valor,

  • T representa Tipo,

  • N representa Número,

  • ? para tipo desconocido

Excepto [?], puede escribir otros símbolos genéricos como cadenas, pero preste atención a la legibilidad. Generalmente, se representan con una sola letra mayúscula, de lo contrario, el código no será conciso y difícil de leer.

4. Principio de implementación de genéricos - borrado de tipo

Asignar un objeto con información genérica a otra referencia de variable sin información genérica borrará la información de tipo .

Caso 1: No se especifica el límite superior de genéricos y es Objeto después de borrar el tipo

public static void main(String[] args) {
    // 定义集合泛型E = String
    List<String> stringArrayList = new ArrayList<>();
    stringArrayList.add("test1");
    // 获取到的类型为:String
    String s = stringArrayList.get(0);
    // 把带有泛型信息的stringArrayList 对象赋给不确定泛型的List
    List listObject = stringArrayList;
    // listObject 只知道get的类型为Object,而不是String
    Object obj = listObject.get(0);
}

Caso 2: especifique un límite superior genérico y el borrado de tipo es el tipo de límite superior

public class DemoFather {}
public class Demo extends DemoFather{}
public class DemoChildren<T extends DemoFather> {
    private T t;
    public T getT(){
        return this.t;
    }
    public void setT(T t){
        this.t= t;
    }
}
// 测试public class DemoTest {
 public static void main(String[] args) {
        //class DemoChildren<T extends DemoFather>,指定泛型T=Demo类型
        DemoChildren<Demo> demoChildren = new DemoChildren<Demo>();
        // 拿到的方法类型确实是T=Demo类型
        Demo demo = demoChildren.getT();
        // 把带有泛型信息的 demoChildren 对象赋给不确定泛型的demoChildren2
        DemoChildren demoChildren2 =demoChildren;
        // 再来获取方法的类型时,变为了上限的DemoFather类型
        DemoFather demoFather = demoChildren2.getT();
    }
}

En conclusión:

Cuando se especifica el límite superior genérico, el tipo del límite superior es el tipo después del borrado del tipo; de lo contrario, el tipo de objeto, porque todas las clases en Java heredan la clase de objeto de forma predeterminada.

Entonces, la clase genérica del caso 2 se ve así en la fase de compilación

public class DemoChildren {
    private DemoFather t;
    public DemoFather getT(){
        return this.t;
    }
    public void setT(DemoFather t){
        this.t= t;
    }
}
// 原来的泛型类,对比一下
public class DemoChildren<T extends DemoFather> {
    private T t;
    public T getT(){
        return this.t;
    }
    public void setT(T t){
        this.t= t;
    }
}

Supongo que te gusta

Origin blog.csdn.net/m0_62396648/article/details/124394925
Recomendado
Clasificación