一、反射
通过Class实例获取 class 信息的方法称为反射(Reflection)。
(一) Class 类
Java 除基本类型外其他都是class类型(包括 interface,例如:String、Object、Runnable、Exception),class 的本质就是一种数据类型;
1、Class 的简介
(1)class/interface 的数据类型是 Class(如下代码),每加载一个 class,JVM 就为其创建了一个 Class 类型的实例,并关联起来(如下图:“创建class.png”)
public final class Class{
private Class(){}
}
(2)JVM 持有的每个 Class 实例都指向一个数据类型(class 或 interface,如下图:”Class实例.png“)。
(3)一个 Class 实例包含了该 class 的完整信息,例如下图是指向 String 类(class)的 Class 实例:它包含了 String 这个类的 name、package、super、interface、field、method 等信息。
(4)JVM 为每个加载的 class 创建对应的 Class 实例,并在实例中保存该 class 的所有信息。
2、反射:通过Class实例获取class信息的方法称为反射。如果获取了某个 Class 实例,则可以获取到该实例对应的 class 的所有信息。
3、获取一个 class 的 Class 实例有三种方法:
(1)使用 Type.class :
// 获取 String 类的 Class 实例
Class cls = String.class;
(2)对实例变量调用 getClass 方法:
// 通过 String 类的实例 s 来获取 String 类的 Class 实例
String s = "hello";
Class cls = s.getClass();
(3)使用 Class 提供的 静态方法 forName():
Class cls = Class.forName("java.lang.String");
4、Class 实例在 JVM 中是唯一的,可以使用 == 比较两个 Class 实例
Class cls1 = String.class;
String s = "Hello";
Class cls2 = s.getClass();
Class cls3 = Class.forName("java.lang.String");
boolean b1 = cls1 == cls2; // true
boolean b2 = cls2 == cls3; // true
5、Class 实例比较和 instanceof 的差别:
(1)instanceof 可以判断是否相等,判断子类关系。
(2)Class 实例比较符 "==",只能判断是否相等,而不能判断是否是子类关系。
6、从 Class 实例获取 class 信息:
(1)getName()
(2)getSimpleName()
(3)getPackage()
Class cls = String.class;
String fname = cls.getName(); // "java.lang.String"
String sname = cls.getSimpleName; // "String"
String pkg = cls.getPackage().getName(); // "java.lang"
7、从 Class 实例本身判断 class 的类型:
(1)isInterface() 判断是不是 interface 类型
(2)isEnum() 判断是不是 枚举 类型
(3)isArray() 判断是不是 Array 类型
(4)isPrimitive() 判断是不是 Primitive 类型
Runnable.class.isInterface(); // true
java.time.Month.class.isEnum(); // true
String[].class.isArray(); // true
int.class.isPrimitive(); // true
8、使用 Class 创建 class 实例
Class cls = String.class;
// new String();
String s = (String) cls.newInstance();
9、利用 JVM 动态加载 class 的特性,可以在运行期根据条件加载不同的实现类(例如 Commons Logging 的使用):
// Commons Logging 优先使用 Log4j,没有 Log4j,则使用 JVM 自带的 Log 模块
LogFactory factory;
if (isClassPresent("org.apache.logging.log4j.Logger")){
factory = createLog4j();
} else {
factory = createJdkLog();
}
boolean isClassPresent(String name){
try{
Class.forName(name); // 存在则不会抛出异常
return true;
} catch (Exception e){
return false;
}
}
10、Class 类使用实际案例
package com.test.sample
public class Main{
public static void main(String[] args) throws Exception{
// Class cls = Student.class;
Class cls;
if (true){ // JVM 总是动态加载 class,所以我们可以在运行期根据条件控制加载 class
cls = Class.forName("com.test.sample.Student");
} else {
cls = Class.forName(""com.test.sample.Teacher");
}
System.out.println("class name" + cls.getName());
System.out.println("class simple name" + cls.getSimpleName());
System.out.println("package name" + cls.getPackage().getName());
System.out.println("is interface?" + cls.isInterface());
// Student s = new Student();
Student s = (Student) cls.newInstance();
s.hello();
}
}
(二) 访问字段
1、通过Class实例获取字段field信息:
(1)getField(name):获取某个 public 的field(包括父类)
(2)getDeclaredField(name):获取当前类的某个field(不包括父类)
(3)getFields():获取所有 public 的field(包括父类)
(4)getDeclaredFields():获取当前类的所有field(不包括父类)
2、Field对象包含一个field的所有信息:
(1)getName()
(2)getType()
(3)getModifiers()
Integer n = new Integer(123);
Class cls = n.getClass();
Field[] fs = cls.getFields();
for (Field f : fs) {
f.getName(); // field name
f.getType(); // field type
f.getModifiers(); // modifiers
}
3、获取和设置field的值:
(1)get(Object obj) 获取一个实例的该字段 field 的值
Integer n = new Integer(123);
Class cls = n.getClass();
Field f = cls.getDeclaredField("value");
f.get(n); // 123 相当于 n.value
(2)set(Object, Object) 设置 一个实例的该字段 field 的值
Integer n = new Integer(123);
Class cls = n.getClass();
Field f = cls.getDeclaredField("value");
f.set(n, 234); // 相当于 n.value = 234
4、通过设置 setAccessible(true) 来访问非 public 字段
package com.test;
public class Main{
public static void main(String[] args) throws Exception{
Student s = new Student();
Class cls = s.getClass();
Field f = cls.getDeclaredField("address");
f.setAccessible(true); // 设置为 true,使 private 字段能够被访问
System.out.println(f.get(s));
s.hello();
}
static void printFieldInfo(Field f){
System.out.println("field name: " + f.getName());
System.out.println("file type: " + f.getType());
System.out.println("filed modifier: " + f.getModifiers());
System.out.println("is public? " + Modifier.isPublic(f.getModifiers()));
System.out.println("is protected? " + Modifier.isProtected(f.getModifiers()));
System.out.println("is final? " + Modifier.isFinal(f.getModifiers()));
}
}
package com.test;
public class Student extends Person{
public static int number;
public String name;
private String address = "北京";
public Student(){
// TODO
}
}
package com.test;
public class Person {
public int age;
public int getAge(){
return age;
}
public void setAge(int age){
this.age = age;
}
}
注意:setAccessible(true) 可能会失败,如果 JVM 中定义了 SecurityManager ,SecurityManager会有一个规则,如果这个规则阻止对 Field 设置 accessible(例如:规则应用于所有 java 和 javax 开头的 package 的类,则就不能访问 private 字段)。通常情况下我们自己编写的类 和 第三方类没有这个限制。
(三) 调用方法
1、通过Class实例获取方法Method信息:
(1)getMethod(name, Class...):获取某个public的method(包括父类)
(2)getDeclaredMethod(name, Class...):获取当前类的某个method(不包括父类)
(3)getMethods():获取所有public的method(包括父类)
(4)getDeclaredMethods():获取当前类的所有method(不包括父类)
2、Method对象包含一个method的所有信息:
(1)getName()
(2)getReturnType()
(3)getParameterTypes()
(4)getModifiers()
Integer n = new Integer(123);
Class cls = n.getClass();
Method[] ms = cls.getMethods();
for (Method m : ms){
m.getName(); // method name
// return type:
Class ret = m.getReturnType();
// parameter types:
Class[] params = m.getParameterTypes();
m.getModifiers(); // modifiers
}
3、调用Method:
(1)调用无参数的 Method 方法:Object invoke(Object obj, Object... args)
Integer n = new Integer(123);
Class cls = n.getClass();
Method m = cls.getMethod("toString"); // 获取 n 的 toString 方法
String s = (String) m.invoke(n) // "123" 相当于 String s = n.toString();
(2)调用带参数的 Method 方法:Object invoke(Object obj, Object...args),即第一个参数是实例变量,第二个参数是方法参数
Integer n = new Integer(123);
Class cls = n.getClass();
Method m = cls.getMethod("compareTo", Integer.class); // 获取 compareTo 方法
int r = (Integer) m.invoke(n, 456); // -1, 相当于 int r = n.compareTo(456)
4、通过设置setAccessible(true)来访问非public方法。
5、反射调用Method也遵守 多态 的规则。
例:从 Person.class 获取的 Method,作用于 Student 实例时,实际调用的 方法是 Student 覆写的方法, 保证了多态的正确性
class Person{
public void hello(){
System.out.println("Person: hello");
}
}
class Student extends Person{
public void hello(){
System.out.println("Student: hello");
}
}
Method m = Person.class.getMethod("hello");
m.invoke(new Student()); // 实际传入的是 Student,这里实现了多态 相当于 Person p = new Student(); p.hello();
(四) 调用构造方法
1、调用 public 无参数构造方法:
(1)使用 Class.newInstance() 调用
(2)newInstance() 只能调用无参数的构造方法
String s = (String) String.class.newInstance(); // String.class 是 Class 对象,String 类定义的了一个 无参数构造方法,所以可以使用 newInstance()
Integer n = (Integer) Integer.class.newInstance(); // error Integer 类没有无参数的构造方法,无法使用 newInstance()
2、调用带参数的 构造方法
(1)调用带参数的构造方法,不能直接使用 Class 的 newInstance 方法。需要获取 Constructor 对象,Constructor 对象包含一个构造方法的所有信息,通过 Constructor 对象的 newInstance() 方法来调用。(即Constructor实例使用 newInstance 可以创建一个实例对象)
(2)实例:
Class cls = Integer.class;
// 调用构造方法 Integer(int)
Constructor cons1 = cls.getConstructor(int.class); // 获取一个 Constructor 对象
Integer n1 = (Integer) cons1.newInstance(123); // 通过 Constructor 对象的 newInstance 方法来调用有参数的构造方法
// 调用构造方法 Integer(String)
Constructor cons2 = cls.getConstructor(String.class);
Integer n2 = (Integer) cons2.newInstance("123");
(3)通过Class实例获取 构造方法 的信息:
a、getConstructor(Class...):获取某个public的Constructor
b、getDeclaredConstructor(Class...):获取某个Constructor
c、getConstructors():获取所有 public 的 Constructor
d、getDeclaredConstructors():获取所有Constructor
package com.test;
import java.lang.reflect.Constructor;
import java.util.Arrays;
public class Main{
public static void main(String[] arg) throws Exception {
Class cls = Student.class;
Constructor c = cls.getDeclaredConstructor(String.class, int.class);
printConstructorInfo(c);
c.setAccessible(true);
Student s = (Student) c.newInstance("XiaoMing", 12); // Constructor实例使用 newInstance 方法创建一个 Student实例
s.hello();
}
static void printConstructorInfo(Constructor c){
System.out.println(c);
System.out.println("parameters: " + Arrays.toString(c.getParameterTypes()));
System.out.println("modifier: " + c.getModifiers());
}
}
package com.test;
public class Student extends Person{
public static int number = 0;
public String name;
private String address = "beijing";
private Student(){
this("Unanmed");
}
private Student(String name){
this(name, 20);
}
private Student(String name, int age){
this.name = name;
this.age = age;
number++;
}
public void hello(){
System.out.printf(this.name + ":" + this.age);
}
}
(4)通过设置setAccessible(true)来访问非public构造方法。
3、注意区分 Class.newinstance 和 Constructor.newinstance 的区别
(五) 获取继承关系
1、获取父类的Class:
(1)使用 Class getSuperclass() 获取父类 Class
(2)Object 的父类是null
(3)interface 的父类是null
Class sup = Integer.class.getSuperclass(); // Number.class
Object.class.getSuperclass(); // null
Runnable.class.getSuperclass(); // null
2、获取当前类直接实现的 interface(不包括间接实现的interface) :
(1)使用 Class[] getInterfaces() 来获取实现的 interface
(2)没有interface的class返回空数组
(3)interface 返回继承的 interface
Class[] ifs = Integer.class.getInterfaces(); // [Comparable.class]
Class[] ifs = java.util.ArrayList.class.getInterfaces();
Class[] ifs = Math.class.getInterfaces(); // []
Class[] ifs = java.util.List.class.getInterfaces(); // [Collection.class]
3、判断一个向上转型是否成立:bool isAssignableFrom(Class)
// Integer i = ...
// Number x = i ?
Number.class.isAssignableFrom(Integer.class); // true Integer 向上转型为 Number 成立
// Number n = ...
// Integer i = n ?
Integer.class.isAssignableFrom(Number.class); // false
4、总结
(1)getSuperclass() // 获取类的父类
(2)getInterfaces() // 获取类直接实现的接口
(3)isAssignableFrom() // 判断向上转型是否正确
二、注解
(一) 使用注解
1、注解的定义:注解(Annotation)是放在Java源码的类、方法、字段、参数前的一种标签,是 java 语言用于工具处理的标注。
2、注解的作用
(1)注解本身对代码逻辑没有任何影响
(2)如何使用注解由工具(例如:编译器)决定
public class Hello {
@Override
public String toString(){
return "Hello";
}
@Deprecated
public void hello(String name){
System.out.println(name);
}
@SuppressWarnings("unused");
public void hello(){
int n;
}
}
3、编辑器可以使用的注解:
(1)@Override:让编辑器检查该方法是否正确地实现了覆写
(2)@Deprecated:告诉编译器该方法已经被标记为"作废",在其他地方引用将会出现编译警告
(3)@SuppressWarnings:告诉编译器忽略某种警告
(4)写注解编译器会帮助检查问题,不写注解编译器就不会检查。
4、注解可以定义配置参数:
(1)配置参数由注解类型定义
(2)配置参数可以包括:
(a)所有基本类型
(b)String
(c)枚举类型
(d)数组
(3)配置参数必须是常量
public class Hello{
int n = 100;
@Test(timeout=100)
public void test(){
System.out.println("Test");
}
}
5、缺少某个配置参数将使用默认值
(1)如果只写常量,相当于省略了 value 参数
(2)如果只写注解,相当于全部使用默认值,默认值的定义是在注解定义时定义的
public class Hello{
@Check(min=0, max=100, value=55)
public int n;
@Check(value=99)
public int p;
@Check(99) // @Check(value=99)
public int x;
@Check
public int y;
}
(二) 定义注解
1、在 Java 中使用 @interface 来定义注解(Annotation)
(1)注解的参数类似无参数方法
(2)可以给注解设定一个 默认值 (推荐)
(3)把最常用的参数命名为 value能(推荐)
public @interface Report{
int type() default 0;
String level() default "info";
String value() default "";
}
2、元注解:
(1)有些注解可以修饰其他注解,称为元注解。
(2)使用 @Target 定义Annotation 可以被应用于源码的哪些位置
(a)类或接口:ElementType.TYPE
(b)字段:ElementType.FIELD
(c)方法:ElementType.METHOD
(d)构造方法:ElementType.CONSTRUCTOR
(e)方法参数:ElementType.PARAMETER
@Target(ElementType.METHOD) // 声明 Resport 注解只能应用于 方法 上
public @interface Report{
int type() default 0;
String level() default "info";
String value() default "";
}
@Target({ElementType.METHOD, ElementType.FIELD}) // 声明 Report 注解只能应用于 方法、字段 上。
public @interface Report{
int type() default 0;
String level() default "info";
String value() default "";
}
(3)使用 @Retention 定义 Annotation 的生命周期:
(a)仅编译期:RetentionPolicy.SOURCE 编译器在编译时直接丢弃,即编译器编译后该注解就不存在了
(b)仅 class 文件:RetentionPolicy.CLASS 仅存储在 class 文件中
(c)运行期:RetentionPolicy.RUNTIME 在运行期可以读取该Annotation注解
(d)如果 @Retention 不存在,则该 Annotation 默认为 CLASS,通常自定义的注解 Annotation 都是 RUNTIME。
@Retention(RetentionPolicy.RUNTIME)
public @interface Report{
int type() default 0;
String level() default "info";
String value() default "";
}
(4)使用 @Repeatable 定义 注解Annotation 是否可重复 (JDK >= 1.8)
@Repeatable
@Target(ElementType.TYPE)
public @interface Report{
int type() default 0;
String level() default "info";
String value() default "";
}
@Report(type=1, level="debug") // 可以多次使用 Report
@Report(type=2, level="warning")
public class Hello{
}
(5)使用 @Inherited 定义子类是否可以继承父类定义的 注解
(a)仅针对 @Target 为 TYPE 类型的 注解
(b)仅针对 class 的继承
(c)对 interface 的继承无效
@Inherited
@Target(ElementType.TYPE)
public @interface Report{
int type defaulet 0;
String level() default "info";
String value() default "";
}
@Report(type=1) // 可以多次使用 Report
public class Person{
}
public class Student extends Person{
}
3、总结定义注解步骤:
(1)用 @interface 定义注解
(2)用 元注解(meta annotation)配置注解
(a)Target:必须设置
(b)Retention:一般设置为 RUNTIME
(c)通常不必写 @Inherited、@Repeatable 等等
(3)定义注解参数和默认值
package com.test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.FIELD) // 1、定义一个应用于字段 Field 的 NotNUll 空注解
// @Target({ElementType.FIELD, ElementType.PARAMETER}) // 1、如果还想要应用于 函数的参数字段上 还需要添加 ElementType.PARAMETER
public @interface NotNull {
}
package com.test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Range {
int min() default 1;
int max() default 100;
}
package com.test;
public class Person{
@NotNull // 说明 name 字段不允许为空
public String name;
@Range(max=20) // 说明 age 这个字段最大值是 20
public int age;
public Person(String name, int age){
// public Person(@NotNull String name, int age){ // 应用方法参数 name, 声明 name 不可为空
this.name = name;
this.age = age;
}
}
4、总结:
(1)使用 @interface 定义注解
(2)可以定义多个参数和默认值,核心参数使用 value 名称
(3)必须设置 @Target 来指定 Annotation 可以应用的范围
(4)应当设置 @Retention 为 RUNTIME 便于运行期读取 该 Annotation
(三) 处理注解
1、使用反射API读取 RUNTIME类型 的注解 Annotation:
(1)Class.isAnnotationPresent(Class) :判断 注解 是否存在
Class cls = Person.class;
// 判断 @Report 是否存在
cls.isAnnotationPresent(Report.class);
(2)Field.isAnnotationPresent(Class)
(3)Method.isAnnotationPresent(Class)
(4)Constructor.isAnnotationPresent(Class)
(5)Class.getAnnotation(Class) :获取 注解
Class cls = Person.class;
Report report = cls.getAnnotation(Report.class);
int type = report.type();
String level = report.level():
(6)Field.getAnnotation(Class)
(7)Method.getAnnotation(Class)
(8)Constructor.getAnnotation(Class)
(9)getParameterAnnotations()
2、使用反射 API 读取 class Annotation 常用的两种方法:
// 方法一
Class cls = Person.class;
if (cls.isAnnotationPresent(Report.class)){
Report report = cls.getAnnotation(Report.class);
...
}
// 方法二
Class cls = Person.class;
Report report = cls.getAnnotation(Report,class);
if (report != null) {
...
}
3、使用反射 API 读取方法参数的注解 PARAMETER Annotation 常用的方法:
public String hello(@NotNull @Range(max=5) String name, @NotNull String prefix){
...
}
Mthod m = ...
Annotation[][] annos = m.getParameterAnnottations();
Annotation[] annosOfName = annos[0]; // name 参数的 Annotation
for (Annotation anno : annosOfName){
if (anno instanceof Range){
Range r = (Range) anno;
}
}
// 以下是 annos 的内容
{
{@NotNull, @Range(max=5)},
{@NotNull}
}
4、可以通过工具处理注解来实现相应的功能:
(1)对JavaBean的属性值按规则进行检查
(2)JUnit会自动运行@Test标记的测试方法
(四) 注解的应用:
1、编写注解 @NotNull:检查该属性为非null
2、编写注解 @Range:检查整型介于min~max,或者检查字符串长度介于min~max
3、编写注解 @ZipCode:检查字符串是否全部由数字构成,且长度恰好为value
三、泛型
(一) 泛型的定义
1、泛型就是定义一种模版,例如 ArrayList
(1)在代码中为用到的类创建对应的 ArrayList<类型>:
ArrayList
(2)编译器针对类型作检查操作:
strList.add("hello");
strList.add(new Integer(123)); // complie error!
// 定义泛型
public class ArrayList{
private T[] array;
public void add(T e){
}
public void remove(int index){
}
public T get(int index) {
}
}
// 调用泛型
ArrayList strList = new ArrayList();
ArrayLIst floatList = new ArrayList();
ArrayLIst psList = new ArrayList();
2、泛型的继承关系
(1)向上转型
public class ArrayList implements List {
private T[] array;
public void add(T e) {
}
public void remove(int index){
}
public T get(int index){
}
}
List list = new ArrayLIst(); // 向上转型为 List
(2)注意泛型的继承关系:
a. 可以把 ArrayLIst
b. 不能把 ArrayLIst
(二) 泛型的使用
1、使用泛型时,可以省略编译器能自动推断出的类型
// 可以省略后面的 Number,编译器可以自动推断类型
List list = new ArrayList<>();
2、Arrays.sort() 可以对 Object[] 数组进行排序:
String[] strs = {"Apple", "Pear", "Orange"};
Arrays.sort(strs);
System.out.println(Arrays.toString(strs));
// {"Apple", "Orange", "Pear"}
3、泛型的好处是在编译的时候检查类型安全,并且所有的 强制转换 都是自动和隐式的,提高代码的重用率。
4、泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
(三) 泛型的编写(不常用)
1、如何编写一个泛型类:
(1)按照某种类型(例如 String)编写类
(2)标记所有的特定类型(例如 String)
(3)把特定类型替换为 T, 并申明
public class Pair {
private T first;
private T last;
public Pair(T first, T last){
this.first = first;
this.last = last;
}
public T getFirst(){
return first;
}
public T getLast(){
return last;
}
}
2、静态方法不能引用泛型类型
public class Pair {
private T first;
private T last;
public Pair(T first, T last){
this.first = first;
this.last = last;
}
public T getFirst(){
return first;
}
public T getLast(){
return last;
}
// 编译错误
public static Pair create(T first, T last){
return new Pair(first, last);
}
// 正确方法如下
public static Pair create(K first, K last){ // static 后也加了
return new Pair(first, last);
}
}
3、编写泛型时可以定义多种类型
public class Pair{
private T first;
private K last;
public Pair(T first, K last){
this.first = first;
this.last = last;
}
public T getFirst(){
}
public K getLast(){
}
}
Pair p = new Pair<>("test", 123);
(四) 擦拭法
1、Java 的 泛型是采用擦拭法实现的,虚拟机其实对泛型一无所知,所有的泛型都是编译器在操作。
编译器把类型
编译器根据
2、擦拭法的局限
(1)
(2)Object字段无法持有基本类型
(3)无法取得带泛型的Class,例如:Pair
Pair s = ...;
Pair i = ...;
Class c1 = s.getClass();
Class c2 = i.getClass();
System.out.println(c1 == c2); // true
System.out.println(c1 == Pair.class); // true
// Compile error;
if (s instanceof Pair.class){
}
(4)无法判断带泛型的Class,例如:x instanceof Pair
(5)不能实例化 T 类型,因为擦拭之后实际上是 new Object(),例如:new T()。 实例化 T类型必须借助 Class
// 错误的实例化
public class Pair{
private T first;
private T last;
public Pair(){
// Compile error;
first = new T();
last = new T();
}
}
Pair pair = new Pair<>();
// 正确的实例化
public class Pair{
private T first;
private T last;
public Pair(Class clazz){
first = clazz.newInstance();
last = clazz.newInstance();
}
}
Pair pair = new Pair<> (String.class);
(6)泛型方法要防止重复定义方法,例如:public boolean equals(T obj)
3、子类可以获取父类的泛型类型
public class IntPair extends Pair{
}
Class clazz = IntPair.class;
Type t = clazz. ();
if (t instanceof Parameterizedtype){
ParameterizedType pt = (ParameterizedType) t;
Type[] types = pt.getActualTypeArguments();
Type firstType = types[0];
Class> typeClass = (Class>) firstType;
System.out.println(typeClass); // Integer
}
4、JDK 定义了 Type接口, 他的实现类包括:Class、ParameterizedType、GenericArrayType、WildcardType。
(五) extends 通配符
1、使用 extends 通配符,可以接收本身类型及子类,例如:
案例一:使用 Pair extends Number> 可以使方法接收所有泛型类型为 Number 或 Number 子类的 Pair 类。
案例二:使用 Pair extends String> 可以使方法接收所有泛型类型为 String 或 String 子类的 Pair 类。
public class Pair{
}
public class PairHelper{
Static void add(Pair extends Number> p){
Number first = p.getFirst();
p.setFirst(first); // ERORR
p.setFirst(null); // OK
Number last = p.getLast();
return first.intValue() + last.intValue();
}
}
PairHelper.add(new Pair(1, 2));
PairHelper.add(new Pair(1,2));
void process(List extends Number> list){
}
public class List {
T get(int index);
void add(T t); // ERORR 无法调用传入 Integer 引用的方法, add 同 set 一样
void remove(T t); // ERORR 无法调用传入 Integer 引用的方法, remove 同 set 一样
}
2、 使用类似 super Integer> 通配符作为 方法参数 是表示(代码如上):
(1)方法内部可以调用获取 Integer 引用的方法(get方法),例如:Number n = obj.getXxx()
(2)方法内部无法调用传入 Integer 引用的方法(set方法 null 除外),例如:obj.setXxx(Number n) // 报错
3、extends 通配符作为 方法参数 实例
void someMethod(List extends Number> list) {
Number n = list.get(0);
list.add(n); // ERROR
}
以上实例:
(1)允许传入List
4、extends 通配符的另一个用法 -- 定义泛型类:
(1)定义泛型时可以通过extends限定T 必须 是Number或Number的子类
(2)方法内部可以调用获取(get) Number 引用的方法,Number n = obj,getXxx()。
(3)方法内部无法调用传入(set、add、remove) Number 引用的方法(null 除外),obj.setXxx(Number n)。
5、使用类似
(六) super 通配符
1、使用 super 通配符,可以接收本身类型及超类,例如:
案例一:使用 Pair super Integer> 可使方法接受所有泛型类型为 Integer 或 Integer 超类的 Pair 类
案例二:使用 Pair super String> 可使方法接受所有泛型类型为 String 或 String 超类的 Pair 类
public class Pair {
}
public class PairHelper{
Static void add(Pair super Integer> p, Integer first, Integer last){
p.setFirst(first);
p.setLast(last);
Integer i = p.getFirst(); // Error 无法调用获取 Integer 引用的方法
Object o = p.getFirst(); // 获取 object 的引用
}
}
PairHelper.add(new Pair(1, 2)); // Integer 是 Integer 本身
PairHelper.add(new Pair(1, 2)); // Number 是 Integer 的父类
void process(List extends Number> list){
}
public class List {
T get(int index); // ERORR
void add(T t);
void remove(T t);
}
2、使用类似 super Integer> 通配符作为 方法参数 时表示(代码如上):
(1)方法内部可以调用传入 Integer 引用的方法:obj.setXxx(Integer n)
(2)方法内部无法调用获取 Integer 引用的方法(Object 除外):Object o = p.getFirst() // 获取 object 的引用
3、super 通配符作为 方法参数 实例
void someMethod(List super Integer> list) {
list.add(123);
Integer n = list.get(0); // ERROR
}
以上实例:
(1)允许传入List
4、supper 通配符的另一个用法 -- 定义泛型类:
(1)使用
(2)实例:
public class Pair{
}
Pair ip = new Pair<>(1, 2); // OK
Pair dp = new Pair<>(1.2, 2.4); // OK
Pair cp = new Pair<>("a", "2"); // error
5、extends 和 super 通配符的区别:
(1) extends T> 允许调用方法获取 T 的引用
(2) super T> 允许调用方法传入 T 的引用
public class Collections {
// 把 src 的每个元素复制到 dest 中
public static void copy(List super T> dest, List extends T> src){
for (int i = 0; i < src.size(); i++){
T t = src.get(i);
dest.add(t);
}
}
}
6、无限定通配符 > 很少使用,只能获取 Object 引用,只能传入 null,可以用
(七) 泛型和反射
1、部分反射 API 是泛型:
(1)Class
// compile warning 编译器警告
Class clazz = String.class;
String str = (String) clazz.newInstance();
// 正确使用 Class
Class clazz = String.class;
String str = clazz.newInstance(); // T.newInstance() 无需强制转型
// getSuperclass()
Class super String> sup = clazz.getSuperclass();
(2)Constructor
Class clz = Integer.class;
Constructor cons = clz.getConstructor(int.class);
Integer i = cons.newInstance(123);
2、泛型数组
(1)可以声明带泛型的数组,但是不能用 new 创建带泛型的数组,必须强制转型:
Pair[] ps = null;
Pair[] ps = new Pair[2]; // erorr
(2)必须通过强制转型实现带泛型的数组:
@SuppressWarnings("unchecked")
Pair[] ps = (Pair[]) new Pair[2];
(3)使用泛型数组时要注意安全地使用,因为数组在运行过程中是没有泛型的。
// 错误用法
Pair[] arr = new Pair[2];
Pair[] ps = (Pair[]) arr; // ps 和 arr 指向了同一个数组
ps[0] = new Pair("a", "b");
arr[1] = new Pair(1, 2); // 这里还不会报错
// ClassCastException:
Pair p = ps[1]; // 会报错,因为 arr 对数组进行了改动,出入了 Integer 类型的参数,而这里返回值是 Pair 类型,不是 Pair
String s = p.getFirst();
// 正确用法
@SuppressWarnings("unchecked")
Pair[] ps = (Pair[]) new Pair[2];
3、带泛型的数组实际上是编译器的类型擦除,所以对带泛型的数组调用 getClass() 方法,返回的是 定义的类型.class
Pair [] arr = new Pair[2];
Pair[] ps = (Pair[]) arr;
System.out.println(ps.getClass() == Pair[].class); // true getClass 的结果为 Pair[].class
String s1 = (String) arr[0].getFirst();
String s2 = ps[0].getFirst();
4、不能直接创建 T[] 数组(因为擦拭后代码变为了 new Object[]),可以通过 Array.newInstance(Class
(1)方法一:借助 Class
public class Abc {
T[] createArray(Class cls){
return (T[]) Array.newInstance(cls, 5);
}
}
(2)方法二:利用可变参数(T...)创建 T[] 数组:
public class ArrayHelper{
@SafeVarargs //消除编译警告
static T[] asArray(T... objs) { // ...表示可变
retrun objs;
}
}
String[] as = ArrayHelper.asArray("a", "b", "c");
Integer[] ns = ArrayHelper.asArray(1, 2, 3);
(八) 泛型使用实例
1、案例一:
package com.test;
public class Pair{
private T first;
private T last;
public Pair(T first, T last){
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
}
package com.test;
public class Main{
public static void main(String[] args){
Pair p = new Pair<>(123, 45.65);
int sum = add(p);
System.out.println(sum);
System.out.println(add(new Pair(123, 456)));
System.out.println(add(new Pair(12.4, 23.4)));
}
static int add(Pair extends Number> p){
Number first = p.getFirst();
Number last = p.getLast();
return first.intValue() + last.intValue();
}
}