【JAVA入门】Day29 - 泛型
文章目录
泛型是 JDK5 中引入的新特性,可以在编译阶段约束操作的数据类型,并进行检查。泛型的格式很简单:
<数据类型>
要注意:泛型中只能写引用数据类型。
泛型就像一个检查数据是否符合要求的质检员,如果数据符合泛型要求,就可以存入集合。
ArrayList<String> list1 = new ArrayList<>();
list1.add("aaa");
list1.add("bbb");
list1.add("ccc");
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(111);
list2.add(222);
list2.add(333);
一、JDK5 之前集合如何存储数据
在没有泛型之前,我们可以往集合中添加任意类型的数据,它们都被认为是 Object 类型,这就导致了一个问题:不能访问子类独有的方法。
package Generics;
import java.util.ArrayList;
import java.util.Iterator;
public class GenericsDemo1 {
public static void main(String[] args) {
//没有泛型时,集合如何存储数据
//1.创建集合对象
ArrayList list = new ArrayList();
//2.添加数据
list.add(123);
list.add("aaa");
list.add(new Student("zhangsan", 123));
//3.遍历结合获取里面的每一个元素
Iterator it = list.iterator();
while(it.hasNext()) {
Object obj = it.next();
System.out.println(obj);
//这里的obj是多态
//多态存在弊端:不能访问子类的特有功能
//此时推出了泛型,约束我们在添加数据时,就约定了添加的元素类型
}
}
}
二、为什么要有泛型?
引入泛型,就是为了统一数据类型。
泛型将运行时期存在的问题提前到了编译期间,避免了运行过程中强制类型转换可能出现的异常,因为要添加的数据类型,在编译阶段就能确定下来。
三、伪泛型
Java 中的泛型其实是伪泛型,即数据在进入集合前,是被泛型约束所检测的,一旦进入集合,内部其实统一还是按 Object 类型来处理,只是在移出集合时,又重新强制转换为了泛型要求的格式。这一点体现在: .java 文件转字节码 .class 文件时,泛型的相关代码就会消失。
因此一定要注意:指定的泛型不能写基本数据类型,因为在内部操作时,Java 会把泛型转换为 Objct 类型,基本数据类型是无法转换的;在约束泛型后,传入集合的类型可以是该类类型,也可以是其子类类型(多态);如果不写泛型,默认还是以 Object 形式传入。
四、泛型的三大用法
泛型实际上不仅仅有集合这一个用途,泛型可以在很多地方进行定义。
泛型可以在类后面定义,此时这个泛型变为泛型类;可以在方法上定义,此时这个方法变为泛型方法;可以在接口后面定义,此时这个接口变为泛型接口。
4.1 泛型类
当我们创建一个类时,如果类中的某个变量数据类型我们不能确定,就可以定义为带有泛型的类。
修饰符 class 类名<类型> {
}
比如我们常用的 ArrayList 类:
public class ArrayList<E> {
}
用泛型<E>表示不确定类型,而是在创建该类对象时,E再确定类型。
此时的 E 可以理解为一个变量,但他不用于记录数据,而是记录数据的类型,所以它本身也是不固定的,可以写作 T、E、K、V 等各种字母,它仅代表数据的类型。
我们在编写类时,可以通过加泛型,把这个类变成一个泛型类。以下是我们利用泛型自己编写的 ArrayList 类。
package Generics;
/*
当我在编写一个类的时候,如果不确定类型,那么这个类就可以定义为泛型类
*/
import java.util.Arrays;
public class MyArrayList<E> {
Object[] obj = new Object[10];
int size;
/* E:表示是不确定的类型。该类型在类名后面已经定义过了。
e:形参的名字,变量名
*/
public boolean add(E e) {
obj[size] = e;
size++;
return true;
}
public E get(int index) {
return (E)obj[index];
}
@Override
public String toString() {
return Arrays.toString(obj);
}
}
4.2 泛型方法
当方法中形参类型不确定时,可以使用类名后面定义的泛型。如果只有极少数方法的形参不确定类型,那么可以直接给这个方法加一个泛型,把它变成一个泛型方法。
public <E> boolean add(E e) {
obj[size] = e;
size++;
return true;
}
要注意,类的泛型在整个类中所有方法都能使用,而方法的泛型只能在方法本体中使用。
泛型方法的格式如下:
修饰符 <类型> 返回值类型 方法名(类型 变量名) {
}
举例:
public <T> void show(T t) {
}
泛型方法的调用例如下所示:
假如我们编写了一个泛型的工具类,里面有静态泛型方法addAll() 和 addAll2() ,用于添加四个或复数个集合元素。
package Generics;
import java.util.ArrayList;
public class ListUtil {
private ListUtil(){}
//类中定义一个静态方法addAll,用来添加多个集合的元素。
/*
参数一:集合
参数二~最后:要添加的元素
*/
public static <E> void addAll(ArrayList<E> list, E e1, E e2, E e3, E e4){
list.add(e1);
list.add(e2);
list.add(e3);
list.add(e4);
}
public static <E> void addAll2(ArrayList<E> list, E...e) { //可变参数
for (E element : e) {
list.add(element);
}
}
public void show() {
System.out.println("这是一个泛型工具类");
}
}
然后我们用一个新的测试类来调用这个工具类中的泛型方法,可以看到,当我们传递 ArrayList<String> list1 为参数时,泛型 E 被自动转化为 String 类型;当我们传递 ArrayList<Integer> list2 为参数时,泛型 E 被自动转化为 Integer 类型。
package Generics;
import java.util.ArrayList;
public class GenericsDemo3 {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
ListUtil.addAll(list1, "aaa", "bbb", "ccc", "ddd");
System.out.println(list1);
ArrayList<Integer> list2 = new ArrayList<>();
ListUtil.addAll2(list2, 111, 222, 333, 444, 555, 666, 777); //可变参数
System.out.println(list2);
}
}
4.3 泛型接口
当一个接口当中要添加的数据类型是不确定的,我们就可以用泛型约束接口,让它成为一个泛型接口。
格式:
修饰符 interface 接口名<类型> {
}
举例:
public interface List<E> {
}
泛型接口的声明并不复杂,但关键是我们如何使用一个带泛型的接口。一般我们有两种用法:
- 方式一:实现类给出具体的类型。
- 方式二:实现类延续泛型,创建对象时再确定。
第一种方式代码如下:
先用实现类实现 List 接口,同时在实现时就声明给出具体的类型,然后这个实现类创建的集合对象中存储的数据就只能是约束的泛型类型了。
package Generics;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
public class MyArrayList2 implements List<String> {
@Override
public int size() {
return 0;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public boolean contains(Object o) {
return false;
}
@Override
public Iterator<String> iterator() {
return null;
}
@Override
public Object[] toArray() {
return new Object[0];
}
@Override
public <T> T[] toArray(T[] a) {
return null;
}
@Override
public boolean add(String s) {
return false;
}
@Override
public boolean remove(Object o) {
return false;
}
@Override
public boolean containsAll(Collection<?> c) {
return false;
}
@Override
public boolean addAll(Collection<? extends String> c) {
return false;
}
@Override
public boolean addAll(int index, Collection<? extends String> c) {
return false;
}
@Override
public boolean removeAll(Collection<?> c) {
return false;
}
@Override
public boolean retainAll(Collection<?> c) {
return false;
}
@Override
public void clear() {
}
@Override
public String get(int index) {
return null;
}
@Override
public String set(int index, String element) {
return null;
}
@Override
public void add(int index, String element) {
}
@Override
public String remove(int index) {
return null;
}
@Override
public int indexOf(Object o) {
return 0;
}
@Override
public int lastIndexOf(Object o) {
return 0;
}
@Override
public ListIterator<String> listIterator() {
return null;
}
@Override
public ListIterator<String> listIterator(int index) {
return null;
}
@Override
public List<String> subList(int fromIndex, int toIndex) {
return null;
}
}
测试类生成实现类对象,调用 add() 方法测试:
package Generics;
public class GenericsDemo4 {
public static void main(String[] args) {
MyArrayList2 list = new MyArrayList2();
list.add("aaa");
list.add(123); //报错,泛型给的String,只能添加字符串数据类型
}
}
第二种方式代码如下:
在实现类实现接口时,把泛型延续下来,还用 <E> 表示。
要注意,在实现类继续声明泛型时,要在类名和接口名后面同时加上 <E> 。
package Generics;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
public class MyArrayList3<E> implements List<E> {
@Override
public int size() {
return 0;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public boolean contains(Object o) {
return false;
}
@Override
public Iterator<E> iterator() {
return null;
}
@Override
public Object[] toArray() {
return new Object[0];
}
@Override
public <T> T[] toArray(T[] a) {
return null;
}
@Override
public boolean add(E e) {
return false;
}
@Override
public boolean remove(Object o) {
return false;
}
@Override
public boolean containsAll(Collection<?> c) {
return false;
}
@Override
public boolean addAll(Collection<? extends E> c) {
return false;
}
@Override
public boolean addAll(int index, Collection<? extends E> c) {
return false;
}
@Override
public boolean removeAll(Collection<?> c) {
return false;
}
@Override
public boolean retainAll(Collection<?> c) {
return false;
}
@Override
public void clear() {
}
@Override
public E get(int index) {
return null;
}
@Override
public E set(int index, E element) {
return null;
}
@Override
public void add(int index, E element) {
}
@Override
public E remove(int index) {
return null;
}
@Override
public int indexOf(Object o) {
return 0;
}
@Override
public int lastIndexOf(Object o) {
return 0;
}
@Override
public ListIterator<E> listIterator() {
return null;
}
@Override
public ListIterator<E> listIterator(int index) {
return null;
}
@Override
public List<E> subList(int fromIndex, int toIndex) {
return null;
}
}
在测试类中,创建实现类对象时,再声明泛型的类型,这样想创建什么类型的对象,就声明什么类型即可。
package Generics;
public class GenericsDemo5 {
public static void main(String[] args) {
MyArrayList3<String> list = new MyArrayList3<>();
list.add("aaa");
MyArrayList3<Integer> integerlist = new MyArrayList3<>();
integerlist.add(123);
}
}
五、泛型的继承性和通配符
泛型本身不具备继承性,但是数据是具备继承性的。
package Generics;
import java.util.ArrayList;
public class GenericsDemo6 {
/*
泛型不具备继承性,但是数据具备继承性
*/
public static void main(String[] args) {
//创建集合的对象
ArrayList<Ye> list1 = new ArrayList<>();
ArrayList<Fu> list2 = new ArrayList<>();
ArrayList<Zi> list3 = new ArrayList<>();
//调用method方法
method(list1);
method(list2); //报错,method方法约束泛型为Ye
method(list3); //报错,method方法约束泛型为Ye
//在<Ye>泛型的list1集合中,可以添加Ye、Fu、Zi三种类型数据,因为数据是可以多态的
list1.add(new Ye());
list1.add(new Fu());
list1.add(new Zi());
}
/*
此时,泛型里写的什么类型,那么只能传什么类型的数据
*/
public static void method(ArrayList<Ye> list){
}
}
class Ye{}
class Fu extends Ye{}
class Zi extends Fu{}
如果我们的需求是,定义一个方法,传递参数的类型不确定,但不希望任何类型都能传递,还是有一个限定的范围,就可以利用类的继承和泛型的通配符来实现。
package Generics;
import JavaStudy1.Array.Array;
import java.util.ArrayList;
public class GenericsDemo7 {
public static void main(String[] args) {
/*
需求:定义一个方法,形参是一个集合,但是集合中的数据类型不确定
虽然不确定类型,但是只希望能传递 Ye Fu Zi 三种类型
*/
ArrayList<Ye> list1 = new ArrayList<>();
ArrayList<Fu> list2 = new ArrayList<>();
ArrayList<Zi> list3 = new ArrayList<>();
ArrayList<Student> list4 = new ArrayList<>();
method(list1); //Ye
method(list2); //Fu
method(list3); //Zi
//method(list4); //Student
}
//泛型方法实现
//小弊端:此方法可以接收任意的数据类型
//因此Student类也能传递过来,这不符合需求
// public static <E> void method(ArrayList<E> list) {
//}
//解决方案,使用泛型通配符
// ? 表示不确定的类型
// 但是他可以进行类型的限定
// ? extends E: 表示可以传递E或者E所有的子类类型
// ? super E: 表示可以传递E或者E的所有父类类型
public static void method(ArrayList<? extends Ye> list) {
}
}
泛型通配符的应用场景:
- 在定义类、方法、接口的时候,如果类型不确定,就可以定义泛型类、泛型方法、泛型接口。
- 在定义类、方法、接口的时候,在类型不确定的前提下,能知道以后只能传递某个继承体系中的类型,就可以使用泛型通配符。