JavaSE基础复习---Class类与反射机制

---恢复内容开始---

目录:

  1、java.lang.class类

  2、Java中的反射机制

  3、运行时与编译时概念

1. java.lang.class类  

Java程序在运行时,Java运行时系统会一直对所有的对象进行所谓的运行时类型标识。这项信息纪录了每个对象所属的。虚拟机通常使用运行时类型信息选准正确方法去执行,用来保存这些类型信息的类是Class类。Class类封装一个对象和接口运行时的状态,当装载类时,Class类型的对象自动创建。

Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。虚拟机为每种类(型)管理一个独一无二的Class对象。也就是说,每个类(型)都有一个Class对象。运行程序时,Java虚拟机(JVM)首先检查是否所要加载的类对应的Class对象是否已经加载。如果没有加载,JVM就会根据类名查找.class文件,并将其Class对象载入。

基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也都对应一个 Class 对象。

每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。     

一般某个类的Class对象被载入内存,它就用来创建这个类的所有对象。

获取Class实例的三种方式:

       (1)利用对象调用getClass()方法获取该对象的Class实例。通过类的实例获取该类的字节码文件对象,该类处于创建对象阶段 

  (2)使用Class类的静态方法Class.forName(“类名”),用类的名字获取一个对应的Class实例。此时该类还是源文件阶段,并没有变为字节码文件。

  (3)运用类名.class的方式来获取Class实例,对于基本数据类型的封装类,还可以采用.TYPE来获取相对应的基本数据类型的Class实例。当类被加载成.class文件时,此时对应类变成了.class,在获取该字节码文件对象,也就是获取自己类自己,该类处于字节码阶段。

  在newInstance()调用类中缺省的构造方法; ObjectnewInstance()(可在不知该类的名字的时候,比如说已经某个类的实例实例),就能实例化一个由此Class对象所标识的对象。

  在运行期间,如果我们要产生某个类的对象,Java虚拟机(JVM)会检查该类型的Class对象是否已被加载。如果没有被加载,JVM会根据类的名称找到.class文件并加载它。一旦某个类型的Class对象已被加载到内存,就可以用它来产生该类型的所有对象。

下面是示例代码:

public calss ClassTest{

    public static void main(String[] args)throws Exception{

      String str1 = “abc”;

      Class c1 = str1.getclass();

      Class c2 = Class.forName(“Java.lang.String”);

      Class c3 = String.class;

      System.out.println(c1==c2 && c1==c3)’

    }

}

       运行结果是:true。上述例子描述了三种用于获取类的Class对象,事实证明,对于某一个类,JVM只会在内存中产生一份字节码,而这份字节码可以用来产生多个该类实例对象。

Class类的常用方法:

       1、getName()

  一个Class对象描述了一个特定类的属性,Class类中最常用的方法getName以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称

  2、newInstance()

  Class还有一个有用的方法可以为类创建一个实例,这个方法叫做newInstance()。例如:x.getClass.newInstance(),创建了一个同x一样类型的新实例。newInstance()方法调用默认构造器(无参数构造器)初始化新建对象。同时,如果一个类没有无参的构造函数,则要用以下方法创建实例:

  Class c1 = Class.forName(“Reflector.User”);

  Constructor constructor = c1.getConstructor(int.class,String.class);

  User user = (User)constructor.newInstance(12,”小明”);

       在上述代码中,假定User类没有无参的构造函数,则用Class的getConstructor()方法获取好该类的有参构造方法,同时参数列表可以再获取有参构造函数时进行指定。

3、getClassLoader()    

  返回该类的类加载器。

4、getComponentType()    

  返回表示数组组件类型的 Class。

5、getSuperclass()    

  返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的父类的 Class。

6、isArray()    

  判定此 Class 对象是否表示一个数组类。

Class的一些小使用技巧:

       1、forName和newInstance结合起来使用,可以根据存储在字符串中的类名创建对象。例如:Object obj = Class.forName(A).newInstance();也就创建了类名为s的类的一个实例对象,调用的是s的无参构造函数。

       注意,利用Object obj = Class.forName(s).newInstance();来创建A的对象时,等效于用父类引用指向子类实例,obj只能够访问父类子类均有的属性与方法,遵循多态准则,必要时可以将obj强制转换成s类型来使用。

    2、虚拟机为每种类型管理一个独一无二的Class对象。因此可以使用==操作符来比较类对象。例如:if(e.getClass() == Employee.class),可以判断,e是否为Employee的实例。

2.Java中的反射机制

       在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取的信息以及动态调用对象的方法的功能就称为java语言的反射机制。通俗点讲,通过反射,该类对我们来说是完全透明的,想要获取任何东西都可以。

       想要使用反射机制,就必须要先获取到该类的字节码文件对象(.class),通过字节码文件对象,就能够通过该类中的方法获取到我们想要的所有信息(方法,属性,类名,父类名,实现的所有接口等等),每一个类对应着一个字节码文件也就对应着一个Class类型的对象,也就是字节码文件对象。上面讲过,有三种方法可以获得该类的的Class实例。

通过反射机制可以获得哪些信息?

1.      获取全部构造方法:

下面是获取User类全部构造方法的代码:

 

2.      获取成员变量并使用Field对象

    获取指定成员变量

              获取全部成员变量

3.      获得方法并使用Method

Class.getMethod(String, Class...)和Class.getDeclaredMethod(String, Class...)方法,

以获取类中的指定方法,如果为私有方法,则需要打开一个权限:setAccessible(true);用invoke(Object, Object...)可以调用该方法。

       同样,也可以一次性获取所有的方法:

4.      获取该类实现的所有接口

Class[ ] cc = User.getInterfaces():确定此对象所表示的类或接口实现的接口

    返回值:接口对应的的字节码文件对象的数组

5.      获取指定资源的输入流

InputStream getResourceAsStream(String name)。return:一个 InputStream 对象;如果找不到带有该名称的资源,则返回 null。

参数:所需资源的名称,如果以"/"开始,则绝对资源名为"/"后面的一部分。

6.      动态代理(待补充)

反射机制的应用:

       1、利用反射,在泛型为int的ArrayList集合中存放一个String类型的对象。

          原理:集合中的泛型只在编译器有效,到了运行期间泛型失效。

 

  2、逆向代码 ,例如反编译

  3、与注解相结合的框架 例如Retrofit

  4、单纯的反射机制应用框架 例如EventBus 2.x

  5、动态生成类框架 例如Gson

 

反射机制的优缺点:

优点:

    运行期类型的判断,动态类加载,动态代理使用反射。

缺点:

    性能是一个问题,反射相当于一系列解释操作,通知jvm要做的事情,性能比直接的java代码要慢很多。

3.运行时与编译时概念

关于java运行时及编译时期的区别:首先我们要了解编译以及运行的概念:编译就是指,编译器帮你把源码翻译成机器能识别的字节码,编译时主要做的事情时检查语法以及优化代码。运行时就是指,代码被加载到内存中,并开始跑起来。

看如下代码:

public class Test{

  static final int a=10;

  static final int b=20;

  int c=10;

  int d=20;

  public static void main(String[] args){

    int num1=a*b;    // 第一行int

    num2=c*d;    //第二行

  }

}

可以思考下,第一行跟第二行在编译时期有什么区别?java编译时会做一些优化操作。第一行,因为是两个常量做运算,那么他们的结果就是确定的,即num1的值是确定的。所以在编译时,编译器就会直接算出num1的值。第二行则不会,java在运行时期才为变量分配内存空间。所以反编译后可以得到如下代码:

public static void main (String [] args){

  int num1=200;

  int num2=c*d;

}

由此,可以证码以上结论。

泛型,重写,重载分别时在运行时还是在编译时期执行?

1.我们知道,泛型的类型检测发生在编译时期,这也正是泛型的好处之一,可以提前暴露问题,而不是等到运行时出现ClassCastException。另外,在检测后编译器会把它重写成实际的对象类型(非泛型类型),这样就可以被JVM执行了,这个过程被称为泛型的擦除。        泛型的擦除关键在于从泛型类型中清楚泛型参数的相关信息,再在必要时添加类型转换和类型检查的方法(例如调用某个泛型方法时,就会执行类型检测)。       

泛型的擦除可以简单理解为,将泛型类型java代码转换成为普通代码。泛型擦除的主要过程如下:       

1).将所有泛型参数用最左边界(最顶级的父类型)的的类型替换       

2).移除所有类型参数

2.方法的重载,重载时在编译时期执行,因为在编译时期如果发生了方法的重载,那么在编译时必须明确具体方法是哪一个

3.方法的重写,重写发生在运行时期。编译时期只会检测父类中是否存在重写的方法,但是并不能明确具体子类的方法。只有在运行时,才知道父子类中到底哪个方法被调用了。这个也被称为运行时多态的体现

 

参考博文:

https://blog.csdn.net/qq_41907991/article/details/79795382?utm_source=copy

https://blog.csdn.net/u014082714/article/details/50004843

转载于:https://www.cnblogs.com/Chris-Zhan/p/9792142.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值