在前面学习集合时,集合中可以存放任意对象的,只要把对象存储集合后,那么这时它们都会被提升成Object类型。当我们取出每一个对象,并且进行相应的操作,这是必须采用类型转换。
泛型:是一种未知的数据类型,当我们不知道使用什么数据类型的时候,可以使用泛型。
即:把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型
泛型也可以看成时一个变量,用来接收数据类型
E e:Element 元素
T t:Type 类型
ArrayList集合在定义的时候,不知道集合中都会存储什么类型的数据,所以类型使用泛型。
创建集合对象的时候,就会确定泛型的数据类型,会把数据类型作为参数传递,赋值给E。
当不使用泛型的时候
好处:默认类型为Object类,可以存储任意类型数据
弊端:不安全,容易引发异常
private static void show() { ArrayList list = new ArrayList(); list.add("abc"); list.add(1); // 使用迭代器遍历list集合 // 迭代器泛型跟着集合走,集合无泛型,迭代器就也无 Iterator iterator = list.iterator(); // 使用迭代器的方法hasNext和next遍历集合 while(iterator.hasNext()){ Object obj = iterator.next(); System.out.println(obj); // 想要使用String类特有的方法,length获取字符串的长度 // 不能使用,因为此处"abc"是多态写法 Object obj = "abc" // 编译看左边,运行看右边,length方法是字符串独有方法,无法编译。 // 需要向下转型 String str = (String)obj String s = (String)obj; // 第一此循环正常运行,第二次抛出异常 // 因为包装类型Integer不能转换为String类型 System.out.println(s.length()); }
运行截图:
当使用泛型的时候:
好处:
1.避免了类型转换的麻烦,存储的是什么类型,取出的就是什么类型
2.把运行期异常(代码运行后才会抛出的异常),提升到了编译期(写代码的时候就会报错)
弊端:
泛型是什么类型,就只能存储什么类型的数据了。
private static void show02() { ArrayList<String> arrayList = new ArrayList<>(); arrayList.add("aaa"); arrayList.add("bbb"); arrayList.add("ccc"); // arrayList.add(1); // 会提示错误 Iterator<String> iterator = arrayList.iterator(); while(iterator.hasNext()){ System.out.println(iterator.next()); } }
泛型:用来灵活地将数据类型应用到不同的类、方法、接口中。将数据类型作为参数进行传递。
定义和使用泛型的类
定义格式:
修饰符 class 类名<代表泛型的变量> { }
例如,ArrayList集合
class ArrayList<E>{ public boolean add(E e){} public E get(int index){} ... }
使用泛型,即什么时候确定泛型。
在创建独享的时候确定泛型。
举例:
不是泛型类时:
public class GenericClass { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
public static void main(String[] args) { GenericClass gc = new GenericClass(); // 目前只能传递字符串类型 gc.setName("222"); System.out.println(gc.getName()); // 222 }
改为泛型类后:
public class GenericClass<E> { private E name; public E getName() { return name; } public void setName(E name) { this.name = name; } }
GenericClass<String> gc = new GenericClass<>(); gc.setName("aaa"); System.out.println(gc.getName()); // aaa // 不同的对象,可以使用不同的泛型,存取不同类型的数据 GenericClass<Integer> gc2 = new GenericClass<>(); gc2.setName(22); System.out.println(gc2.getName()); // 22
定义和使用泛型的方法
定义格式:
修饰符 <代表泛型的变量> 返回值类型 方法名(参数) { }
回忆知识点:静态方法,也可以用对象名调用
public static void main(String[] args) { String str = "aaa"; show(str); // aaa // 不用明确写类型,且此处自动装箱 int num = 9; show(num); // 9 } /* 含有泛型的方法,在调用方法的时候确定泛型的数据类型 传递什么类型的参数,泛型就是什么类型(不用明确写出!) */ private static <E> void show(E e) { System.out.println(e); }
定义和使用泛型的接口
定义格式:
修饰符 interface接口名<代表泛型的变量> { }
public interface GenericInterface<E> { public abstract void method(E e); }
含有泛型的接口,有两种使用方法。
第一种:定义接口的实现类,实现接口,指定接口的泛型。
public class GenericInterfaceImpl<String> implements GenericInterface<String>{ @Override public void method(String s) { // 重写抽象方法 // 定义实现类的时候,指定了接口的泛型,所以此处只能用String System.out.println(s); } }
public static void main(String[] args) { // 创建GenericInterfaceImpl对象 GenericInterfaceImpl impl = new GenericInterfaceImpl(); // 调用方法 impl.method("aaa"); // aaa }
第二种:接口使用什么泛型,实现类就使用什么泛型,类跟着接口走,就相当于定义了一个含有泛型的类,所以还是在创建对象的时候确定泛型的类型。
public class GenericInterfaceImpl2<E> implements GenericInterface<E>{ @Override public void method(E e) { System.out.println(e); } }
GenericInterfaceImpl2<String> impl2 = new GenericInterfaceImpl2<>(); impl2.method("bbb"); // bbb // 创建不同类型泛型的对象 GenericInterfaceImpl2<Integer> impl3 = new GenericInterfaceImpl2<>(); impl3.method(222); // 222
泛型通配符
当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。
通配符基本使用
泛型的通配符:不知道使用什么类型来接受的时候,此时可以使用?,?表示未知通配符。
此时只能接收数据,不能往该集合中存储数据。
注意:定义集合的时候,不可以使用泛型通配符,只能作为方法的参数使用!
public static void main(String[] args) { ArrayList<String> arr = new ArrayList<>(); arr.add("aaa"); arr.add("bbb"); ArrayList<Integer> arr2 = new ArrayList<>(); arr2.add(1); arr2.add(2); // ArrayList<?> arr3 = new ArrayList<>(); // 错误写法 printArray(arr); // 如果指定泛型类型为Integer,无法编译 printArray(arr2); // 如果指定泛型类型为Integer,可以编译 } /* 定义一个方法,能遍历所有类型的ArrayList集合 这时候我们不知道ArrayList集合使用什么数据类型 可以使用泛型的通配符?来接收数据类型 */ private static void printArray(ArrayList<?> arr){ // 记住迭代器的类型总是跟着集合走 Iterator<?> iterator = arr.iterator(); while(iterator.hasNext()){ System.out.println(iterator.next()); } }
泛型通配符的高级使用--受限泛型
之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在Java的泛型中可以指定一个泛型的上限和下限。
泛型的上限:
格式:类型名称<? extends 类> 对象名称
意义:只能接收该类型及其子类
泛型的下限:
格式:类型名称<? super 类> 对象名称
意义:只能接收该类型及其父类型(此处不限于直接父类)
比如:现一直Object类,String类,Number类,Integer类,其中Number类是Integer类的父类。
public static void main(String[] args) { Collection<Integer> list1 = new ArrayList<Integer>(); Collection<String> list2 = new ArrayList<String>(); Collection<Number> list3 = new ArrayList<Number>(); Collection<Object> list4 = new ArrayList<Object>(); getElement(list1); // getElement(list2); // 报错,String非Number子类 getElement(list3); // getElement(list4); // 报错,Object非Number子类 getElement2(list1); // getElement2(list2); // 报错,String非Integer父类 getElement2(list3); getElement2(list4); } // 泛型的上限 public static void getElement(Collection<? extends Number> coll){} // 泛型的下限 public static void getElement2(Collection<? super Integer> coll){}