泛型的定义与概述:
将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法
。
泛型的好处是在编译的时候检查类型安全,并能捕捉类型不匹配的错误,并且所有的强制转换都是隐式的和自动的,提高代码的重用率
泛型类:
泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。
定义格式:
修饰符 class 类名 <类型>{
}
public class Generic<T> {
}
泛型方法:
定义格式:
修饰符 <类型> 返回值类型 方法名(类型 变量名){
}
范例:
public <T> void show(T T){
}
泛型接口:
定义格式:
修饰符 interface 接口名 <类型>{
}
范例
public interface Generic<T>{
}
当实现泛型接口的类,未传入泛型实参时:
/**
* 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
* 即:class FruitGenerator<T> implements Generator<T>{
* 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class"
*/
class FruitGenerator<T> implements Generator<T>{
@Override
public T next() {
return null;
}
}
当实现泛型接口的类,传入泛型实参时:
/**
* 传入泛型实参时:
* 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator<T>
* 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。
* 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
* 即:Generator<T>,public T next();中的的T都要替换成传入的String类型。
*/
public class FruitGenerator implements Generator<String> {
private String[] fruits = new String[]{"Apple", "Banana", "Pear"};
@Override
public String next() {
Random rand = new Random();
return fruits[rand.nextInt(3)];
}
}
泛型没有多态的概念
类型通配符
1. <?>:无限通配符,可以在?中放入里放入任何引用数据类型,用来接收任意引用数据类型。
1.无限通配符<?>的使用:可以传入任何引用数据类型
A 在调用方法时使用?通配符的过程中无法使用add方法。
原因分析:因为通配符?代表任意的数据类型,但是当我们调用的时候或者用在方法的声明上,其实这个时候还是没有给?通配符一个指定的引用数据类型,那么Java出于安全起见,就不可能允许添加元素。
B 以上的add方法虽然无法调用,add<null>是例外。
原因分析:因为null可以给任意引用数据类型赋值,代表任意引用数据,但是很容易引起NullPointerException。
C 注意使用List<?>和List<Object>当作形参时的作用不能等同,比如当传入List<Integer>时List<?>可以接收,但是List<Object>无法接收。
原因分析:因为?代表任何参数类型可以接收,但是List<Object>中虽然Object是所有子类的父类,但是List<Object>不是List<Integer>的父类,List<Object>是ArrayList<Object>等类的父类,这就是为什么泛型前后要一致的原因,从数组的角度去理解集合就比如Object[ ] arr不是Integer[ ] arr1的父类。
public class WildCardTest<E> {
public static void main(String[] args) {
// ArrayList<Object> list2 = new ArrayList<Integer>();//泛型前后一致
ArrayList<?> list = new ArrayList<String>();
// list.add("122");//注意此处无法添加"122"字符串,但是可以添加null,因为null可以给任何引用数据类型赋值
list.add(null);//添加null没有报错
Object o = list.get(0);
System.out.println(list);
ArrayList<Integer> list1 = new ArrayList<>();
list1.add(34);
// printCollection1(list1);//报错了,原因因为这里要接收的是List<Object>,那么可以接收的有ArrayList<Object>或者List<Object>等与List<Object>有继承关系的集合,但是无法接收ArrayList<Integer>,因为和List<Object>没有继承关系,写成List<?>才能使用
printCollection(list1);//?通配符可以正常接收
}
public static void printCollection (List<?> col){//此方法使用了无限通配符
for (Object object : col) {
System.out.println(object);
}
}
public static void printCollection1 (List<Object> col){//此方法没有使用泛型通配符
for (Object object : col) {
System.out.println(object);
}
}
}
原文链接:https://blog.csdn.net/l13591302862/article/details/82378622
2. <? extends E>:这个表明的是通配符的上界,通俗讲就如同取值范围中的负无穷到E,即小于等于E的所有类型, 因为E是最大的类型(最大可以达到Object),表明可以输入所有的E的子类和E,等下会进行细致的讲解。
上界通配符<? extends E>的使用:可以传入E和E的子类
A <? extends E>作为形参时例如List<? extends E>可以使用集合的get方法来获取E或者E类型本身的元素。
原因分析:当我们用get方法时我们其实是在获取集合里内部的元素,但是我们的集合的数据类型还没有确定,但是我们可以获得一些明确的已知条件,那就是在<? extends E>中最大的类型是E,而且这个E最大是Object,所以我们可以利用这一点,那么我们就可以清楚地了解到该集合里面的获取的元素肯定是E或者Object的子类,他们的范围肯定小于E或者Object,那么我们就可以用Object和E这两个范围比集合里面的元素大的类去接收集合里面的元素。(注:可能略显啰嗦但是我就是想解释清楚。)
B 在使用上界通配符时,无法调用add方法来添加非null的元素。
原因分析:由于上面已经说得很清楚了,<? extends E>作为形参时例如List<? extends E>这时最大类型是E和Object,但是我们不清楚最小的类型是什么,因为此时?这个通配符没有被赋值,我们调用add方法是要添加集合元素或者集合元素的子类,但是我们没法明确肯定该集合元素类型,或者比该集合元素范围更小的子类,那么Java就不会允许添加元素。
public class WildCardTest2 {
public static void main(String[] args) {
ArrayList<? extends Number> list = new ArrayList<>();
// list.add(3);报错了,无法添加非null元素
list.add(null);//没有报错
Object o = list.get(0);//用Object接收没有报错
Number n = list.get(0);//用Number接收没有报错
}
}
3. <? super E>:这个表明的是通配符的下界,通俗讲其取值范围就是E到最大值Object(因为Object是所有类的基类),就是大于等于E,小于等于Object类。
下界通配符<? super E>的使用:可以传入E或者E的父类
A 在使用下界通配符时,无法使用get方法获取Object以外的元素,或者需要向下转型,但是可能出现ClassCastException的异常。
原因分析:上界通配符,在使用get方法的时候,此时类型没有明确还是问号?我们只能明确其最大父类或者接口时,我们才能接收,但是我们只能明白<? super E>作为形参时例如List<? super E>时,只能明确Object是最大父类,其他的一概不知,所以只能Object o = list.get(0)。
B 可以使用集合的add方法添加E或者E的子类。
原因分析:上界通配符已经解释很清楚了,add方法添加元素时,?类型不确定就要明确该?类型的最小子类,只要比可能存在的最小子类或者子接口小的任意引用数据类型的对象,我们都可以将其添加,而下界通配符<? super E>当作形参时例如List<? super E>,此时E就是最小子类,此时add方法可以添加E或者E的父类或者接口。
public class WildCardTest3 {
public static void main(String[] args) {
ArrayList<? super Number> list = new ArrayList<>();
list.add(3);//没有报错,自动装箱成Integer,Number的子类
list.add(3.4F);//没有报错,自动装箱成Float,Number的子类
list.add(32L);//没有报错,自动装箱成Long,Number的子类
Object o = list.get(1);
// Integer i = list.get(0);//报错了,无法用Integer接收
}
}
总结
列表内容
限定通配符总是包括自己
上界类型通配符:add方法受限
下界类型通配符:get方法受限
如果你想从一个数据类型里获取数据,使用 ? extends 通配符
如果你想把对象写入一个数据结构里,使用 ? super 通配符
如果你既想存,又想取,那就别用通配符
不能同时声明泛型通配符上界和下界
可变参数:
package cn.itcast.api.d.param;
public class paramDemo {
public static void main(String[] args) {
// 1
int[] arr = {23,43,54};
int sum = add(arr);
System.out.println("sum="+sum);
// 2
int[] arr1 = {25,46,50,23,54,66,78};
int sum1 = add(arr1);
System.out.println("sum1="+sum1);
// API1.5之后出现了简化操作 ...只用于参数上称之为可变参数
// 同样是代表数组但是在调用这个可变参数时不可以创建数组(这就是简单之处)
// 直接将数组中的元素作为实际参数进行传递,其实编译成的class文件将这些实参先封装到一个数组中在进行传递
// 这些动作在编译器生成class文件时就帮你完成了
// 注意:可变参数一定要定义在参数列表的最后
// public static int add(int a,int...arr) 正确
// public static int add(int...arr,int a) 错误
int sum2 =add(23,43,54);
System.out.println("sum2="+sum2);
int sum3 = add(25,46,50,23,54,66,78);
System.out.println("sum3="+sum3);
}
// 两个整数 的和
public static int add(int a , int b)
{
return a + b ;
}
// 三个整数和
public static int add(int a , int b ,int c)
{
return a + b + c;
}
// 多个整数和1
/*public static int add(int[] arr)
{
int sum = 0 ;
for(int i = 0 ; i < arr.length ; i++ )
{
sum += arr;
}
return sum;
}*/
// 多个整数和2 API1.5之后的方法
public static int add(int.../*int类型的数据*/arr)//数组参数的简化表现形式
{
int sum = 0 ;
for(int i = 0 ; i < arr.length ; i++ )
{
sum += arr;
}
return sum;
}
}
|
|