Java泛型
描述
当需要操作特定对象的时候,如果不设置特定的对象类型,那么取出来都是Object类,这时就有可能诱发类转换异常的操作,所以需要使用泛型来确定当前容器操作的类型。
比方说:我们的一个集合,可以存储数字,又可以存储字符串、自定义对象,他返回的都是Object。
泛型就是相当于给容器贴标签,说明该容器只能存储什么样类型的对象。
泛型是再JDK5.0之后出现的,就是因为之前会出现大量的 类转换异常。
语法格式
1.集合类
集合类<泛型的类型,如Integer> 变量名 = new 集合类<泛型的类型,前后要一致>();
——将运行时异常提前至编译时异常,可以让开发人员提前预知。
——获取元素时无需强制类型转换,就避免了类转换异常问题。
——泛型的类型必须是引用类型,不能是基本数据类型。
2.泛型方法
可以作为泛型方法,也就是说,调用者调用该方法时,返回的就是此固定的类型。
格式:public void demo(T name) / piblic T demo(T t){ return t}
(其中,T代表任意的引用数据类型)
Integer[] i = {1, 2, 34, 5, 8};
Double[] d = {1.1, 2.2, 3.4, 5.0, 8.1};
String[] str = {"你", "好", "!"};
//使用泛型,这么多类型写一个就行了
public static <T> void show(T[] t) {
for (T j : t) {
System.out.print(j + " ");
}
}
3.泛型类
可以作为泛型类,也就是说,可以在使用时指定未知的类型,可以在使用时固定特有的类型格式:public class Demo{}
package package02;
/*
泛型
泛型类
* @Author WENG Jun
* @Date 2021/7/17 - 11:03
*/
public class GenericClassTest {
public static void main(String[] args) {
Demo<Integer> d1 = new Demo<Integer>();
d1.setName(123);
Demo<String> d2 = new Demo<String>();
d2.setName("张三");
}
}
class Demo<T>{
T name;
public void setName(T name) {
this.name = name;
}
}
4.泛型接口
可以作为泛型接口,和定义泛型类相似,可以将其看成是一个特殊的类
格式:public interface lDemo<T,V>{}
——便于接口扩展
package package02;
/*
泛型
泛型接口
* @Author WENG Jun
* @Date 2021/7/17 - 11:10
*/
public class GenericInterfaceTest implements IDemo<Integer> {
public static void main(String[] args) {
GenericInterfaceTest test = new GenericInterfaceTest();
test.run(100);
}
@Override
public void run(Integer value) {
System.out.println(value);
System.out.println("重写了泛型接口的方法:Integer类型参数");
}
}
//泛型接口
interface IDemo<T> {
public void run(T value);
}
泛型的好处
1、利于代码的重用性,增加其通用性,使得我们的代码更加的简洁,便于维护
2、避免了强制类型转换,也不用担心会丢失精度
3、编译时确定类型,保证类型安全,使异常再开发时变成预知
泛型的限制和规则
1、泛型的类型参数只能是引用类型,不能是值类型
2、泛型类不是真正存在的类,所以不能使用instanceo运算符比较
3、如果定义泛型,但是又不指定具体的类型,泛型默认指定Object类型
4、泛型类的类型参数不能在静态中声明
5、泛型的类型参数可以有多个
6、泛型使用?作为通配符,表示未知类型,可以匹配任意类型,因为是未知,所以无法添加元素
——<?extends > :?代表的是T类型本身或者T类型的子类型,常用于泛型方法,避免类型转换。
7、泛型类中不能定义泛型静态方法
——是因为类中的泛型需要在对象初始化的时候制定具体的类型,而静态优先于对象存在,那么类中的方法就需要单独进行泛型声明,声明泛型时需注意这一点。
8.限定泛型通配符边界
8.1限定泛型通配符的上边界
——接收本类型和本类型的子类类型
正确:Demo <? extends Number> d=new Demo ();
错误:Demo <? extends Number> d=new Demo ();
8.2限定通配符的下边界
——接收本类型或者本类型的父类类型
正确:Demo <? super Integer> d=new Demo ();
错误:Demo <? super Integer> d=new Demo ();
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fINXu5RL-1626698235455)(images/32.jpg)]
涉及上下边界时使用add和get方法的问题?
(类A、B、C,其中B继承自A、C继承自B)
上边界时:
表示所有继承了A的子类,也包括A,但是具体是哪个子类,无法确定,所以调用add时,要add什么类型,编译器不知道,所以编译不通过,但是get的时候,不管是什么子类,不管追溯到多少层级,肯定都有一个父类A,所以我们都可以使用父类A接着,也就是把所有的子类向上转型成为A类。
下边界时:
表示B的所有父类,包括A,也包括追溯最上层的Object,那么当我们做add的操作时,我们不能add父类,因为不能确定list里面存放的到底是哪个父类,但是我们可以add B类及它的子类,因为我不管我的子类是什么类型,它都可以向上转型位B类以及所有的父类甚至包括Object,但是当使用get时,B类的父类不止一个,所以无法接着,除了Object,其他的都接不住,那么编译也不通过,这就是下界。
概括:
使用编译器可以支持向上转型,但不支持向下转型,如果必须要向下转型的话,就需要cast。简而言之,不能add父类,不能get父类。
注意事项
泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,既可以挡住集合中插入非法的数据,当编译完带有泛型的java程序后,生成的class文件将不再带有泛型信息,以此程序运行效率不受影响,这个过程我们称之为“擦除”。
**[练习]**上下边界
public class BorderTest {
public static void main(String[] args) {
//上边界
List<? extends Number> list1 = new ArrayList<Integer>();
//String不是Number类型或者其子类类型。如下:
// List<? extends Number> list2 = new ArrayList<String>();
//下边界
List<? super Integer> list3 = new ArrayList<Number>();
//Double不是Integer类型或者其父类类型。如下:
// List<? super Integer> list4 = new ArrayList<Double>();
}
}