一、类加载
- 类加载是指将类的class文件读入内存,并为之创建一个java.lang.Class对象,即:当程序需要使用一个类时,必须且系统会自动为之创建一个java.lang.Class对象。java.lang.Class类封装一个对象和接口运行时的状态——某个类的数据成员名、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。
- 系统可以在第一次使用某个类时加载该类,也可以采用预加载机制来加载某个类。
- 上面我们知道了,类被加载之后,系统会为该类生成一个对应的Class对象,通过该Class对象就可以访问到JVM中该类的信息。一旦类被加载入JVM中,同一个类将不会再次被载入。被载入JVM的类都有一个唯一标识,该标识是该类的全限定名(包名.类名)。
1.Class类
- Class类没有公共构造方法,其对象是JVM在加载类时通过调用类加载器中的defineClass()方法自动构造的,因此不能显式的实例化一个Class对象。
- Class类的常用方法
public static Class<?> forName(String className)//返回指定类名的Class对象
public Object newInstance() //调用默认构造方法,返回该Class对象的一个实例
String getName()//返回Class对象所对应类的名称:包名.类名
public Constructor<?>[] getContructors() //返回Class对象所对应类的所有public构造方法
public Constructor<T>[] getContructor(Class<?> ...parameterTypes) //返回Class对象所对应类的指定参数列表的public构造方法
public Constructor<?>[] getDeclaredContructors() //返回Class对象所对应类的所有构造方法
public Constructor<T>[] getDeclaredContructor(Class<?> ...parameterTypes) //返回Class对象所对应类的指定参数列表的构造方法
public Method[] getMethods() //返回Class对象所对应类的所有public方法
public Method[] getMethods(String name, Class<?> ...parameterTypes) //返回Class对象所对应类的指定参数列表的public方法
public Method[] getDeclaredMethods() //返回Class对象所对应类的所有方法
public Method[] getDeclaredMethod(String name, Class<?> ...parameterTypes) //返回Class对象所对应类的指定参数列表的方法
public Field[] getFields() //返回Class对象所对应类的所有public成员变量
public Field[] getField(String name)//返回Class对象所对应类的指定参数的public成员变量
public Field[] getDeclaredFields() //返回Class对象所对应类的所有成员变量
public Annotation[] getAnnotations()//返回Class对象所对应类上存在的所有注解
<A extends Annotation>A getAnnotation(Class<A> annotationClass)//返回Class对象所对应类上存在的指定类型的注解
public Class<?> getDeclaringClass() //返回Class对象所对应类的外部类
public Class<?>[] getDeclaredClasses() //返回Class对象所对应类中包含的所有内部类
public Class<? super T> getSuperclass() //返回Class对象所对应类的父类Class对象
public int getModifiers() //返回Class对象所对应类的修饰符的常量表示
public Package getPackage() //返回Class对象所对应类的包
public Class[] getInterfaces() //返回Class对象所对应类所实现的所有接口
public ClassLoader getClassLoader(0 //返回该类的类加载器
public boolean isInstance(Object obj) //判断obj是否是该Class对象的实例
public boolean isArray() //判断此class对象是否表示一个数组类
public boolean isEnum() //判断此class对象是否表示一个枚举
public boolean isInterface() //判断此class对象是否表示一个接口
public boolean isAnnotation() //判断此class对象是否标识一个注解类型
- 对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。Class 对象只能由系统建立对象,一个类(而不是一个对象)在 JVM 中只会有一个Class实例。
- 获取Class对象的三种方式:
上面知道了不能用new创建Class对象,但可以使用下面的方式:
//1.使用Class.forName()方法
Class strClass = Class.forName("java.lang.String");//Class.forName()方法声明抛出ClassNotFoundException异常,需捕获或抛出异常
//2.使用类的class属性(PS.该方式能够使代码更安全、程序性能更好,但有些情况下不能使用该方式)
Class<Float> fclass = Float.class;
//3.使用实例对象的getClass()方法(PS.该方法是Object类中的一个方法,因此所有对象调用该方法都可以返回所属类对应的Class对象)
Date nowTime = new Date();
Class dateClass = nowTime.getClass();
- Class类是一个对象照镜子的结果,对象可以看到自己有哪些属性,方法,构造器,实现了哪些接口等等。对象为什么需要照镜子呢?
- 有可能这个对象是别人传过来的
- 有可能没有对象,只有一个全类名
- 通过反射,可以得到这个类里面的信息。
2.类加载步骤
- 类加载的三个步骤<加载、连接和初始化>(由JVM完成)统称为类的加载。
3.类加载器
- 类加载器负责将磁盘或网络上的.class文件加载到内存中,并为之生成对应的java.lang.Class对象。
二、反射
- 反射是动态语言的关键,是JAVA语言中特有的一个特性。它指的是:允许运行中的Java程序对自身进行检查,即“自审”,并能直接操作程序的内部属性及方法。
- 反射机制是实现很多流行框架(如Spring、Hibernate等)的基础。
- 反射的用处:
1. 动态初始化类:在不知道类中有何构造方法的情况下创建一个此类的实例对象
对于构造器(即构造方法),不能像执行普通的方法那样进行,因为执行一个构造器就意味着创建了一个新的对象(准确的说,创建一个对象的过程包括分配内存和构造对象)。
package demo;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Reflect_Dynamics_Function {
public Reflect_Dynamics_Function() { }
public Reflect_Dynamics_Function(int a, int b) {
System.out.println("a = " + a);
System.out.println("b = " + b);
}
public static void main(String[] args) throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException{
//1、返回指定类名的Class对象
Class cls = Class.forName("demo.Test");
//2、创建形参列表
Class parTypes[] = new Class[2];
//3、获取形参列表的Class对象,即:得到每个形参的准确类型
parTypes[0] = Integer.TYPE;
parTypes[1] = Integer.TYPE;
//4、按照参数列表找到cls类(即Test类)中的一个适合的构造方法/构造器
Constructor cons = cls.getConstructor(parTypes);
//5、创建实参对象并赋值
Object objs[] = new Object[2];
objs[0] = 123;
objs[1] = 456;
//6、调用构造方法的实例对象cons的newInstance()方法,并传入实参objs,从而获取一个Test类的实例对象
Object test = cons.newInstance(objs);
//以上的做法就相当于Test test = new Test(123, 456);
//在运行时创建新对象时只能使用上面的方法,而不能直接new对象,因为new对象是在编译时执行的。
//另外,当不知道某个类中的构造方法是怎样的时,也只能采用这种方法进行动态分析。(好像应该前面先调用取得所有构造方法的方法,然后再写上面的代码,就动态对一个类进行了分析,“动态”就指的是在程序运行时,由我们写的代码进行运行时分析某个类)
}
}
2.动态调用类中的方法
package demo;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class TestReflect {
public TestReflect() {}
public int testMethod(int a, int b) {
return a + b;
}
/**
* @param args
* @throws ClassNotFoundException
* @throws IllegalAccessException
* @throws InstantiationException
* @throws NoSuchMethodException
* @throws SecurityException
* @throws InvocationTargetException
* @throws IllegalArgumentException
*/
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, SecurityException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException {
//获取TestReflect类对应的Class对象
Class cls = Class.forName("demo.TestReflect");
//返回所有方法
Method[] ms = cls.getDeclaredMethods();
System.out.println("----------------查看TestReflect类中所有的方法----------------");
for(Method m : ms){
System.out.print(Modifier.toString(m.getModifiers()) + " " + m.getReturnType() + " " + m.getName());
System.out.print("(");
Class[] paramTypes = m.getParameterTypes();//获取方法的参数类型
for(Class paramType : paramTypes)
{
System.out.print(paramType.getName() + " ");
}
System.out.println(");");
}
System.out.println("------------通过反射机制调用TestReflect类中的某个方法------------");
//1、获得参数列表及其Class对象
Class mps[] = new Class[2];
mps[0] = Integer.class;
mps[1] = Integer.class;
//2、根据参数类型获取到指定方法
Method sum = cls.getMethod("testMethod", mps);
//3、设置执行指定方法需要用到的实参
Integer num1 = new Integer(2);
Integer num2 = new Integer(7);
Object[] arguments = new Object[]{num1, num2};
//4、动态创建一个实例类对象,用来进行方法的调用
Object obj = cls.newInstance();
//5、执行所需要执行的方法:sum.invoke()指的是执行sum这个方法,执行的调用obj实例对象的sum方法,参数为arguments,返回值sum_result
int sum_result = (Integer) sum.invoke(obj, arguments);
//6、查看执行结果
System.out.println(num1 + "+" + num2 + "=" + sum_result);
}
}
3.修改属性的值
- 反射可以从正在运行的程序中根据属性的名称找到实例对象的这个属性并改变它。
package demo;
import java.lang.reflect.Field;
public class Reflect_ModifyField {
public double d;
public static void main(String[] args) throws ClassNotFoundException, SecurityException, NoSuchFieldException, InstantiationException, IllegalAccessException {
//获取Reflect_ModifyField类对应的Class对象
Class cls = Class.forName("demo.Reflect_ModifyField");
//根据属性名获取属性
Field fld = cls.getField("d");
//根据Class对象,获取对应类的实例对象
Object obj = cls.newInstance();
//打印未更改之前的d的值
System.out.println("d = " + ((Reflect_ModifyField)obj).d);
//修改d的属性值为12.34
fld.setDouble(obj, 12.34);
//打印更改后的d的值
System.out.println("d = " + ((Reflect_ModifyField)obj).d);
}
}
/* 控制台结果如下:
d = 0.0
d = 12.34
*/
4.操作数组
- 反射机制体现在:创建数组时,其类型是运行期间动态创建的,在编译时并不知道其类型。
1)创建并操作一位数组
public static void main(String[] args) throws ClassNotFoundException {
Class cls = Class.forName("java.lang.String");
//实例化String对象:一个长度为10的数组
Object arr = Array.newInstance(cls, 10);
//为arr数组的第(5-1)个元素赋值“five”
Array.set(arr, 5, "five");
//获取arr数组的第(5-1)个元素的值
String s = (String)Array.get(arr, 5);
System.out.println(s);
}
2)创建并操作一位数组三维数组
回顾创建数组的几种方式:
package demo;
import java.lang.reflect.Array;
public class Reflext_Array {
/**
* @param args
* @throws ClassNotFoundException
*/
private static void main(String[] args) throws ClassNotFoundException {
//Step1.String arr[][][] = new String[5][10][15];
int dims[] = {5, 10, 15};
Object arr = Array.newInstance(String.class, dims);//dims作为参数传入,表示创建了一个三维的数组
//Step2.String arr2[][] = arr[3];(PS.拿到三维数组arr的第一维的索引为3的对应的一块10 * 15的二维数组)
Object arrObj = Array.get(arr, 3);
//Step3.String arr3[] = arr2[5];(PS.拿到二维数组arr2的第一维的索引为5的对应的一个长度为15的一维数组)
arrObj = Array.get(arrObj, 5);
//Step4.为一位数组arr3所在的索引为10处赋值“okokokokoooooo”
Array.set(arrObj, 10, "okokokokoooooo");
//Step5.打印通过上面一系列操作为arr[3][5][10]赋的值(PS.但不能直接System.out.println(arr[3][5][10]); 得利用反射的规则去写):
String arrCast[][][] = (String[][][])arr; //arr转换为String[][][]类型的实例对象,并将其赋给新的实例对象arrCast
System.out.println(arrCast[3][5][10]); //打印arrCast指定位置处的值
}
}
/* 综上,我们看出:
* 不管是Array.get(arr, 3)语句还是Array.get(arrObj, 5),以及最后赋值的形式,
* 事实上,其实都将其看做一维数组来进行操作的。
* */
5.实现简单的工具类
package demo;
public class ATestBean {
private String uuid,name;
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "uuid = " + uuid + ", name = " + name;
}
}
package demo;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
public class ATest {
/**
* 这是一个工具类:通过传递过来的参数,利用反射机制,最终返回一个属性均被赋值了的实例对象。
* @param map 存储key-value键值对;其中key表示参数Class实例对象中具有的属性的名称,而对应的value就是需要给此属性赋的值。
* @param cls 一个属性需要被赋值的类型
* @return 返回属性已被赋值的cls实例对象
* @throws NoSuchMethodException
* @throws SecurityException
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InstantiationException
* */
//反射的应用:使用反射来实现某些通用功能
public static Object setValues(Map map, Class cls) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, InstantiationException {
//0、得到对象的实例
Object obj = cls.newInstance();
//1、使用反射获取这个类的所有属性
Field fs[] = cls.getDeclaredFields();
//2、循环这些属性,判断传入的map集合中是否有与该属性对应的value值
for(Field f : fs) {
Object v = map.get(f.getName());//获取属性f.getName()对应的value,由于map里不一定存储有这个属性以及属性对应的值,所以v可能为null
//3、如果不能够相对应,就继续循环
if (v == null) {
//continue;
}else{
//4、如果能够相对应,就用反射的方法,把属性的值(value)赋给属性(key)
//4.1:根据属性名称得到对应的getXXX()方法名称
String methodName = "set" + f.getName().substring(0,1).toUpperCase() + f.getName().substring(1);
//4.2.1:获得参数类型
Class ps[] = new Class[1];
ps[0] = f.getType();
//请注意上面的两行代码:Class类不能直接new对象(没有公共构造方法),但可以暂时先写成一个数组的形式(先不对其赋值),然后再进行赋值。
//上面的两行代码也可以写成(都是为了得到实例对象的Class类加载):
//Class<?> paramType = Class.forName(f.getType().getName());
//4.2.2:根据方法名称和参数类型得到方法
Method method = cls.getDeclaredMethod(methodName, ps);
//4.3.1:创建实例对象并赋值
Object os[] = new Object[1];
os[0] = v;
//4.3.2:使用反射动态调用getXXX()方法
method.invoke(obj, os);
}
}
return obj;
}
}
package demo;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
//测试类
public class ATestClient {
/**
* @param args
* @throws InstantiationException
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws NoSuchMethodException
* @throws IllegalArgumentException
* @throws SecurityException
*/
public static void main(String[] args) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Map map = new HashMap();
map.put("id", "111");
map.put("uuid", "222");
map.put("name", "333");
map.put("age", "444");
//希望:ATestBean bean = (ATestBean)map; 但实际上这种做法不存在的...于是可以利用我们的工具类:
ATestBean bean = (ATestBean)ATest.setValues(map, ATestBean.class);
System.out.println(bean);
}
}
对下面两行代码的理解
Class cls = Class.name();
Object obj = cls.newInstance();
第一行是用来获取Class对象,这是创建一个类的实例对象的必要前提,要把这个类加载出来。第二行便是用这个Class对象的newInstance()方法创建一个类的实例对象。要注意:用这种方法创建实例对象利用的是该类的空参构造,所以必须保证这个类里有空参构造,否则就会发生InstantiationException异常。一般地,一个类若声明一个带参的构造器,同时也一定要声明一个无参数的构造器。
反射相关类及常用方法总结
三、注解
- 如:
- 类就是一个元数据,因为一个类中包含多个数据——属性,即:类描述了属性,所以类被看做元数据。
- Java中的注解是Java源代码的元数据,用来描述Java源代码,告知编译器要做什么事情。在程序中可以对任何元素进行注解,包括Java包、类、构造方法、域、方法、参数以及局部变量。
- 基本语法:“@” + “注解名称” + “:” + “注解内容”
- 注解内容被存在注解的“name = values”键值对中,通过配套的工具——APT(Annotation Processing Tool,注解处理工具)即可对注解中的信息进行访问和处理。
- APT负责提取注解中包含的元数据,并会根据这些元数据增加额外功能。注解中的元数据有以下三个方面的作用:
编写文档——通过注解中标识的元数据生成doc文档
代码分析——通过注解中标识的元数据,使用反射对代码进行分析
编译检查——通过注解中标识的元数据,让编译器能够实现基本的编译检查
基本注解
- @Override
- 用于限定重写父类的方法,使用该注解修饰的方法必须重写父类中的方法,否则会发生编译错误。
- 只能修饰方法。
- 可以避免发生一些低级错误。因为被@Override修饰的方法必须要重写父类的某个方法,如果不小心写错了父类的方法名,由于@Override的作用,就会在编译时出现错误提示,及时的进行了修正。
- @Deprecated
- 用于标示某个程序元素(接口、类、方法等)已过时,当程序使用已过时的类、方法等,编译器会给出警告。
- 使用被@Deprecated修饰的已过时的方法会被编译警告,但代码是仍然可以正常运行的。
- @SuppressWarnings
- 用于抑制编译警告的发布,取消显式指定的编译器警告,且会一直作用于该元素的子元素。例如:使用@SuppressWarnings修饰某个类来取消某个编译器警告的显式,同时又修饰这个类的某个方法来取消这个方法的某个编译器警告的显式时,这个方法最终将会同时取消两个编译器的警告显示(一个单独对自己设置的,一个从上一元素继承下来的)。
- 标注在类、字段、方法、参数、构造方法以及局部变量上。
- 语法格式:
@SuppressWarnings(“参数”)
@SuppressWarnings(value = “参数”) - 实例——通常在程序中使用没有泛型限制的集合将会引起编译器警告,为了避免这种编译器警告,可以使用注解抑制此类编译器警告的发布。
自定义注解及其使用
- 注解大多是为其他工具(工具类等)提供运行信息或决策依据而设计的,任何Java程序都可以通过使用反射机制来查询注解实例的信息。
- 使用语法及相关规则或参数请参考这里。下面只给出自己动手的实例:
- 自定义注解
package demo.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
//注解中包含的成员由未实现的方法组成(在使用时进行实现):
String comment();//将此成员用来描述注解功能(成员的功能/用来作什么都是自己定义的)
int order() default 1;
}
- 使用自定义的注解
package demo.annotation;
import java.lang.reflect.Method;
@MyAnnotation(comment = "这里是类注解")
class MyClass{
@MyAnnotation(comment = "这里是不带参数的方法的注解", order = 2)
public void myMethod(){
}
}
public class MyAnnotationTest {
public static void main(String[] args) throws SecurityException, NoSuchMethodException {
//1、获取MyClass类注解
MyAnnotation anno = MyClass.class.getAnnotation(MyAnnotation.class);
//2、获取类注解信息
System.out.println("MyClass类的注解信息为:" + anno.comment() + ";序号:" + anno.order());
//1、获取MyClass类中的myMethod()方法
Method mth = MyClass.class.getMethod("myMethod");
//2、获取myMethod()方法的注解信息
MyAnnotation anno1 = mth.getAnnotation(MyAnnotation.class);
System.out.println("myMethod()方法的注解信息为:" + anno1.comment() + ";序号:" + anno1.order());
}
}
使用自定义注解时,用到反射,介绍如下:
3. 运行结果
元注解
- 元注解:用于注解某个注解。
@Retention
- 定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取。
@Documented
- 用于指示被修饰的注解可以被javadoc工具提取成文档。定义注解类时使用@Documented注解进行修饰,则所有使用该注解修饰的程序元素的API文档中将会包含该注解说明。
@Inherited
- 该元注解使父类的注解将被其子类继承。如果某个注解使用@Inherited修饰,则某个类使用这个注解时,这个类的子类将自动被/用@Inherited修饰的/注解/所修饰。
(详细介绍 点击这里)