
- 外文名
- CLASS
- 一般组词
- CLASSLESS CLASSFUL
- 释 义
- 网络工程中的一种协议
- 所属学科
- 网络工程
-
2020-07-18 15:12:39
目录
1.什么是class对象
类是程序的一部分,每个类都有一个class对象。换言之,每当编写并且编译了一个新类,就会产生一个class对象(更恰当的说,是被保存在一个同名的class文件中)。为了生成这个类的对象,运行这个程序的Java虚拟机(jvm)将使用被称为“类加载器”的子系统。
所有的类都是在对其第一次使用的时候被加载到JVM中。如当程序创建对第一个静态成员的引用时,就会加载这个类。或者使用new关键字创建新的对象的时候。
因此java程序在它运行之前并非完全加载,其各个部分是在必须的时候才加载的。类加载器首先检查这个类的class对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找.class文件。
实际上在Java中每个类都有且只有一个Class对象。
Class 没有公共构造方法,因此不能显式地声明一个Class对象,Class 对象是在载入类时由Java 虚拟机以及通过调用类载入器中的 defineClass 方法自己主动构造的。
Class类被创建后的对象就是Class对象,注意,Class对象表示的是自己手动编写类的类型信息,比如创建一个Shapes类,那么,JVM就会创建一个Shapes对应Class类的Class对象,该Class对象保存了Shapes类相关的类型信息。实际上在Java中每个类都有一个Class对象,每当我们编写并且编译一个新创建的类就会产生一个对应Class对象并且这个Class对象会被保存在同名.class文件里(编译后的字节码文件保存的就是Class对象),那为什么需要这样一个Class对象呢?是这样的,当我们new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。需要特别注意的是,手动编写的每个class类,无论创建多少个实例对象,在JVM中都只有一个Class对象,即在内存中每个类有且只有一个相对应的Class对象,挺拗口,通过下图理解(内存中的简易现象图):
到这我们也就可以得出以下几点信息:
-
Class类也是类的一种,与class关键字是不一样的。
-
手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件),比如创建一个Shapes类,编译Shapes类后就会创建其包含Shapes类相关类型信息的Class对象,并保存在Shapes.class字节码文件中。
-
每个通过关键字class标识的类,在内存中有且只有一个与之对应的Class对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象。
-
Class类只存私有构造函数,因此对应Class对象只能有JVM创建和加载
-
Class类的对象作用是运行时提供或获得某个对象的类型信息,这点对于反射技术很重要(关于反射稍后分析)。
2.获得class对象的三种方法
1、调用Object类的getClass()方法来得到Class对象,这也是最常见的产生Class对象的方法。比如:
MyObject x; Class c1 = x.getClass();
.Object.getClass(); Object中自带的方法,getclass(),返回一个class对象。
2、使用Class类的中静态forName()方法获得与字符串相应的Class对象。比如:
Class c2=Class.forName("MyObject"),MyObject必须是接口或者类的名字。class.forname()
Class c=Class.forName("类的全限定名")
传入string类型参数,要求jvm查找并加载指定的类,返回的是一个class对象的引用。
3、获取Class类型对象的第三个方法很easy。假设T是一个Java类型。那么T.class就代表了匹配的类对象。
比如
Class cl1 = Manager.class; Class cl2 = int.class; Class cl3 = Double[].class;
注意:Class对象实际上描写叙述的仅仅是类型。而这类型未必是类或者接口。
比如上面的int.class是一个Class类型的对象。
因为历史原因。数组类型的getName方法会返回奇怪的名字。
3.class的作用和方法
- getname():以string类型返回class对象表示的实体(类,接口,数组,基本类型,void等)名称
- newInstance():创建一个实例,只能调用默认构造器。
- getsuperclass():返回class表示的实体超类的名称
- getSimpleName()。不办含包名的类名。
- isInterfence:告诉你这个class对象是否表示某个接口。
1、getName()
一个Class对象描写叙述了一个特定类的属性,Class类中最经常使用的方法getName以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。
2、newInstance()
Class另一个实用的方法能够为类创建一个实例,这种方法叫做newInstance()。比如:
x.getClass.newInstance(),创建了一个同x一样类型的新实例。newInstance()方法调用默认构造器(无參数构造器)初始化新建对象。3、getClassLoader()
返回该类的类载入器。
4、getComponentType()
返回表示数组组件类型的 Class。5、getSuperclass()
返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class。6、isArray()
判定此 Class 对象是否表示一个数组类。1、forName和newInstance结合起来使用,能够依据存储在字符串中的类名创建对象。比如
Object obj = Class.forName(s).newInstance();2、虚拟机为每种类型管理一个独一无二的Class对象。因此能够使用==操作符来比較类对象。比如:
if(e.getClass() == Employee.class)...4 Class.forName()用法
主要功能 Class.forName(xxx.xx.xx)返回的是一个类。 Class.forName(xxx.xx.xx)的作用是要求JVM查找并加载指定的类,也就是说JVM会执行该类的静态代码段。
Class.forName是一个静态方法,相同能够用来载入类。
该方法有两种形式:Class.forName(String name, boolean initialize, ClassLoader loader)和 Class.forName(String className)。
第一种形式的參数 name表示的是类的全名;initialize表示是否初始化类。loader表示载入时使用的类载入器。
另外一种形式则相当于设置了參数 initialize的值为 true。loader的值为当前类的类载入器
4.1 什么时候用Class.forName()?
先来个热身,给你一个字符串变量,它代表一个类的包名和类名,你怎么实例化它?你第一想到的肯定是new,但是注意一点:
A a = (A)Class.forName(“pacage.A”).newInstance();
这和你 A a = new A(); 是一样的效果。现在言归正传。
动态加载和创建Class 对象,比如想根据用户输入的字符串来创建对象时需要用到:- String str = “用户输入的字符串” ;
- Class t = Class.forName(str);
- t.newInstance();
在初始化一个类,生成一个实例的时候,newInstance()方法和new关键字除了一个是方法,一个是关键字外,最主要有什么区别?
它们的区别在于创建对象的方式不一样,前者是使用类加载机制,后者是创建一个新类。
4.2 newInstance和new关键字的区别
Java中工厂模式经常使用newInstance()方法来创建对象,因此从为什么要使用工厂模式上可以找到具体答案。 例如:
class c = Class.forName(“Example”); factory = (ExampleInterface)c.newInstance();
其中ExampleInterface是Example的接口,可以写成如下形式:
String className = “Example”; class c = Class.forName(className); factory = (ExampleInterface)c.newInstance();
进一步可以写成如下形式:
String className = readfromXMlConfig;//从xml 配置文件中获得字符串 class c = Class.forName(className); factory = (ExampleInterface)c.newInstance();
上面代码已经不存在Example的类名称,它的优点是,无论Example类怎么变化,上述代码不变,甚至可以更换Example的兄弟类Example2 , Example3 , Example4……,只要他们继承ExampleInterface就可以。
从JVM的角度看,我们使用关键字new创建一个类的时候,这个类可以没有被加载。但是使用newInstance()方法的时候,就必须保证:
1、这个类已经加载;
2、这个类已经连接了。
而完成上面两个步骤的正是Class的静态方法forName()所完成的,这个静态方法调用了启动类加载器,即加载 java API的那个加载器。现在可以看出,newInstance()实际上是把new这个方式分解为两步,即首先调用Class加载方法加载某个类,然后实例化。
这样分步的好处是显而易见的。我们可以在调用class的静态加载方法forName时获得更好的灵活性,提供给了一种降耦的手段。
最后用最简单的描述来区分new关键字和newInstance()方法的区别:
- newInstance: 弱类型。低效率。只能调用无参构造。
- new: 强类型。相对高效。能调用任何public构造。
- Class.forName(“”)返回的是类。
- Class.forName(“”).newInstance()返回的是object
5 应用问题解析
情景一:载入数据库驱动的时候
Class.forName的一个非经常见的使用方法是在载入数据库驱动的时候
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); Connection con=DriverManager.getConnection("jdbc:sqlserver://localhost:1433;DatabaseName==JSP","jph","jph");
为什么在我们载入数据库驱动包的时候有的却没有调用newInstance( )方法呢?
即有的jdbc连接数据库的写法里是Class.forName(xxx.xx.xx);而有一些:Class.forName(xxx.xx.xx).newInstance()。为什么会有这两种写法呢?
通过查询Java Documentation我们会发现使用Class.forName( )静态方法的目的是为了动态加载类。通常编码过程中,在加载完成后,一般还要调用Class下的newInstance( )静态方法来实例化对象以便操作。因此,单使用Class.forName( )是动态加载类是没有用的,其最终目的是为了实例化对象。
刚才提到,Class.forName("");的作用是要求JVM查找并加载指定的类,如果在类中有静态初始化器的话,JVM必然会执行该类的静态代码 段。而在JDBC规范中明确要求这个Driver类必须向DriverManager注册自己,即任何一个JDBC Driver的 Driver类的代码都必须类似如下:
public class MyJDBCDriver implements Driver { static { DriverManager.registerDriver(new MyJDBCDriver()); } }
既然在静态初始化器的中已经进行了注册,所以我们在使用JDBC时只需要Class.forName(XXX.XXX);就可以了。
既然在静态初始化器的中已经进行了注册,所以我们在使用JDBC时只需要Class.forName(XXX.XXX);就可以了。
public class ProxoolDriver implements Driver { private static final Log LOG = LogFactory.getLog(ProxoolDriver.class); static { try { DriverManager.registerDriver(new ProxoolDriver()); } catch (SQLException e) { System.out.println(e.toString()); } } }
情景二:使用AIDL与电话管理Servic进行通信
Method method =Class.forName("android.os.ServiceManager") .getMethod("getService",String.class); // 获取远程TELEPHONY_SERVICE的IBinder对象的代理 IBinder binder =(IBinder) method.invoke(null, new Object[] { TELEPHONY_SERVICE}); // 将IBinder对象的代理转换为ITelephony对象 ITelephonytelephony = ITelephony.Stub.asInterface(binder); // 挂断电话 telephony.endCall();
更多相关内容 -
-
Eclipse中查看没有源码的Class文件的方法
2014-01-26 17:18:11对于存在源代码的类,它不会强行反编译,也就是说它还是会用eclipse自带的Class File Viewer查看class文件,手动设置Window->Preferences->Java->JadClipse,把Ignore existing source选中,就不会管有没有源码,都会反... -
kotlin 中::class 、class.java、javaClass、javaClass.kotlin区别
2020-04-05 15:37:55class Person { var name: String = "" var age: Int = 0 companion object { } } java类 public class Man { } 要知道获取的到底是什么,可以利用andorid stuido的类型提示,一目了然。 ....Kotlin的类
class Person { var name: String = "" var age: Int = 0 companion object { } }
java类public class Man { }
要知道获取的到底是什么,可以利用andorid stuido的类型提示,一目了然。
第二张:
引申
在Java中使用Class很常见的就是,xxx类.class,比如我们在startActivity的时候
startActivity(new Intent(this, OtherActivity.class));
这里接收的就是CLass<?> cls参数。
那么在java中获取Class的方法有哪些呢?1、Class c = person.getClass(); //对象获取 2、Class cc =Person.class;//类获取
而我们来看看kotlin
//对象获取 person.javaClass// javaClass person::class.java // javaClass //类获取 Person::class// kClass person.javaClass.kotlin// kClass (Person::class as Any).javaClass// javaClass Person::class.java // javaClass
哇,这么多种,他们是不是一样的,有没有什么区别?
log看看他们到底是不是相同的Classprintln(person.javaClass == person::class.java) //true println(person.javaClass == Person::class.java)//true println(person::class.java == Person::class.java)//true //person.javaClass == person::class.java == Person::class.java println(person.javaClass == Person::class)//false println(person.javaClass.kotlin == Person::class)//true println(person::class == Person::class)//true
从log来看,
person.javaClass == person::class.java == Person::class.java
三者是相同的。但是person.javaClass == Person::class
却是不同的。为什么呢?原因是在kotlin中的Class与Java不同,kotlin中有一个自己的Class叫做KClass,
person::class
和Person::class
都是获取kotlin的KClass,所以println(person::class == Person::class)
为true。
我们可以从kotlin的KClass获取到java的Class,person::class.java
就是如此,先获取到kotlin的KClass然后再获取javaClass。
object/class->kClass->Class
同样也可以通过java的Class获取kotlin的KClass,person.javaClass.kotlin
就是先获取javaClass然后再获取kotlin的KClass
object/class->Class->KClass
那么KClass都有些什么用呢?Find Usages 可以看到
几乎多数跟Reflect相关,而用的最多的也是在KClasses里面
KClasses扩展了许多跟反射相关的方法,算的上是kotlin的反射中类主力输出。
如果要使用kotlin的反射类的话,要加入compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
举个例子
我们要把一个类的所有字段通过反射给打印出来,调用java的方法来实现是这样
println (Person::class.java.declaredFields.map { it.isAccessible = true "${it.name}: ${it.get(person)}" }.joinToString(","))
我们通过Person::class拿到KClass,然后.java拿到java的Class<?>,再获取declaredFields,最后通过map,然后把获取的Field获取到值打印出来
问题
而使用kotlin,我们还有别的做法
println (Person::class.memberProperties.map { it.isAccessible = true "${it.name}: ${it.get(person)}" }.joinToString(","))
通过Person::class拿到KClass,直接调用KClass的memberProperties来拿到KProperty的Collection集合,然后进行操作。当然,这里KProperty也是kotlin的反射类中的,也类似于Java的Field。
那么既然可以这样,理论上这样也应该是可以的//person是对象不是Person类 println (person::class.memberProperties.map { it.isAccessible = true "${it.name}: ${it.get(person)}" }.joinToString(","))
这下却出错了,真的奇怪!
Error:(73, 31) Out-projected type 'KProperty1<out OtherActivity.Person, Any?>' prohibits the use of 'public abstract fun get(receiver: T): R defined in kotlin.reflect.KProperty1'
为什么会这样?
查看it发现
kotlin的property的Person为out逆变的,R只能作为输出,不能作为get的参数传入,所报错了。
这里的kotlin泛型还是有点小坑的需要你踩一踩,网上有一篇文章解释说明了一番那么怎么改呢?三种办法:
第一种:
//扩展KProperty的get方法为getUnsafed,其实就是去掉了out fun <T, R> KProperty1<T, R>.getUnsafed(receiver: Any): R { return get(receiver as T) } //然后 println(person::class.memberProperties.map { it.isAccessible = true "${it.name}: ${it.getUnsafed(person)}" }.joinToString(","))
第二种:
//强转一下 println(person::class.memberProperties.map { it.isAccessible = true it as KProperty1<Person, Any> "${it.name}: ${it.get(person)}" }.joinToString(","))
第三种:
//这一种就涉及到kotlin中的获取KClass的方式, 先获取java的Class再获取kotlin的KClass //神奇的是,这种获取到的it的类型没有out,而是我们期望的 KProperty1<Person, Any> println(person.javaClass.kotlin.memberProperties.map { it.isAccessible = true "${it.name}: ${it.get(person)}" }.joinToString(","))
摘自:链接:https://www.jianshu.com/p/a900ee71ae7f
-
Java中class与Class的区别
2020-07-16 10:49:44一.class与Class区别 class是Java中的关键字,如public class Xxx 或者 class Xxx ,在声明Java类时使用。 而Class是一个类。 我们通常认为类是对象的抽象和集合,Class就相当于是对类的抽象和集合。 也可以认为对象...目录
一.class与Class区别
class是Java中的关键字,如public class Xxx 或者 class Xxx ,在声明Java类时使用。
而Class是一个类。
我们通常认为类是对象的抽象和集合,Class就相当于是对类的抽象和集合。
也可以认为对象是类的实例,类是Class的实例。下图是对象在堆中的结构图:
对象的几个部分的作用:1.对象头中的Mark Word(标记字)主要用来表示对象的线程锁状态,另外还可以用来配合GC、存放该对象的hashCode;
2.Klass Word是一个指向方法区中Class信息的指针,意味着该对象可随时知道自己是哪个Class的实例;
3.数组长度也是占用64位(8字节)的空间,这是可选的,只有当本对象是一个数组对象时才会有这个部分;
4.对象体是用于保存对象属性和值的主体部分,占用内存空间取决于对象的属性数量和类型;
5.JVM规定了对象地址为8的整数倍,这个地方可能需要字节进行对齐
其中Klass Word指向的就是Java中对象使用到的类,类的Class对象,JVM通过这个指针确定对象是哪个类的实例。该指针的位长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。
二.Class介绍
Class是一个类。如下图,它在java.lang包中。
它的构造函数是private属性,所以我们不能直接new一个Class对象出来,如下图注释段所说:
“私有构造函数。只有Java虚拟机创建类对象。不使用此构造函数,并阻止生成默认构造函数。”
三.如何得到Class对象
1.通过getClass()方法获取到Class对象
getClass()方法是Object类的一部分,如下图:
如果我们已经创建了某个类型的对象,那么我们可以通过getClass()方法来获取该类型的Class的对象:package Task; import org.junit.Test; public class Try0 { @Test public void toTry() throws ClassNotFoundException { // //forName方法:参数为其类的路径 // Class a = Class.forName("Task.Try1"); // System.out.println(a); //通过对象得到类 Try1 try1 = new Try1(); Class b = try1.getClass(); System.out.println(b); } } class Try1{ }
测试运行结果:
2.通过forName()方法获取到Class对象
Class.forName方法是Class类的一个静态方法,如下图:
所以可以直接通过Class.forName(“类的路径”)获取Class对象:package Task; import org.junit.Test; public class Try0 { @Test public void toTry() throws ClassNotFoundException { //forName方法:参数为其类的路径 Class a = Class.forName("Task.Try1"); System.out.println(a); } } class Try1{ }
测试运行截图:
3.类.class获得Class对象(类字面常量)
package Task; import org.junit.Test; public class Try0 { @Test public void toTry() throws ClassNotFoundException { // //forName方法:参数为其类的路径 // Class a = Class.forName("Task.Try1"); // System.out.println(a); // //通过对象得到类 // Try1 try1 = new Try1(); // Class b = try1.getClass(); // System.out.println(b); //类字面常量 Class c = Try1.class; System.out.println(c); } } class Try1{ }
测试运行结果:
四.Class常用方法
package Task; import org.junit.Test; public class Try0 { @Test public void toTry() throws ClassNotFoundException { // //forName方法:参数为其类的路径 // Class a = Class.forName("Task.Try1"); // System.out.println(a); //通过对象得到类 Try1 try1 = new Try1(); Class b = try1.getClass(); System.out.println(b); // //类字面常量 // Class c = Try1.class; // System.out.println(c); //isInterface方法,判断是否为接口 System.out.println(b.isInterface()); //isArray方法,判断是否为数组 System.out.println(b.isArray()); //isPrimitive方法,判断是否是基本类型,例如int是基本类型,Integer为包装类 System.out.println(b.isPrimitive()); //isAnnotation方法,判断是否为注解,注解如常见的重写注解@Override,我们所用的单元测试@Test注解 System.out.println(b.isAnnotation()); //isInstance方法参数是一个对象,判断该对象try1对应的类Try1是否是b的对象 System.out.println(b.isInstance(try1)); //getClassLoader方法,获取类加载器 System.out.println(b.getClassLoader()); //getSuperclass方法,获取父类信息 System.out.println(b.getSuperclass()); //getGenericSuperclass方法,也是获取父类信息 System.out.println(b.getGenericSuperclass()); //getComponentType方法,如果b的类型是数组,则获取数组里元素的类型 System.out.println(b.getComponentType()); //getDeclaredClasses方法,获取b继承于哪个类 System.out.println(b.getDeclaredClasses()); //几个获取name的方法: //getName方法 System.out.println(b.getName()); //getTypeName方法 System.out.println(b.getTypeName()); //getCanonicalName方法 System.out.println(b.getCanonicalName()); //getSimpleName方法 System.out.println(b.getSimpleName()); // 以下还有一些方法只列举出来 // getTypeParameters方法,获取泛型类中的泛型参数数组 // getClasses方法,获取Class对象中public修饰的内部类 // getDeclaredClasses方法,获取Class对象中的内部类,继承成员不包含在内 // getConstructors方法,获取public修饰的构造函数 // getConstructor方法,查找对应的构造函数 // getDeclaredConstructors方法,获取Class对象中的构造函数 } } class Try1 { }
输出截图如下:
五.Java反射机制
参考本人另一篇博客:Java反射机制(初步认识)
-
深入理解Java类型信息(Class对象)与反射机制
2017-05-01 23:19:19【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) ...深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)
http://blog.csdn.net/javazejian/article/details/70768369
出自【zejian的博客】关联文章:
本篇主要是深入对Java中的Class对象进行分析,这对后续深入理解反射技术非常重要,主要内容如下:
深入理解Class对象
RRTI的概念以及Class对象作用
认识Class对象之前,先来了解一个概念,RTTI(Run-Time Type Identification)运行时类型识别,对于这个词一直是 C++ 中的概念,至于Java中出现RRTI的说法则是源于《Thinking in Java》一书,其作用是在运行时识别一个对象的类型和类的信息,这里分两种:传统的”RRTI”,它假定我们在编译期已知道了所有类型(在没有反射机制创建和使用类对象时,一般都是编译期已确定其类型,如new对象时该类必须已定义好),另外一种是反射机制,它允许我们在运行时发现和使用类型的信息。在Java中用来表示运行时类型信息的对应类就是Class类,Class类也是一个实实在在的类,存在于JDK的java.lang包中,其部分源码如下:
public final class Class<T> implements java.io.Serializable,GenericDeclaration,Type, AnnotatedElement { private static final int ANNOTATION= 0x00002000; private static final int ENUM = 0x00004000; private static final int SYNTHETIC = 0x00001000; private static native void registerNatives(); static { registerNatives(); } /* * Private constructor. Only the Java Virtual Machine creates Class objects.(私有构造,只能由JVM创建该类) * This constructor is not used and prevents the default constructor being * generated. */ private Class(ClassLoader loader) { // Initialize final field for classLoader. The initialization value of non-null // prevents future JIT optimizations from assuming this final field is null. classLoader = loader; }
Class类被创建后的对象就是Class对象,注意,Class对象表示的是自己手动编写类的类型信息,比如创建一个Shapes类,那么,JVM就会创建一个Shapes对应Class类的Class对象,该Class对象保存了Shapes类相关的类型信息。实际上在Java中每个类都有一个Class对象,每当我们编写并且编译一个新创建的类就会产生一个对应Class对象并且这个Class对象会被保存在同名.class文件里(编译后的字节码文件保存的就是Class对象),那为什么需要这样一个Class对象呢?是这样的,当我们new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。需要特别注意的是,手动编写的每个class类,无论创建多少个实例对象,在JVM中都只有一个Class对象,即在内存中每个类有且只有一个相对应的Class对象,挺拗口,通过下图理解(内存中的简易现象图):
到这我们也就可以得出以下几点信息:
Class类也是类的一种,与class关键字是不一样的。
手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件),比如创建一个Shapes类,编译Shapes类后就会创建其包含Shapes类相关类型信息的Class对象,并保存在Shapes.class字节码文件中。
每个通过关键字class标识的类,在内存中有且只有一个与之对应的Class对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象。
Class类只存私有构造函数,因此对应Class对象只能有JVM创建和加载
Class类的对象作用是运行时提供或获得某个对象的类型信息,这点对于反射技术很重要(关于反射稍后分析)。
Class对象的加载及其获取方式
Class对象的加载
前面我们已提到过,Class对象是由JVM加载的,那么其加载时机是?实际上所有的类都是在对其第一次使用时动态加载到JVM中的,当程序创建第一个对类的静态成员引用时,就会加载这个被使用的类(实际上加载的就是这个类的字节码文件),注意,使用new操作符创建类的新实例对象也会被当作对类的静态成员的引用(构造函数也是类的静态方法),由此看来Java程序在它们开始运行之前并非被完全加载到内存的,其各个部分是按需加载,所以在使用该类时,类加载器首先会检查这个类的Class对象是否已被加载(类的实例对象创建时依据Class对象中类型信息完成的),如果还没有加载,默认的类加载器就会先根据类名查找.class文件(编译后Class对象被保存在同名的.class文件中),在这个类的字节码文件被加载时,它们必须接受相关验证,以确保其没有被破坏并且不包含不良Java代码(这是java的安全机制检测),完全没有问题后就会被动态加载到内存中,此时相当于Class对象也就被载入内存了(毕竟.class字节码文件保存的就是Class对象),同时也就可以被用来创建这个类的所有实例对象。下面通过一个简单例子来说明Class对象被加载的时机问题(例子引用自Thinking in Java):
package com.zejian; class Candy { static { System.out.println("Loading Candy"); } } class Gum { static { System.out.println("Loading Gum"); } } class Cookie { static { System.out.println("Loading Cookie"); } } public class SweetShop { public static void print(Object obj) { System.out.println(obj); } public static void main(String[] args) { print("inside main"); new Candy(); print("After creating Candy"); try { Class.forName("com.zejian.Gum"); } catch(ClassNotFoundException e) { print("Couldn't find Gum"); } print("After Class.forName(\"com.zejian.Gum\")"); new Cookie(); print("After creating Cookie"); } }
在上述代码中,每个类Candy、Gum、Cookie都存在一个static语句,这个语句会在类第一次被加载时执行,这个语句的作用就是告诉我们该类在什么时候被加载,执行结果:
inside main Loading Candy After creating Candy Loading Gum After Class.forName("com.zejian.Gum") Loading Cookie After creating Cookie Process finished with exit code 0
从结果来看,new一个Candy对象和Cookie对象,构造函数将被调用,属于静态方法的引用,Candy类的Class对象和Cookie的Class对象肯定会被加载,毕竟Candy实例对象的创建依据其Class对象。比较有意思的是
Class.forName("com.zejian.Gum");
其中forName方法是Class类的一个static成员方法,记住所有的Class对象都源于这个Class类,因此Class类中定义的方法将适应所有Class对象。这里通过forName方法,我们可以获取到Gum类对应的Class对象引用。从打印结果来看,调用forName方法将会导致Gum类被加载(前提是Gum类从来没有被加载过)。
Class.forName方法
通过上述的案例,我们也就知道Class.forName()方法的调用将会返回一个对应类的Class对象,因此如果我们想获取一个类的运行时类型信息并加以使用时,可以调用Class.forName()方法获取Class对象的引用,这样做的好处是无需通过持有该类的实例对象引用而去获取Class对象,如下的第2种方式是通过一个实例对象获取一个类的Class对象,其中的getClass()是从顶级类Object继承而来的,它将返回表示该对象的实际类型的Class对象引用。
public static void main(String[] args) { try{ //通过Class.forName获取Gum类的Class对象 Class clazz=Class.forName("com.zejian.Gum"); System.out.println("forName=clazz:"+clazz.getName()); }catch (ClassNotFoundException e){ e.printStackTrace(); } //通过实例对象获取Gum的Class对象 Gum gum = new Gum(); Class clazz2=gum.getClass(); System.out.println("new=clazz2:"+clazz2.getName()); }
注意调用forName方法时需要捕获一个名称为ClassNotFoundException的异常,因为forName方法在编译器是无法检测到其传递的字符串对应的类是否存在的,只能在程序运行时进行检查,如果不存在就会抛出ClassNotFoundException异常。
Class字面常量
在Java中存在另一种方式来生成Class对象的引用,它就是Class字面常量,如下:
//字面常量的方式获取Class对象 Class clazz = Gum.class;
这种方式相对前面两种方法更加简单,更安全。因为它在编译器就会受到编译器的检查同时由于无需调用forName方法效率也会更高,因为通过字面量的方法获取Class对象的引用不会自动初始化该类。更加有趣的是字面常量的获取Class对象引用方式不仅可以应用于普通的类,也可以应用用接口,数组以及基本数据类型,这点在反射技术应用传递参数时很有帮助,关于反射技术稍后会分析,由于基本数据类型还有对应的基本包装类型,其包装类型有一个标准字段TYPE,而这个TYPE就是一个引用,指向基本数据类型的Class对象,其等价转换如下,一般情况下更倾向使用.class的形式,这样可以保持与普通类的形式统一。
boolean.class = Boolean.TYPE; char.class = Character.TYPE; byte.class = Byte.TYPE; short.class = Short.TYPE; int.class = Integer.TYPE; long.class = Long.TYPE; float.class = Float.TYPE; double.class = Double.TYPE; void.class = Void.TYPE;
前面提到过,使用字面常量的方式获取Class对象的引用不会触发类的初始化,这里我们可能需要简单了解一下类加载的过程,如下:
加载:类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象
链接:验证字节码的安全性和完整性,准备阶段正式为静态域分配存储空间,注意此时只是分配静态成员变量的存储空间,不包含实例成员变量,如果必要的话,解析这个类创建的对其他类的所有引用。
初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量。
由此可知,我们获取字面常量的Class引用时,触发的应该是加载阶段,因为在这个阶段Class对象已创建完成,获取其引用并不困难,而无需触发类的最后阶段初始化。下面通过小例子来验证这个过程:
import java.util.*; class Initable { //编译期静态常量 static final int staticFinal = 47; //非编期静态常量 static final int staticFinal2 = ClassInitialization.rand.nextInt(1000); static { System.out.println("Initializing Initable"); } } class Initable2 { //静态成员变量 static int staticNonFinal = 147; static { System.out.println("Initializing Initable2"); } } class Initable3 { //静态成员变量 static int staticNonFinal = 74; static { System.out.println("Initializing Initable3"); } } public class ClassInitialization { public static Random rand = new Random(47); public static void main(String[] args) throws Exception { //字面常量获取方式获取Class对象 Class initable = Initable.class; System.out.println("After creating Initable ref"); //不触发类初始化 System.out.println(Initable.staticFinal); //会触发类初始化 System.out.println(Initable.staticFinal2); //会触发类初始化 System.out.println(Initable2.staticNonFinal); //forName方法获取Class对象 Class initable3 = Class.forName("Initable3"); System.out.println("After creating Initable3 ref"); System.out.println(Initable3.staticNonFinal); } }
执行结果:
After creating Initable ref 47 Initializing Initable 258 Initializing Initable2 147 Initializing Initable3 After creating Initable3 ref 74
从输出结果来看,可以发现,通过字面常量获取方式获取Initable类的Class对象并没有触发Initable类的初始化,这点也验证了前面的分析,同时发现调用
Initable.staticFinal
变量时也没有触发初始化,这是因为staticFinal属于编译期静态常量,在编译阶段通过常量传播优化的方式将Initable类的常量staticFinal
存储到了一个称为NotInitialization类的常量池中,在以后对Initable类常量staticFinal
的引用实际都转化为对NotInitialization类对自身常量池的引用,所以在编译期后,对编译期常量的引用都将在NotInitialization类的常量池获取,这也就是引用编译期静态常量不会触发Initable类初始化的重要原因。但在之后调用了Initable.staticFinal2
变量后就触发了Initable类的初始化,注意staticFinal2虽然被static和final修饰,但其值在编译期并不能确定,因此staticFinal2并不是编译期常量,使用该变量必须先初始化Initable类。Initable2和Initable3类中都是静态成员变量并非编译期常量,引用都会触发初始化。至于forName方法获取Class对象,肯定会触发初始化,这点在前面已分析过。到这几种获取Class对象的方式也都分析完,ok~,到此这里可以得出小结论:获取Class对象引用的方式3种,通过继承自Object类的getClass方法,Class类的静态方法forName以及字面常量的方式”.class”。
其中实例类的getClass方法和Class类的静态方法forName都将会触发类的初始化阶段,而字面常量获取Class对象的方式则不会触发初始化。
初始化是类加载的最后一个阶段,也就是说完成这个阶段后类也就加载到内存中(Class对象在加载阶段已被创建),此时可以对类进行各种必要的操作了(如new对象,调用静态成员等),注意在这个阶段,才真正开始执行类中定义的Java程序代码或者字节码。
关于类加载的初始化阶段,在虚拟机规范严格规定了有且只有5种场景必须对类进行初始化:
使用new关键字实例化对象时、读取或者设置一个类的静态字段(不包含编译期常量)以及调用静态方法的时候,必须触发类加载的初始化过程(类加载过程最终阶段)。
使用反射包(java.lang.reflect)的方法对类进行反射调用时,如果类还没有被初始化,则需先进行初始化,这点对反射很重要。
当初始化一个类的时候,如果其父类还没进行初始化则需先触发其父类的初始化。
当Java虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机会先初始化这个主类
当使用JDK 1.7 的动态语言支持时,如果一个java.lang.invoke.MethodHandle 实例最后解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应类没有初始化时,必须触发其初始化(这点看不懂就算了,这是1.7的新增的动态语言支持,其关键特征是它的类型检查的主体过程是在运行期而不是编译期进行的,这是一个比较大点的话题,这里暂且打住)
理解泛化的Class对象引用
由于Class的引用总数指向某个类的Class对象,利用Class对象可以创建实例类,这也就足以说明Class对象的引用指向的对象确切的类型。在Java SE5引入泛型后,使用我们可以利用泛型来表示Class对象更具体的类型,即使在运行期间会被擦除,但编译期足以确保我们使用正确的对象类型。如下:
/** * Created by zejian on 2017/4/30. * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创] */ public class ClazzDemo { public static void main(String[] args){ //没有泛型 Class intClass = int.class; //带泛型的Class对象 Class<Integer> integerClass = int.class; integerClass = Integer.class; //没有泛型的约束,可以随意赋值 intClass= double.class; //编译期错误,无法编译通过 //integerClass = double.class } }
从代码可以看出,声明普通的Class对象,在编译器并不会检查Class对象的确切类型是否符合要求,如果存在错误只有在运行时才得以暴露出来。但是通过泛型声明指明类型的Class对象,编译器在编译期将对带泛型的类进行额外的类型检查,确保在编译期就能保证类型的正确性,实际上
Integer.class
就是一个Class<Integer>
类的对象。面对下述语句,确实可能令人困惑,但该语句确实是无法编译通过的。//编译无法通过 Class<Number> numberClass=Integer.class;
我们或许会想Integer不就是Number的子类吗?然而事实并非这般简单,毕竟Integer的Class对象并非Number的Class对象的子类,前面提到过,所有的Class对象都只来源于Class类,看来事实确实如此。当然我们可以利用通配符“?”来解决问题:
Class<?> intClass = int.class; intClass = double.class;
这样的语句并没有什么问题,毕竟通配符指明所有类型都适用,那么为什么不直接使用Class还要使用
Class<?>
呢?这样做的好处是告诉编译器,我们是确实是采用任意类型的泛型,而非忘记使用泛型约束,因此Class<?>
总是优于直接使用Class,至少前者在编译器检查时不会产生警告信息。当然我们还可以使用extends关键字告诉编译器接收某个类型的子类,如解决前面Number与Integer的问题://编译通过! Class<? extends Number> clazz = Integer.class; //赋予其他类型 clazz = double.class; clazz = Number.class;
上述的代码是行得通的,extends关键字的作用是告诉编译器,只要是Number的子类都可以赋值。这点与前面直接使用
Class<Number>
是不一样的。实际上,应该时刻记住向Class引用添加泛型约束仅仅是为了提供编译期类型的检查从而避免将错误延续到运行时期。关于类型转换的问题
在许多需要强制类型转换的场景,我们更多的做法是直接强制转换类型:
package com.zejian; /** * Created by zejian on 2017/4/30. * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创] */ public class ClassCast { public void cast(){ Animal animal= new Dog(); //强制转换 Dog dog = (Dog) animal; } } interface Animal{ } class Dog implements Animal{ }
之所可以强制转换,这得归功于RRTI,要知道在Java中,所有类型转换都是在运行时进行正确性检查的,利用RRTI进行判断类型是否正确从而确保强制转换的完成,如果类型转换失败,将会抛出类型转换异常。除了强制转换外,在Java SE5中新增一种使用Class对象进行类型转换的方式,如下:
Animal animal= new Dog(); //这两句等同于Dog dog = (Dog) animal; Class<Dog> dogType = Dog.class; Dog dog = dogType.cast(animal)
利用Class对象的cast方法,其参数接收一个参数对象并将其转换为Class引用的类型。这种方式似乎比之前的强制转换更麻烦些,确实如此,而且当类型不能正确转换时,仍然会抛出ClassCastException异常。源码如下:
public T cast(Object obj) { if (obj != null && !isInstance(obj)) throw new ClassCastException(cannotCastMsg(obj)); return (T) obj; }
instanceof 关键字与isInstance方法
关于instanceof 关键字,它返回一个boolean类型的值,意在告诉我们对象是不是某个特定的类型实例。如下,在强制转换前利用instanceof检测obj是不是Animal类型的实例对象,如果返回true再进行类型转换,这样可以避免抛出类型转换的异常(ClassCastException)
public void cast2(Object obj){ if(obj instanceof Animal){ Animal animal= (Animal) obj; } }
而isInstance方法则是Class类中的一个Native方法,也是用于判断对象类型的,看个简单例子:
public void cast2(Object obj){ //instanceof关键字 if(obj instanceof Animal){ Animal animal= (Animal) obj; } //isInstance方法 if(Animal.class.isInstance(obj)){ Animal animal= (Animal) obj; } }
事实上instanceOf 与isInstance方法产生的结果是相同的。对于instanceOf是关键字只被用于对象引用变量,检查左边对象是不是右边类或接口的实例化。如果被测对象是null值,则测试结果总是false。一般形式:
//判断这个对象是不是这种类型 obj.instanceof(class)
而isInstance方法则是Class类的Native方法,其中obj是被测试的对象或者变量,如果obj是调用这个方法的class或接口的实例,则返回true。如果被检测的对象是null或者基本类型,那么返回值是false;一般形式如下:
//判断这个对象能不能被转化为这个类 class.inInstance(obj)
最后这里给出一个简单实例,验证isInstance方法与instanceof等价性:
class A {} class B extends A {} public class C { static void test(Object x) { print("Testing x of type " + x.getClass()); print("x instanceof A " + (x instanceof A)); print("x instanceof B "+ (x instanceof B)); print("A.isInstance(x) "+ A.class.isInstance(x)); print("B.isInstance(x) " + B.class.isInstance(x)); print("x.getClass() == A.class " + (x.getClass() == A.class)); print("x.getClass() == B.class " + (x.getClass() == B.class)); print("x.getClass().equals(A.class)) "+ (x.getClass().equals(A.class))); print("x.getClass().equals(B.class)) " + (x.getClass().equals(B.class))); } public static void main(String[] args) { test(new A()); test(new B()); } }
执行结果:
Testing x of type class com.zejian.A x instanceof A true x instanceof B false //父类不一定是子类的某个类型 A.isInstance(x) true B.isInstance(x) false x.getClass() == A.class true x.getClass() == B.class false x.getClass().equals(A.class)) true x.getClass().equals(B.class)) false --------------------------------------------- Testing x of type class com.zejian.B x instanceof A true x instanceof B true A.isInstance(x) true B.isInstance(x) true x.getClass() == A.class false x.getClass() == B.class true x.getClass().equals(A.class)) false x.getClass().equals(B.class)) true
到此关于Class对象相关的知识点都分析完了,下面将结合Class对象的知识点分析反射技术。
理解反射技术
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。一直以来反射技术都是Java中的闪亮点,这也是目前大部分框架(如Spring/Mybatis等)得以实现的支柱。在Java中,Class类与java.lang.reflect类库一起对反射技术进行了全力的支持。在反射包中,我们常用的类主要有Constructor类表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象、Field表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private)、Method表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private),下面将对这几个重要类进行分别说明。
Constructor类及其用法
Constructor类存在于反射包(java.lang.reflect)中,反映的是Class 对象所表示的类的构造方法。获取Constructor对象是通过Class类中的方法获取的,Class类与Constructor相关的主要方法如下:
方法返回值 方法名称 方法说明 static Class<?>
forName(String className) 返回与带有给定字符串名的类或接口相关联的 Class 对象。 Constructor<T>
getConstructor(Class<?>... parameterTypes)
返回指定参数类型、具有public访问权限的构造函数对象 Constructor<?>[]
getConstructors() 返回所有具有public访问权限的构造函数的Constructor对象数组 Constructor<T>
getDeclaredConstructor(Class<?>... parameterTypes)
返回指定参数类型、所有声明的(包括private)构造函数对象 Constructor<?>[]
getDeclaredConstructor()
返回所有声明的(包括private)构造函数对象 T newInstance() 创建此 Class 对象所表示的类的一个新实例。 下面看一个简单例子来了解Constructor对象的使用:
package reflect; import java.io.Serializable; import java.lang.reflect.Constructor; /** * Created by zejian on 2017/5/1. * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创] */ public class ReflectDemo implements Serializable{ public static void main(String[] args) throws Exception { Class<?> clazz = null; //获取Class对象的引用 clazz = Class.forName("reflect.User"); //第一种方法,实例化默认构造方法,User必须无参构造函数,否则将抛异常 User user = (User) clazz.newInstance(); user.setAge(20); user.setName("Rollen"); System.out.println(user); System.out.println("--------------------------------------------"); //获取带String参数的public构造函数 Constructor cs1 =clazz.getConstructor(String.class); //创建User User user1= (User) cs1.newInstance("xiaolong"); user1.setAge(22); System.out.println("user1:"+user1.toString()); System.out.println("--------------------------------------------"); //取得指定带int和String参数构造函数,该方法是私有构造private Constructor cs2=clazz.getDeclaredConstructor(int.class,String.class); //由于是private必须设置可访问 cs2.setAccessible(true); //创建user对象 User user2= (User) cs2.newInstance(25,"lidakang"); System.out.println("user2:"+user2.toString()); System.out.println("--------------------------------------------"); //获取所有构造包含private Constructor<?> cons[] = clazz.getDeclaredConstructors(); // 查看每个构造方法需要的参数 for (int i = 0; i < cons.length; i++) { //获取构造函数参数类型 Class<?> clazzs[] = cons[i].getParameterTypes(); System.out.println("构造函数["+i+"]:"+cons[i].toString() ); System.out.print("参数类型["+i+"]:("); for (int j = 0; j < clazzs.length; j++) { if (j == clazzs.length - 1) System.out.print(clazzs[j].getName()); else System.out.print(clazzs[j].getName() + ","); } System.out.println(")"); } } } class User { private int age; private String name; public User() { super(); } public User(String name) { super(); this.name = name; } /** * 私有构造 * @param age * @param name */ private User(int age, String name) { super(); this.age = age; this.name = name; } //..........省略set 和 get方法 }
运行结果:
User [age=20, name=Rollen] -------------------------------------------- user1:User [age=22, name=xiaolong] -------------------------------------------- user2:User [age=25, name=lidakang] -------------------------------------------- 构造函数[0]:private reflect.User(int,java.lang.String) 参数类型[0]:(int,java.lang.String) 构造函数[1]:public reflect.User(java.lang.String) 参数类型[1]:(java.lang.String) 构造函数[2]:public reflect.User() 参数类型[2]:()
关于Constructor类本身一些常用方法如下(仅部分,其他可查API),
方法返回值 方法名称 方法说明 Class<T>
getDeclaringClass()
返回 Class 对象,该对象表示声明由此 Constructor 对象表示的构造方法的类,其实就是返回真实类型(不包含参数) Type[]
getGenericParameterTypes()
按照声明顺序返回一组 Type 对象,返回的就是 Constructor对象构造函数的形参类型。 String
getName()
以字符串形式返回此构造方法的名称。 Class<?>[]
getParameterTypes()
按照声明顺序返回一组 Class 对象,即返回Constructor 对象所表示构造方法的形参类型 T
newInstance(Object... initargs)
使用此 Constructor对象表示的构造函数来创建新实例 String toGenericString()
返回描述此 Constructor 的字符串,其中包括类型参数。 代码演示如下:
Constructor cs3=clazz.getDeclaredConstructor(int.class,String.class); System.out.println("-----getDeclaringClass-----"); Class uclazz=cs3.getDeclaringClass(); //Constructor对象表示的构造方法的类 System.out.println("构造方法的类:"+uclazz.getName()); System.out.println("-----getGenericParameterTypes-----"); //对象表示此 Constructor 对象所表示的方法的形参类型 Type[] tps=cs3.getGenericParameterTypes(); for (Type tp:tps) { System.out.println("参数名称tp:"+tp); } System.out.println("-----getParameterTypes-----"); //获取构造函数参数类型 Class<?> clazzs[] = cs3.getParameterTypes(); for (Class claz:clazzs) { System.out.println("参数名称:"+claz.getName()); } System.out.println("-----getName-----"); //以字符串形式返回此构造方法的名称 System.out.println("getName:"+cs3.getName()); System.out.println("-----getoGenericString-----"); //返回描述此 Constructor 的字符串,其中包括类型参数。 System.out.println("getoGenericString():"+cs3.toGenericString()); /** 输出结果: -----getDeclaringClass----- 构造方法的类:reflect.User -----getGenericParameterTypes----- 参数名称tp:int 参数名称tp:class java.lang.String -----getParameterTypes----- 参数名称:int 参数名称:java.lang.String -----getName----- getName:reflect.User -----getoGenericString----- getoGenericString():private reflect.User(int,java.lang.String) */
其中关于Type类型这里简单说明一下,Type 是 Java 编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。
getGenericParameterTypes
与getParameterTypes
都是获取构成函数的参数类型,前者返回的是Type类型,后者返回的是Class类型,由于Type顶级接口,Class也实现了该接口,因此Class类是Type的子类,Type 表示的全部类型而每个Class对象表示一个具体类型的实例,如String.class
仅代表String类型。由此看来Type与 Class 表示类型几乎是相同的,只不过 Type表示的范围比Class要广得多而已。当然Type还有其他子类,如:TypeVariable:表示类型参数,可以有上界,比如:T extends Number
ParameterizedType:表示参数化的类型,有原始类型和具体的类型参数,比如:
List<String>
WildcardType:表示通配符类型,比如:?, ? extends Number, ? super Integer
通过以上的分析,对于Constructor类已有比较清晰的理解,利用好Class类和Constructor类,我们可以在运行时动态创建任意对象,从而突破必须在编译期知道确切类型的障碍。
Field类及其用法
Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。同样的道理,我们可以通过Class类的提供的方法来获取代表字段信息的Field对象,Class类与Field对象相关方法如下:
方法返回值 方法名称 方法说明 Field
getDeclaredField(String name)
获取指定name名称的(包含private修饰的)字段,不包括继承的字段 Field[]
getDeclaredField()
获取Class对象所表示的类或接口的所有(包含private修饰的)字段,不包括继承的字段 Field
getField(String name)
获取指定name名称、具有public修饰的字段,包含继承字段 Field[]
getField()
获取修饰符为public的字段,包含继承字段
下面的代码演示了上述方法的使用过程/** * Created by zejian on 2017/5/1. * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创] */ public class ReflectField { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException { Class<?> clazz = Class.forName("reflect.Student"); //获取指定字段名称的Field类,注意字段修饰符必须为public而且存在该字段, // 否则抛NoSuchFieldException Field field = clazz.getField("age"); System.out.println("field:"+field); //获取所有修饰符为public的字段,包含父类字段,注意修饰符为public才会获取 Field fields[] = clazz.getFields(); for (Field f:fields) { System.out.println("f:"+f.getDeclaringClass()); } System.out.println("================getDeclaredFields===================="); //获取当前类所字段(包含private字段),注意不包含父类的字段 Field fields2[] = clazz.getDeclaredFields(); for (Field f:fields2) { System.out.println("f2:"+f.getDeclaringClass()); } //获取指定字段名称的Field类,可以是任意修饰符的自动,注意不包含父类的字段 Field field2 = clazz.getDeclaredField("desc"); System.out.println("field2:"+field2); } /** 输出结果: field:public int reflect.Person.age f:public java.lang.String reflect.Student.desc f:public int reflect.Person.age f:public java.lang.String reflect.Person.name ================getDeclaredFields==================== f2:public java.lang.String reflect.Student.desc f2:private int reflect.Student.score field2:public java.lang.String reflect.Student.desc */ } class Person{ public int age; public String name; //省略set和get方法 } class Student extends Person{ public String desc; private int score; //省略set和get方法 }
上述方法需要注意的是,如果我们不期望获取其父类的字段,则需使用Class类的getDeclaredField/getDeclaredFields方法来获取字段即可,倘若需要连带获取到父类的字段,那么请使用Class类的getField/getFields,但是也只能获取到public修饰的的字段,无法获取父类的私有字段。下面将通过Field类本身的方法对指定类属性赋值,代码演示如下:
//获取Class对象引用 Class<?> clazz = Class.forName("reflect.Student"); Student st= (Student) clazz.newInstance(); //获取父类public字段并赋值 Field ageField = clazz.getField("age"); ageField.set(st,18); Field nameField = clazz.getField("name"); nameField.set(st,"Lily"); //只获取当前类的字段,不获取父类的字段 Field descField = clazz.getDeclaredField("desc"); descField.set(st,"I am student"); Field scoreField = clazz.getDeclaredField("score"); //设置可访问,score是private的 scoreField.setAccessible(true); scoreField.set(st,88); System.out.println(st.toString()); //输出结果:Student{age=18, name='Lily ,desc='I am student', score=88} //获取字段值 System.out.println(scoreField.get(st)); // 88
其中的
set(Object obj, Object value)
方法是Field类本身的方法,用于设置字段的值,而get(Object obj)
则是获取字段的值,当然关于Field类还有其他常用的方法如下:方法返回值 方法名称 方法说明 void
set(Object obj, Object value)
将指定对象变量上此 Field 对象表示的字段设置为指定的新值。 Object
get(Object obj)
返回指定对象上此 Field 表示的字段的值 Class<?>
getType()
返回一个 Class 对象,它标识了此Field 对象所表示字段的声明类型。 boolean
isEnumConstant()
如果此字段表示枚举类型的元素则返回 true;否则返回 false String
toGenericString()
返回一个描述此 Field(包括其一般类型)的字符串 String
getName()
返回此 Field 对象表示的字段的名称 Class<?>
getDeclaringClass()
返回表示类或接口的 Class 对象,该类或接口声明由此 Field 对象表示的字段 void setAccessible(boolean flag) 将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性
上述方法可能是较为常用的,事实上在设置值的方法上,Field类还提供了专门针对基本数据类型的方法,如setInt()/getInt()、setBoolean()/getBoolean、setChar()/getChar()等等方法,这里就不全部列出了,需要时查API文档即可。需要特别注意的是被final关键字修饰的Field字段是安全的,在运行时可以接收任何修改,但最终其实际值是不会发生改变的。Method类及其用法
Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法可能是类方法或实例方法(包括抽象方法)。下面是Class类获取Method对象相关的方法:
方法返回值 方法名称 方法说明 Method
getDeclaredMethod(String name, Class<?>... parameterTypes)
返回一个指定参数的Method对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。 Method[]
getDeclaredMethod()
返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。 Method
getMethod(String name, Class<?>... parameterTypes)
返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。 Method[]
getMethods()
返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。 同样通过案例演示上述方法:
import java.lang.reflect.Method; /** * Created by zejian on 2017/5/1. * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创] */ public class ReflectMethod { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException { Class clazz = Class.forName("reflect.Circle"); //根据参数获取public的Method,包含继承自父类的方法 Method method = clazz.getMethod("draw",int.class,String.class); System.out.println("method:"+method); //获取所有public的方法: Method[] methods =clazz.getMethods(); for (Method m:methods){ System.out.println("m::"+m); } System.out.println("========================================="); //获取当前类的方法包含private,该方法无法获取继承自父类的method Method method1 = clazz.getDeclaredMethod("drawCircle"); System.out.println("method1::"+method1); //获取当前类的所有方法包含private,该方法无法获取继承自父类的method Method[] methods1=clazz.getDeclaredMethods(); for (Method m:methods1){ System.out.println("m1::"+m); } } /** 输出结果: method:public void reflect.Shape.draw(int,java.lang.String) m::public int reflect.Circle.getAllCount() m::public void reflect.Shape.draw() m::public void reflect.Shape.draw(int,java.lang.String) m::public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException m::public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException m::public final void java.lang.Object.wait() throws java.lang.InterruptedException m::public boolean java.lang.Object.equals(java.lang.Object) m::public java.lang.String java.lang.Object.toString() m::public native int java.lang.Object.hashCode() m::public final native java.lang.Class java.lang.Object.getClass() m::public final native void java.lang.Object.notify() m::public final native void java.lang.Object.notifyAll() ========================================= method1::private void reflect.Circle.drawCircle() m1::public int reflect.Circle.getAllCount() m1::private void reflect.Circle.drawCircle() */ } class Shape { public void draw(){ System.out.println("draw"); } public void draw(int count , String name){ System.out.println("draw "+ name +",count="+count); } } class Circle extends Shape{ private void drawCircle(){ System.out.println("drawCircle"); } public int getAllCount(){ return 100; } }
在通过getMethods方法获取Method对象时,会把父类的方法也获取到,如上的输出结果,把Object类的方法都打印出来了。而getDeclaredMethod/getDeclaredMethods方法都只能获取当前类的方法。我们在使用时根据情况选择即可。下面将演示通过Method对象调用指定类的方法:
Class clazz = Class.forName("reflect.Circle"); //创建对象 Circle circle = (Circle) clazz.newInstance(); //获取指定参数的方法对象Method Method method = clazz.getMethod("draw",int.class,String.class); //通过Method对象的invoke(Object obj,Object... args)方法调用 method.invoke(circle,15,"圈圈"); //对私有无参方法的操作 Method method1 = clazz.getDeclaredMethod("drawCircle"); //修改私有方法的访问标识 method1.setAccessible(true); method1.invoke(circle); //对有返回值得方法操作 Method method2 =clazz.getDeclaredMethod("getAllCount"); Integer count = (Integer) method2.invoke(circle); System.out.println("count:"+count); /** 输出结果: draw 圈圈,count=15 drawCircle count:100 */
在上述代码中调用方法,使用了Method类的
invoke(Object obj,Object... args)
第一个参数代表调用的对象,第二个参数传递的调用方法的参数。这样就完成了类方法的动态调用。方法返回值 方法名称 方法说明 Object
invoke(Object obj, Object... args)
对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。 Class<?>
getReturnType()
返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型,即方法的返回类型 Type getGenericReturnType()
返回表示由此 Method 对象所表示方法的正式返回类型的 Type 对象,也是方法的返回类型。 Class<?>[]
getParameterTypes()
按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。即返回方法的参数类型组成的数组 Type[]
getGenericParameterTypes()
按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的,也是返回方法的参数类型 String
getName()
以 String 形式返回此 Method 对象表示的方法名称,即返回方法的名称 boolean
isVarArgs()
判断方法是否带可变参数,如果将此方法声明为带有可变数量的参数,则返回 true;否则,返回 false。 String
toGenericString()
返回描述此 Method 的字符串,包括类型参数。
getReturnType方法/getGenericReturnType方法都是获取Method对象表示的方法的返回类型,只不过前者返回的Class类型后者返回的Type(前面已分析过),Type就是一个接口而已,在Java8中新增一个默认的方法实现,返回的就参数类型信息public interface Type { //1.8新增 default String getTypeName() { return toString(); } }
而getParameterTypes/getGenericParameterTypes也是同样的道理,都是获取Method对象所表示的方法的参数类型,其他方法与前面的Field和Constructor是类似的。
反射包中的Array类
在Java的java.lang.reflect包中存在着一个可以动态操作数组的类,Array,它提供了动态创建和访问 Java 数组的方法。Array 允许在执行 get 或 set 操作进行取值和赋值。在Class类中与数组关联的方法是:
方法返回值 方法名称 方法说明 Class<?>
getComponentType()
返回表示数组元素类型的 Class,即数组的类型 boolean
isArray()
判定此 Class 对象是否表示一个数组类。 java.lang.reflect.Array中的常用静态方法如下:
方法返回值 方法名称 方法说明 static Object
set(Object array, int index)
返回指定数组对象中索引组件的值。 static int
getLength(Object array)
以 int 形式返回指定数组对象的长度 static object
newInstance(Class<?> componentType, int... dimensions)
创建一个具有指定类型和维度的新数组。 static Object
newInstance(Class<?> componentType, int length)
创建一个具有指定的组件类型和长度的新数组。 static void
set(Object array, int index, Object value)
将指定数组对象中索引组件的值设置为指定的新值。 下面通过一个简单例子来演示这些方法
package reflect; import java.lang.reflect.Array; /** * Created by zejian on 2017/5/1. * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创] */ public class ReflectArray { public static void main(String[] args) throws ClassNotFoundException { int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; //获取数组类型的Class 即int.class Class<?> clazz = array.getClass().getComponentType(); //创建一个具有指定的组件类型和长度的新数组。 //第一个参数:数组的类型,第二个参数:数组的长度 Object newArr = Array.newInstance(clazz, 15); //获取原数组的长度 int co = Array.getLength(array); //赋值原数组到新数组 System.arraycopy(array, 0, newArr, 0, co); for (int i:(int[]) newArr) { System.out.print(i+","); } //创建了一个长度为10 的字符串数组, //接着把索引位置为6 的元素设为"hello world!",然后再读取索引位置为6 的元素的值 Class clazz2 = Class.forName("java.lang.String"); //创建一个长度为10的字符串数组,在Java中数组也可以作为Object对象 Object array2 = Array.newInstance(clazz2, 10); //把字符串数组对象的索引位置为6的元素设置为"hello" Array.set(array2, 6, "hello world!"); //获得字符串数组对象的索引位置为5的元素的值 String str = (String)Array.get(array2, 6); System.out.println(); System.out.println(str);//hello } /** 输出结果: 1,2,3,4,5,6,7,8,9,0,0,0,0,0,0, hello world! */ }
通过上述代码演示,确实可以利用Array类和反射相结合动态创建数组,也可以在运行时动态获取和设置数组中元素的值,其实除了上的set/get外Array还专门为8种基本数据类型提供特有的方法,如setInt/getInt、setBoolean/getBoolean,其他依次类推,需要使用是可以查看API文档即可。除了上述动态修改数组长度或者动态创建数组或动态获取值或设置值外,可以利用泛型动态创建泛型数组如下:
/** * 接收一个泛型数组,然后创建一个长度与接收的数组长度一样的泛型数组, * 并把接收的数组的元素复制到新创建的数组中, * 最后找出新数组中的最小元素,并打印出来 * @param a * @param <T> */ public <T extends Comparable<T>> void min(T[] a) { //通过反射创建相同类型的数组 T[] b = (T[]) Array.newInstance(a.getClass().getComponentType(), a.length); for (int i = 0; i < a.length; i++) { b[i] = a[i]; } T min = null; boolean flag = true; for (int i = 0; i < b.length; i++) { if (flag) { min = b[i]; flag = false; } if (b[i].compareTo(min) < 0) { min = b[i]; } } System.out.println(min); }
毕竟我们无法直接创建泛型数组,有了Array的动态创建数组的方式这个问题也就迎刃而解了。
//无效语句,编译不通 T[] a = new T[];
ok~,到这反射中几个重要并且常用的类我们都基本介绍完了,但更重要是,我们应该认识到反射机制并没有什么神奇之处。当通过反射与一个未知类型的对象打交道时,JVM只会简单地检查这个对象,判断该对象属于那种类型,同时也应该知道,在使用反射机制创建对象前,必须确保已加载了这个类的Class对象,当然这点完全不必由我们操作,毕竟只能JVM加载,但必须确保该类的”.class”文件已存在并且JVM能够正确找到。关于Class类的方法在前面我们只是分析了主要的一些方法,其实Class类的API方法挺多的,建议查看一下API文档,浏览一遍,有个印象也是不错的选择,这里仅列出前面没有介绍过又可能用到的API:
/** * 修饰符、父类、实现的接口、注解相关 */ //获取修饰符,返回值可通过Modifier类进行解读 public native int getModifiers(); //获取父类,如果为Object,父类为null public native Class<? super T> getSuperclass(); //对于类,为自己声明实现的所有接口,对于接口,为直接扩展的接口,不包括通过父类间接继承来的 public native Class<?>[] getInterfaces(); //自己声明的注解 public Annotation[] getDeclaredAnnotations(); //所有的注解,包括继承得到的 public Annotation[] getAnnotations(); //获取或检查指定类型的注解,包括继承得到的 public <A extends Annotation> A getAnnotation(Class<A> annotationClass); public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass); /** * 内部类相关 */ //获取所有的public的内部类和接口,包括从父类继承得到的 public Class<?>[] getClasses(); //获取自己声明的所有的内部类和接口 public Class<?>[] getDeclaredClasses(); //如果当前Class为内部类,获取声明该类的最外部的Class对象 public Class<?> getDeclaringClass(); //如果当前Class为内部类,获取直接包含该类的类 public Class<?> getEnclosingClass(); //如果当前Class为本地类或匿名内部类,返回包含它的方法 public Method getEnclosingMethod(); /** * Class对象类型判断相关 */ //是否是数组 public native boolean isArray(); //是否是基本类型 public native boolean isPrimitive(); //是否是接口 public native boolean isInterface(); //是否是枚举 public boolean isEnum(); //是否是注解 public boolean isAnnotation(); //是否是匿名内部类 public boolean isAnonymousClass(); //是否是成员类 public boolean isMemberClass(); //是否是本地类 public boolean isLocalClass();
ok~,本篇到此完结。
-
python之class
2020-10-31 23:08:51python之class class 的定义格式 class的组成 名称: 类名,注意其首字母需要大写 属性:一组数据 方法:能进行操作的方法 class书写的格式 class 类名: 类的属性 类的方法 注意缩进 class 成员与方法 ... -
Vue动态绑定class
2022-01-06 22:42:28我们在学习js时有学到过如何去动态切换元素的样式,具体有以下步骤: ... .class1 { border: 1px solid black; height: 20px; width: 20px; } </style> </head> <body> <div id... -
JS es6的Class类详解
2020-09-05 10:17:18文章目录JS es6的Class类详解class基本语法Class的基本语法之constructorClass的基本语法之类的调用方式Class的基本语法之getter和setterClass的基本语法之类的属性名Class的基本语法的特别注意点Class的静态属性和... -
StorageClass
2019-07-15 20:30:21本文个人博客地址:...StorageClass提供了一种描述存储类(class)的方法,不同的class可能会映射到不同的服务质量等级和备份策略或其他策略等。 StorageClass 对象中包含... -
【C/C++面试必备】struct和class的区别
2021-07-23 07:47:53???? 作者:Linux猿 ???? 简介:CSDN博客专家?...,C/C++、面试、刷题、算法尽管咨询我,关注我,有...首先,注意本文讨论的是 C++ 中 struct 和 class 的区别,因为 C 中 struct 和 class 的区别已经很明显了! 先说下 -
Java中Class对象详解
2018-06-01 14:50:40从某种意义上来说,java有两种对象:实例对象和Class对象。每个类的运行时的类型信息就是用Class对象表示的。它包含了与类有关的信息。其实我们的实例对象就通过Class对象来创建的。Java使用Class对象执行其RT... -
Java中的Object类和Class类
2019-04-24 19:59:33在Java中,无论是类还是接口,它们在JVM的内存逻辑模型中都会存在Super和Class指针,分别指向根类(Object类)和反射类(Class类) 在这一篇blog中会了解到很多常用方法,建议大家看api 1. 位置和内容 Object类和... -
python——class类和方法的用法详解
2020-08-07 16:17:00因为一直不太清楚面向对象的类和方法的编程思想,所以特地补了一下python-class的知识,在这里记录和分享一下。 文章目录类和方法的概念和实例1.python类:`class`2.类的构造方法`__init__()`3.类中方法的参数`self... -
Android 删除aar包中的jar包解决Duplicate class包冲突问题
2019-09-20 16:59:24项目开发编译过程中报...Duplicate class com.google.gson.DefaultDateTypeAdapter found in modules gson-2.2.4.jar (com.xindecoiot.xdreader:zebra-scanner:2.4.6) and gson-2.8.5.jar (com.google.code.gson:... -
Class类简介
2018-05-25 19:39:411、Class类简介: Java程序在运行时,Java运行时系统一直对所有的对象进行所谓的运行时类型标识。这项信息纪录了每个对象所属的类。虚拟机通常使用运行时类型信息选准正确方法去执行,用来保存这些类型... -
public class和class的区别
2019-03-21 21:38:50在编写类的时候可以使用两种方式定义类:public class定义类和class定义类,那么这这两种方式有什么区别吗?如果一个类声明的时候使用了public class进行了声明,则类名称必须与文件名称完全一致,如果类的声明使用... -
Spring ClassPathResource详解
2019-08-07 17:43:47org.springframework.core.io.ClassPathResource位于Spring核心core下,用以表达类路径下的资源。 -
理解python metaclass
2018-06-12 16:35:32原文地址:...metaclass在python中永远是一个争议的话题。许多开发者避免使用它们,而我认为这很大程度上是有任意的工作流程和查找规则引起的,它们没能很好的解释。同时... -
深入理解(4)Java类型信息(Class对象)与反射机制
2019-05-01 10:34:40深入理解Class对象 ...认识Class对象之前,先来了解一个概念,RTTI(Run-Time Type Identification)运行时类型识别,对于这个词一直是 C++ 中的概念,至于Java中出现RRTI的说法则是源于《Thin... -
VUE :class 动态class方法
2020-03-27 17:59:02操作元素的 class 列表和内联样式是数据绑定的一个常见需求。因为它们都是属性,所以我们可以用 v-bind 处理它们:只需要通过表达式计算出字符串结果即可。不过,字符串拼接麻烦且易错。因此,在将 v-bind 用于 ... -
FindClass 流程分析
2018-06-15 17:10:55当我们调用 FindClass 时,例如: JNIEnv* env env-&gt;FindClass("java/lang/Class") 实际上会经过如下调用栈: _JNIEnv::FindClass() |_ art::JNI::FindClass() 本篇文章就来... -
深入理解Java类型信息(一):Class对象与类型转换
2019-03-20 19:54:19初始化有效地实现了尽可能的“惰性”,通过.Class语法来获取Initable类的Class对象时没有触发初始化,通过Class.forName()方式来获取Initable3类的Class对象时就进行了初始化。 调用Initable.staticFinal变量时,... -
Spring中的各种Utils(四):ClassUtils详解
2018-06-12 09:12:19原创文章,转载请注明出处本节中主要介绍ClassUtils,这是关于类级别相关的工具类,虽然只是提供给Spring框架内部使用,但是很多方法还是有一定使用价值,并且理解这些方法的实现,也是有一定价值的。首先ClassUtils... -
RUNTIME_CLASS
2018-12-01 14:42:17RUNTIME_CLASS RUNTIME_CLASS( class_name ) 【参数】 class_name:类的实际名字(不用引号括起来)。 【说明】 利用这个宏通过C++类的名字获得一个运行时类结构。 RUNTIME_CLASS为class_name指定的类返回一个... -
详解Class和MetaClass
2018-08-30 14:42:10想必对象大家都清楚(不是恋爱对象哦~),那么Class又是什么?这中文意思大家都是知道是“类”,可为什么偏偏就有这么一个结构体叫Class?Objective-C Runtime里面有个api: id objc_getMetaClass(const char *name),... -
Class -- 10 -- Method类常用方法解析
2018-11-17 16:06:50原文链接:Class – 10 – Method类常用方法解析 相关文章: Class – 01 – System类常用方法解析 Class – 02 – Arrays类常用方法解析 Class – 03 – Random类常用方法详解析 Class – 04 – Date类常用方法解析... -
Kubernetes集群StorageClass持久化存储资源核心概念以及使用(四十六)
2022-02-15 09:09:19Kubernetes集群StorageClass持久化存储资源核心概念以及使用 文章目录Kubernetes集群StorageClass持久化存储资源核心概念以及使用1.StorageClass持久化存储介绍2.在K8S集群部署StorageClass资源2.1.编写nfs-client的... -
java中的class是什么意思
2021-02-26 08:51:16java中的class代表类,class类存在于java.lang包中。在Java中,每个class都有一个相应的Class对象。当我们编写一个类并进行编译后,在生成的【.class】文件中就会产生一个Class对象,它用于表示这个类的类型信息。... -
Class的基本用法
2018-07-19 18:25:05Class(类)这个概念,作为对象的模板。class可以看作只是一个语法糖,通过class关键字,可以定义类。让对象原型的写法更加清晰、更像面向对象编程的语法。类和模块的内部,默认就是严格模式,所以不需要使用use ... -
Java中的java.lang.Class API 详解
2019-02-14 18:48:17Class是一个位于java.lang包下面的一个类,在Java中每个类实例都有对应的Class对象。类对象是由Java虚拟机(JVM)自动构造的。 Class类的方法经常在反射时被调用。 创建Class对象 有三种方法可以创建Class对象 Class....