Java泛型知识点总结


更新2020年5月8日11:16:07 :加入PECS的内容

1. 泛型

在JDK5之前,想要表示可变类型,基本都会用上Object类,但是这样会出现类型转换失败的风险。

于是从JDK5开始,Java引入了泛型。泛型的本质是类型参数化,解决不确定具体对象类型的问题。将明确类型这项工作推迟到创建对象时再决定。

  • 参数化类型:是指将类型当成参数的一种来传递。且泛型中的类型只能是引用类型不能是基本数据类型。.
  • 泛型只存在于编译阶段,在编译结束后就不会有泛型概念了。

2. 泛型的基本用法

泛型可以广泛的运用于类,接口,方法当中。编译器通过尖括号<>内部的字母来解析泛型。这个字母可以随意取名,也可以设置多个泛型,中间用 ‘,’ 隔开。

一般我们规定:

  • E表示Element,集合中的某个元素。
  • T表示Type of object,表示某个类
  • K表示key,V表示Value,表示键值对的元素。

2.1 泛型类

在类上面定义泛型,直到编程者使用这个类的时候,才定义泛型具体的类型。定义在类上的泛型可以在类的内部随意调用。最原始使用泛型的方法是用setter和getter来获取对象的实例。

public class GenericTest<T> {
    private T target;

    public void setTarget(T target) {
        this.target = target;
    }

    public T getTarget() {
        return target;
    }
}
public class Test {
	public static void main(String []){
		// 直到要用到这个类的时候,才明确泛型的具体类型,这里用String
		GenericTest<String> genericTest = new GenericTest<String>();

		//genericTest.setTarget(new Integer(10)); // 由于已经明确类型,就不能再用别的类型了。
		genericTest.setTarget(new String("generic"));

		// 明确类型后可以调用String独有的getBytes()方法。
		System.out.println(genericTest.getTarget().getBytes()); 

	}
}

但是对于泛型的对象由于没有明确类型,只能调用object的方法。只有在外部明确类型之后,才能调用对象的所有方法。
在这里插入图片描述

2.2 泛型方法

有时候,泛型并不需要运用到整个类中,只有某个方法需要使用到泛型,这个时候就可以只把泛型设置在这个方法上。
要注意, 代表泛型的尖括号只能写在返回类型和其他修饰符的中间,不可调换位置。

public class Test {
    public static <T>void func(T t){
        System.out.println(t.toString());
    }

    public static void main(String[] args) {
        func(new String("genericMethod")); // 和泛型类一样,在调用的那一刻才决定类型
        func(555); 

    }
}

2.3 泛型可以随意取名

看如下这个例子。

public class GenericDemo<T> {
	static <String, T, Ali> String get(String string, Ali ali){
		return string;
	}

	public static void main(String[] args){
		Integer a = 100;
		Boolean b = false;
		Integer c = get(a, b);
	}
}

这段代码可以编译且可以正常执行,get方法中,String出现在<>中,就不再代表我们熟知的java.lang.String类,而仅仅代表一个参数名,而在main的调用中,String明显就是值Integer类型。

3. 泛型的继承

3.1 子类明确泛型的类型

子类可以直接写明父类泛型中具体的类型。这种情况下子类本身就不用再使用尖括号标明泛型了。这种时候,子类完全脱离泛型,就直接当成普通的类使用。

public class SonGeneric extends GenericTest<String> {
}
public class Test {
	public static void main(String []){
		// 不需要再用<>标明泛型
		SonGeneric sonGeneric = new SonGeneric();

		//sonGeneric.setTarget(new Integer(10)); // 由于已经明确类型,就不能再用别的类型了。
		sonGeneric.setTarget(new String("sonGeneric"));

		// 明确类型后可以调用String独有的getBytes()方法。
		System.out.println(sonGeneric.getTarget().getBytes()); 

	}
}

3.2 子类不明确泛型的类型

当子类无法明确泛型的时候,有如下几种情况:

  1. 子类不添加泛型,也不重新注明父类的泛型,这种情况下父类的泛型直接当object类型判断。

    public class SonGeneric extends GenericTest {
    }
    

    在这里插入图片描述

  2. 子类添加原有的泛型。首先这种情况是不能通过编译的。
    在这里插入图片描述
    然后子类在添加泛型的时候,可以给泛型重新命名,但是还是表明是同一个参数。

    // 重新取名为A,但是还是原来的泛型
    public class SonGeneric1<A> extends GenericTest<A> {
    }
    

    在这里插入图片描述

  3. 子类重新编写泛型列表,这种情况下如果有引用父类的泛型,则是同一个,不是则代表是新增的。

    // C虽然是第三个,但是却代表了父类中的T
    public class SonGeneric2<A, B, C> extends GenericTest<C> {
    }
    

    注意看提示中的类型是Boolean
    在这里插入图片描述

4. 泛型通配符

先用表格和示例表明List,List和List<?>的区别。

    public static void main(String[] args) {
    	// 泛型出现之前的集合定义方法
        List a1 = new ArrayList();
        a1.add(new Object());
        a1.add(new Integer(10));
        a1.add(new String("hello generic1"));
		
		// 泛型定义为Object,可以添加各种类型的数据
        List<Object> a2 = a1;
        a2.add(new Object());
        a2.add(new Integer(11));
        a2.add(new String("hello generic2"));

		// 泛型定义为Integer,只能添加Integer类型
        List<Integer> a3 = a1;
        a3.add(new Integer(12));
        /*
        // 由于已经确定是Integer类型,不能添加别的类型
        a3.add(new Object());
        a3.add(new String("hello generic3"));
         */

		// 使用了通配符,不能添加元素,但是可以做其他的操作
        List<?> a4 = a1;
        /*
        // 使用通配符的时候,不允许添加元素
        a4.add(new Object());
        a4.add(new Integer(14));
        a4.add(new String("hello generic4"))
         */
        a4.remove(1);
        a4.clear();
    }
用法 名称 可以被赋值的类型 添加元素 特点
List 原始类型
泛型出现前的类型
所有List< E>且包括List<?> 允许 没有规定类型,元素都是Object
使用强制类型转换容易ClassCastException
List<?> 通配符类型 所有List< E>且包括List<?> 禁止 由于不能添加元素,一般用于方法的参数中
List< Object> 实际类型参数为Object 仅可以接受List和其本身类型 允许 相比List有被赋值的限制,安全性强。
// 可以被赋值的类型样例代码
List a = new ArrayList();
List<?> b = new ArrayList();
List<Object> c = new ArrayList();
List<Integer> d = new ArrayList();

List list;
list = a;
list = b;
list = c;
list = d;

List<?> list1;
list1 = a;
list1 = b;
list1 = c;
list1 = d;

// Integer类型同Object类型,只能赋值a和d
List<Object> list2;
list2 = a;
//list2 = b;
list2 = c;
//list2 = d;

4.1 无边界通配符

通配符的作用就是它的限制:不能写入数据和可以接受任何类型的赋值。我们一般将其放在方法的参数列表中。既能保证灵活性(各种各样的List都能赋值),又能保证安全性(不可插入元素)。

public void fun(List<?> list){
}

但是使用通配符还有个问题:List<?>不能使用get方法, 只有Object类型是个例外,但是获取Object类型的意义不大。

这个时候为了限定可以获取的类型,但是又不限定成一个而是多个类型,就要用到extends和super关键字了。用法不同于是类继承中的extends和方法中的super,但意义相近。

下面先列举三个类

public class A {
    public void funA(){}
}

class B extends A {
    public void funB() {}
}

class C extends B {
    public void func() {}
}

4.2 上边界通配符<? extends E>

表明参数化的类型只能是这个类或这个类的子类。

public class ExtendsGeneric {
    public static <T extends A>void fun1(T t){
        t.funA();
    }

    public static void fun2(List<? extends A> list){
        for (A a : list)
            a.funA();
    }

    public static void main(String[] args) {
        fun1(new A());
        fun1(new C());

		
        fun2(new ArrayList<A>());
        fun2(new ArrayList<C>());
    }
}

有定义和实例化的两种写法,一种是 T extends E,另一种是? extends E。

4.3 下边界通配符<? super E>

表明参数化的类型只能是这个类或这个类的父类。由于是父类的不确定性,所以只能调用Object的方法。

public class superGeneric {

    public static void fun1(List<? super A> list){
        for (Object o: list)
            o.toString();
    }

    public static void main(String[] args) {
        fun1(new ArrayList<A>());
        //fun1(new ArrayList<C>()); 报错,C不是A的父类
    }
}

只有一种写法。

4.4 使用场景

泛型的通配符主要是遵循一个PECS原则(Producer extends,Consumer super)。

  • 当你的泛型只读(比如只用list.get(),不用list.add())时,使用extends。
  • 当你的泛型只写(比如只用list.add(),不用list.get())时,使用super。
  • 当你的泛型只读且只需要使用Object类的方法时,使用无边界通配符。
  • 如果你需要泛型既能读又能写的话,不要用通配符,使用普通的泛型。

5. 泛型擦除

之前说泛型只存在于编译阶段,意思是说在Java文件编译生成Class文件后,泛型的概念就不复存在,class文件也不会带有任何泛型的信息。这个经过编译后取消泛型内容我们一般就称作泛型擦除。

参考材料

码出高效 Java开发手册 p87-90,p168-173

泛型就这么简单 - Java知识点大全 - SegmentFault 思否
https://segmentfault.com/a/1190000014120746

泛型通配符详解 - 竹马今安在 - 博客园
https://www.cnblogs.com/wxw7blog/p/7517343.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值