1.了解基础
概述
编译阶段就会约束操作的数据类型,便于检查
格式:
<数据类型>
注意:数据类型只能填写引用数据类型,泛型也只支持引用数据类型。
集合体系的全部接口和实现类都支持泛型的使用
2.优点
统一数据类型
把运行时期的问题提前到编译时期,避免了强制类型转换可能出现的异常
这样说可能会笼统,用代码演示解释
import java.util.ArrayList;
import java.util.List;
public class GenericityDemo {
public static void main(String[] args) {
//好处1:
//创建一个有泛型的集合,数据约束为字符串
List<String> list1 = new ArrayList<>();
//向集合中添加字符串的元素不会报错
list1.add("Hadoop");
list1.add("Spark");
list1.add(23.3); //一旦添加非字符串的元素就会直接标红
list1.add(10);
//好处2:
//防止了强制数据类型出现错位于
//例子
//定义普通的集合
List list2 = new ArrayList();
list2.add("Hadoop");
list2.add("Spark");
list2.add(23.3);
list2.add(10);
for (Object o : list2) {
//未带泛型的集合直接强转数据类型编译时不会报错,运行时可能会报错
String ele = (String) o;
System.out.println(ele);
//double 无法强转 String
/**报错:Exception in thread "main" java.lang.ClassCastException:
java.lang.Double cannot be cast to java.lang.String
at com.hu.d8_genericity_class.GenericityDemo.main(GenericityDemo.java:29)
*/
}
//而使用了有泛型的集合,在编译阶段就已经规定了数据类型,根本就不需要强转
for (String s : list1) {
System.out.println(s);
}
//输出结果:
//Hadoop
//Spark
//若真想填入任意类型,那么泛型可以使用Object
List<Object> list3 = new ArrayList<>();
list3.add("Hadoop");
list3.add("Spark");
list3.add(23.3);
list3.add(10);
}
}
3.泛型的定义范围
类后面 —— 泛型类
方法申明上 —— 泛型方法
接口后面 —— 泛型接口
2.自定义泛型
2.1泛型类
定义类时同时定义了泛型的类就是泛型类
泛型类的格式:
修饰符 class 类名<泛型变量>{}
//泛型变量可以写任何字母,但是为了专业点还是使用E、T、K、V作为变量
案例:模拟ArrayList集合自定义一个集合MyArrayList集合,完成添加和删除功能的泛型设计即可
MyArrayList
import java.util.ArrayList;
//定义MyArrayList的泛型设计
public class MyArrayList<E> {
//扩展:因为我只是简单的定义泛型,并不可能真正实现输出存储到内存
//所以我投机取巧:我直接用现有的ArrayList,调用人家已经写好的方法
private ArrayList lists = new ArrayList();
//添加增加元素功能,注意传入参数的数据类型是用E来表示了
public void add(E e){
//扩展内容
lists.add(e);
}
//添加删除元素的功能
public void remove(E e){
//扩展内容
lists.remove(e);
}
//扩展内容
@Override
public String toString() {
return lists.toString();
}
}
Text
public class Text {
public static void main(String[] args) {
//需求:模拟ArrayList定义一个MyArrayList,关注泛型设计
//创建类的时候,限制了数据类型只能是字符串
MyArrayList<String> list = new MyArrayList<>();
//调用功能时候就只能出入的是字符串了
list.add("Java");
System.out.println(list.toString());
list.remove("Java");
}
}
总结:
泛型类的原理:就是把出现泛型变量的地方全部替换成传输的真实数据类型
2.2泛型方法
定义方法的同时定义了泛型的方法就是泛型方法
泛型方法格式
修饰符 <泛型变量> 方法返回值 方法名称(形参列表){}
注意
//在上面泛型类的例子中
public void add(E e){}
//这种并不是泛型方法,它的E是由泛型类定义的并非是它自己
//正确的是
public <T> void add(T t){}
泛型方法的作用:方法中可以使用泛型接收一切实际类型的参数,方便更具有通用性
案例:给定一个任意类型的数组,都能返回它的内容
public class GenericDemo {
public static void main(String[] args) {
String[] name = {"hadoop", "spark", "java"};
printArray(name);
}
//定义泛型方法
public static <T> void printArray(T[] arr){
StringBuilder sb = new StringBuilder("[");
if (arr != null) {
for (int i =0; i < arr.length; i++) {
sb.append(arr[i]).append(i == arr.length - 1 ? "" : ", ");
}
sb.append("]");
System.out.println(sb);
}else {
System.out.println(arr);
}
}
}
2.3泛型接口
使用了泛型定义的接口就是泛型接口
泛型接口格式
修饰符 interface 接口名称<泛型变量>{}
//实例
public interface Data<E>{}
泛型接口的作用:泛型接口可以让实现类选择当前功能需要操作的数据类型
案例:在一个教务系统中,定义一个接口,该接口可以约束一定要完成的数据(学生,老师)的增加和更新操作
学生类
public class Student {
}
老师类
public class Teacher {
}
接口
public interface Data<E> {
//没有添加泛型的接口时的方法
//void add(Student s);
//void add(Teacher s);
//可见不同的数据类型要重新定义方法,而添加了泛型之后就可以不用多次定义方法
void add(E e);
void updata(E e);
}
实现方法
//Data方法的实现了,泛型中指定的时Teacher数据类型
public class TeacherData implements Data<Teacher>{
//重写的方法中的数据类型都是Teacher
@Override
public void add(Teacher teacher) {
}
@Override
public void updata(Teacher teacher) {
}
}
泛型接口的原理:实现类可以在实现接口的时候传入自己的数据操作的类型,这样重写的方法都将针对于该类型的操作。
3.泛型的通配符和上下限
3.1通配符:?
? 可以在“使用泛型”的时候代表一切类型。
而E T K V则时在定义的时候使用,并不是使用的时候用
案例
开发一个赛车游戏中,让所有的汽车都能比赛
public class GenericDemo {
//方法:让所有汽车都能比赛
//第一种:让宝马作为泛型数据变量
//public static void go(ArrayList<BMW> cars){}
//第二种:让父类作为泛型数据变量 原因是:虽然BMW和BENZ都继承了Car但是ArrayList<car>,ArrayList<BMW>, ArrayList<BENZ>没有任何关系,都是不同的集合
//public static void go(ArrayList<car> cars){}
//第三种:使用通配符
public static void go(ArrayList<?> cars){}
public static void main(String[] args) {
//宝马车
ArrayList<BMW> bmws = new ArrayList<>();
bmws.add(new BMW());
bmws.add(new BMW());
bmws.add(new BMW());
//第一种:奔驰可以跑
//第二种:谁都不能跑了
//第三种:谁都可以跑了
go(bmws);
//奔驰车
ArrayList<BENZ> benzs = new ArrayList<>();
benzs.add(new BENZ());
benzs.add(new BENZ());
benzs.add(new BENZ());
//第一种:但是奔驰车不能跑
//第二种:谁都不能跑了
//第三种:谁都可以跑了
go(benzs);
}
}
//奔驰车类
class BENZ extends Car{}
//宝马车类
class BMW extends Car{}
//汽车类父类
class Car{}
3.2上下限
依旧是上一个案例,如果我定义一个狗类,把狗放入到比赛能不能跑?
import java.util.ArrayList;
public class GenericDemo {
public static void go(ArrayList<?> cars){}
public static void main(String[] args) {
ArrayList<BMW> bmws = new ArrayList<>();
bmws.add(new BMW());
bmws.add(new BMW());
bmws.add(new BMW());
go(bmws);
//奔驰车
ArrayList<BENZ> benzs = new ArrayList<>();
benzs.add(new BENZ());
benzs.add(new BENZ());
benzs.add(new BENZ());
go(benzs);
//狗开跑
ArrayList<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
dogs.add(new Dog());
dogs.add(new Dog());
//发现dogs也能跑 这怎么能行
go(dogs);
}
}
//奔驰车类
class BENZ extends Car{}
//宝马车类
class BMW extends Car{}
//汽车类父类
class Car{}
//狗类
class Dog{}
发现狗也能跑,所以就由了通配符
? extends Car //表示 ? 必须是Car或者其子类 ———— 泛型的上限
? super Car //表示 ? 必须是Car或者其父类 ———— 泛型的下限
修改后的代码
import java.util.ArrayList;
public class GenericDemo {
//使用上下限后
public static void go(ArrayList<? extends Car> cars){}
public static void main(String[] args) {
//宝马车
ArrayList<BMW> bmws = new ArrayList<>();
bmws.add(new BMW());
bmws.add(new BMW());
bmws.add(new BMW());
go(bmws);
//奔驰车
ArrayList<BENZ> benzs = new ArrayList<>();
benzs.add(new BENZ());
benzs.add(new BENZ());
benzs.add(new BENZ());
go(benzs);
ArrayList<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
dogs.add(new Dog());
dogs.add(new Dog());
//发现dogs也能跑 这怎么能行
//使用上下限后dogs就进不来了
go(dogs);
}
}
//奔驰车类
class BENZ extends Car{}
//宝马车类
class BMW extends Car{}
//汽车类父类
class Car{}
//狗类
class Dog{}
到这Java关于泛型的所有知识就说完了