泛型使用

1 定义:申明中存在类型参数(type parameter)的“类”、“接口”、“方法”,称为泛型。


1.1泛型类

1.1.1 申明

public class Apple<T> {

private  T t;

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

public T getInfo() {
return t;
}
}

当泛型定义的类型参数T后,参数T可以在类申明中使用(包括属性、方法中)


1.1.2 实例化泛型必须要指定类型参数的具体类型(这也是类型参数不能在静态域的原因)

Apple<String> apple = new Apple<>("red");


1.1.3 泛型继承(继承分为两种:指定类型参数,不指定类型参数)

public class AppleSon1 extends Apple<String>{

public AppleSon1(String t) {
super(t);
}

@Override
public String getInfo() {
return super.getInfo();
}
}

从重写父类的方法可以看出,继承指定参数类型后,实际已经将类型参数的具体类型String告知了编译器


public class AppleSon2 extends Apple{

public AppleSon2(Object t) {
super(t);
}

@Override
public Object getInfo() {
return super.getInfo();
}
}


AppleSon2 son2 = new AppleSon2(new Some())

son2.getInfo()在编译时,编译器只知道是一个Object(但是实际运行时还是Some),需要强制转换为一个Some,才能使用Some的方法。


参数类型T不能在静态域中,原因显而易见:泛型参数是实例化才能使用的!


运行时有: 

List<String> list1 = new ArraryList<String>(), List<Integer> list2 = new ArraryList<Integer>()

总有list1.getClass() == list2.getClass()成立,因为list1和list2的类型都是java.util.ArrayList,和类型参数半点关系没有


1.2 泛型接口


1.3 泛型方法

1.3.1 泛型方法定义

<T,S,R> 返回类型 方法名(含有T、S、R的参数列表)

public static <T,R> R fun(T t, R r) {
List<T> list = new ArrayList<>();
list.add(t);// 指定类型参数T,编译器认为集合类型是已知,可以存入T
return r;
}


调用:

String s = "ssss";

Integer i = 1;

Integer result = fun(s,1);

本质是由编译器根据s类型,i类型 以及类型参数列表参数位置,自动推断出类型

1.3.2 编译器推断

public static <T> T fun(T t1, T t2){}

Integer i

String s

fun(i,s)

尽管t1和t2的实际类型不同,参数类型都是T,故而编译器自动推断T为Object


如果有:

String s1, s2

fun(s1,s2)

编译器自动推断T为String类型


List<String> list1

List<Integer> list2

fun(list1, list2)

编译器自动推断为List<? extends Object>


如果

<T> void fun(List<T> list1, List<T> list2)

List<String> list1

List<Integer> list2

fun(list1,list2)

出现编译错误,把编译器搞懵逼了,因为list1和list2是两个完全不同类型,泛型方法的类型参数是两个相同类型。

注意:即使是两个list中的元素具有继承关系,编译器也无法推断:


List<Object> list1

List<String> list2

fun(list1,list2)  

编译器推断错误,需要使用通配符?改造:


<T> void fun(list<T> t1, list<? extend T> t2)

编译器自动推断 List<Object> 和 List<? extends Object>


泛型方法和泛型类、接口最大区别是,调用时候前者需要编译器自动推断参数类型是否匹配,后者需要程序员指定类型


如果将List<E>认为是泛型,在定义另一个泛型类A,将List作为方法参数时,下面等价的

class A<T>{

void fun(List<? extends T> t)

}

VS.

class A<T>{

<E extends T> void fun(List<E> t)

}

几乎效果相同,但是建议如果集合的参数类型之间没有依赖关系,参数与返回没有依赖关系,使用通配符


1.4 类型参数上限

public class Apple<T extends Number> {

private  T t;

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

public T getInfo() {
return t;
}
}


在使用或者继承Apple时,指定的泛型参数不是Number的子类,会报编译错误,提高代码安全性


2 通配符?

2.1在使用泛型时,如果不指定参数类型,编译器会发出警告(注意不是报错,主要提示用户防止误使用运行时类型犯错)

void test(List list); 会存在编译警告


2.2但是注意编译器进行类型检查时,List<Object>和List<String> 以及 ArraryList<Object>和ArraryList<String>是完全不同两种类型(与运行类型区别:.getClass()拿到运行时类型)


void test(List<Object> list);


List<String> list  = new ArraryList<>();

test(list);会有编译错误,编译器任务List<Object>和List<String>是完全不同的类型


2.3 类型通配符号应运而生

void test(List<?> list);


List<String> list  = new ArraryList<>();

test(list);完全正确,List<?> 是所有List<String>,List<Integer>的父类


2.4 通配符上限

只想要通配符表示一部分泛型类型的父类

定义:void test(List<? extends Number> list);

那么 List<Integer> list1 和 List<Long> list2 可以作为参数,List<String>作为参数非法

注意:通配符?任然是不能确定泛型类型的

void test(List<?> list){

 list.add(new Object())// 报错,这时的泛型任然是未知类型,不能存入,只能通过参数类型化泛型化解决

}

如:

void test(List<T> list, T t){

list.add(t)

}


2.5 通配符下限

需求从src把数据拷贝到des中,为了通用性,des中元素保证是src中元素的父类,有以下两种方法

T copy1(List<? extends T> src, List<T> des)

T copy2(List<T> src, List<? super T> des)

List<Integer> res

List<Number> des

Number result1 = copy1(res, des);

Integer result2 = copy2(res, des);

好处:copy2返回值能拿到更准确的子类

 

2.6 使用通配符?定义方法参数List<?> list,不能在方法内部操作?,只能将list作为一个整体操作

void fun(List<?> list){

    ? a = list.get(0);  // 错误

    int size = list.size() // 正确   

}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值