泛型是Java编程语言的一项重要功能,它允许开发人员在定义类、接口和方法时使用类型参数。使用泛型可以实现类型的参数化,使代码更加灵活和可重用。
【问题】没有泛型的时候,集合如何存储数据?
结论:如果我们没有给集合指定类型,默认认为所有的数据类型都是Object类型的,此时可以往集合里添加任意的数据类型(带来一个坏处:我们在获取数据的时候,无法使用它的特有方法),因此产生了泛型,可以在添加数据的时候对数据进行统一,而且在获取数据的时候,也省的强转了。
使用泛型带来了以下几个优势:
类型安全性:通过使用泛型,编译器可以在编译时检查代码中的数据类型错误,并提供编译时类型安全性。这意味着在编译时就可以检测到类型不匹配的错误,而不是在运行时出现问题。
代码重用:使用泛型可以编写更为通用的代码,可适用于多种数据类型。这样可以减少代码的冗余,并增加代码的可重用性和维护性。
更强的类型检查和自动转换:使用泛型可以在编译时进行更强的类型检查,并自动执行类型转换,避免了手动的类型转换。
泛型的好处:
统一数据类型
把运行时期的问题提前到了编译时期,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确认下来。
泛型的细节:
泛型中不能有基本数据类型
指定泛型的具体类型后,传递数据时,可以传入该类类型或者子类类型
如果不写泛型,类型默认是Object
代码示例
package text.text02;
import java.util.ArrayList;
import java.util.Iterator;
/*
泛型:
没有泛型的时候,集合如何存储数据
结论:如果我们没有给集合指定类型,默认认为所有的数据类型都是Object类型的,此时可以往集合里添加任意的数据类型(带来一个坏处:我们在获取数据的时候,无法使用它的特有方法),因此产生了泛型,可以在添加数据的时候对数据进行统一,而且在获取数据的时候,也省的强转了。
泛型的好处:
1.统一数据类型
2.把运行时期的问题提前到了编译时期,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确认下来。
泛型的细节:
1.泛型中不能有基本数据类型
2.指定泛型的具体类型后,传递数据时,可以传入该类类型或者子类类型
3.如果不写泛型,类型默认是Object
*/
public class text30 {
public static void main(String[] args) {
//没有用泛型的方法
System.out.print("没有用泛型的方法:");
method1();
System.out.println();
//有用泛型的方法
System.out.print("有用泛型的方法:");
method2();
}
//没有用泛型的方法
public static void method1() {
//创建集合对象并添加元素
ArrayList list = new ArrayList();
list.add(123);//添加Integer类型
list.add("aaa");//添加字符串类型
list.add(true);//添加布尔类型
//通过迭代器遍历集合
Iterator it = list.iterator();
while (it.hasNext()) {
//多态的弊端是不能访问子类的特有方法
//我们没有给集合指定类型,默认认为所有的数据类型都是Object类型的
Object o = it.next();
//o.length(); 采用字符串中Length()方法获取字符串的长度报错 (可以采用强转,但是集合里面有别的类型的,强转比较麻烦)
System.out.print(o + " "); //没有用泛型的方法:123 aaa true
}
}
//有用泛型的方法
public static void method2() {
//创建集合并添加元素
ArrayList<String> list = new ArrayList<>(); //泛型表明该集合只能存入String类型的数据
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
//遍历集合并输出数据
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String str = it.next();
System.out.print(str + " "); //有用泛型的方法:aaa bbb ccc ddd
}
}
}
泛型类是使用泛型参数的类。通过在类名后使用尖括号<>包围的类型参数,可以定义泛型类。这样的泛型类可以在类的任何位置使用类型参数作为参数类型或返回类型。
泛型类:当一个类中,某个变量的数据类型不确定时,就可以定义带有泛型的类
格式:
修饰符 class 类名 <类型> {
}
举例:
public class ArrayList<E>{ //创建该对象时,E就确定了类型
//此处E可以理解为变量,但是不是用来记录数据的,而是记录数据的类型。
}
要求:自己创建一个带有泛型的ArrayList集合(当在编写一个类的时候,如果不能确认类型,那么这个类可以定义为泛型类)
代码示例
package text.text02;
import java.util.Arrays;
/*
泛型类:
当一个类中,某个变量的数据类型不确定时,就可以定义带有泛型的类
格式:
修饰符 class 类名 <类型> {
}
要求:自己创建一个带有泛型的ArrayList集合(当在编写一个类的时候,如果不能确认类型,那么这个类可以定义为泛型类)
*/
public class text31 {
public static void main(String[] args) {
//创建MyArrayList的集合对象并添加数据
MyArrayList<String> list = new MyArrayList<>();
list.add("aaa");
list.add("ddd");
list.add("ccc");
System.out.println(list); //[aaa, ddd, ccc, null, null, null, null, null, null, null]
//获取索引为1的元素
String str = list.get(1);
System.out.println("索引为1的元素:" + str); //索引为1的元素:ddd
}
}
class MyArrayList<E> {
//创建一个长度为10的数组
Object[] arr = new Object[10];
//定义变量记录存储数据的长度,初始化为0
int size;
/*
E:表示是不确定的类型,该类型在类名后面已经定义过了
e:形参的名字,变量名
*/
//添加数据的方法
public boolean add(E e) {
arr[size] = e;
size++;
return true;
}
//获取数据的方法
public E get(int index) {
return (E) arr[index];
}
//重写toString方法
@Override
public String toString() {
return Arrays.toString(arr);
}
}
泛型方法是在方法定义中使用泛型参数的方法。通过在方法名前使用尖括号<>包围的类型参数,可以定义泛型方法。这样的泛型方法可以在方法的参数类型、返回类型或方法体中使用类型参数。
泛型方法:
使用类名后面定义的类型(所有的方法都能使用)
在方法申明上定义自己的泛型(只有本方法能用)
格式:
修饰符 <类型> 返回值类型 方法名 (类型 变量名){
}
例如:
public <T> void add(T t){
//此处T可以理解为变量,但是不是用来记录数据的,而是记录数据的类型。
}
要求:定义一个工具类:ListUtil,类中定义一个静态方法addAll,用来将多个元素添加进集合。
代码示例
package text.text02;
import java.util.ArrayList;
import java.util.Arrays;
/*
泛型方法:
1.使用类名后面定义的类型(所有的方法都能使用)
2.在方法申明上定义自己的泛型(只有本方法能用)
格式:
修饰符 <类型> 返回值类型 方法名 (类型 变量名){
}
要求:定义一个工具类:ListUtil,类中定义一个静态方法addAll,用来将多个元素添加进集合。
*/
public class text32 {
public static void main(String[] args) {
//创建集合对象
ArrayList<String> list = new ArrayList<>();
//调用addAll方法
ListUtil.addAll(list, "aaa", "bbb", "ccc", "ddd", "eee", "fff");
System.out.println(list); //[aaa, bbb, ccc, ddd, eee, fff]
}
}
//工具类:ListUtil
class ListUtil {
//私有化构造方法
private ListUtil() {
}
//在方法申明上定义自己的泛型(只有本方法能用)
public static <T> void addAll(ArrayList<T> list, T t1, T t2, T t3, T t4, T t5, T t6) { //添加多个元素:T... t(底层是一个可变数组))
list.add(t1);
list.add(t2);
list.add(t3);
list.add(t4);
list.add(t5);
list.add(t6);
}
}
泛型接口是在接口定义中使用泛型参数的接口。通过在接口名称后面使用尖括号<>包围的类型参数,可以定义泛型接口。这样的泛型接口可以在接口的方法参数类型、返回类型或方法体中使用类型参数。
泛型接口:
定义一个接口时,数据类型不确定时,就可以定义带有泛型的接口。
格式:
修饰符 interface 接口名 <类型>{
}
例如:
public interface List<E>{
//此处E可以理解为变量,但是不是用来记录数据的,而是记录数据的类型。
}
泛型接口的使用方法:
方法1:实现类给出具体的类型
方法2:实现类延续泛型,创建对象时再确定
代码示例
package text.text02;
import java.util.*;
/*
泛型接口:
定义一个接口时,数据类型不确定时,就可以定义带有泛型的接口。
格式:修饰符 interface 接口名 <类型>{
}
泛型接口的使用方法:
方法1:实现类给出具体的类型
方法2:实现类延续泛型,创建对象时再确定
*/
public class text33 {
public static void main(String[] args) {
//方法1:实现类给出具体的类型,创建对象时不用确认数据类型
MyArrayList1 list1 = new MyArrayList1();
list1.add("aaa");
list1.add("bbb");
list1.add("ccc");
//list1.add(123);由于在实现类中确认的是字符串类型的,因此添加Integer类型的会报错
System.out.print("方法1:实现类给出具体的类型,创建对象时不用确认数据类型:");
for (int i = 0; i < list1.size(); i++) {
System.out.print(list1.get(i) + " "); //aaa bbb ccc
}
System.out.println();
//方法2:实现类延续泛型,创建对象时再确定
MyArrayList2<String> list2 = new MyArrayList2<>();
list2.add("ddd");
list2.add("eee");
list2.add("fff");
//list2.add(123);由于在创建对象时确定了是字符串类型的,因此添加Integer类型的会报错
System.out.print("方法2:实现类延续泛型,创建对象时再确定:");
for (int i = 0; i < list2.size(); i++) {
System.out.print(list2.get(i) + " "); //ddd eee fff
}
}
}
//方法1:实现类给出具体的类型
class MyArrayList1 implements List<String> { //List是一个Java已经定义好的为泛型的接口 :public interface List extends Collection
Object[] arr = new Object[10];
int size;
@Override
public int size() {
return size;
}
@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) {
arr[size] = s;
size++;
return true;
}
@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 (String) arr[index];
}
@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;
}
}
//方法2:实现类延续泛型,创建对象时再确定
class MyArrayList2<E> implements List<E> {
Object[] arr = new Object[10];
int size;
@Override
public int size() {
return size;
}
@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 s) {
arr[size] = s;
size++;
return true;
}
@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 (E) arr[index];
}
@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;
} //List是一个Java已经定义好的为泛型的接口 :public interface List extends Collection
}
泛型是一种参数化类型的机制,它在编程语言中引入了类型参数,使得代码可以更加通用和类型安全。
泛型不具备继承关系,但是数据可以继承
package text.text02;
import java.util.ArrayList;
/*
泛型不具备继承关系,但是数据可以继承
*/
public class text34 {
public static void main(String[] args) {
}
//泛型不具备继承关系,但是数据可以继承
public static void method1() {
//创建集合对象
ArrayList<Ye1> list1 = new ArrayList<>();
ArrayList<Fu1> list2 = new ArrayList<>();
ArrayList<Zi1> list3 = new ArrayList<>();
//泛型不具备继承关系
//调用method2方法,将创建的集合对象作为参数传进去
method2(list1);
//method2(list2);代码报错,说明泛型不具备继承关系
//method2(list3);代码报错,说明泛型不具备继承关系
//泛型数据可以继承
list1.add(new Ye1());
list1.add(new Fu1());
list1.add(new Zi1());
}
//定义一个方法,只能接受Fu类类型的数据
public static void method2(ArrayList<Ye1> list) {
}
}
//定义一个继承关系:Ye1 Fu1 Zi1
class Ye1 {
}
class Fu1 extends Ye1 {
}
class Zi1 extends Fu1 {
}
泛型通配符(Wildcard)是 Java 泛型中的一个特性,用于在泛型类、接口和方法中表示一种未知类型。它使用问号(?)来表示。通配符允许你使用不具体的类型参数,从而增加代码的灵活性和通用性。
泛型的通配符:? (限定类型的范围)
上界通配符:使用上界通配符? extends 类型表示未知类型是给定类型的子类型或本身。它限制了类型的上界,表示类型必须是某个具体类型或其子类型。通常用于限制方法的参数类型或方法的返回类型。
下界通配符:使用下界通配符? super 类型表示未知类型是给定类型的父类型或本身。它限制了类型的下界,表示类型必须是某个具体类型或其父类型。通常用于限制方法的参数类型。
泛型通配符的主要特点:
灵活性:通配符允许你使用一种未知的类型,从而使得代码可以处理不同的类型参数。
通用性:通配符可以适用于任何类型,使得代码更加通用和可重用。
安全性:泛型通配符提供了类型安全性,编译器会检查通配符使用的合法性,并在编译时发出警告或错误。
应用场景:
如果我们在定义类,方法,接口的时候,如果类型不确定,就可以定义泛型类,泛型方法,泛型接口
如果类型不确定,但是能知道以后只能传递某个继承体系的,就可以使用泛型的通配符
需求:定义一个方法,形参是一个集合,但是集合中的数据类型不能确定
package text.text02;
/*
泛型的通配符:? (限定类型的范围)
? extends E : 表示可以传递E或者E的子类类型
? super E :表示可以传递E或者E的父类类型
*/
import java.util.ArrayList;
public class text35 {
public static void main(String[] args) {
//创建集合并调用method系列方法
ArrayList<Ye2> list1 = new ArrayList<>();
ArrayList<Fu2> list2 = new ArrayList<>();
ArrayList<Zi2> list3 = new ArrayList<>();
ArrayList<Student> list4 = new ArrayList<>();
//利用泛型方法有个弊端,此时method1方法可以接受任意的数据类型
method1(list1);
method1(list2);
method1(list3);
method1(list4); //list4是学生类型的,没有继承关系,但是依旧可以调用method1方法
//利用泛型通配符的方式一:? extends E : 表示可以传递E或者E的子类类型
method2(list1);
method2(list2);
method2(list3);
//method2(list4);代码报错,因为list4是学生类型的,没有继承关系,因此不能调用method2方法
//利用泛型通配符的方式二:? super E :表示可以传递E或者E的父类类型
method3(list1);
method3(list2);
method3(list3);
//method3(list4);代码报错,因为list4是学生类型的,没有继承关系,因此不能调用method3方法
}
//可以利用泛型方法的方式,但是利用泛型方法有个弊端,此时method1方法可以接受任意的数据类型
public static <E> void method1(ArrayList<E> list) {
}
//利用泛型通配符的方式
//? extends E : 表示可以传递E或者E的子类类型
//? super E :表示可以传递E或者E的父类类型
public static void method2(ArrayList<? extends Ye2> list) { //说明该方法只能传递Ye2数据类型的或者继承Ye2数据类型的list对象
}
public static void method3(ArrayList<? super Zi2> list) { //说明该方法只能传递Zi2数据类型的或者Zi2数据类型的父类的list对象
}
}
//定义一个继承关系:Ye2 Fu2 Zi2
class Ye2 {
}
class Fu2 extends Ye2 {
}
class Zi2 extends Fu2 {
}
//再定义一个没有继承结构的学生类
class Student {
}
需求:
定义一个继承结构:
动物
| |
猫 狗
| | | |
波斯猫 狸花猫 泰迪 哈士奇
属性:名字,年龄
行为:吃东西
方法体打印:一只叫做XXX的,X岁的波斯猫,正在吃小饼干
方法体打印:一只叫做XXX的,X岁的狸花猫,正在吃鱼
方法体打印:一只叫做XXX的,X岁的泰迪,正在吃骨头,边吃边蹭
方法体打印:一只叫做XXX的,X岁的哈士奇,正在吃骨头,边吃边拆家
测试类中定义一个方法,用来饲养动物
public static void keepPet(ArrayList list){
//遍历集合,调用动物的eat方法
}
要求1:该方法能养所有品种的猫,但是不能养狗
要求2:该方法能养所有品种的狗,但是不能养猫
要求3:该方法能养所有品种的动物,但是不能传递其他类型
代码实现:
package code.code36;
public abstract 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 + "}";
}
public abstract void eat();
}
package code.code36;
public abstract class Cat extends Animal {
public Cat() {
}
public Cat(String name, int age) {
super(name, age);
}
}
package code.code36;
public abstract class Dog extends Animal {
public Dog() {
}
public Dog(String name, int age) {
super(name, age);
}
}
package code.code36;
//波斯猫
public class PersianCat extends Cat {
public PersianCat() {
}
public PersianCat(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("一只叫做" + this.getName() + "的," + this.getAge() + "岁的波斯猫,正在吃小饼干");
}
}
package code.code36;
//狸花猫
public class LiHuaCat extends Cat {
public LiHuaCat() {
}
public LiHuaCat(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("一只叫做" + this.getName() + "的," + this.getAge() + "岁的狸花猫,正在吃鱼");
}
}
package code.code36;
//哈士奇
public class HaShiQi extends Dog {
public HaShiQi() {
}
public HaShiQi(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("一只叫做" + this.getName() + "的," + this.getAge() + "岁的哈士奇,正在吃骨头,边吃边拆家");
}
}
package code.code36;
//泰迪
public class TaiDi extends Dog {
public TaiDi() {
}
public TaiDi(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("一只叫做" + this.getName() + "的," + this.getAge() + "岁的泰迪,正在吃骨头,边吃边蹭");
}
}
package code.code36;
import java.util.ArrayList;
public class CodeText36 {
public static <E> void main(String[] args) {
//创建动物的集合对象,并将动物对象添加进集合
ArrayList<PersianCat> list1 = new ArrayList<>();
ArrayList<LiHuaCat> list2 = new ArrayList<>();
ArrayList<TaiDi> list3 = new ArrayList<>();
ArrayList<HaShiQi> list4 = new ArrayList<>();
list1.add(new PersianCat("波斯", 2));
list2.add(new LiHuaCat("狸花", 3));
list3.add(new TaiDi("泰迪", 4));
list4.add(new HaShiQi("二哈", 5));
//要求1:该方法能养所有品种的猫,但是不能养狗
System.out.println("要求1:该方法能养所有品种的猫,但是不能养狗:");
keepPet1(list1); //一只叫做波斯的,2岁的波斯猫,正在吃小饼干
keepPet1(list2); //一只叫做狸花的,3岁的狸花猫,正在吃鱼
System.out.println("===============================");
//要求2:该方法能养所有品种的狗,但是不能养猫
System.out.println("要求2:该方法能养所有品种的狗,但是不能养猫:");
keepPet2(list3); //一只叫做泰迪的,4岁的泰迪,正在吃骨头,边吃边蹭
keepPet2(list4); //一只叫做二哈的,5岁的哈士奇,正在吃骨头,边吃边拆家
System.out.println("===============================");
//要求3:该方法能养所有品种的动物,但是不能传递其他类型
System.out.println("要求3:该方法能养所有品种的动物,但是不能传递其他类型:");
keepPet3(list1); //一只叫做波斯的,2岁的波斯猫,正在吃小饼干
keepPet3(list2); //一只叫做狸花的,3岁的狸花猫,正在吃鱼
keepPet3(list3); //一只叫做泰迪的,4岁的泰迪,正在吃骨头,边吃边蹭
keepPet3(list4); //一只叫做二哈的,5岁的哈士奇,正在吃骨头,边吃边拆家
}
//要求1:该方法能养所有品种的猫,但是不能养狗
public static void keepPet1(ArrayList<? extends Cat> list) {
//遍历集合,调用动物的eat方法
for (Cat cat : list) {
cat.eat();
}
}
//要求2:该方法能养所有品种的狗,但是不能养猫
public static void keepPet2(ArrayList<? extends Dog> list) {
//遍历集合,调用动物的eat方法
for (Dog dog : list) {
dog.eat();
}
}
//要求3:该方法能养所有品种的动物,但是不能传递其他类型
public static void keepPet3(ArrayList<? extends Animal> list) {
//遍历集合,调用动物的eat方法
for (Animal animal : list) {
animal.eat();
}
}
}
泛型的类型参数只能是引用类型,不能是基本数据类型。如果需要使用基本数据类型,可以使用对应的包装类(如
Integer
代替int
)。
不能直接使用泛型类型来创建对象,例如
List
是错误的。可以使用原始类型(如list = new List (); List
)或通配符来创建对象,例如List list = new ArrayList<>();
是正确的。
泛型在编译时进行类型检查,但在运行时会被擦除为原始类型。这意味着在运行时无法获得泛型类型的具体参数类型。例如,对于
List
和List
,在运行时它们都被视为List
。
无法直接创建带有泛型类型参数的数组,例如
List
是错误的。可以使用通配符或原始类型数组,例如[] array = new List [10]; List[] array = new List[10];
是正确的。
泛型类型参数不能是基本类型,只能是引用类型。如果需要使用基本类型作为类型参数,可以使用对应的包装类。
泛型类型的类型参数不能是基本类型的数组,例如
List
是错误的。
由于泛型的类型参数在运行时被擦除,可能会导致在运行时出现类型相关的异常。因此,在使用泛型时要格外小心,并进行必要的类型检查和转换。
使用通配符时,不能使用
null
以外的具体值,只能用于方法声明、参数传递和通配符类型的赋值操作。
泛型类型在类型参数相同的情况下才具有兼容性。例如,
List
和List
不是兼容类型。