java泛型与反射总结

android面试准备 同时被 2 个专栏收录
26 篇文章 1 订阅
13 篇文章 0 订阅


由于借鉴的较多,这里就直接作为转载了。 Java 反射由浅入深 | 进阶必备

泛型基础

泛型的好处

在使用泛型之前,使用的是继承,会有两个问题:1.强制转换,2.没有错误检查。泛型提供了类型参数,具有更好的可读性。在使用get方法时,不需要强制转换。在add时会出现编译错误,以提示使用者。

//不使用泛型需要强制转换
 List list = new ArrayList();
 list.add("hello");
 list.add(5);
System.out.println(list.get(0));
System.out.println((String)list.get(1));% class java.lang.Integer cannot be cast to class java.lang.String
//使用泛型可避免强制转换
List<String> list = new ArrayList<String>();
list.add("hello");
list.add(5)%error
String s = list.get(0);

泛型使用

使用<类型参数>代表这是一个泛型类、泛型方法、泛型接口。其实 是为了说明类型参数

泛型类:

public class ObjectTool<T> { 
      private T obj; 
      public T getObj() { 
         return obj; 
      } 
      public void setObj(T obj) { 
           this.obj = obj;
     }
}

泛型方法:

public class ObjectTool {  
      public <T> T show(T t) {
          if (t != null){
              return t;
          }else{
              return null;
          }
}

泛型接口:
注意在具体类实现泛型接口的时候不能在使用类型参数T了,而是要具体的类型。

public class Ttry {
    public static void main(String[]args){
        showTry showTry = new showTry();
        System.out.println(showTry.show(20));
    }
}
interface show<T>{
    T show(T t);
}
class showTry implements show<Integer>{
    public showTry(){

    }
    @Override
    public Integer show(Integer num){
        return  num;
    }
}

类型变量的限定

例如我们有一个泛型方法min,其中的形参是也是一个泛型数组。我们要使用泛型变量smallest的comparTo方法,就要求类型变量T是可以实现Comparable接口的类
在这里插入图片描述
为了解决这种问题,我们可以使用这种方式。虽然是接口,但这里也使用了extends。因为这里的含义表示T是绑定类型的子类型。这里T和绑定类型可以是类,也可以是接口。同时一个类型变量可以有多个限定,使用’&'来分隔。

public static<T extends Comparable> T min(T[]a)
T extends Comparable & Serializable

类型擦除

类型擦除概述

虚拟机没有泛型类型对象,所有对象都属于普通类。无论何时定义一个泛型类型,都会有一个原始类型(raw type)。会将无限定类型(T)转换为限定类型(如果没有的话默认为Object)。一个简单的例子如下代码,这是一个泛型类在虚拟机中的原始类型。

class Pair<T>{
    private T first;
    public Pair(T first) this.first = first;
    public T getFirst(){return first;}
    public void setFirst(T newvalue)first = newvalue;
}
class Pair{
    private Object first;
    public Pair(Object first) this.first = first;
    public Object getFirst(){return first;}
    public void setFirst(Object newvalue)first = newvalue;
}

假如类型变量有限定的话,就会用第一个限定的类型来作为原始类型,如下图。
在这里插入图片描述
当程序调用泛型方法时,如果没有擦除返回类型的话,编辑器插入强制转换,下述代码一共分两步,首先通过原始方法getFirst获得一个Object对象,再强制转换为Employee对象。
在这里插入图片描述
类型擦除也会出现在泛型方法中:

public static <T extends Comparable> T min(T[]a)
public static Comparable min(Comparable a)

类型擦除带来的影响

1.不能使用基本类型作为类型变量,类型擦除之后List含有Object的域,而Object域不能存储int值。

List<int>list = new ArrayList<>();//error
List<Integer>list = new ArrayList<>();

2.运行时类型查询仅适用于原始类型
虚拟机中的对象有一个特定的非泛型类型,因此所有的类查询都只会产生原始类型。

if (list instanceof ArrayList<Integer>)//error
if (list instanceof ArrayList<T>)//error
//其实查询的只是判断list是否是任意一个类型的ArrayList
if(list instanceof ArrayList<?>)//ok
ArrayList<String>()list1 = new ArrayList<>();
ArrayList<Integer>list2 = new ArrayList<>();
list1.getClass()==list2.getClass();//return true,都是ArrayList.class

3.不能创建参数化类型的数组
List和 List在 jvm 中等同于List,所有的类型信息都被擦除,程序也无法分辨一个数组中的元素类型具体是 List类型还是 List类型。

List<Integer>[] li2 = new ArrayList<Integer>[10];//error
List<Boolean> li3 = new ArrayList<Boolean>[10];//error
List<List<Integer>>lists = new ArrayList;//ok

4.不能实例化类型变量

public Pair(){first = new T();second = new T();}//error

5.不能抛出或捕获泛型类的实例

//不能创建、捕捉或抛出参数化类型的对象
class MathException<T> extends Exception {}    // error
class QueueFullException<T> extends Throwable {} // error
public static <T extends Exception, J> void execute(List<J> jobs) {
    try {
    } catch (T e) {   // error
    }
}

6.不能重载参数类型为相同原始类型的方法

在擦除后都是List list。

public class Example { // error
    public void print(List<String> list) {}
    public void print(List<Integer> list) {}
}

通配符类型

1.?extends Employee,而当前代码代表它的参数类型是Employee的子类,但一定不是其他的比如String。

Pair<? extends Employee>

我理解通配符类型可以做到如下事情:当泛型类MyChar中的类型变量可以是类A,类B时,我们有一个函数想要使用类型变量中的一个元素。如果我们这个方法传入的形参中的类型变量是A,就只能使用A的元素.但类B是类A的子类,也同样拥有这个元素,我们想要让A的子类都可以被使用。就需要使用通配符

 public static void returnNum(MyChar<A> c)
 public static void returnNum(MyChar<? extends A> c)
public class Ttry {
    public static void main(String[]args){
        //showTry showTry = new showTry();
        //System.out.println(showTry.show(20));
        A a = new A(10);
        B b = new B(20,30);
        MyChar<A> aMyChar = new MyChar<>(a);
        MyChar<B> bMyChar = new MyChar<>(b);
        returnNum(aMyChar);
        returnNum(bMyChar);
    }
    public static void returnNum(MyChar<? extends A> c){
        A a = c.getT();
        System.out.println(a.getNum());
    }
}
class  MyChar<T>{
   private T first;
   public MyChar(T t)
   {
       this.first = t;
   }
   public T getT(){
       return first;
   }
}
class  A{
    private int num;
    public A(int num)
    {
        this.num = num;
    }
    public int getNum(){
        return num;
    }
}
class B extends A{
    private int size;
    public B(int num){
        super(num);
    }
    public B(int num,int size)
    {
        super(num);
        this.size = size;
    }
    public  int getNum()
    {
        return  super.getNum();
    }
}

2.?super Employee代表当前通配符限制为Employee的父类。

3.还可以使用无限定通配符:“?”

反射

java反射机制:在程序运行时,能知道任意一个类的所有属性和方法,对于任意一个对象都能调用它的任意一个方法和属性。这种动态的获取信息或动态的调用对象的方法称之为反射。

这里最重要的一点是运行时,反射允许我们可以在程序运行时加载使用探索运行时生成的.class 文件。换句话说,Java 程序可以加载一个运行时才得知名称的 .class 文件,然后获悉其完整构造,并生成其对象实体、或对其 fields(变量)设值、或调用其 methods(方法)。

反射基础

我们可以很轻松的通过下述代码获取一个Java类的class对象。

 TestClass testClass = new TestClass();
    Class mClass = testClass.getClass();

我们通过可以动态的创建一个类的对象。可以创建一个与testClass具有相同构造的对象,newInstance方法是使用默认方法(无参)初始化新创建的对象。

 TestClass testClass = new TestClass();
testClass.getName().newInstance();

使用反射获取类的信息

这里设置两个类(Father.class与Son.class)来体现效果。当然实际开发中变量域应该是private,但这里为了体现效果写出public。

FatherClass.java

public class FatherClass {
    public String mFatherName;
    public int mFatherAge;
    
    public void printFatherMsg(){}
}

SonClass.java

public class SonClass extends FatherClass{

    private String mSonName;
    protected int mSonAge;
    public String mSonBirthday;

    public void printSonMsg(){
        System.out.println("Son Msg - name : "
                + mSonName + "; age : " + mSonAge);
    }

    private void setSonName(String name){
        mSonName = name;
    }

    private void setSonAge(int age){
        mSonAge = age;
    }

    private int getSonAge(){
        return mSonAge;
    }

    private String getSonName(){
        return mSonName;
    }
}

获取类的所有变量信息

/**
 * 通过反射获取类的所有变量
 */
private static void printFields(){
    //1.获取并输出类的名称
    Class mClass = SonClass.class;
    System.out.println("类的名称:" + mClass.getName());
    
    //2.1 获取所有 public 访问权限的变量
    // 包括本类声明的和从父类继承的
    Field[] fields = mClass.getFields();
    
    //2.2 获取所有本类声明的变量(不问访问权限)
    //Field[] fields = mClass.getDeclaredFields();
    
    //3. 遍历变量并输出变量信息
    for (Field field :
            fields) {
        //获取访问权限并输出
        int modifiers = field.getModifiers();
        System.out.print(Modifier.toString(modifiers) + " ");
        //输出变量的类型及变量名
        System.out.println(field.getType().getName()
		         + " " + field.getName());
    }
}

值得注意的是:
调用 getFields() 方法输出 SonClass 类以及其所继承的父类( 包括 FatherClass 和 Object ) 的 public 成员变量。注:Object 类中没有成员变量,所以没有输出。

类的名称:obj.SonClass
public java.lang.String mSonBirthday
public java.lang.String mFatherName
public int mFatherAge

调用 getDeclaredFields()输出 SonClass 类的所有成员变量,不问访问权限。

类的名称:obj.SonClass
private java.lang.String mSonName
protected int mSonAge
public java.lang.String mSonBirthday

获取类的所有方法信息

可以更好的体现getMethods()方法是获取所有包括自己声明与父类继承的public权限方法。

/**
 * 通过反射获取类的所有方法
 */
private static void printMethods(){
    //1.获取并输出类的名称
    Class mClass = SonClass.class;
    System.out.println("类的名称:" + mClass.getName());
    
    //2.1 获取所有 public 访问权限的方法
    //包括自己声明和从父类继承的
    Method[] mMethods = mClass.getMethods();
    
    //2.2 获取所有本类的的方法(不问访问权限)
    //Method[] mMethods = mClass.getDeclaredMethods();
    
    //3.遍历所有方法
    for (Method method :
            mMethods) {
        //获取并输出方法的访问权限(Modifiers:修饰符)
        int modifiers = method.getModifiers();
        System.out.print(Modifier.toString(modifiers) + " ");
        //获取并输出方法的返回值类型
        Class returnType = method.getReturnType();
        System.out.print(returnType.getName() + " "
                + method.getName() + "( ");
        //获取并输出方法的所有参数
        Parameter[] parameters = method.getParameters();
        for (Parameter parameter:
             parameters) {
            System.out.print(parameter.getType().getName()
		            + " " + parameter.getName() + ",");
        }
        //获取并输出方法抛出的异常
        Class[] exceptionTypes = method.getExceptionTypes();
        if (exceptionTypes.length == 0){
            System.out.println(" )");
        }
        else {
            for (Class c : exceptionTypes) {
                System.out.println(" ) throws "
                        + c.getName());
            }
        }
    }
}

调用 getMethods() 方法
**获取 SonClass 类所有 public 访问权限的方法,包括从父类继承的。**打印信息中,printSonMsg() 方法来自 SonClass 类, printFatherMsg() 来自 FatherClass 类,其余方法来自 Object 类。

类的名称:obj.SonClass
public void printSonMsg(  )
public void printFatherMsg(  )
public final void wait(  ) throws java.lang.InterruptedException
public final void wait( long arg0,int arg1, ) throws java.lang.InterruptedException
public final native void wait( long arg0, ) throws java.lang.InterruptedException
public boolean equals( java.lang.Object arg0, )
public java.lang.String toString(  )
public native int hashCode(  )
public final native java.lang.Class getClass(  )
public final native void notify(  )
public final native void notifyAll(  )

访问以及操作类的私有变量及方法(重要)

通过反射我们可以访问类的私有方法,以及更改私有变量或常量(以final修饰)。

这是一个TestClass。

public class TestClass {
	
    private String MSG = "Original";

    private void privateMethod(String head , int tail){
        System.out.print(head + tail);
    }

    public String getMsg(){
        return MSG;
    }
}

访问私有方法

以访问 TestClass 类中的私有方法 privateMethod(...) 为例

/**
 * 访问对象的私有方法
 * 为简洁代码,在方法上抛出总的异常,实际开发别这样
 */
private static void getPrivateMethod() throws Exception{
    //1. 获取 Class 类实例
    TestClass testClass = new TestClass();
    Class mClass = testClass.getClass();
    
    //2. 获取私有方法
    //第一个参数为要获取的私有方法的名称
    //第二个为要获取方法的参数的类型,参数为 Class...,没有参数就是null
    //方法参数也可这么写 :new Class[]{String.class , int.class}
    Method privateMethod =
            mClass.getDeclaredMethod("privateMethod", String.class, int.class);
            
    //3. 开始操作方法
    if (privateMethod != null) {
        //获取私有方法的访问权
        //只是获取访问权,并不是修改实际权限
        privateMethod.setAccessible(true);
        
        //使用 invoke 反射调用私有方法
        //privateMethod 是获取到的私有方法
        //testClass 要操作的对象
        //后面两个参数传实参
        privateMethod.invoke(testClass, "Java Reflect ", 666);
    }
}

这里的重点就是privateMethod.setAccessible(true)方法与privateMethod.invoke方法,我们想要调用私有方法必须要要将这个方法通过setAccessible(true)来获取访问权。而我们想要调用时,就要使用invoke方法来调用,首先传入要操作的对象(就是这个类的实例),之后传入privateMethod的形参。

访问(更改)私有变量

/**
 * 修改对象私有变量的值
 * 为简洁代码,在方法上抛出总的异常
 */
private static void modifyPrivateFiled() throws Exception {
    //1. 获取 Class 类实例
    TestClass testClass = new TestClass();
    Class mClass = testClass.getClass();
    
    //2. 获取私有变量
    Field privateField = mClass.getDeclaredField("MSG");
    
    //3. 操作私有变量
    if (privateField != null) {
        //获取私有变量的访问权
        privateField.setAccessible(true);
        
        //修改私有变量,并输出以测试
        System.out.println("Before Modify:MSG = " + testClass.getMsg());
        
        //调用 set(object , value) 修改变量的值
        //privateField 是获取到的私有变量
        //testClass 要操作的对象
        //"Modified" 为要修改成的值
        privateField.set(testClass, "Modified");
        System.out.println("After Modify:MSG = " + testClass.getMsg());
    }
}

与访问私有方法类似,同样需要privateField.setAccessible(true),给我们的私有变量权限,之后通过set(object , value)修改私有变量的值。

Before Modify:MSG = Original
After Modify:MSG = Modified

修改私有常量

常规情况

私有常量就是用final修饰的成员变量。jvm会在编译.java文件得到.class文件时,会进行优化代码。简单来说会把引用常量的代码直接更改成具体的常量值。

编译前:

//注意是 String  类型的值
private final String FINAL_VALUE = "hello";

if(FINAL_VALUE.equals("world")){
    //do something
}

编译后:

private final String FINAL_VALUE = "hello";
//替换为"hello"
if("hello".equals("world")){
    //do something
}

因此我们可以知道即使我们通过反射更改了私有常量的值,更改后的值也不会对程序造成影响,因为jvm在编译阶段得到的.class文件时,已经将引用私有常量的位置都直接改成了私有常量的具体值。

为了验证结果,我们更改测试类

//String 会被 JVM 优化
private final String FINAL_VALUE = "FINAL";

public String getFinalValue(){
    //剧透,会被优化为: return "FINAL" ,拭目以待吧
    return FINAL_VALUE;
}

之后修改私有常量,其实代码与修改私有变量类似。

/**
 * 修改对象私有常量的值
 * 为简洁代码,在方法上抛出总的异常,实际开发别这样
 */
private static void modifyFinalFiled() throws Exception {
    //1. 获取 Class 类实例
    TestClass testClass = new TestClass();
    Class mClass = testClass.getClass();
    
    //2. 获取私有常量
    Field finalField = mClass.getDeclaredField("FINAL_VALUE");
    
    //3. 修改常量的值
    if (finalField != null) {
    
        //获取私有常量的访问权
        finalField.setAccessible(true);
        
        //调用 finalField 的 getter 方法
        //输出 FINAL_VALUE 修改前的值
        System.out.println("Before Modify:FINAL_VALUE = "
                + finalField.get(testClass));
        
        //修改私有常量
        finalField.set(testClass, "Modified");
        
        //调用 finalField 的 getter 方法
        //输出 FINAL_VALUE 修改后的值
        System.out.println("After Modify:FINAL_VALUE = "
                + finalField.get(testClass));
        
        //使用对象调用类的 getter 方法
        //获取值并输出
        System.out.println("Actually :FINAL_VALUE = "
                + testClass.getFinalValue());
    }
}

我们可以根据程序的结果看出,我们虽然已经更改了FINAL_VALUE,但在getFinalValue()中的结果仍然是之前的FINAL

Before Modify:FINAL_VALUE = FINAL
After Modify:FINAL_VALUE = Modified
Actually :FINAL_VALUE = FINAL

下图是编译后的.class文件。
在这里插入图片描述

非常规情况

java允许我们不在声明时赋值,可以在构造函数再赋值。

public class TestClass {

    //......
    private final String FINAL_VALUE;
    
    //构造函数内为常量赋值 
    public TestClass(){
        this.FINAL_VALUE = "FINAL";
    }
    //......
}

此时再运行结果就会得到想要的效果:

Before Modify:FINAL_VALUE = FINAL
After Modify:FINAL_VALUE = Modified
Actually :FINAL_VALUE = Modified

具体原因在于FINAL_VALUE不是声明时赋值而是构造函数后赋值,就会让jvm在.class中就不将类中使用该常量的位置直接更改为该常量的具体值,而是该常量的引用,因此就可以得到我们想要的效果。
在这里插入图片描述

泛型与反射的结合

当泛型类被类型擦除后,可以通过反射API(Type接口)来确定信息。
在这里插入图片描述

使用反射编写泛型数组

java.lang.reflect包中的Array允许动态的创建数组。例如CopyOf方法,可以将数组扩充至想要的长度。

  int[]num = new int[]{5,4,3,2};
  num = Arrays.copyOf(num,10);
  System.out.println(num.length);%10

我们希望将int[]num可以变成object[]num,以便更多的类型都可以使用该方法。但下述方法直接创建了一个Object的数组,之后就无法再转换成我们想要的int数组了。

public static Object[] badCopyOf(Object[]num,int length){
        Object[] xin = new Object[length];
        System.arraycopy(num,0,xin,0,length);
        return xin;
    }

正确的方法:核心思想就是将要扩充的数组比如int类型,暂时转换为Object类型,之后再转换回来是可以的。

我们可以通过反射获取到这个类的Class对象,并通过getComponentType获取数组的元素类型,之后我们通过newInstance(commpentType,length)创建Object对象。

这里使用Object对象而不是用Object数组是为了让比如int[]数组使用,因为int[]数组可以转换成Object,而不可以转成Object数组。

 public static Object goodCopyOf(Object num,int length){
        Class c1 = num.getClass();
        //获取数组的元素类型
        Class commpentType = c1.getComponentType();
        int oldlength = Array.getLength(num);
        Object newArray = Array.newInstance(commpentType,length);
        System.arraycopy(num,0,newArray,0,Math.min(length,oldlength));
        return newArray;
    }

泛型与反射的总结(复习必看)

1.泛型总结

1.泛型的最大好处是可以省去强制转换,也可以增加代码的清晰度。同时当执行add操作时就会报错,而原始的继承方法只会在get时出错。

2.泛型类:class A<T>
泛型方法: public T <T> A(T t)
泛型接口:inteface A<T> ,泛型接口在具体类实现接口时要具体的类型比如:class showTry implements show<Integer>

3.类型变量T可以有限定,比如当我们想调用类型变量的compare方法,就要求类型变量T所对应的那些类都实现了Comparable接口,虽然是接口,我们也使用extends关键词。<T extends Comparable>

4.类型擦除虚拟机中没有泛型类型对象,会对其进行类型擦除。如果只是类型变量T,则会将其擦除为Object,如果<T extends Comparable>,就会擦除为Comparabale。

//例子1
class Pair<T>{
    private T first;
    public Pair(T first) this.first = first;
    public T getFirst(){return first;}
    public void setFirst(T newvalue)first = newvalue;
}
class Pair{
    private Object first;
    public Pair(Object first) this.first = first;
    public Object getFirst(){return first;}
    public void setFirst(Object newvalue)first = newvalue;
}
//例子2
public static <T extends Comparable> T min(T[]a)
public static Comparable min(Comparable a)

5.类型擦除带来的影响
1)不能使用基本类型作为类型变量,而要使用其包装类。

List<int>list = new ArrayList<>();//error
List<Integer>list = new ArrayList<>();

2)不能使用instanceOfgetclass运行时类型查询仅适用于原始类型。虚拟机中的对象有一个特定的非泛型类型,因此所有的类查询都只会产生原始类型。

if (list instanceof ArrayList<Integer>)//error
ArrayList<String>()list1 = new ArrayList<>();
ArrayList<Integer>list2 = new ArrayList<>();
list1.getClass()==list2.getClass();//return true,都是ArrayList.class

3)不能创建参数化类型数组
虚拟机无法知道传入的数据类型究竟是什么。

List<Integer>[] li2 = new ArrayList<Integer>[10];//error
List<Boolean> li3 = new ArrayList<Boolean>[10];//error
List<List<Integer>>lists = new ArrayList;//ok

4)不能实例化类型变量

public Pair(){first = new T();second = new T();}//error

5)不能抛出或捕获泛型类的实例

//不能创建、捕捉或抛出参数化类型的对象
class MathException<T> extends Exception {}    // error
class QueueFullException<T> extends Throwable {} // error
public static <T extends Exception, J> void execute(List<J> jobs) {
    try {
    } catch (T e) {   // error
    }
}

6)不能重载参数类型为相同原始类型的方法
类型擦除后都是List list,分不清其中的数据类型。

public class Example { // error
    public void print(List<String> list) {}
    public void print(List<Integer> list) {}
}

6.通配符,可以使用通配符来让约束类型变量,<? extends A>,则要求传入的数据类型必须是A的子类,
? super A,则要求传入的数据类型必须是A的父类。

反射总结

1.反射的作用:可以通过反射在运行时动态的获取任意类的任意成员变量及任意方法,以及动态的进行访问任意成员变量及任意方法(包括private方法与private对象)当类中存在私有常量,反射也可以进行更改,但如果私有常量在声明时赋值,则无法对其相关的方法产生影响。(因为虚拟机会在编译时将.java文件得到.class文件时,会将静态常量的引用代码都直接更改为静态常量的具体数值)。

2.获取类的变量信息与获取类的方法信息类似。创建Class对象,并分别通过getFields()getMethods()方法可以获取到类的变量与方法。注意这里获取到的只有当前类及其父类的public方法与public的成员变量。

3.反射可以访问私有方法和私有对象。都需要通过privateMethod.setAccessible(true)获取访问权,对应私有方法就使用privateMethod.invoke(对象,形参)方法来调用privateMethod;对应私有对象就使用`privateField.set(对象, 要修改成的值);

4.修改私有常量的方法需要在构造函数中对私有常量赋值才可以,否则则无法对引用私有常量的方法起到作用。

5.由于泛型会被类型擦除,使用反射的type接口,可以来确定信息。同时,使用反射可以构建泛型数组。因为通过Class commpentType = c1.getComponentType();可以获取到数组的元素类型,进而创建相应的数组Object newArray =newInstance(commpentType,length)。只暂时让数组为Object,之后再转换类型num = (int)Arrays.goodcopyOf(num,10);

  • 0
    点赞
  • 0
    评论
  • 4
    收藏
  • 扫一扫,分享海报

参与评论
请先登录 后发表评论~
©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值