菜鸟笔记
提升您的技术认知

java之泛型

泛型

含义:集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象。所以在jdk1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此,此时把元素类型设计成一个参数,这个类型参数叫做泛型。

泛型形式:<类型>

泛型的优缺点

  • 不使用泛型的缺点:一般我们在使用的时候,基本往集合中存入的都是相同类型的数据,便于管理,所以,如果什么引用数据类型都可以存入集合就不方便
  • 加入泛型的优点:编译时期就可以对类型进行检查,不是泛型那么对应的类型就不可以加入该集合

集合中使用泛型案例

public class Tset6 {
    public static void main(String[] args) {
        Object o = new Object();
        String s = new String();
        o=s;//多态的一种形式

        Object[] objects = new Object[10];
        String[] strings = new String[10];
        objects=strings;//多态的一种形式

        ArrayList<Object> l1 = new ArrayList<>();
        ArrayList<String> l2 = new ArrayList<>();
        //l1=l2;报错
        //因为l1,l2底层都是Object类型的数组,而这个泛型仅仅是对我们写代码时进行限制,所以l1和l2本质上完全没继承关系,全是Object类型
    }
}

泛型总结

  • 泛型就相当于一个标签,其是jdk1.5以后推出的
  • 泛型实际上就是一个<>引起来的参数类型,这个参数类型只有在确定的时候才会确定具体类型
  • 使用泛型以后可以确定集合中存放的数据类型,并且数据类型是否一致在编译期就会检查出来
  • 泛型的类型都是引用数据类型,不能是基本数据类型
  • 使用泛型之后,后续的遍历等操作简单
  • 在jdk1.7以后ArrayList<Integer> integers = new ArrayList<>()(new ArrayList<>())里面的<>(钻石运算符)可以不用写类型了,因为后面的<>会根据前面的泛型进行类型推断
  • A和B是子类父类关系,但G<A>和G<B>不存在继承关系,其是并列关系,因为泛型的本质就是限定,G<A>和G<B>底层都是Object数组

自定义泛型结构

  • 泛型类
  • 泛型接口
  • 泛型方法

泛型类

//普通类
public class GenericTest {}
//泛型类
public class GenericTest<A> {}

泛型类案例 

普通情况

//泛型类
public class GenericTest<E> {
    int age;
    String name;
    E sex;
    public void method1(E n){
        System.out.println(n);
    }
    public void method2(E[] m){
        System.out.println(Arrays.toString(m));
    }
}
class Test{
    public static void main(String[] args) {
        //实例化时不指定泛型默认为Object
        GenericTest gt1 = new GenericTest();
        gt1.method1("hello");
        gt1.method1(17);
        gt1.method2(new String[]{"a","b","c"});
        gt1.method2(new Integer[]{1,2,3});
        //实例化的时候指定泛型
        GenericTest<String> gt2 = new GenericTest<>();
        gt2.method1("必须是string了,我失去了自由");
        gt2.sex="男";//因为上面已经指定了泛型为string,因此sex为string类型
    }
}

继承情况

父类指定泛型,子类可以直接使用父类方法属性等

//泛型类
public class GenericTest<E> {
    int age;
    String name;
    E sex;
    public void method1(E n){
        System.out.println(n);
    }
    public void method2(E[] m){
        System.out.println(Arrays.toString(m));
    }
}
class SubGenericTest extends GenericTest<Integer>{}
class Test{
    public static void main(String[] args) {
        //父类指定泛型,子类可以直接使用
        SubGenericTest sgt1 = new SubGenericTest();
        sgt1.method1(12);//这里父类指定泛型了,因此必须传Integer类型
    }
}

父类不指定泛型,那么子类直接变成一个泛型类,这个泛型类型可以在子类创建对象时确定

public class GenericTest<E> {
    int age;
    String name;
    E sex;
    public void method1(E n){
        System.out.println(n);
    }
    public void method2(E[] m){
        System.out.println(Arrays.toString(m));
    }
}
class SubGenericTest<E> extends GenericTest<E>{}
class Test{
    public static void main(String[] args) {
        SubGenericTest<String> sgt1 = new SubGenericTest();
        sgt1.method1("hello");
    }
}

泛型类可以定义多个参数类型

public class GenericTest1<A,B,C> {
    A age;
    B name;
    C sex;
    public void method(A m,B n,C x){
        System.out.println(m);
        System.out.println(n);
        System.out.println(x);
    }
    //泛型类的构造器不写泛型类型
    public GenericTest1(){}
}

泛型类总结

  • <>里面就是一个参数类型,这个类型现在是不确定的,相当于一个占位符,但是现在确定它是一个引用数据类型
  • 实例化的时候才会确定泛型类型
  • 遇到继承情况,父类为泛型类,若父类指定了泛型,那么子类可以直接使用
  • 父类不指定泛型,那么子类直接变成一个泛型类,这个泛型类型可以在子类创建对象时确定
  • 若在实例化的时候,不明确指定泛型,那么默认此泛型为Object类型
  • 泛型类可以定义多个参数类型
  • 泛型类型的构造器不加<>,就按常规写法即可
  • 不同泛型的引用类型不可以相互赋值
  • 泛型如果不指定,那么就会被擦除,对应类型为Object类型
  • 泛型类里的静态方法不能使用类的泛型(因为static方法优先于对象存在,而泛型是创建对象时指定的,怎么用)
  • 创建泛型数组:A[] i=(A[])new Object[10]

泛型方法

public class GenericTest2<E> {
    //不是泛型方法
    public void method1(E e){}
    //是泛型方法
    public <T> void method2(T t){}
    public <T,U> void method3(T t,U u){}
}

泛型方法的要求:这个方法的泛型的参数类型要和当前类的泛型无关。

注意:泛型方法定义的时候需要在返回值类型前面加上<T>,若不加的话会把T当作一种数据类型,然而代码中并没有T类型,便会报错。

泛型方法案例

public class GenericTest2<E> {
    //不是泛型方法
    public void method1(E e){}
    //是泛型方法
    public <T> void method2(T t){}
    public static <T,U> void method3(T t,U u){}
}
class Test5{
    public static void main(String[] args) {
        GenericTest2<Object> gt2 = new GenericTest2<>();
        gt2.method2("hello");//调用方法时确定泛型方法为字符串类型
        gt2.method2(12);//调用方法时确定泛型方法为Integer类型
        gt2.method3("hello", true);//调用方法时分别确定了2个泛型
    }
}

泛型方法总结

  • 不是带有泛型的方法就是泛型方法
  • 泛型方法的泛型类型是在方法调用时确定的
  • 泛型方法的泛型类型可以是多个
  • 泛型方法可以是静态方法

通配符

泛型通配符写法:<?>

经典案例

public class Test7 {
    public static void main(String[] args) {
        ArrayList<Object> objects = new ArrayList<>();
        ArrayList<String> strings = new ArrayList<>();
        ArrayList<Integer> integers = new ArrayList<>();
        List<?> list=null;
        list=objects;
        list=strings;
        list=integers;
    }
}

通配符类型变量操作

    public void method3(List<?> list){
        //遍历
        for (Object a:list){
            System.out.println(a);
        }
        //数据的写入操作
        //list.add("abc");报错,因为什么类型都可能接收到,接收到的类型若不是String类型的泛型就没意义了
        list.add(null);//可行,但没意义
        //数据的读取操作
        Object o = list.get(0);
        System.out.println(o);
    }

通配符总结

  • A和B是子类父类的关系,G<A>和G<B>不存在子类父类的关系,其是并列关系,但加入通配符?后,G<?>就变成了G<A>和G<B>的父类
  • 如果内部遍历该通配符类型的变量,那么用Object即可
  • 在List<?>类型变量作为参数的方法中,不能用list随意添加数据
  • 在List<?>类型变量作为参数的方法中,数据的读取操作用Object类型接收

泛型受限

public class Person extends Object{}
class Student extends Person{}
class Test8{
    public static void main(String[] args) {
        //下面3个集合不具备子类父类的关系
        ArrayList<Object> objects = new ArrayList<>();
        ArrayList<Person> persons = new ArrayList<>();
        ArrayList<Student> students = new ArrayList<>();

        //泛型的上限
        //List<? extends Person>是List<Person>的父类,也是List<Person的子类>的父类
        List<? extends Person> list1=null;
        //list1=objects;报错
        list1=persons;
        list1=students;

        //泛型的下限
        //List<? super Person>是List<Person>的父类,也是List<Person的父类>的父类
        List<? super Person> list2=null;
        list2=objects;
        list2=persons;
        //list2=students;报错
    }
}