九里徐州网站开发,秦皇岛市教育考试院官网,南昌it制作电商网站的公司,公司网站恶意评价1、概念
泛型的本质是参数化类型#xff0c;即给类型指定一个参数#xff0c;然后在使用时再指定此参数具体的值#xff0c;那样这个类型就可以在使用时决定了。这种参数类型可以用在类、接口和方法中#xff0c;分别被称为泛型类、泛型接口、泛型方法。
Java中引入泛型最…1、概念
泛型的本质是参数化类型即给类型指定一个参数然后在使用时再指定此参数具体的值那样这个类型就可以在使用时决定了。这种参数类型可以用在类、接口和方法中分别被称为泛型类、泛型接口、泛型方法。
Java中引入泛型最主要的目的是将类型检查工作提前到编译时期将类型强转cast工作交给编译器从而让你在编译时期就获得类型转换异常以及去掉源码中的类型强转代码。 2、泛型作用的对象
泛型有三种使用方式分别为泛型类、泛型接口和泛型方法。 1、泛型类
在类的申明时指定参数即构成了泛型类例如下面代码中就指定T为类型参数那么在这个类里面就可以使用这个类型了。例如申明T类型的变量name申明T类型的形参param等操作。
public class GenericT {public T name;public Generic(T param){nameparam;}public T m(){return name;}
}
那么在使用类时就可以传入相应的类型构建不同类型的实例如下面代码分别传入了String,Integer,Boolean3个类型
private static void genericClass(){GenericString strnew Generic(总有刁民想害朕);GenericInteger integernew Generic(110);GenericBoolean bnew Generic(true);
System.out.println(传入类型str.name integer.name b.name);
}
输出结果为传入类型总有刁民想害朕 110 true
如果没有泛型我们想要达到上面的效果需要定义三个类或者一个包含三个构造函数三个取值方法的类。 2、泛型接口
泛型接口与泛型类的定义基本一致 3、泛型方法
public class GenericT {public T name;public Generic(){}public Generic(T param){nameparam;}public T m(){return name;}public E void m1(E e){ }public T T m2(T e){ }
}
重点看public E void m1(E e){ }这就是一个泛型方法判断一个方法是否是泛型方法关键看方法返回值前面有没有使用标记的类型有就是没有就不是。这个里面的类型参数就相当于为这个方法声明了一个类型这个类型可以在此方法的作用块内自由使用。
上面代码中m()方法不是泛型方法m1()与m2()都是。值得注意的是m2()方法中声明的类型T与类申明里面的那个参数T不是一个也可以说方法中的T隐藏了类型中的T。下面代码中类里面的T传入的是String类型而方法中的T传入的是Integer类型。
GenericString strnew Generic(总有刁民想害朕);
str.m2(123); 3、类型通配符
首先思考以下问题
public class Animal {
}
public class Cat extends Animal {
}
public static void main(String[] args) {Cat cat new Cat(); Animal animal cat; ❶ListCat cats new ArrayList();ListAnimal animals cats; ❷
}
以上代码中①处以及②处代码是否正确
①处的代码没有任何问题是多态的典型用法即父类引用指向子类对象而②处的代码与①处类似但是却提示编译错误。原因是因为Java泛型中规定即使泛型类型具有继承关系但是并不意味着该泛型类型的容器也具有继承关系。
可以把泛型理解成一个标签一个list中贴上了animal的标签另一个list贴上了cat的标签可以说cat是animal的子类但是不能说贴了cat标签的list就是贴了animal标签list的子类他们实际上都是list。
知道泛型的这一特性后就出现了一个比较麻烦的问题: 泛型容器似乎没办法实现多态。
举个例子在上例中我们为Animal添加一个方法getFood同时Cat类需要重写该方法
public class Animal {public String getFood() {return 食肉动物吃肉食草动物吃草;}
}
public class Cat implements Animal {Overridepublic String getFood() {return 猫粮小鱼干猫薄荷;}
}
如果想在控制台输出不同动物的食物此时需要定一个print方法利用多态特性形参可定义为父类Animal这样实参就可以传Animal以及其子类对象了。
public void print(Animal animal) {System.out.println(animal.getFood());
}
public static void main(String[] args) {Test test new Test();test.print(new Animal());test.print(new Cat());
}
但是如果想打印一批动物的食物信息就无法利用多态这一特性因为ListCat并不是ListAnimal的子类所以只能额外增加方法。
public void printAnimalFood(ListAnimal animals) {animals.forEach(a - System.out.println(a.getFood());
}
public void printCatFood(ListCat cats) {cats.forEach(c - System.out.println(c.getFood());
}
public static void main(String[] args) {Test test new Test();ListCat cats Arrays.asList(new Cat());test.printAnimalFood(cats); // 编译错误ListCat不是ListAnimal的子类test.printCatFood(cats); // 正确执行
}
如果还需要增加DogRabbit等子类那么就需要增加相应对应的方法printDogFoodprintRabbitFood等久而久之代码就会很臃肿 1、无界通配符
Java泛型提供了通配符(Wildcards)用?表示例如List?、Set?等。我们知道Object是任意类的父类而在Java泛型中通配符就是任意同泛型类型容器的父类如List?是所有泛型List的父类为了与接下来的要介绍的另外两种形式的通配符予以区分可以称之为无界通配符意味没有界限限制。
由于?不是泛型标识所以也就无法在类、接口以及方法中传递以下写法是不允许的
public class TestGeneric? {private ? a;
}
public ? ? test(? a) {// Do something
}
?只能出现在引用中或方法入参中如
// 出现在引用中
Class? cls xxx;
// 出现在方法入参中
public void print(List? list) {// Do something
}
需要注意的是方法入参中的无界通配符只起到了告知的作用并不能进行传递也不需要传递(因为是任意类型)。
总结 无界通配符不能标注在泛型类、泛型接口以及泛型方法上 无界通配符只能使用在引用以及方法入参中并且无法进行传递
另外无界通配符也可以起到占位以及修饰作用可以直观告诉使用者这里可以接受任意类型。比如Java中的反射我们经常写成下面的写法
Class? cls xxx; 知道了无界通配符的含义之后我们可以把前言中的例子做一下优化
public void print(List? animals) {animals.forEach(a - System.out.println(((Animal) a).getFood()));
}
public static void main(String[] args) {Test test new Test();ListAnimal animals Arrays.asList(new Animal());ListCat cats Arrays.asList(new Cat());test.print(animals); // 正确执行test.print(cats); // 正确执行
}
使用无界通配符后即使后续有新增的子类此方法也可以正确执行。至此我们解决了参数传递问题但是新问题出现了使用无界通配符似乎与不使用泛型而直接使用List没有任何区别list中的元素可以是任意类型并且取元素时需要强制转换。无界通配符看起来并没有特殊的用途那为什么要需要使用无界通配符呢? List可以理解为持有任意Object类型的原始列表。 List?可以理解为想要使用泛型列表但是不确定具体的类型用?标识任意类型都可以。由于是泛型所以列表中的元素的类型理应一致但是以下写法是可以通过编译的
// 正确执行
List list new ArrayList();
list.add(1);
list.add(2);
List? list2 list;
public void print(List? list) {//..
}
// 正确执行
public static void main(String[] args) {List list new ArrayList();list.add(1);list.add(2);new Test().print(list);
}
共同点由于List?和List在编译期间都无法确定元素的实际类型所以获取的时候都为Object类型
List? list1 ...;
Object obj1 list1.get(0);
List list2 ...;
Object obj2 list2.get(0);
不同点List?由于无法在编译期间确定泛型的实际类型所以没法向List?中添加除了null外的任意类型元素。而List由于可以存放Object对象所以List可以添加任意类型的元素。
List? list1 new ArrayList();
list1.add(1); // 编译错误
list1.add(2); // 编译错误
list1.add(null); // 正确执行
List list2 new ArrayList();
list2.add(1); // 正确执行
list2.add(2); // 正确执行 应用场景
public void testMap(MapString, ? map) {// Do something
}
MapString, String map1 ...;
MapString, Integer map2 ...;
MapString, Object map3 ...;
Test test new Test();
test.testMap(map1); // 正确执行
test.testMap(map2); // 正确执行
test.testMap(map3); // 正确执行
testMap方法入参map的value想接受任意类型只能使用无界通配符来进行占位。 2、上界通配符
无界通配符由于可以接受任意类型所以某些情况下还是不太适用往往需要强制转换类型一是不方便二是可能引发转换异常这个时候就可能需要使用上界通配符来解决我们的问题了。
上界通配符(Upper Bounde Wildcard)顾名思义存在一个最上级的界限即指定一个最高级别的父类它表示对于该上界类型以及其子类都适用。 基本写法
? extends xx
同无界通配符由于包含?所以这种写法只能出现在引用以及方法入参中如
List? extends Number numbers xxx;public void test(Class? extends Number cls) {
}
前面分析?并不能进行泛型类型传递所以如果想在在泛型类、泛型接口、泛型方法中使用上界通配符需要将?指定为标识类型如
// 泛型类
public class TestGenericT extends Number {private T t;public TestGeneric(T t) {this.t t;}
}
// 泛型接口
public interface TestGernericT extends Number {
}
// 泛型
public E extends Collection void test(E e) {System.out.println(e.size());
} 多重上界
当需要指定多个上界时需要使用来连接并且只能在指定泛型标识的时候使用例如
public class TestGenericT extends LongFur BlueEye {
}
上例中表示该泛型类型为同时满足为两个指定上界类型或其子类的类型。
需要注意的是被指定的多个上界不能有有冲突的方法否则会编译错误 特点
我们在编译期只能知道上界通配符的上界是什么类型所以在取元素时只能获取到上界的类型。具体的实际类型只有在运行时才能确定同时也构成了多态。
ListInteger ints Arrays.asList(1);
ListDouble doubles Arrays.asList(2.2);
public void test(List? extends Number numbers) {// 编译期间无法确定实际类型是Integer、Long、Double还是其他只能使用Number接收// 运行时构成多态Number number 1; Number number 2.2Number number numbers.get(0);System.out.println(number);
}
同时与无界通配符相似由于编译期无法知晓具体的实际类型所以不支持上界通配符的容器添加元素(或赋值)
List? extends Number list new ArrayList();
list.add(1); // 编译错误
list.add(2.2); // 编译错误
// 如果以上操作允许那么list中的类型就不统一了 应用场景
public void print(List? extends Animal animals) {animals.forEach(a - System.out.println(a.getFood()));
}
public static void main(String[] args) {Test test new Test();ListAnimal animals Arrays.asList(new Animal());ListCat cats Arrays.asList(new Cat());test.print(animals); // 正确执行test.print(cats); // 正确执行
}
由于方法形参定义为List? extends Animal所以不用担心转型的问题获取的元素肯定为Animal类型(包括子类)也不用担心实参类型传递错误。
通过优化我们可以感受到上界通配符的好处既能够做到向无界通配符一样的通用型又能够限制具体的类型范围所以如果想达到此目的就可以使用上界通配符而实际编码中我们使用上界通配符的次数也是最多的。 3、下界通配符
与上界通配符相反下界通配符(Lower Bound Wildcard)顾名思义存在一个最低级的界限即指定一个最低级别的子类它表示对于该下界类型以及其父类都适用。 基本用法
下界通配符与上界通配符类似只需将extends改为super即可
? super xx
List? super Integer list xxx;
public void test(Class? super Integer cls) {
}
值得注意的是下界通配符不支持制定泛型标识以及多重下界的写法。 特点
与上界通配符相反我们在编译期只能知道下界通配符的下界是什么类型所以在添加元素时只能向其中添加下界类型。
public void test(List? super Integer list) {// 编译期间无法确定实际类型是Integer还是Number或更高级别的父类只能添加Integerlist.add(1);
}ListNumber numbers Arrays.asList(1, 2L, 3.3);
xxx.test(numbers);
同时由于编译期无法知晓具体的实际类型所以只能使用Object来接收获取的元素
List? super Integer list new ArrayList();
// 编译失败因为无法确定是否是Integer有可能是Number
Integer i list.get(0);
// 编译失败因为无法确定是否是Number有可能是Object
Number m list.get(1);
// 编译正确Object可以接受任意值
Object o list.get(2);