为什么需要
1.不能对加入集合的数据类型进行约束(不安全)
- Java 集合有个缺点,就是把一个对象“丢进”集合里之后,集合就会“忘记”这个对象的数据类型,当再次取出该对象时,该对象的编译类型就变成了 Object 类型(其运行时类型没变);
- 例如,想创建一个只能保存 Dog 对象的集合,但程序也可以轻易地将 Cat 对象“丢”进去,所以可能引发异常。
2.集合遍历时,需进行类型转换,若集合数据量大,影响效率
- 由于把对象“丢进”集合时,集合丢失了对象的状态信息;
- 集合只知道它盛装的是 Object,因此取出集合元素后通常还需要进行强制类型转换;
- 强制类型转换既增加了编程的复杂度,也可能引发 ClassCastException 异常
介绍
泛型又称参数化类型,是jdk5.0出现的新特性,解决数据类型的安全性问题;
- 若程序在编译时候没有异常,则运行时候不会产生ClassCastException异常
- 为类、接口或方法指定一个类型参数,通过这个参数限制操作的数据类型,从而保证类型转换的绝对安全
泛型本质上是提供类型的“类型参数”,也就是参数化类型(Integer、String等)
- 在类声明或实例化时只要指定好需要的具体的类型即可
作用
- 可以在类声明时通过一个标识表示类中某个属性的类型;
- 或是某个方法的返回值的类型或参数类型
优点
- 1.编译时,检查添加元素的类型,提高安全性
- 2.减少类型转换的次数,提高效率
- 3.不在提示编译警告
import java.util.List;
public class Generic01 {
public static void main(String[] args) {
Person<String> person = new Person<String>("java课程");
person.show(); //String
Person<Integer> person2 = new Person<Integer>(100);
person2.show();//Integer
}
}
class Person<E> {
E s ;//E表示 s的数据类型, 该数据类型在定义Person对象的时候指定,即在编译期间,就确定E是什么类型
public Person(E s) {//E也可以是参数类型
this.s = s;
}
public E f() {//返回类型使用E
return s;
}
public void show() {
System.out.println(s.getClass());//显示s的运行类型
}
}
/*
你可以这样理解,上面的Person类
class Person {
String s ;//E表示 s的数据类型, 该数据类型在定义Person对象的时候指定,即在编译期间,就确定E是什么类型
public Person(String s) {//E也可以是参数类型
this.s = s;
}
public String f() {//返回类型使用E
return s;
}
}
*/
/*
class Person {
Integer s ;//E表示 s的数据类型, 该数据类型在定义Person对象的时候指定,即在编译期间,就确定E是什么类型
public Person(Integer s) {//E也可以是参数类型
this.s = s;
}
public Integer f() {//返回类型使用E
return s;
}
}
*/
系统提供泛型
import java.util.*;
/**
泛型实例化
需求:
创建 3个学生对象,放入到HashSet中学生对象
创建 3个学生对象,放入到 HashMap中,要求 Key 是 String name, Value 是学生对象
使用两种方式遍历
*/
@SuppressWarnings({"all"})
public class GenericExercise {
public static void main(String[] args) {
//使用泛型方式给HashSet 放入3个学生对象
HashSet<Student> students = new HashSet<Student>();
students.add(new Student("jack", 18));
students.add(new Student("tom", 28));
students.add(new Student("mary", 19));
//遍历HashSet集合
for (Student student : students) {
System.out.println(student);
}
//使用泛型方式给HashMap 放入3个学生对象
//K -> String V->Student
HashMap<String, Student> hm = new HashMap<String, Student>();
hm.put("milan", new Student("milan", 38));
hm.put("smith", new Student("smith", 48));
hm.put("hsp", new Student("hsp", 28));
Set<Map.Entry<String, Student>> entries = hm.entrySet();
Iterator<Map.Entry<String, Student>> iterator = entries.iterator();
System.out.println("==============================");
while (iterator.hasNext()) {//遍历
Map.Entry<String, Student> next = iterator.next();
System.out.println(next.getKey() + "-" + next.getValue());
}
}
}
class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
List<Integer> list = new ArrayList<Integer>();//OK
List<int> list = new ArrayList<int>();//错误;
//因为 E 指定了 A 类型, 构造器传入了 new A()
//在给泛型指定具体类型后,可以传入该类型或者其子类类型
Pig<A> aPig = new Pig<A>(new A());
aPig.f();
Pig<A> aPig2 = new Pig<A>(new B());
aPig2.f();
class A {}
class B extends A {}
自定义泛型
泛型类
//自定义泛型类
语法格式:
public class class_name<data_type1,data_type2,…>{}
class_name 表示类的名称,data_ type1 等表示类型参数;
泛型类一般用于类中的属性类型不确定的情况下。
在声明属性时,使用下面的语句:
private data_type1 property_name1;
private data_type2 property_name2;
该语句中的 data_type1 与类声明中的 data_type1 表示的是同一种数据类型。
在实例化泛型类时,需要指明泛型类中的类型参数,并赋予泛型类属性相应类型的值。
例如,下面的示例代码创建了一个表示学生的泛型类,该类中包括 3 个属性,分别是姓名、年龄和性别。
public class Stu<N, A, S> {
private N name; // 姓名
private A age; // 年龄
private S sex; // 性别
// 创建类的构造函数
public Stu(N name, A age, S sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
// 下面是上面3个属性的setter/getter方法
public N getName() {
return name;
}
public void setName(N name) {
this.name = name;
}
public A getAge() {
return age;
}
public void setAge(A age) {
this.age = age;
}
public S getSex() {
return sex;
}
public void setSex(S sex) {
this.sex = sex;
}
}
接着创建测试类。
在测试类中调用 Stu 类的构造方法实例化 Stu 对象,并给该类中的 3 个属性赋予初始值,最终需要输出学生信息。
测试类的代码实现如下:
public class Test14 {
public static void main(String[] args) {
Stu<String, Integer, Character> stu = new Stu<String, Integer, Character>("张晓玲", 28, '女');
String name = stu.getName();
Integer age = stu.getAge();
Character sex = stu.getSex();
System.out.println("学生信息如下:");
System.out.println("学生姓名:" + name + ",年龄:" + age + ",性别:" + sex);
}
}
该程序的运行结果如下:
学生信息如下:
学生姓名:张晓玲,年龄:28,性别:女
在该程序的 Stu 类中,定义了 3 个类型参数,分别使用 N、A 和 S 来代替,同时实现了这 3 个属性的 setter/getter 方法。
在主类中,调用 Stu 类的构造函数创建了 Stu 类的对象,同时指定 3 个类型参数,分别为 String、Integer 和 Character。
在获取学生姓名、年龄和性别时,不需要类型转换,程序隐式地将 Object 类型的数据转换为相应的数据类型。
泛型接口
//2、自定义泛型接口
public class CustomInterfaceGeneric {
public static void main(String[] args) {
}
}
interface IUsb<U, R> {
int n = 10;
//U name; 不能这样使用 ,接口内属性为静态成员变量;
//普通方法中,可以使用接口泛型
R get(U u);
void hi(R r);
void run(R r1, R r2, U u1, U u2);
//在jdk8 中,可以在接口中,使用默认方法, 也是可以使用泛型
default R method(U u) {
return null;
}
}
//在继承接口 指定泛型接口的类型
//当我们去实现IA接口时,因为IA在继承IUsu 接口时,指定了U 为String R为Double,在实现IUsu接口的方法时,使用String替换U, 是Double替换R
interface IA extends IUsb<String, Double> {
}
class AA implements IA {
@Override
public Double get(String s) {
return null;
}
@Override
public void hi(Double aDouble) {
}
@Override
public void run(Double r1, Double r2, String u1, String u2) {
}
}
//实现接口时,直接指定泛型接口的类型
//给U 指定Integer ,给 R 指定了 Float。那么当我们实现IUsb方法时,会使用Integer替换U, 使用Float替换R
class BB implements IUsb<Integer, Float> {
@Override
public Float get(Integer integer) {
return null;
}
@Override
public void hi(Float aFloat) {
}
@Override
public void run(Float r1, Float r2, Integer u1, Integer u2) {
}
}
//没有指定类型,默认为Object
//建议直接写成 IUsb<Object,Object>
class CC implements IUsb {
//等价 class CC implements Usb<Object,Object>
@Override
public Object get(Object o) {
return null;
}
@Override
public void hi(Object o) {
}
@Override
public void run(Object r1, Object r2, Object u1, Object u2) {
}
}
泛型方法
//3、自定义泛型方法
import java.util.*;
public class Test02 {
public static void main(String[] args) {
Car car = new Car();
car.fly("宝马", 100);//当调用方法时,传入参数,编译器,就会确定类型
System.out.println("=======");
car.fly(300, 100.1);//当调用方法时,传入参数,编译器,就会确定类型
//测试
//T->String, R-> ArrayList
Fish<String, ArrayList> fish = new Fish<>();
fish.hello(new ArrayList(), 11.3f);
System.out.println("=======");
fish.he("abc")//只能输入String类型的参数
fish.hi(20); //10 会被自动装箱 Integer10, 输出 Integer
fish.hi(new Car());//Car
}
}
//泛型方法,可以定义在普通类中, 也可以定义在泛型类中
class Car {//普通类
public void run() {//普通方法
}
//说明 泛型方法
//1. <T,R> 就是泛型
//2. 是提供给 fly使用的
public <T, R> void fly(T t, R r) {//泛型方法
System.out.println(t.getClass().getSimpleName());//String-->Integer
System.out.println(r.getClass().getSimpleName());//Integer-->Double
}
}
class Fish<T, R> {//泛型类
public void run() {//普通方法
}
public<U,M> void eat(U u, M m) {//泛型方法
}
//说明
//1. 下面he方法不是泛型方法
//2. 是he方法使用了类声明的 泛型
public void he(T t) {
System.out.println(h.getClass().getSimpleName());
}
public<H> void hi(H h) {
System.out.println(h.getClass().getSimpleName());//Integer
}
// public void hi(N n) {//错误,因为N没有声明;
// }
//泛型方法,可以使用类声明的泛型,也可以使用自己声明泛型
public<K> void hello(R r, K k) {
System.out.println(r.getClass().getSimpleName());//ArrayList
System.out.println(k.getClass().getSimpleName());//Float
}
}
泛型的高级用法
创建了一个 ListClass 类,并对该类的类型限制为只能是实现 List 接口的类。
// 限制ListClass的泛型类型必须实现List接口
public class ListClass<T extends List> {
public static void main(String[] args) {
// 实例化使用ArrayList的泛型类ListClass,正确
ListClass<ArrayList> lc1 = new ListClass<ArrayList>();
// 实例化使用LinkedList的泛型类LlstClass,正确
ListClass<LinkedList> lc2 = new ListClass<LinkedList>();
// 实例化使用HashMap的泛型类ListClass错误,因为HasMap没有实现List接口,所以编译时候就报错
// ListClass<HashMap> lc3=new ListClass<HashMap>();
}
}
在上述代码中,定义 ListClass 类时设置泛型类型必须实现 List 接口。
例如,ArrayList 和 LinkedList 都实现了 List 接口,所以可以实例化 ListClass 类。
而 HashMap 没有实现 List 接口,所以在实例化 ListClass 类时会报错。
import java.util.ArrayList;
import java.util.List;
//泛型的继承和通配符
public class GenericExtends {
public static void main(String[] args) {
Object o = new String("xx");
//泛型没有继承性
// List<Object> list = new ArrayList<String>(); //no
//举例说明下面三个方法的使用
List<Object> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
List<AA> list3 = new ArrayList<>();
List<BB> list4 = new ArrayList<>();
List<CC> list5 = new ArrayList<>();
//如果是 List<?> c ,可以接受任意的泛型类型
printCollection1(list1);
printCollection1(list2);
printCollection1(list3);
printCollection1(list4);
printCollection1(list5);
//List<? extends AA> c: 表示 上限,可以接受 AA或者AA子类
// printCollection2(list1);//×
// printCollection2(list2);//×
printCollection2(list3);//√
printCollection2(list4);//√
printCollection2(list5);//√
//List<? super AA> c: 支持AA类以及AA类的父类,不限于直接父类
printCollection3(list1);//√
//printCollection3(list2);//×
printCollection3(list3);//√
//printCollection3(list4);//×
//printCollection3(list5);//×
}
}
class AA {
}
class BB extends AA {
}
class CC extends BB {
}
public class FatherClass<T1>{}
public class SonClass<T1,T2,T3> extents FatherClass<T1>{}
如果要在 SonClass 类继承 FatherClass 类时保留父类的泛型类型,需要在继承时指定,否则直接使用 extends FatherClass 语句进行继承操作,此时 T1、T2 和 T3 都会自动变为 Object,所以一般情况下都将父类的泛型类型保留。