作用:
是 jdk5 中引入的特性,可以在编译阶段 约束 操作的数据类型,并进行检查。
格式:
<数据类型>
注意泛型只能支持引用数据类型,基本数据类型可转成对应的包装类。
问题:
在没有泛型的时候,集合如何存储数据?
结论:如果我们没有给集合类型,默认数据为 Object 类型。
即可以添加任意类型的数据
如图:
等等;
弊端 1:
我们在获取集合内的数据时,无法使用它里面的特有方法。
如,以下情况:
其实这是构成了一种多态结构,而在讲解多态调用成员的特点中说过
调用成员方法,编译看左边,运行看右边
需要将 Object 强转成 String 才可以调用。
此时推出了泛型,
可以再添加数据时把类型进行统一。
而且我们在获取数据的时候,也省的强转了,非常方便。
如图:
好处
总的来说泛型的好处:
- 统一数据类型。
- 把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确定下来
- 扩展:
- 泛型擦除(Type Erasure)是Java中实现泛型的一种方式,泛型类型信息在编译时会被擦除,使得在运行时无法获取泛型的具体类型信息。这可能会导致一些限制和需要额外注意的地方,比如无法直接创建泛型类型的实例。
- 例如:
- 例如:
- 泛型擦除(Type Erasure)是Java中实现泛型的一种方式,泛型类型信息在编译时会被擦除,使得在运行时无法获取泛型的具体类型信息。这可能会导致一些限制和需要额外注意的地方,比如无法直接创建泛型类型的实例。
泛型细节:
- 泛型中不能写基本数据类型
- 指定泛型的具体类型后,传递数据时,可以传入该类类型或者其子类类型
- 如果不写泛型,类型默认是Object
泛型的应用:
泛型类:在类名后用
使用场景:当一个类中,某个变量的数据类型不确定时,就可以定义带有泛型的类
格式:
这里的 E 是用来记录数据类型的,可以写成:T、E、K、V 等等
为了方便理解,在编写泛型类时,可以把自己想想成一个大佬,你需要考虑到使用这个类的人 是如何 使用这个类的。
现在我们就来实际体会下:
/*
泛型类
*/
public class MYArrayList <E> {
//ArrayList底层是一个Object数组
Object[]obj=new Object[10];
//集合长度
int size;
//add方法,其中E表示不确定的类型,e变量名
public boolean add(E e){
obj[size]=e;//size既表示长度,又表示下一元素存入位置
size++;//添加后长度++
return true;
}
//在底层是Object类型,在获取时强转为测试类中泛型指定的类型
public E get(int index){
return (E) obj[index];
}
@Override
public String toString(){
return Arrays.toString(obj);
}
}
测试类
public static void main(String[] args) {
//创建对象,指定类型String
MYArrayList<String> list = new MYArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
System.out.println(list.get(0));
System.out.println(list);
//-----------------------,
//创建对象,指定类型Integer
MYArrayList<Integer> list1 = new MYArrayList<>();
list1.add(123);
list1.add(123);
list1.add(123);
System.out.println(list1.get(0));
System.out.println(list1);
}
控制台:
aaa
[aaa, bbb, ccc, null, null, null, null, null, null, null]
123
[123, 123, 123, null, null, null, null, null, null, null]
泛型方法:修饰符后声明泛型
应用场景:方法形参类型不确定时,有两种情况
- 使用类名候命定义的泛型----所有方法都能用
- 在方法的修饰符后声明自己的泛型------只有本方法能用
格式:public static
用一个例子来理解:
//在工具类 ListUtil 类中定义一个静态方法addAll,用来添加多个集合元素。
//工具类
public class ListUtil {
//私有化构造方法,不让外界创造对象
private ListUtil() {
}
//类中定义一个静态方法add A11,用来添加多个集合的元素。
// 参数一:集合
// 参数二:要添加的元素
public static <E>void addAll1(ArrayList<E> list,E e1,E e2,E e3){
list.add(e1);
list.add(e2);
list.add(e3);
}
/* public static <E>void addAll2(ArrayList<E> list,E...e){
for (E element:e){
list.add(element);
} */
}
注释部分: E…e 表示可变参数,用 E[] 来存储,只能通过遍历来获取元素,后面再说
测试类:
//创建集合对象,规定泛型为String
ArrayList<String>list1=new ArrayList<>();
ListUtil.addAll1(list1,"a","b","c");
System.out.println(list1);
//创建集合对象,规定泛型String
ArrayList<Integer>list2=new ArrayList<>();
ListUtil.addAll1(list2,1,2,3);
System.out.println(list2);
若没限定,默认 Object 型
泛型接口:在接口名后用
格式:
重点:如何使用来个带泛型的接口?
-
在实现接口的类中给出具体类型
-
在实现类中延续泛型,创建实现类的对象时再确定类型
-
在实现接口的类中给出具体类型:
这是一个泛型接口
现在我们创建一个类来实现接口,并直接给出具体类型
此时实现类中的 add 方法形参直接确定类型为 String
所以在测试类中创建实现类的对象后 只能添加 String类型
- 在实现类中延续泛型,创建实现类的对象时再确定类型
现在我们创建一个类来实现 List 接口,但延续泛型
此时实现类中的 add 方法形参仍是泛型
创建实现类的对象并确定类型
泛型的通配符:
严格的泛型类型系统使用起来并不那么令人愉快,
“解决方案”:通配符类型。
- <?>表示 和 一样也表示不确定的类型,但是?不需要提前定义,而 E 需要提前定义
- ?extends E:表示可以传递E或者E所有的子类类型
- ?super E:表示可以传递E或者E所有的父类类型
如果类型不确定,但是能知道以后只能传递某个继承体系中的,就可以泛型的通配符,关键点:可以限定类型的范围。
泛型的继承问题
- 泛型不具备继承性,但是数据具备继承性
1. 如:
方法 KeepPet3, 形参为 Animal 的集合:
传递一个同是 Animal 类型的集合,传递没问题,
这时若换成传递子类 Cat 的集合,会报错
所以说泛型不具备继承性
2.但是数据具备继承性
综合练习:
Animal:
public abstract class Animal {
private String name;
private int age;
//构造方法+set+get
//定义一个抽象方法
public abstract void eat();
Cat:
public abstract class Cat extends Animal{
// 调用构造方法
public Cat(){};
public Cat (String name,int age){
super(name,age);
}
}
Dog:
public abstract class Dog extends Animal{
//调用构造方法
public Dog(){}
public Dog(String name, int age){
super (name ,age);
}
}
哈士奇:
public class Huskies extends Dog {
//调用构造
public Huskies(){};
public Huskies(String name,int age){
super(name,age);
}
@Override
public void eat() {
System.out.println("一只叫做"+getName()+"的,"+getAge()+"岁的哈士奇,正在吃骨头,边吃边拆家");
}
}
泰迪:
public class Teddy extends Dog{
//调用构造方法
public Teddy(){}
public Teddy(String name, int age){
super (name ,age);
}
@Override
public void eat() {
System.out.println("一只叫做"+getName()+"的,"+getAge()+"岁的泰迪,正在吃骨头,边吃边蹭");
}
}
波斯猫:
public class PersianCat extends Cat{
调用构造方法
public PersianCat(){};
public PersianCat(String name,int age){
super(name,age);
}
@Override
public void eat() {
System.out.println("一只叫做"+getName()+"的,"+getAge()+"岁的波斯猫,正在吃小饼干");
}
}
狸花猫:
public class TanukiCat extends Cat{
//调用构造
public TanukiCat(){};
public TanukiCat(String name,int age){
super(name,age);
}
@Override
public void eat() {
System.out.println("一只叫做"+getName()+"的,"+getAge()+"岁的狸花猫,正在吃鱼");
}
}
Test:
public class Test {
public static void main(String[] args) {
//动物
ArrayList<Animal> list1 = new ArrayList<>();
//猫
ArrayList<Cat> list2 = new ArrayList<>();
list2.add(new PersianCat("小花", 3));
list2.add(new TanukiCat("小喵", 2));
//狗
ArrayList<Dog> list3 = new ArrayList<>();
list3.add(new Teddy("小黑", 2));
list3.add(new Huskies("小汪", 3));
KeepPet1(list2);//猫
KeepPet2(list3);//狗
KeepPet3(list1);//动物
}
//要求1该方法能养所有品种的猫,但是不能养狗
public static void KeepPet1(ArrayList<? extends Cat> list) {
list.forEach(cat -> cat.eat());
}
//要求2:该方法能养所有品种的狗,但是不能养猫
public static void KeepPet2(ArrayList<? extends Dog> list) {
Iterator it=list.listIterator();
while(it.hasNext()){
Dog dog=(Dog)it.next();
dog.eat();
}
}
//要求3:该方法能养所有的动物,但是不能传递其他类型
public static void KeepPet3(ArrayList<Animal> list) {
for (Animal a : list) {
a.eat();
}
}
}
控制台:
一只叫做小花的,3岁的波斯猫,正在吃小饼干
一只叫做小喵的,2岁的狸花猫,正在吃鱼
一只叫做小黑的,2岁的泰迪,正在吃骨头,边吃边蹭
一只叫做小汪的,3岁的哈士奇,正在吃骨头,边吃边拆家