Java泛型类、泛型接口以及泛型方法入门

184 阅读5分钟

泛型.png

import java.security.CodeSigner;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.FutureTask;

public class GenericClass {
    /**
     * 为什么使用泛型的痛点一?
     * 让同一段逻辑相同的代码适应不同的数据类型
     */
    private int add(int a,int b){
        //仅适用于int类型
        return a + b;
    }

    private float add(float a, float b){
        //仅适用于float类型
        return a + b;
    }
    private double add(double a, double b){
        //仅适用于double类型
        return a + b;
    }

    /**
     * 以上三个方法中逻辑完全一样,仅仅是因为传入的参数的类型不同,就需要拆分成三个方法。
     * 这是泛型需要解决的第一个痛点问题。
     */
    private void calculate(){
        add(1 , 2);
        add(1f,2f);
        add(1d,2d);
    }


    /**
     * 为什么使用泛型的痛点二?
     * 在list等集合类型中,在往其中插入数据类型时,不会进行类型检查,也不会报错。
     * 但是在取出时需要进行类型检查, 并进行强制转换,如果因为转换类型出错,会引发崩溃问题。
     *
     * 如果在list声明的时候能指定可以插入的数据类型,
     * 在插入的时候就进行类型检查,只允许限定的类型插入。
     * 在使用时,取出后也不用进行强制类型转换进而引发崩溃问题。
     */
    private void list(){
        //在一个list中同时插入不同的数据类型,并且不会报任何异常
        List  list = new ArrayList();
        list.add("title");
        list.add("name");
        list.add("address"); //前三个数据为String类型
        list.add(100); //最后一个为int类型

        for(int i=0;i<list.size();i++){
            //在使用时需要进行强制类型转换,而且会因为类型转换不对,而导致异常
//            System.out.println((String)list.get(i) );
        }

        List<String> listGeneric = new ArrayList<>();
        listGeneric.add("title");
        listGeneric.add("name");
        listGeneric.add("address"); //前三个数据为String类型
//        listGeneric.add(100); //最后一个为int类型,插入时会报异常
    }


    /**
     * 使用一个泛型类
     */
    private void userAGenericClass(){
        //实现泛型类时用Integer类型初始化
        GenericClass1<Integer> genericClassInt = new GenericClass1<Integer>();
        genericClassInt.c = 12;
        genericClassInt.compare(12,15);

        //实现泛型类时用Float类型初始化
        GenericClass1<Float> genericClassFloat = new GenericClass1<Float>();
        genericClassFloat.c = 12F;
        genericClassFloat.compare(12F,15F);

        //实现泛型类时用Double类型初始化
        GenericClass1<Double> genericClassDouble = new GenericClass1<Double>();
        genericClassDouble.c = 12D;
        genericClassDouble.compare(12D,15D);
    }


    public static void main(String[] args){
        GenericClass genericClass = new GenericClass();
        // 为什么使用泛型
        genericClass.calculate();
        genericClass.list();
        //定义并使用一个泛型类
        genericClass.userAGenericClass();
        //定义并使用一个泛型接口
        //使用泛型方法
        genericClass.useAGenericMethod();
        genericClass.extendsObj();
    }


    /**
     * 定义一个泛型类
     *  用T抽象一个类中的某一类型,
     *  该类型可用来限定属性、方法的参数以及方法的返回值
     * @param <T>
     */
    class GenericClass1<T>{
        //使用了泛型的属性声明
        private T c;

        //使用了泛型的方法声明
        public void setC(T c) {
            this.c = c;
        }

        //使用了泛型的方法声明
        private T compare(T a, T b){
            if(a == c){
                return a;
            }else{
                return b;
            }
        }
    }

    /**
     * 定义一个泛型接口
     */
    interface GenericInterface<T>{
        void hello(T a);
    }

    /**
     * 泛型接口的使用方式1 ,在实现泛型接口时指定了具体的类型。
     * 实现类是一个普通类,实现的方法也是一个普通方法。
     * 使用时当成普通类使用。
    */
    class GenericInterface1 implements GenericInterface<String> {
        @Override
        public void hello(String a) {

        }
    }

    /**
     * 泛型接口的使用方式2,继续沿用泛型,用一个泛型类实现一个泛型接口。
     * 实现类是一个泛型类,实现的接口中的方法也依然使用泛型抽象了具体类型。
     * 使用时当成泛型类使用。
     * @param <T>
     */
    class GenericInterface2<T> implements GenericInterface<T>{
        @Override
        public void hello(T a) {

        }
    }

    /**
     * 定义泛型类和泛型接口时可以指定多个泛型参数
     * @param <T>
     * @param <V>
     * @param <L>
     */
    class GenericClassMulti<T,V,L>{
        private void helloWorld(T t, V v, L l){
            System.out.println(t + " " + v + " " + l);
        }
    }

    interface GenericInterfaceMulti<M,N>{
        void hello(M m, N n);
    }


    /**
     * 泛型方法
     * 泛型方法和泛型类以及泛型接口是完全独立的,并不是说在泛型类或者泛型接口中声明的方法就是泛型方法,
     * 而是必须使用尖括号加泛型参数定义的方法。<T>
     *
     * 以上的所有方法包括add、hello、helloWorld等虽然有泛型参数,但都不是泛型方法。
     *
     * 泛型方法中限定的泛型参数,可以应用到函数返回值和参数类型中,同样不限定泛型参数的个数
     * @param <T>
     */
    private <T> void myName(T level){
        //这里可能有好多不知道的逻辑
    }

    private <K,M> M myName1(K k, M m){
        //这里可能有好多不知道的逻辑
        return m;
    }


    /**
     * 在泛型类中定义的泛型方法
     * @param <P>
     */
    class GeneClass<P>{
        //这是一个普通方法,仅仅是使用了在泛型类中限定的泛型参数P
        private P get(P p){
            return p;
        }

        //这是我一个泛型方法, 这里使用的泛型参数P和泛型类中使用的P不是同一个,两者没有任何关系。
        private <P> void getName(P p){

        }
    }


    /**
     * 使用泛型方法
     */
    private void useAGenericMethod(){
        myName("top"); //限定String类型
        myName(0); //限定Integer类型
        myName(new GenericClass()); //限定自定义的GenericClass类型

        myName1("top","t"); //两个泛型参数都限定为String类型
        myName1(1,"string"); //分别限定为Integer类型以及String类型
        myName1(new GenericClass(),"2132432"); //分别限定为GenericClass类型和String类型

        //在泛型类中使用泛型方法
        GeneClass<String> geneClass = new GeneClass();
        geneClass.get("name"); //必须传入String类型的参数
        //泛型方法的使用,和对象初始化时限定的类型无关
        geneClass.<String>getName("hi"); //可以传入String
        geneClass.<Integer>getName(90); //可以传入Integer类型
        geneClass.getName(new GeneClass<Integer>()); //可以传入自定义的类型
    }


    /**
     * 泛型中的约束
     */

    class Fruit{

    }

    class Apple extends Fruit {

    }

    class Person{

    }

    private void extendsObj(){
        //用Fruit限定泛型参数
        GeneClass<Fruit> fruitGeneClass = new GeneClass<>();
        fruitGeneClass.get(new Fruit()); //可以传入Fruit对象
        fruitGeneClass.get(new Apple()); //可以传入Apple对象,因为Apple继承Fruit。

//        fruitGeneClass.get(new Person()); //不可以传入Person对象,因为Person对象与Fruit对象无关

        //在泛型类中定义的泛型方法,在使用时限定的类型与对象初始化时限定的类型无关
        fruitGeneClass.getName(new Fruit());
        fruitGeneClass.getName(new Apple());
        fruitGeneClass.getName(new Person());
    }

    /**
     * 限定泛型参数的类型
     * @param <T>
     */
    class GenericExtends<T extends Comparable>{
        //如何保证传入的T类型中含有compareTo()方法。通过让泛型参数继承自Comparable,
        // 此时初始化该泛型类时传入的类型必须实现了Comparable接口
        private int compare(T a, T b){
            return a.compareTo(b);
        }
    }
    /**
     * 补充:
     * <T extends Comparable> 中extends 后的类型可以是类,也可以是接口,可以有一个,也可以有多个。
     * 但是如果同时集成了类和接口,类必须放在第一个,并且类只能有一个,因为Java是单继承的。
     * 比如 <T extends Person,Comparable>
     */

    /**
     * 泛型在使用过程中的约束以及局限性
     */
    static class GenericLimit<T> {
        //1、不能new一个T
        public void instance(){
            T t = new T();
        }
        //2、静态方法或者静态变量中不能使用类型变量。
        //因为类型变量是在对象初始化的时候指定的,而静态变量和静态域是在这之前就完成了初始化。
        public static T t1;
        public static void run(T t){

        }
        //3、静态方法如果是一个泛型方法是可以的,比如
        public static <T> void run1(T t){

        }
        //基础类型不允许实例化一个泛型参数,必须使用其包装类型。因为double、int等基础类型不是对象
        public static void run2(){
//            new GenericLimit<double>();
            new GenericLimit<Double>();
        }
    }
}