目录
为什么会出现泛型?
首先我们要知道没有泛型的时候,集合是怎么存储数据的?
我们希望集合接收任意类型的数据,可以使用Object类型进行所有类型参数的设置(因为它是所有类型的父类),可以往集合中添加任意的数据类型。但是这样有一个问题:就是我们在使用get获取该Object类型的值时,需要强制转换为具体的类型,且不能使用它的特有行为。因此出现了泛型,可以在添加数据的时候对类型进行统一,而且获取数据的时候不需要类型的强转,非常的方便。可以参考下面的代码理解:
泛型的测试类:
public class Genericity {
public static void main(String[] args) {
/**
* 问题:在没有泛型的时候,我们是怎么在集合中添加数据的?
*/
//(1)定义一个没有泛型的集合:此时默认存储的数据类型是Object类型
ArrayList list = new ArrayList<>();
//(2)在集合中添加数据:可以是任意的数据类型
list.add(1);
list.add("bbb");
list.add(new Student("zhangSan",12));//自定义的类
//(3)遍历集合,获取集合中的每一个元素——>使用迭代器的方式
Iterator it = list.iterator();
while (it.hasNext()){
// Object obj = it.next();//获取当前数据并移动指针
//多态的方式存在一个问题:就是不能使用子类的特有方法
//比如:我要使用String的.length()方法是不可以的;如果要使用,则要进行数据的强转
// obj.length();//报错
String str = (String)it.next();
str.length();
System.out.println(str);
}
/**
* 泛型的出现:。
* 好处:进行数据类型的统一,就是在编译阶段进行数据类型的检查,其他的类型就会报错
* 而且获取数据的时候因为是指定的类型,所以就不需要进行数据类型的强转。
*/
ArrayList<String> list1 = new ArrayList<>();
ArrayList<Animal> list2 = new ArrayList<>();
list1.add("aaa");
// list1.add(1);//添加其他类型的数据就会在编译阶段报错
list2.add(new Animal());
list2.add(new Cat());//泛型还可以添加自定义类型的子类
}
}
上面的测试类中使用到的类:Student类,Animal父类和继承Animal的Cat类。
Student类:
//Student类
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
Animal类:
//Animal类
public class Animal {
private String name;
private int age;
public Animal() {
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Animal{name = " + name + ", age = " + age + "}";
}
}
Cat类:
//Cat类
public class Cat extends Animal{
public Cat() {
}
public Cat(String name, int age) {
super(name, age);
}
}
知道了泛型为什么出现,接下来我们看一下泛型是什么~
泛型是什么?
——> 在定义具体的类或者方法的时候,参数的类型暂定,只有在具体使用的时候才明确具体类型。
泛型的格式:
<数据类型>
注意:
- 泛型只能支持引用数据类型,如果是基本数据类型,要使用它对应的包装类。
- 指定泛型的具体类型后,传递数据时,可以传递该类类型或者是其子类类型
- 如果不写泛型,那么默认类型是Object类型
泛型的好处:统一数据类型 +将运行阶段的问题提前到编译阶段,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确定下来。
泛型主要分为泛型类,泛型方法,泛型接口和泛型数组
1、泛型类
使用场景:当一个类中,某个变量的数据类型不确定时,就可以使用带有泛型的类。
格式: 修饰符 class 类名<类型>{
}
举例: public class ArrayList<E>{
}
具体使用: 类名称<具体的参数类型> 类引用 = new 类名称<>();
举例: ArrayList<String> list = new ArrayList<>();
public class GClassUse<T,E> {
//T,E表示类型参数:因为这里我希望x和y设置为不同的类型
//一般的类型参数用T,E,S,(K,V一般表示键值对)表示
private T x;
private E y;
public GClassUse() {
}
public GClassUse(T x, E y) {
this.x = x;
this.y = y;
}
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public E getY() {
return y;
}
public void setY(E y) {
this.y = y;
}
public static void main(String[] args) {
//创建类,在具体使用的时候确定x类型为Integer,y的类型为String类型
GClassUse<Integer,String> gClassUse = new GClassUse<>();
gClassUse.setX(10);//设置x属性值必须是整型
// gClassUse.setX(10.2);//传入其他类型报错
gClassUse.getX();//直接get获取
gClassUse.setY("fighting!");//设置y的属性值类型为String
gClassUse.getY();//get直接获取y值
}
}
2、泛型方法
当方法中的形参类型不确定时,就可以使用泛型类,将该方法写在泛型类里面;但是这样存在一个问题:就是该泛型类中所有的方法都要使用泛型。但是如果我只是有一个或少数几个方法的类型参数不确定,那么就没有必要使用泛型类,此时就出现了泛型方法,我们直接将该方法定义为泛型方法即可。
格式: 访问修饰符<参数类型> 方法名称( 类型 变量名){
}
public [static] <E> void fun(E e){
}
调用该方法时,E就会确定类型。
特点:
- 看是不是泛型方法主要看的是有没有<>;
- 泛型方法和泛型类是独立存在的,也就是说泛型方法中的类可以不是泛型类;
- 泛型在调用时明确自己的参数类型,泛型类则是实例化类时明确自己的参数类型。
总结: 当方法中的形参类型不确定时:
方法1:使用类名后面定义的泛型:所有的方法 都能用
方法2:在方法申明上定义自己的泛型:只有本方法能用
public class GMethodUse<T> {
private T x;
//2、区分:这不是泛型方法,这是定义了参数类型T的普通类
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
//1、泛型方法的定义
public static <E> void fun(E e){
System.out.println(e);
}
public static void main(String[] args) {
GMethodUse<String> gMethodUse = new GMethodUse<>();
fun(20.2);//3、泛型方法调用时的参数类型是double
gMethodUse.fun("hello");//泛型方法调用时的参数类型是String
}
}
举例:要求定义一个工具类ListUtil,在类中定义一个静态方法addAll,用来添加多个元素。
工具类ListUtil:
public class ListUtil {
/**
* 定义工具类,我们一般私有化它的构造方法,防止外界调用
*/
public ListUtil(){}
/**
* 要求:静态方法addAll,用来添加多个元素
* 考虑:需要传递哪些参?——>将元素添加到哪里?因此要传递一个集合和所添加的元素
* 注意:现在不知道传递的集合类型和添加元素的数据类型,所以定义该方法为泛型方法
* 泛型方法:只有在调用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 addAll3(ArrayList<E> list, E...e){
/**
* 采用遍历的方式添加数据:比如增强for
*/
for (E element : e) {
System.out.println(element);
}
}
}
测试类 :ListUtilTest
public class ListUtilTest {
public static void main(String[] args) {
//创建一个String类型的集合
ArrayList<String> list1 = new ArrayList<>();
//在调用泛型方法的时候在确定具体的数据类型
ListUtil.addAll(list1,"111","222","333","444");
System.out.println(list1);
//创建一个Integer类型的集合
ArrayList<Integer> list2 = new ArrayList<>();
//在调用泛型方法的时候在确定具体的数据类型
ListUtil.addAll(list2,111,222,333,444);
System.out.println(list2);
/**
* 注意:如果此时不确定要往集合中添加的元素个数是多少,可以使用 可变参数
* 在addAll3方法中传入参数时:写为E...e表示不确定元素个数的多少
*/
//创建一个Integer类型的集合
ArrayList<Integer> list3 = new ArrayList<>();
//在调用泛型方法的时候在确定具体的数据类型
ListUtil.addAll3(list3,000,111,222,333,444,555,666,777,888,999,123);
System.out.println(list3);
}
}
3、泛型接口
格式:泛型接口
interface IMessage<T> {
void printFun(T t );//抽象方法
}
特点:
- 情况1:当子类实现泛型接口时:子类仍然是泛型类,在创建对象时再确定具体类型
- 情况2:当子类实现泛型接口时,子类就已经明确了具体类型,不需要再给出具体的数据类型
public class GInterfaceUse {
interface IMessage<T>{//泛型接口
public abstract void printFun(T t);//抽象方法
}
//注意两种情况的写法格式~~
//情况1:当子类实现泛型接口时,子类也是个泛型类
class Fun1<T> implements IMessage<T>{
@Override//重写接口中的抽象方法
public void printFun(T t) {
System.out.println(t);//
}
}
//情况2:当子类实现泛型接口时,子类已经明确了具体的数据类型
class Fun2 implements IMessage<String>{
@Override
public void printFun(String s) {
System.out.println(s);
}
}
//测试
public static void main(String[] args) {
GInterfaceUse.Fun1 fun1 = new GInterfaceUse().new Fun1();
fun1.printFun(111);//传任意类型
GInterfaceUse.Fun2 fun2 = new GInterfaceUse().new Fun2();
fun2.printFun("hello");//只能传字符串
}
}
4、泛型数组
特点:
- Java中可以声明类型参数的数组,但是不能new出来参数类型的数组;
- java中没有泛型数组,如果要使用“泛型数组”,要使用Object类型的数组!
- 关于该数组的所有增删改查操作通过方法来操作,其中方法使用类型参数。
public class GArrayUse<T> {
//特点1:只能声明泛型数组,不能创建
// T[] arr;//可以声明
// T[] arr = new T[10];//报错:不能创建
//特点2:要使用泛型数组,要使用Object类型的数组
Object[] arr = new Object[10];//创建Object类型的数组
int i;
public void add(T t){// 在使用这个数组时,增删查改,使用方法来操作,方法使用类型参数~
arr[i] = t;
i++;
}
public void add1(Object obj){//对比: 在使用这个数组时,增删查改,使用方法来操作,方法使用Object类型~
arr[i] = obj;
i++;
}
public T getArr(int index) {
return (T) arr[index];
}
public Object getArr1(int index) {
return arr[index];
}
public static void main(String[] args) {
GArrayUse<Integer> gArrayUse = new GArrayUse<>();//泛型类实例化时确定参数类型Integer
//--------------对比add和add1的区别----------
//--------------对比add:参数类型是泛型----------
gArrayUse.add(11);//只能传入整数类型
gArrayUse.add(18);
int res1 = gArrayUse.getArr(0);//不需要类型转换,直接获取
System.out.println(res1);
//------------add1:Object类型的参数:需要类型的强转-------------
gArrayUse.add1(12);
int res2 = (int)gArrayUse.getArr1(0);
System.out.println(res2);
}
}
补充:泛型中的通配符
首先知道泛型的一个特点:泛型不具备继承性,但是数据具备继承性。
同时了解第二个知识点:泛型的通配符是什么?可以参考下面的代码及注释。
* 泛型的通配符:
* 希望:这个方法虽然不确定要传递的类型,但是希望只能传递Ye Fu Zi,Student不行,则可以使用泛型的通配符
* ?表示不确定的类型
* ? extends E:表示可以传递E或者E的所有子类
* ? super E:表示可以传递E或者E的所有父类
public class MyGenericity {
public static void main(String[] args) {
//创建集合:数据类型不同
ArrayList<Ye> list1 = new ArrayList<>();
ArrayList<Fu> list2 = new ArrayList<>();
ArrayList<Zi> list3 = new ArrayList<>();
ArrayList<Student> list4 = new ArrayList<>();
/**
* 泛型特点:泛型不具备继承性:由于method方法里的类型参数为Ye,即便是Fu,Zi也不能传递,只能传递Ye类型
*/
//调用method方法
method1(list1);
// method1(list2);//报错
// method1(list3);//报错
/**
* 泛型特点:但是数据具备继承性
*/
list1.add(new Ye());
list1.add(new Fu());
list1.add(new Zi());
method2(list1);
method2(list2);
method2(list3);
method2(list4);
method3(list1);
method3(list2);
method3(list3);
// method3(list4);//报错:只能传递Ye和Ye的子类
method4(list1);
method4(list2);
method4(list3);
// method4(list4);//报错:只能传递Zi的父类
}
/**
* method1只能传递Ye类型
*/
public static void method1(ArrayList<Ye> list){
}
/**
* method2是一个泛型方,可以传递任意的数据类型,包括有继承关系的Ye Fu Zi,以及单独的没有任何关系的Student类
*/
public static<E> void method2(ArrayList<E> list){
}
/**
* ? extends E:表示可以传递E或者E的所有子类
* ? super E:表示可以传递E或者E的所有父类
*/
public static void method3(ArrayList<? extends Ye> list){
}
public static void method4(ArrayList<? super Zi> list){
}
}
//创建具有继承关系的三个类
class Ye{}
class Fu extends Ye {}
class Zi extends Fu {}
class Student{}
好啦~ 今天终于到周六啦,明天再学~