泛型产生的背景:Java推出泛型以前,是构建一个元素类型为 Object 的集合,该集合能够存储任意的对象,而在使用该集合的过程中,需要明确知道存储每个元素的数据类型,否则很容易引发 ClassCastException 异常(类型转换异常)。
Java泛型(generics)是 JDK5 中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许在编译时检测到非法的类型数据结构。
泛型的本质就是参数化类型,也就是所操作的数据类型被指定为一个参数。
import java.util.ArrayList;
public class Test01 {
public static void main(String[] args) {
/*
ArrayList list = new ArrayList();//创建一个集合
list.add("java");//默认数据类型为Object
list.add(100);
list.add(true);
for (int i = 0 ;i < list.size();i++){
Object o = list.get(i);
String str = (String)o;
System.out.println(str); //会报错 ClassCastException
} */
//使用泛型(作为一个安全检测限制)
ArrayList<String> strList = new ArrayList<>();
strList.add("a");
strList.add("b");
strList.add("c");
for (int i = 0; i < strList.size(); i++) {
String s = strList.get(i);
System.out.print(s);//abc
}
//泛型的好处:编译期间检查类型,减少了数据类型转换
// <> 数据类型作为参数
ArrayList<Integer> intList = new ArrayList<>();
intList.add(100);//自动转换为int类型
//如果输入浮点数、字符串等其他类型就会报错
}
}
泛型类的定义语法:
class 类名称 < 泛型标识,泛型标识 ,... > {
private 泛型标识 变量名;
....
}
E ----Element(在集合中使用,因为集合中存放的是元素)
T ----Type(Java类)
K ----Key(键)
N ----Number(数值类型)
V ----Value(值)
?----表示不确定的java类型
/*
泛型类的定义:
泛型标识--类型形参
T 创建对象的时候里指定具体的数据类型
*/
public class Test02 <T>{
// T 是由外部使用类的时候来指定
private T key;
public Test02 (T key) {
this.key = key;
}
public T getKey(){
return key;
}
public void setKey(T key){
this.key = key;
}
@Override
public String toString() {
return "Test02{" +
"key=" + key +
'}';
}
}
泛型类的使用:
类名<具体的数据类型> 对象名 = new 类名 <具体的数据类型>();
类名<具体的数据类型> 对象名 = new 类名<>();
/*
泛型类的定义:
泛型标识--类型形参
T 创建对象的时候里指定具体的数据类型
*/
public class Test02 <T>{
// T 是由外部使用类的时候来指定
public static void main(String[] args) {
//泛型类在创建对象的时候,来指定操作的具体数据类型
Test02<String> strTest = new Test02<>("abc");//只能传指定的类型数据,不然会报错
String key1 = strTest.getKey();
System.out.println(key1);//abc
//定义一个泛型类,可以操作不同的数据类型,减少了转换,提高了代码的复用率
Test02<Integer> intTest = new Test02<>(100);//只能传指定的类型数据,不然会报错
int key2 = intTest.getKey();
System.out.println(key2);//100
//泛型类不支持基本数据类型(如错误),只支持类类型(来继承Object类接收类型转换)
//泛型类在创建对象的时候,没有指定类型,将按照Object类型来操作
Test02 test = new Test02("ABC");
Object key3 = test.getKey();
System.out.println(key3);//ABC
System.out.println(strTest.getClass());//class com.practice.fanxing.Test02
System.out.println(intTest.getClass());//class com.practice.fanxing.Test02
System.out.println(intTest.getClass() == strTest.getClass());//true 说明就是同一个类
//同一泛型类,根据不同类型的数据创建的对象,本质上是同一类型
}
private T key;
public Test02 (T key) {
this.key = key;
}
public T getKey(){
return key;
}
public void setKey(T key){
this.key = key;
}
@Override
public String toString() {
return "Test02{" +
"key=" + key +
'}';
}
}
泛型类注意事项:
class ChildGeneric<T> extends Generic<T>;
class ChildGeneric extends Generic<String>;
interface 接口名称 <泛型标识,泛型标识,...> {
泛型标识 方法名();
...
}
实现类不是泛型类:
语法:
修饰符 <T,E,...> 返回值类型 方法名(形参列表){
方法体...
}
public class Method01<E> {
//这不是泛型方法
public E method00(){return null;}
/* 不能声明为静态Static的 */
//这才是泛型方法 可以声明为静态的Static
public <E> E method01 ( E input ){
return input;
}
public static void main(String[] args) {
Method01<String> str = new Method01<>();
Method01<Integer> integer = new Method01<>();
String s = str.method01("Hello");
Integer i = integer.method01(100);
System.out.println(s);//Hello
System.out.println(i);//100
}
}
泛型方法和可变参数:
public <E> void print (E... e){ //就是加上三个点
for (E eee : e){
System.out.println(e);
}
}
- 泛型方法能使方法独立于类而产生变化;
- 如果 static 方法要使用泛型能力,就必须要定义为泛型方法;
什么是类型通配符:
为什么要用通配符:
public static void show(Box<?> box){
Object value = box.getValue();
}
类型通配符的上限:
类/接口 <? extends 实参类型>
要求该泛型的类型,只能是实参类型,或实参类型的子类类型;
(也就是泛型的类型最高的上限)
public static void show(Box<? extends Number> box){
Number value = box.getValue();//类型最大为Number
}
类型通配符的下限:
类/接口 <? super 实参类型>
要求该泛型的类型,只能是实参类型,或实参类型的父类类型;
泛型是Java1.5版本才引进的概念,在这之前是没有泛型的,但是,泛型代码能够很好地和之前版本的代码兼容,是因为,泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除,称为----类型擦除;
import java.util.ArrayList;
public class Test04 {
public static void main(String[] args) {
ArrayList<Integer> intList = new ArrayList<>();
ArrayList<String> strList = new ArrayList<>();
// 输出类型是一样的,说明经过了类型擦除
System.out.println(intList.getClass().getSimpleName());//ArrayList
System.out.println(strList.getClass().getSimpleName());//ArrayList
System.out.println(intList.getClass() == strList.getClass());//true
}
}
泛型数组的创建:
import java.util.ArrayList;
//泛型和数组
public class Test07 {
public static void main(String[] args) {
/* ArrayList[] listArray = new ArrayList[5];
可以声明带泛型的数组 但不能直接创建带泛型的数组对象
*/
ArrayList[] list1 = new ArrayList[5];
ArrayList<String>[] listArray1 = list1;
//先创建一个普通数组,再赋给泛型数组
// 但这样是有弊端的,不安全,
// 因为定义了是String类型的,如果给原生list1赋值传递Integer类型会导致类型转换异常
// 把list1隐藏起来,就可以解决,因为编译器会进行泛型检查
ArrayList<String>[] listArray2 = new ArrayList[5];
ArrayList<String> strList = new ArrayList<>();
strList.add("abc");
listArray2[0] = strList;
String s = listArray2[0].get(0);
System.out.println(s);
}
}
import java.lang.reflect.Array;
//泛型会经历一个擦除,但数组会从始至终保持数据类型;
//所以JDK不允许直接创建带泛型的数组;
//可以通过java.lang.reflect.Array的newInstance(Class,int)创建T[]数组
public class Test08<T> {
private T[] array;
//通过构造方法创建泛型数组 传递一个泛型类和数组长度
public Test08(Class<T> clz,int length){
array = (T[])Array.newInstance(clz,length);//需要强转
}
//封装起来
public void put(int index,T item){
array[index] = item; //填充数组
}
public T get(int index){
return array[index]; //获取数组元素
}
public T[] getArray(){
return array; //获取整个数组
}
}
再通过调用来操作数组
import java.util.Arrays;
public class Test09 {
public static void main(String[] args) {
Test08<String> arr = new Test08<>(String.class,3);
arr.put(0,"apple");
arr.put(1,"banana");
arr.put(2,"orange");
//先获取数组再通过Arrays.toString方法全部打印出来 (就不用遍历)
System.out.println(Arrays.toString(arr.getArray()));
// [apple, banana, orange]
String s1 = arr.get(2);
System.out.println(s1);//orange
}
}
反射常用的泛型类: