Java не включает обучающую серию — Generics

Java

предисловие

Когда я впервые связался с Java, я был скомпилирован, и я бегал везде, я чувствую себя очень сильным, я должен войти в яму. Но сейчас мейнстрим Java использует сцены или бэкэнд, и разработка графического интерфейса не очень хороша. Java по-прежнему остается в конкурентной борьбе среди серверных языков, и экология Java имеет прекрасные отношения, но нельзя отрицать, что Java действительно надежен в сценах с большим числом одновременных вычислений. Я также прочитал оценку языка голаанг, а также готов рассмотреть поступление в качестве технического резерва. Когда у меня есть время учиться, я должен учиться больше.

В последнее время я планирую уделить время пересмотру java, и я решил взглянуть на исходный код jdk и поднять его на новый уровень. Также хочу написать серию блогов о java и SpringBoot.

Дженерики

До JDK 1.5

Когда я попал в яму java, это был еще мир jdk 1.7. Однако дженерики — это хорошо. Перед дженериками мы могли бы написать такой код

    @Test
    public void run1() {
        // 有个集合你想保存 String 类型数据
        List list =new ArrayList();

        list.add("1111");

        String o = (String) list.get(0);
        System.out.println(o.length());

        // 可能会导致别的开发人员存储的是 int 类型
        list.add(2222);
    }

Проблемы с приведенным выше кодом:

  • Элементы, хранящиеся в коллекциях, должны быть преобразованы для использования соответствующих API.
  • Синтаксис времени компиляции не может обнаружить проблему, которую я упомянул, и во время выполнения будут скрытые опасности.

JDk 1.5

1.7 Добавлены функции Алмазные дженерики, которые я смешал вместе, чтобы сказать

        // 有个集合你想保存 String 类型数据
        List<String> list =new ArrayList();

        list.add("1111");

        String s = list.get(0);
        System.out.println(s.length());

        // 下列会在编译时期就会报错
        // list.add(2222);

Разве это не весело и не так античеловечно?

Дженерики

Общие сведения: ввод параметров

  • Обобщения могут быть объявлены в классах или методах, а обобщения в методах имеют приоритет над классами. Для простоты понимания лучше не использовать то же имя, что и объявление класса.
  • Статические методы не могут быть объявлены с использованием дженериков в классе, только в методах.
  • Часто используемые общие объявления (T, E, K, V,?)
    • ? представляет неопределенный тип java
    • T (тип) представляет определенный тип Java
    • K V (значение ключа) соответственно представляют значение ключа в значении ключа java.
    • E (элемент) означает элемент
    • S, U, V и т. д.: типы 2, 3, 4 в многопараметрическом случае.

проверка кода

  • Обобщения могут быть объявлены в классах или методах, а обобщения в методах имеют приоритет над классами.
public class ClientTest<T> {

    /**
     * 验证泛型可以在类上和方法上声明
     * 不能这样 <K1 super Parent>
     */

    public <T2 extends ArgsParent> T2 run2(T k) {
        System.out.println(k.getName());
        System.out.println(k);
        return (T2) k;
    }
}

Вышеупомянутое подтверждает, что дженерики могут быть объявлены в классах и методах.

package com.fly.study.java.generics;

import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;

/**
 * @author 张攀钦
 * @date 2019-09-16-00:04
 * @description 泛型验证
 */
public class ClientTest<T> {

    private ClientTest clientTest;

    private Parent parent;

    private Son son;

    private ClientTest<Son> sonClientTest;

    private ArgsParent argsParent;

    private ArgsSon argsSon;


    /**
     * 限制方法参数的泛型和返回值类型,泛型优先使用方法上的,当方法上没有使用类上的
     * 不能这样 <K1 super Parent>
     */

    public <T extends ArgsParent, T2 extends ArgsParent> T2 run2(T k) {
        System.out.println(k.getName());
        System.out.println(k);
        return (T2) k;
    }



    @Before
    public void before() {
        clientTest = new ClientTest();
        parent = new Parent();
        parent.setName("parent");

        son = new Son();
        son.setAge(18);

        sonClientTest = new ClientTest<>();

        argsParent = new ArgsParent();
        argsParent.setName("argsParent");
        argsSon = new ArgsSon();
    }

    @Test
    public void test2() {
        System.out.println(clientTest.run2(argsParent));
        System.out.println(clientTest.run2(argsSon));
        // 验证方法上的泛型优先于类上的泛型
        sonClientTest.run2(argsSon);
//        下面用法错误
//        sonClientTest.run2(son);
    }
}

@Data
public class ArgsParent {
    private String name;
}

@Data
public class ArgsSon extends ArgsParent {
    private Integer age;
}

@Data
public class Parent {
    private String name;
}

@Data
public class Son extends Parent {
    private Integer age;
}

Я использую один и тот же общий T в классе и методе, но ограничения типа разные, универсальный T в классе — это ClientTest sonClientTest, когда я создаю новый объект, и я указываю T в методе, как при запуске метода, если параметры может передаваться только в ArgsParent и его подклассах, я могу проверить свой вывод.

        // public class ClientTest<T> 
        // public <T extends ArgsParent, T2 extends ArgsParent> T2 run2(T k)
        // argsSon 为 ArgsParent 的子类对象
        sonClientTest.run2(argsSon);
        //  下面用法错误,son 为 Son 的实例对象
        //  sonClientTest.run2(son);
  • Статические методы не могут использовать общие объявления в классах, они могут быть объявлены только в методах.
public class ClientTest<T> {
    /**
     * 静态方法上的泛型不能使用类上的,只能再方法上声明泛型
     */
    public static <T extends ArgsParent, T2 extends ArgsParent> void run4(T k) {
        System.out.println(k);
    }
  	
  	// 语法错误
    public static  void run4(T k) {
        System.out.println(k);
    }
}
  • общий квалификатор

Родовой квалификатор очень удобно понимать буквально, а комментарии в коде могут пояснить смысл

// 限定参数类型只能为 ArgsParent及其子类
<S extends ArgsParent>

// 语法错误,jdk 1.8
<S super ArgsParent>

// 限定参数类型只能为 ArgsParent 及其子类
<? extends ArgsParent>

// 限定参数只能为 ArgsParent 及其父类
<? extends ArgsParent>
public class AllGenerics<T> {

    public void run1(T t) {
        System.out.println(t);
    }

    public <S> void run2(S s) {
        System.out.println(s);
    }
    
    // 限定参数类型只能为 ArgsParent及其子类
    public <S extends ArgsParent> void run3(S s) {
        System.out.println(s);
    }
    
    // 语法错误,
    // public <S super ArgsParent> void run3(S s) {
    // System.out.println(s);
    //}
    
    // 限定参数类型只能为 ArgsParent及其子类
    public void run4(List<? extends ArgsParent>  s) {
        System.out.println(s);
    }

    // 限定参数只能为 ArgsParent及其父类
    public void run5(List<? super ArgsParent>  s) {
        System.out.println(s);
    }
}

Общий стирание

Общий эффективен только во время компиляции, будет заменен после составления общего оператора, чтобы определить тип неопределенности вместо того, чтобы использовать объект, чтобы определить тип использования.

  • Когда универсальный тип не может быть определен
// 源码
public class AllGenerics<T> {
     public void run1(T t) {
        System.out.println(t);
    }
}

// 编译之后的代码可以这样理解

public class AllGenerics<Object> {
     public void run1(Object t) {
        System.out.println(t);
    }
}

  • Используйте общие квалификаторы для
public class AllGenerics<T> {
     public <S extends ArgsParent> void run2(S s) {
        System.out.println(s);
    }
}

// 编译之后的代码可以这样理解

public class AllGenerics<ArgsParent> {
     public void run1(ArgsParent t) {
        System.out.println(t);
    }
}

проверка кода

  • Дженерики действительны только во время компиляции
@Data
public class ClientTestSuperT {
    @Test
    public void test1() throws Exception{
        List<String>t=new ArrayList<>();
        Method add = t.getClass().getMethod("add",Object.class);
        
        // 添加 Integer 类型数字
        add.invoke(t,1);
        System.out.println(t);
    }
}
  • Общее стирание использует замену определяемого типа
package com.fly.study.java.generics;


import org.junit.Test;

import java.lang.reflect.Method;


public class AllGenerics<T> {

    public <S extends ArgsParent> void run2(S s) {
        System.out.println(s);
    }

    public void run1(T t) {
        System.out.println(t);
    }

    @Test
    public void test1() throws Exception {
        AllGenerics<String> allGenerics =new AllGenerics<>();
        Method method= allGenerics.getClass().getMethod("run1", Object.class);
// java.lang.NoSuchMethodException:
// com.fly.study.java.generics.AllGenerics.run1(java.lang.String)
//      Method method= allGenerics.getClass().getMethod("run1", String.class);
        method.invoke(allGenerics,"111");
    }

    @Test
    public void run2() throws Exception {
        AllGenerics<String> allGenerics =new AllGenerics<>();
        Method method= allGenerics.getClass().getMethod("run2", ArgsParent.class);
        ArgsParent argsParent = new ArgsParent();
        argsParent.setName("测试");
// java.lang.NoSuchMethodException: 
// com.fly.study.java.generics.AllGenerics.run1(java.lang.String)
//  Method method= allGenerics.getClass().getMethod("run2", Object.class);
        method.invoke(allGenerics,argsParent);
    }

}
  • Идеи для проверки
AllGenerics<T> 编译之后 AllGenerics<Object>
    
    // 编译之后为 public void run1(Object t)
    public void run1(T t) {
        System.out.println(t);
    }
    
    // 编译之后 public void run2(ArgsParent s)
    public <S extends ArgsParent> void run2(S s) {
        System.out.println(s);
    }
  • Используя Reflected Get Method run1, можно получить параметры объекта метода, получить runl, не удается найти параметр String

@Test
public void test1() throws Exception {
    AllGenerics<String> allGenerics =new AllGenerics<>();
    Method method= allGenerics.getClass().getMethod("run1", Object.class);
    method.invoke(allGenerics,"111");
}

Приведенный выше код может работать нормально, убедитесь, что код действительно заменяется после компиляции.

@Test
public void test1() throws Exception {
    AllGenerics<String> allGenerics =new AllGenerics<>();
    Method method= allGenerics.getClass().getMethod("run1", String.class);
    method.invoke(allGenerics,"111");
}

В приведенном выше коде возникает ошибка java.lang.NoSuchMethodException: com.fly.study.java.generics.AllGenerics.run1(java.lang.String), что указывает на то, что этот метод не существует в скомпилированном коде.

  • Для дженериков, использующих квалифицированный тип, замените его квалифицированным типом.
// 方法编译之后为:public  void run2(ArgsParent s)
public <S extends ArgsParent> void run2(S s) {
    System.out.println(s);
}
@Test
public void run2() throws Exception {
    AllGenerics<String> allGenerics =new AllGenerics<>();
    Method method= allGenerics.getClass().getMethod("run2", ArgsParent.class);
    ArgsParent argsParent = new ArgsParent();
    argsParent.setName("测试");
    method.invoke(allGenerics,argsParent);
}

Вышеупомянутый метод был получен, что указывает на то, что моя догадка верна

@Test
public void run2() throws Exception {
    AllGenerics<String> allGenerics =new AllGenerics<>();
    ArgsParent argsParent = new ArgsParent();
    argsParent.setName("测试");
    Method method= allGenerics.getClass().getMethod("run2", Object.class);
    method.invoke(allGenerics,argsParent);
}

Код сообщает об ошибке, и соответствующий метод не может быть найден

Рекомендуемое чтение

понимание сковороды в форме java

Дженерики: как это работает и почему это важно