一、定义
Java泛型(Generic)是J2SE1.5中引入的一个新特性,其本质是参数化类型,也
就是说所操作的数据类型被指定为一个参数(type parameter)。
这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法
泛型的好处:
- 将运行时的异常提前至了编译时,保障了类型的安全。
- 避免了无谓的强制类型转换 。
- 提高代码的重用率
泛型的几种写法:
ArrayList list = new ArrayList(); true 推荐使用。
ArrayList
注意:泛型没有多态的概念,左右两边的数据类型必须要一致,或者只是写一边的泛型类型。
支持上述一边只有泛型的情况:
MyUtil:
/**
* jdk1.4时写的
* @author hcx
*
*/
public class MyUtil {
public static ArrayList getList() {
return new ArrayList();
}
public static void print(ArrayList list) {
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
Demo1:
/**
* jdk1.5之后写的
* @author hcx
*
*/
public class Demo1 {
public static void main(String[] args) {
//此处可以使用带有泛型的类型来接收
ArrayList list = MyUtil.getList();
MyUtil.print(list);
}
}
二、自定义泛型
自定义泛型:是一个数据类型的占位符或者是一个数据类型的变量
1.方法上自定义泛型
格式:
修饰符 <声明自定义的泛型>返回值类型 函数名(使用自定义泛型){
}
在返回值类型的前面声明
需求:定义一个方法可以接受任意类型的参数,而且返回值类型必须要与实参的类型一致。
public class Demo2 {
public static void main(String[] args) {
String str = getData("abc");
Integer i = getData(123);
}
//返回值的是用于声明的
public static T getData(T o){
return o;
}
}
注意:在泛型中不能使用基本数据类型,如果需要使用基本数据类型,那么就使用对应的包装类。
基本数据类型与包装类型的对应:
byte--->Byte
short--->Short
int--->Integer
long--->Long
double--->Double
float--->Float
boolean--->Boolean
char--->Character
方法泛型注意的事项:
- 在方法上自定义泛型,这个自定义泛型的具体数据类型是在调用该方法的时候传入实参时确定具体的数据类型的。
- 自定义泛型只要符合标识符的命名规则即可,但是自定义泛型一般习惯使用一个大写字母表示 T(Type) E(Element)
数组工具类:
package mydemo;
class MyArrays {
public void reverse(T[] arr){
for (int startIndex = 0,endIndex = arr.length-1;startIndex < endIndex; startIndex++,endIndex--) {
T temp = arr[startIndex];
arr[startIndex] = arr[endIndex];
arr[endIndex] = temp;
}
}
//在返回值类型的前面声明
public String toString(T[] arr){
StringBuilder sb = new StringBuilder();
for(int i = 0 ; i < arr.length ; i++){
if(i==0){
sb.append("["+arr[i]+",");
}else if(i==arr.length-1){
sb.append(arr[i]+"]");
}else{
sb.append(arr[i]+",");
}
}
return sb.toString();
}
}
public class Demo3 {
public static void main(String[] args) {
Integer[] arr = {1,2,3,4,5};
MyArrays myArr = new MyArrays();
myArr.reverse(arr);
System.out.println(myArr.toString(arr));
}
}
2.类上自定义泛型
泛型类的定义格式:
class 类名<声明自定义泛型>{
}
改进数组工具类:
class MyArrays {
public void reverse(T[] arr){
for (int startIndex = 0,endIndex = arr.length-1;startIndex < endIndex; startIndex++,endIndex--) {
T temp = arr[startIndex];
arr[startIndex] = arr[endIndex];
arr[endIndex] = temp;
}
}
//在返回值类型的前面声明
public String toString(T[] arr){
StringBuilder sb = new StringBuilder();
for(int i = 0 ; i < arr.length ; i++){
if(i==0){
sb.append("["+arr[i]+",");
}else if(i==arr.length-1){
sb.append(arr[i]+"]");
}else{
sb.append(arr[i]+",");
}
}
return sb.toString();
}
//静态方法需要自己声明
public static void print(T[] t){
}
}
public class Demo3 {
public static void main(String[] args) {
Integer[] arr = {1,2,3,4,5};
//此时确定的数据类型
MyArrays myArr = new MyArrays();
myArr.reverse(arr);
System.out.println(myArr.toString(arr));
//如果想存别的类型,则需要再创建对象
MyArrays myArr2 = new MyArrays();
String[] arr2 = {"a","b","c"};
myArr2.reverse(arr2);
System.out.println(myArr2.toString(arr2));
//如果创建对象时没有指定,则默认是object类型
}
}
泛型类要注意的事项:
- 在类上自定义泛型的具体数据类型是在使用该类的时候创建对象时确定的。
- 如果一个类在类上已经声明了自定义泛型,使用该类创建对象的时候没有指定泛型的具体数据类型,那么默认为Object类型。
- 在类上自定义泛型不能作用于静态的方法,如果静态的方法需要使用自定义泛型,那么需要在方法上自己声明使用。
在类上自定义泛型不能作用于静态的方法:
因为泛型类的具体数据类型是在使用该类的时候创建对象时确定的,但是静态方法不需要创建对象就可以使用了,所以静态方法的自定义泛型需要自己声明。
3.接口上自定义泛型
泛型接口的定义格式:
interface 接口名<声明自定义泛型>{
}
Demo:
interface Dao{
public void add(T t);
}
public class Demo implements Dao {
@Override
public void add(String t) {
// TODO Auto-generated method stub
}
}
泛型接口要注意的事项:
- 接口上自定义的泛型的具体数据类型是在实现一个接口的时候指定的。
- 在接口上自定义的泛型如果在实现接口的时候没有指定具体的数据类型,那么默认为Object类型。
需求: 目前实现一个接口的时候,还不明确目前要操作的数据类型,要等到创建接口实现类对象的时候才指定泛型的具体数据类型。
延长接口自定义泛型的具体数据类型,格式如下:
public class Demo implements Dao{
}
Dao
Demo
Demo:
interface Dao{
public void add(T t);
}
public class Demo implements Dao {
public static void main(String[] args) {
Demo d = new Demo();
}
public void add(T t){
}
}
4.提高代码利用率
/**
* 保证了类型安全
* 避免了强转
* 提高代码重复使用率
* @param
*/
public class Reuse{
public Integer compareTo(T t1, T t2){
return t1.compareTo(t2);
}
@Test
public void testCompare(){
Reuse reuse = new Reuse<>();
assertTrue(reuse.compareTo(123,234)==0);
Reuse stringReuse = new Reuse<>();
stringReuse.compareTo("!23","234");
}
}
三、泛型的上下限
需求1: 定义一个函数可以接收接收任意类型的集合对象,要求接收的集合对象只能存储Integer或者是Integer的父类类型数据。
需求2: 定义一个函数可以接收接收任意类型的集合对象,要求接收的集合对象只能存储Number或者是Number的子类类型数据。
泛型中通配符: ?
? super Integer : 只能存储Integer或者是Integer父类元素。 泛型的下限
? extends Number : 只能存储Number或者是Number类型的子类数据。 泛型的上限
Demo:
public class Demo {
public static void main(String[] args) {
ArrayList list1 = new ArrayList();
ArrayList list2 = new ArrayList();
HashSet set = new HashSet();
//getData(list1);
//print(list2);
}
//泛型的上限
public static void getData(Collection extends Number> c){
}
//泛型的下限
public static void print(Collection super Integer> c){
}
}
四、泛型的的类型擦除
泛型只在编译阶段有效,泛型类型在逻辑上可看成是多个不同的类型,但是其实
质都是同一个数据类型。
编译之后程序会采取去泛型化的措施
/**
* 泛型不影响实际的数据类型
* 泛型只在编译阶段期作用,在编译之后程序会采取去泛型化的处理
*/
public class GenType {
@Test
public void m01(){
List list = new ArrayList<>();
List strList = new ArrayList<>();
System.out.println(list.getClass());
System.out.println(strList.getClass());
System.out.println(list.getClass()==strList.getClass()); //true
}
@Test
public void method02() throws Exception{
List strList = new ArrayList<>();
strList.add("q");
strList.add("nx");
// strList.add(new Object()); //会报错
System.out.println(strList.size());
System.out.println("--------------------");
Class extends List> clazz = strList.getClass();
//通过反射加入一个Object类型的元素
Method addMeth = clazz.getDeclaredMethod("add", Object.class);
addMeth.invoke(strList,new Object());
System.out.println(strList.size());// 3
}
}
五、堆污染
当一个可变泛型参数指向一个无泛型参数时,产生堆污染
public class Pollution {
public static void main(String[] args) {
Set set = new TreeSet();
set.add("abc");
varMethod(set);
Iterator iterator = set.iterator();
while (iterator.hasNext()){
String next = iterator.next();
System.out.println(next);
}
}
private static void varMethod(Set set) {
set.add(new Integer(100));
System.out.println(set.size());
}
}
六、方法桥接
JDK 1.5 引入泛型后,为了使Java的泛型方法生成的字节码和 1.5 版本前的字
节码相兼容,由编译器自动生成的方法
在java1.5以前,比如声明一个集合类型:List list = new ArrayList();
那么往list中可以添加任何类型的对象,但是在从集合中获取对象时,无法确定获取到的对象是什么具体的类型,所以在1.5的时候引入了泛型,在声明集合的时候就指定集合中存放的是什么类型的对象:List
那么在获取时就不必担心类型的问题,因为泛型在编译时编译器会检查往集合中添加的对象的类型是否匹配泛型类型
如果不正确会在编译时就会发现错误,而不必等到运行时才发现错误。
public class SubClass implements SuperClass {
@Override
public String m01(String param) {
return param + "---";
}
}
public interface SuperClass {
T m01(T param);
}
public class BridgeMethodTest {
public static void main(String[] args) throws Exception {
SuperClass cl = new SubClass();//多态
System.out.println(cl.m01("123"));
System.out.println(cl.m01(new Object())); //编译没问题
System.out.println(cl.getClass().getDeclaredMethod("m01", Object.class).isBridge());
}
}
使用javap查看字节码:javap -verbose SubClass.class
,会发现m01方法生成了两个:
public java.lang.String m01(java.lang.String);
public java.lang.Object m01(java.lang.Object); //桥接方法,为了兼容旧版本
Object方法底层还是调用的String类型的
public java.lang.Object m01(java.lang.Object);
descriptor: (Ljava/lang/Object;)Ljava/lang/Object;
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: checkcast #7 // class java/lang/String
5: invokevirtual #8 // Method m01:(Ljava/lang/String;)Ljava/lang/String;
8: areturn
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lgeneric/bridge/SubClass;