Java泛型学习

泛型入门

java集合有个缺点,把对象放进去之后,集合就会忘了这个对象的数据类型,再次取出后,就变为object类型,这样设计是为了集合可以保存任意类型的对象;但是也造成两个问题:

  • 集合对元素类型没有限制,会造成保存的对象混乱
  • 从集合中取出元素还要进行类型强制转换
如果不用泛型,不进行类型的检查将会引发异常
//会抛出ClassCastException异常
public class Liststr {
    public static void main(String[] args) {
        //创建一个只想保存字符串的list
        List strList = new ArrayList();
        strList.add("aaa");
        strList.add("bbb");
        //现在不小心保存一个Integer对象
        strList.add(5);
        strList.forEach(str -> System.out.println(((String) str).length()));
    }

}
复制代码
使用泛型

java5之后,引入“参数化类型”的概念,允许程序在创建集合是制定集合元素的类型;例如List<String>,即泛型。下面的程序,编译时会报异常

public class Liststr {
    public static void main(String[] args) {
        //创建一个只想保存字符串的list
        List<String> strList = new ArrayList<String>();
        strList.add("aaa");
        strList.add("bbb");
        //现在不小心保存一个Integer对象,编译时即报异常
        strList.add(5);
        strList.forEach(str -> System.out.println(((String) str).length()));
    }
}
复制代码

List<String>,可以称list为一个带参数类型的泛型接口,添加元素时进行类型检查,运行取出元素时直接使用即可,避免了类型的强制转换

java9增强的"菱形"语法

java7,构造器<>必须带泛型

List<String> strList = new ArrayList<String>();
复制代码

java7之后,构造器泛型不用带泛型

List<String> strList = new ArrayList<>();
复制代码

java9再次增强了菱形语法,甚至允许在创建内部类的时候使用菱形语法,java可以根据上下文来腿、推断匿名内部类中的泛型类型

public class AnnoymousTest {
    public static void main(String[] args) {
        //指定Foo类中泛型为String
        Foo<String> f = new Foo<>() {
            @Override
            public void Test(String s) {
                System.out.println("test方法的t参数为: " + s);
            }
        };
        //使用泛型通配符
        Foo<?> foo = new Foo<>() {
            @Override
            public void Test(Object o) {
                System.out.println("test方法的t参数为: " + o);
            }
        };
        //使用泛型通配符, 上限诶number
        Foo<? extends Number> foo1 = new Foo<>() {
            @Override
            public void Test(Number number) {
                System.out.println("test方法的number参数为: " + number);
            }
        };
    }
}
interface Foo<T> {
    void Test(T t);
}
复制代码

深入泛型

所谓泛型,就是允许在定义类、接口,方法是使用类型形参,这个类型形参(泛型)将在声明变量、创建对象、调用方法是动态的指定

简单示例:

public class Apple<T> {
    private T info;
    
    public Apple() {
    }
    
    public Apple(T info) {
        this.info = info;
    }

    public T getInfo() {
        return info;
    }

    public void setInfo(T info) {
        this.info = info;
    }

    public static void main(String[] args) {
        Apple<String> a1 = new Apple<>("苹果");
        System.out.println(a1.getInfo());
        Apple<Double> a2 = new Apple<>(5.32);
        System.out.println(a2.getInfo());
    }
}
复制代码

注意:

当创建带泛型声明的自定义类,为该类定义构造器时,构造器名还是原来的类名,不要增加泛型声明

从泛型类派生子类

基本语法

//如果使用原始类型,编译器发出警告,使用了未经检查或不安全的操作,即泛型检查的警告
public class A extends Apple{
    //类内容
}
//如果指定类型参数,则在父类中所有使用T类型的地方都将被替换成指定的类型,这里指String类型
public class A extends Apple<String>{
    //类内容
}
//这种写法是**错误**的,不能带泛型
public class A extends Apple<T>{
}
复制代码
并不存在泛型类
    public static void main(String[] args) {
        Apple<String> a1 = new Apple<>("苹果");
        Apple<Double> a2 = new Apple<>(5.32);
        //输出true
        System.out.println(a1.getClass() == a2.getClass());
    }
复制代码

不管泛型的实际类型参数是什么,他们在运行是总是同样的类

类型通配符

使用泛型时,类型参数不确定的时候,可以使用类型通配符

注意:

这里需要注意:List<String>并不是List<Object>的子类;

如果Foo是Bar的一个子类型(子类或者接口),而G是具有泛型声明的类或者接口,G<Foo>并不是G<Bar>的子类型!!!

java泛型设计的原则就是,只要代码在编译时没有出现警告,就不会遇到ClassCastException异常

使用类型通配符

类型通配符就是一个问号 ?,如写作List<?>,表示类型未知的List集合

为了表示各种泛型List的父类,可以使用类型通配符

public class Test {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        test(list);
        List<Integer> list1 = new ArrayList<>();
        list1.add(5);
        list1.add(8);
        test(list1);
    }

    public static void test(List<?> list) {
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }

    }
}
复制代码

在这里List<?>代表一切List类型的父类,所以test方法里可以传入其他参数类型的List参数

但是,这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素加入其中

List<?> list2 = new ArrayList<>();
//这里将会报错,因为并不知道List中的具体泛型类型是什么,
list2.add("qqq");
//唯一的例外为null,它是所有引用类型的实例
list2.add(null);
复制代码
设定类型通配符的上限

如果只希望泛型是某一类型的父类,可以使用上限;

如List<? extends Number>,则 List<Integer>、List<Double> 都是它的子类;

和类型通配符一样,使用类型通配符上限表示的集合,无法向其中添加元素,因为,无法确定具体表示的类型是什么

对于更广泛的泛型来说,指定通配符上限就是为了支持类型型变.比如MocaCoffe是Coffe的子类,这样A<MocaCoffe>就相当于A<? extends Coffe>的子类,可以将A<MocaCoffe>赋值给A<? extends Coffe>类型的变量,这种型变方式成为协变;

//定义基本泛型类
public class A<T> {
    private T info;
    public A(T info) {
        this.info = info;
    }
}
//定义父类Coffe
public class Coffe {
    private String name;
    private String cup;
    //省略get/set方法和构造函数
    }
    
//定义子类MocaCoffe
public class MocaCoffe extends Coffe {
    private String number;
    //省略get/set方法和构造函数
}
//定义子类EspressoCoffe
public class EspressoCoffe extends Coffe {
    private String number;
    //省略get/set方法和构造函数
}

//进行赋值测试
public class TestCaffe {
    public static void main(String[] args) {
    
        A<? extends MocaCoffe> a = new A<>(new MocaCoffe());
        A<? extends Coffe> a1 = new A<>(new Coffe());
        A<? extends Coffe> a2 = new A<>(new MocaCoffe());
        
        A<? extends MocaCoffe> e = new A<>(new EspressoCoffe());//编译检查报错
        A<? extends Coffe> e1 = new A<>(new EspressoCoffe());
        A<? extends MocaCoffe> e2 = new A<>(new Coffe());//编译检查报错
    }
}
复制代码
设定类型通配符的下限

语法: List<? super 类型> 假如为List</ super String> 要求传入的参数类型为String或者父类Object即可

//在上例的测试中,用通配下限表示如下:
 A<? super MocaCoffe> c = new A<>(new MocaCoffe());
 A<? super Coffe> c1 = new A<>(new MocaCoffe());
 A<? super MocaCoffe> c2 = new A<>(new Coffe());
 A<? super MocaCoffe> c3 = new A<>(new EspressoCoffe());
 A<? super Coffe> c4 = new A<>(new EspressoCoffe());
 A<? super EspressoCoffe> c5 = new A<>(new MocaCoffe());
 A<? super EspressoCoffe> c7 = new A<>(new Apple<>());
复制代码
设定泛型形参的上线
//这里定义的Apple形参上线为Number,只有使用NUmber或者Number的子类才可以
public class Apple<T extends Number> {
    private T info;

    public Apple() {
    }

    public Apple(T info) {
        this.info = info;
    }

    public static void main(String[] args) {
        Apple<String> a1 = new Apple<>("苹果");//编译报错,因为String不是Number的子类
        Apple<Double> a2 = new Apple<>(5.32);
        System.out.println(a1.getClass() == a2.getClass());
    }
}
复制代码

泛型方法

所谓泛型方法就是在定义方法的时候,定义一个或多个泛型形参;语法如下:

修饰符 <T,S> 返回值类型 方法名(形参列表){\
    //方法体
}
复制代码

//如:

static <T> void copyList(T[] a,Collection<T> c){
    //dosomething
}
复制代码

下面示范用法:

public class GenericMethodTest {
    static <T> void fromArrayToCollection(T[] a, Collection<T> c){
        for (T o : a){
            c.add(o);
        }
    }

    public static void main(String[] args) {
        Object[] oa = new Object[100];
        Collection<Object> co = new ArrayList<>();
        //代码中的T代表Object类型
        fromArrayToCollection(oa,co);
        
        String[] sa = new String[100];
        Collection<String> cs = new ArrayList<>();
        //代码中的T代表String类型
        fromArrayToCollection(sa,cs);
        
        Integer[] ia = new Integer[100];
        Collection<Integer> ci = new ArrayList<>();
        //代码中的T代表Integer类型
        fromArrayToCollection(ia,ci);
        //将会报错,泛型冲突,编译器无法确定泛型的具体类型
        fromArrayToCollection(ia,cs);
        
        Collection<Number> cn = new ArrayList<>();
        //代码中的T代表Number类型
        fromArrayToCollection(ia,cn);
        
    }
}
复制代码

与接口、类中定义的泛型不一样,方法中定义的泛型只能在该方法中使用;

编译器可以根据实参推断出泛型锁代表的类型,它通常推断出最直接的类型;所以不要制造迷惑,否则编译器无法正常推断;

public class GenericMethodTest {
    static <T> void fromArrayToCollection(Collection<T> a, Collection<T> c){
        for (T o : a){
            c.add(o);
        }
    }

    public static void main(String[] args) {
         List<String> list = new ArrayList<>();
        List<Object> list1 = new ArrayList<>();
        //编译器将报错
        fromArrayToCollection(list,list1);
    }
}
复制代码

上例中编译器无法确定类型,可以改为如下,使用类型通配符:

public class GenericMethodTest {
    static <T> void fromArrayToCollection(Collection<? extends T> a, Collection<T> c){
        for (T o : a){
            c.add(o);
        }
    }

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        List<Object> list1 = new ArrayList<>();
        //编译器正常工作
        fromArrayToCollection(list,list1);
    }
}
复制代码

但是,何时使用泛型方法,何时使用类型通配符呢?

泛型方法与类型通配符的区别

大多数时候,可以使用泛型方法来代替泛型通配符

在java中Collection接口中的两个方法定义:

public interface Collection<E>{
    boolean containAll(Collection<?> c);
    boolean addAll(Collection<? extends E> c);
}
复制代码

也可以改为下面这样的形式:

public interface Collection<E>{
    <T> boolean containAll(Collection<T> c);
    <T extends E> addAll(Collection<T> c);
}
复制代码

但是改写之后的方法中的T只被使用了一次,泛型T的唯一效果就是在不同的调用点可以传入不同的实际类型;这种情况,应该使用通配符;

通配符就是被设计用来支持灵活的子类化的;

关于泛型构造器和 泛型方法与方法重载暂且不做介绍;

擦除与转转

如果没有为泛型类指定实际的类型,此时称为原始类型

当把一个具有泛型信息的对象赋给另外一个没有泛型信息的变量时,所有的尖括号之间的类型信息都将被扔掉.比如讲一个List<String>类型转换为List,则该List对集合元素的类型检查变成了泛型参数的上限(即object);List<Integer>转换为List ,上限为List;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值