文章目录
- 🥽 泛型简介
- 🌊 泛型的概念
- 🌊 使用泛型的原因
- 🌊 小结
- 🌊 类型推断
- 🥽 自定义泛型
- 🌊 自定义泛型类
- 🌊 自定义泛型类/泛型接口注意点
- 🌊 自定义泛型方法
- 🥽 泛型在继承上的体现
- 🥽 通配符的使用
- 🥽 有限制的通配符
🥽 泛型简介
🌊 泛型的概念
JDK1.5以后,Java引入了“参数化类型(Parameterized type)”(泛型)的概念,允许我们在创建集合时再指定集合元素的类型。JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时指定元素的类型。
泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参
🌊 使用泛型的原因
不使用泛型可能出现的问题:
public static void main(String[] args) {
ArrayList list = new ArrayList<>();
// 需求:存放学生成绩
list.add(89);
list.add(88);
list.add(87);
list.add(86);
// 问题1:类型不安全,可以存放不同类型的元素
list.add("TOM");
for (Object score: list ) {
// 问题2:强转时,可能出现类型转换异常
int stuScore = (int)score;
System.out.println(stuScore);
}
}
使用泛型:
泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换。
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
// 需求:存放学生成绩
list.add(89);
list.add(88);
list.add(87);
list.add(86);
// 编译时,会进行类型的检查,保证数据的安全
// list.add("TOM");
// 获取时,获取的数据为使用泛型指定元素类型
for (Integer score: list ) {
// 避免了强转操作
int stuScore = score;
System.out.println(stuScore);
}
}
Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时,代码更加简洁(可以不用强转)、健壮。
🌊 小结
① 集合接口或集合类在jdk5.0时都修改为带泛型的结构。
② 在实例化集合类时,可以指明具体的泛型类型
③ 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。比如:add(E e) —>实例化以后:add(Integer e)
④ 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换
⑤ 如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。
🌊 类型推断
// ArrayList<Integer> list = new ArrayList<Integer>();
// 如果在变量前面指明了泛型的类型,则在new的后面可以不用再写泛型的类型
// jdk7新特性:类型推断
ArrayList<Integer> list = new ArrayList<>();
🥽 自定义泛型
🌊 自定义泛型类
public class Order<T> {
int orderId;
String orderName;
// 给自定义类加上泛型后,
// 在类的内部结构就可以使用类的泛型
// 某个属性的类型为T,属性类型T由实例化类时指定
T orderT;
public Order() {}
public Order(int orderId, String orderName, T orderT) {
this.orderId = orderId;
this.orderName = orderName;
this.orderT = orderT;
}
public T getOrderT() {
return orderT;
}
public void setOrderT(T orderT) {
this.orderT = orderT;
}
}
public static void main(String[] args) {
// 如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为Object
// 如果定义了泛型,建议在实例化时指明类的泛型
Order order = new Order();
order.setOrderT(123);
order.setOrderT("Tom");
// 建议:实例化时指明类的泛型
Order<String> order1 = new Order<>();
// 报错
// order1.setOrderT(123);
order1.setOrderT("Order1");
}
定义泛型类的子类,并且指明泛型的类型:
// SubOrder1 指明了泛型类型,不为泛型类
public class SubOrder extends Order<Integer> {
public SubOrder() {}
}
public static void main(String[] args) {
SubOrder sub1 = new SubOrder();
// 由于子类在继承带泛型的父类时,指明了泛型类型
// 实例化时不再需要指明泛型
// 且在子类中用到泛型的位置都为继承时指定的泛型类型
sub1.setOrderT(123);
}
定义泛型类的子类,不指明泛型的类型:
// SubOrder1<T> 没有指定泛型类型,仍然为泛型类
public class SubOrder1<T> extends Order<T> {
public SubOrder1() {}
}
public static void main(String[] args) {
SubOrder1<String> sub1 = new SubOrder1();
sub1.setOrderT("ABC");
}
🌊 自定义泛型类/泛型接口注意点
-
泛型类可能有多个参数,此时应将多个参数一起放在尖括号内,使用逗号进行分隔。比如:
<E1,E2,E3>
-
泛型类的构造器如下:
public GenericClass(){}
。而下面是错误的:public GenericClass<E>(){}
。声明泛型类的构造器不需要写<E>
。 -
实例化后,使用到泛型的位置的类型都为指定的泛型类型,没有指定则为Object。
-
泛型不同的引用(泛型类不同的实例化对象)之间不能相互赋值。
-
泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。泛型要使用一路都用。要不用,一路都不要用。
-
如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象(接口或抽象类不能实例化)。
-
jdk1.7,泛型的简化操作:
ArrayList<Fruit> flist = new ArrayList<>();
-
泛型的指定中不能使用基本数据类型,可以使用包装类替换。
-
在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。类的泛型是在实例化时进行指定,而静态结构在类加载时就被加载,静态结构的使用早于类的实例化。
-
异常类不能是泛型的
-
不能使用
new E[]
。但是可以:E[] elements = (E[])new Object[capacity];
public void test() { // 编译不通过 // 此时new需要一个具体的类型,而T不确定 // T[] arr = new T[]; // 编译通过,Object为具体的类型 // 运行时运行到此处会将其强转为T类型 T[] arr = (T[])new Object[10]; }
-
父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
- 子类不保留父类的泛型:按需实现
- 没有类型 擦除
- 具体类型
- 子类保留父类的泛型:泛型子类
- 全部保留
- 部分保留
class Father<T1, T2> { } // 子类不保留父类的泛型 // 1)没有类型 擦除 class Son1 extends Father {// 等价于class Son extends Father<Object,Object>{ } // 2)具体类型 class Son2 extends Father<Integer, String> { } // 子类保留父类的泛型 // 1)全部保留 class Son3<T1, T2> extends Father<T1, T2> { } // 2)部分保留 class Son4<T2> extends Father<Integer, T2> { }
class Father<T1, T2> { } // 子类不保留父类的泛型 // 1)没有类型 擦除 // A B 为子类额外提供的泛型,从父类继承的泛型都为Object类型,在此基础上子类多了 A B 两个泛型 // A B 属于子类 class Son<A, B> extends Father{//等价于class Son extends Father<Object,Object>{ } // 2)具体类型 // 从父类继承的泛型为Integer, String类型,在此基础上子类多了 A B 两个泛型 // A B 属于子类 class Son2<A, B> extends Father<Integer, String> { } // 子类保留父类的泛型 // 1)全部保留 // 保留父类的全部泛型,子类又额外定义了 A B 泛型 class Son3<T1, T2, A, B> extends Father<T1, T2> { } // 2)部分保留 // 保留父类的T2泛型,子类又额外定义了 A B 泛型 class Son4<T2, A, B> extends Father<Integer, T2> { }
结论:子类除了指定或保留父类的泛型,还可以增加自己的泛型
- 子类不保留父类的泛型:按需实现
🌊 自定义泛型方法
泛型方法:在方法的结构中出现了泛型的结构,且方法的泛型和类的泛型没有任何关系,即类的泛型参数与方法的泛型参数为两个不同的独立的参数,泛型方法与所属的类是否为泛型类没有关系。
public static void main(String[] args) {
_1 v = new _1();
Integer[] arr = new Integer[]{1, 2, 3, 4};
// 泛型方法在调用时指明泛型参数的类型
// 泛型方法的泛型类型与泛型类的泛型类型无关
List<Integer> list = v.test(arr);
}
// <E> 表明后面使用的E为泛型
// 否则E会被认为类进行处理 public List<E> test(E[] arr)
public <E> List<E> test(E[] arr) {
ArrayList<E> list = new ArrayList<>();
for (E e: arr) {
list.add(e);
}
return list;
}
泛型方法可以声明为静态的,因为泛型方法的泛型参数是在调用方法时确定的,并非在实例化时确定。
public static void main(String[] args) {
Integer[] arr = new Integer[]{1, 2, 3, 4};
// 泛型方法在调用时指明泛型参数的类型
// 泛型方法的泛型类型与泛型类的泛型类型无关
List<Integer> list = test(arr);
}
// <E> 表明后面使用的E为泛型
// 否则E会被认为类进行处理 public List<E> test(E[] arr)
public static <E> List<E> test(E[] arr) {
ArrayList<E> list = new ArrayList<>();
for (E e: arr) {
list.add(e);
}
return list;
}
🥽 泛型在继承上的体现
如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,G<B>
并不是G<A>
的子类型!比如:String是Object的子类,但是List<String>
并不是List<Object>
的子类。
public void test1() {
List<Object> l1 = new ArrayList<>();
List<String> l2 = new ArrayList<>();
// 如果 l1 = l2
// 则可以通过 l1 = l2, l1.add(123)向l2中加入不为String类型的数据
}
如果B是A的一个子类型,且A与B是具有泛型声明的类或接口,B<G>
是A<G>
的子类型
public void test2() {
List<String> l1 = null;
ArrayList<String> l2 = null;
l1 = l2;
}
🥽 通配符的使用
类型通配符:?
比如:List<?>
,Map<?,?>
,List<?>
是List<String>
、List<Object>
等各种泛型List的父类。
如果B是A的一个子类型,G<A>
与G<B>
没有关系,二者共同的父类为G<?>
。
public void test3() {
List<String> l1 = new ArrayList<>();
List<Integer> l2 = new ArrayList<>();
print(l1);
print(l2);
}
public void print(List<?> l) {
l.forEach(System.out::println);
}
对于List<?>
不能向其内部添加数据,因为我们不知道需要向其中添加的元素类型,不能向其中添加对象。唯一的例外是null,它是所有类型的成员。
读取List<?>
的对象中的元素时,永远是安全的,因为不管List<?>
对象中包含的真实类型是什么,都可以赋值给Object。
🥽 有限制的通配符
<?>
- 允许所有泛型的引用调用
- 通配符指定上限
- 上限extends:泛型使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
- 通配符指定下限
- 下限super:泛型使用时指定的类型不能小于操作的类,即>=
- 举例:
<? extends Number>
(无穷小 , Number]- 只允许泛型为Number及Number子类的引用调用
<? super Number>
[Number , 无穷大)- 只允许泛型为Number及Number父类的引用调用
<? extends Comparable>
- 只允许泛型为实现Comparable接口的实现类的引用调用
// Student为Person的子类
@Test
public void test4(){
List<? extends Person> list1 = null;
List<? super Person> list2 = null;
List<Student> list3 = new ArrayList<Student>();
List<Person> list4 = new ArrayList<Person>();
List<Object> list5 = new ArrayList<Object>();
list1 = list3;
list1 = list4;
// list1 = list5; // Object的范围大于Person
// list2 = list3; // Student的范围小于Person
list2 = list4;
list2 = list5;
//读取数据:
list1 = list3;
Person p = list1.get(0); // 最大不超过Person
//编译不通过
//Student s = list1.get(0); // Person的子类不一定都为Student
list2 = list4;
Object obj = list2.get(0); // 最小不低于Person
编译不通过
// Person obj = list2.get(0); // 都为Person的父类或Person类
//写入数据:
// 注意允许的范围
// 子类可以放进去,子类可以赋值给父类,范围小的可以赋值给范围大的
//编译不通过
// 可能存在比Student范围还小的类,父类对象赋值给子类
// list1.add(new Student());
//编译通过
list2.add(new Person());
list2.add(new Student());
}