第一:参数类型用于类上案例:
编写泛型类GenericClass:
package com.test.cgb; /** * * @Description: 定义泛型类,在类名后添加 对尖括号 * 并在尖括号中填写类型参数,参数可以有多个,多个参数使用逗号分隔 * T表示的是任意类型 type * E表示的是元素的类型 element * K表示的是key/value中的key * V表示的是key/value中的value * 通常类型参数我们都是使用大写。 * @author: caigb * @createTime: 2020年2月29日 * @version: */ public class GenericClass<T> { public T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } }
编写测试类:
public class GenericDemo { public static void main(String[] args) { // 参数类型为String GenericClass<String> genericClassOfString = new GenericClass<>(); genericClassOfString.setValue("用于类上的泛型"); String str = genericClassOfString.getValue(); System.out.println("str = " + str); // 参数类型为Integer GenericClass<Integer> genericClassOfInteger = new GenericClass<>(); genericClassOfInteger.setValue(111); Integer intNum = genericClassOfInteger.getValue(); System.out.println("intNum = " + intNum); }
输出结果为:
str = 用于类上的泛型
intNum = 111
==================================================================================================================================
第二个:泛型接口:
定义接口如:
/** * * @Description: 用于接口上的泛型 * @author: caigb28355 * @createTime: 2020年2月29日 * @version: */ public interface GenericInterface<T> { void show(T value); }
那么针对这个接口我们可以分别定义多种类型的实现类如:
IntegerShowGenericInterfaceImpl:
package com.test.cgb; // 泛型类型参数为Integer public class IntegerShowGenericInterfaceImpl implements GenericInterface<Integer>{ @Override public void show(Integer value) { System.out.println(" 整数型 "+ value); } }
StringShowGenericInterfaceImpl:
package com.test.cgb; // 泛型类型参数为String public class StringShowGenericInterfaceImpl implements GenericInterface<String>{ @Override public void show(String value) { System.out.println(" 字符串 "+ value); } }
编写测试类如下:
public class GenericDemo { public static void main(String[] args) { GenericInterface IntFace = new IntegerShowGenericInterfaceImpl(); // 向上转型 IntFace.show(200); GenericInterface StringFace = new StringShowGenericInterfaceImpl(); // 向上转型 StringFace.show("字符串"); } // 输出结果为: 整数型 200 字符串 字符串
==================================================================================================================================
第三:泛型方法,可以定义在泛型类中,也可以定义在普通类中。如果可以定义泛型方法,那就尽量定义泛型方法
泛型方法类定义如:
package com.test.cgb; /** * * @Description: 泛型方法,可以定义在泛型类中,也可以定义在普通类中 * 定义泛型方法,在返回值前边,修饰符后边添加尖括号,并在其中填写类型参数 * 如果可以定义泛型方法,那就尽量定义泛型方法 * @author: caigb * @createTime: 2020年2月29日 * @version: */ public class GenericFun { public static<T> void saveScore(T score) { // 这是一个保存分数的方法,分数的类型可以是int,double,float...数值类型 // 我们对分数进行复杂操作...... System.out.println("score = " + score); } }
编写测试类如下:
public class GenericDemo { public static void main(String[] args) { GenericFun.saveScore(20.0); GenericFun.saveScore(20); } 打印结果如下: score = 20.0 score = 20
但是上面这个例子有个不足的就是,命名这个方法是用来保存分数的,由于使用了static<T>形式,所以可以接收所有类型的值比如字符串中文,那么就有点违背最初的想法了,如:
public static void main(String[] args) { GenericFun.saveScore(20.0); GenericFun.saveScore(20); GenericFun.saveScore("保存分数的入参竟然是中文"); } 打印结果有: score = 20.0 score = 20 score = 保存分数的入参竟然是中文
所以为了改进上面的缺陷,我们需要限制一下参数类型,于是要对引入类型参数做一下限定,这个限定可以是类,也可以是接口,而且可以同时限定多个,如果有多个,可以使用&符号连接,如果限定中既有类又有接口,那么类一定要写在前面<T extends Number>或者<T extends Number & Comparable>如:
public class GenericFun {
// T extends Number表示T这个类型必须继承自Number类型,也就是一定要是Number类的字类,那么也就不能是String类型了
public static<T extends Number> void saveScore(T score) {
// 这是一个保存分数的方法,分数的类型可以是int,double,float...数值类型
// 我们对分数进行复杂操作......
System.out.println("score = " + score);
}
// Double 和Float都是继承字Number类如:
// public final class Double extends Number implements Comparable<Double> {
// /**
// * A constant holding the positive infinity of type
// * {@code double}. It is equal to the value returned by
// * {@code Double.longBitsToDouble(0x7ff0000000000000L)}.
// */
// public static final double POSITIVE_INFINITY = 1.0 / 0.0;
// }
// public final class Float extends Number implements Comparable<Float> {
// /**
// * A constant holding the positive infinity of type
// * {@code float}. It is equal to the value returned by
// * {@code Float.intBitsToFloat(0x7f800000)}.
// */
// public static final float POSITIVE_INFINITY = 1.0f / 0.0f;
}
这样改造之后,在测试类中如果还是调用GenericFun.saveScore("保存分数的入参竟然是中文")方法的话就会报错如:
public class GenericDemo { public static void main(String[] args) { GenericFun.saveScore(20.0); GenericFun.saveScore(20); // 下面这句报错信息就是The method saveScore(T) in the type GenericFun is not applicable for the arguments (String) GenericFun.saveScore("这里有中文"); }
===============================================================================================================================
泛型之类型擦出重要知识点扩展:下面让我们一起学习下泛型是如何擦出的,我们要知道,对于JVM来说,根本没有泛型这个概念,只有具体类型也就是说在运行期间根本就没有GenericClass<String>,有的只是普通类和方法。JVM都会自动提供了一个相应的原始类型替换。在编译期间必要时会插入强制类型转换方法。
JVM在泛型编译时,做两件事:
1.去掉类名后的尖括号和尖括号的类型参数,剩下的名字就是对应的类名
2.对于类中使用了类型参数的变量的类型,如果没有限制类型的情况下,使用Object替换,如果有类型限定,使用限定的类型替换。下面我们一起看看:
首先我们在工作空间找到对应的class字节码文件路径,然后在该路径打开命令符窗口:
最初我们的源文件GenericClass.java代码如:
public class GenericClass<T> { public T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } }
第二步我们可以在cmd.exe窗口输入命令javap -help可以看到各种命令:
第三步我们可以输入输入命令javap -c -s GenericClass.class,我们可以看到Object类
那么如果源代码是这样的呢?就是有类型限定的如:
public class GenericClass<T extends Number> { public T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } }
那么就替换成Number了: