java.util包下提供了一个资源绑定器,便于获取属性配置文件的内容
这个资源绑定器只能获取类路径(src目录)下的属性配置文件(以.properties结尾的文件)
类加载器:ClassLoad,是专门负责加载类的命令/工具
JDK中自带3个类加载器:
启动类加载器(父加载器)rt.jar
扩展类加载器(母加载器)ext/*.jar
应用类加载器 classpath
假设有这样的代码:
String s="abc";
代码在开始执行之前会将所以需要的类全部加载到JVM中,通过类加载器加载,看到以上代码,类加载器会找到String.class文件,找到就加载;
加载过程:
首先通过启动类加载器加载,启动类加载器专门加载:xxx/jdk/jre/lib/rt.jar;
rt.jar中都是JDK最核心的类库
如果通过启动类加载器加载不到的时候,会通过扩展类加载器加载;
扩展类加载器专门加载:xxx/jdk/jre/lib/ext/*.jar;
如果通过扩展类加载器加载不到的时候,会通过应用类加载器加载;
应用类加载器专门加载:classpath中的jar包(class文件),即环境变量里面配置的jar包;
JAVA中为了保证类加载的安全,使用了双亲委派机制,优先从启动类加载器中加载,这个称为父,父无法加载到,再从扩展类加载器中加载,这个称为母,双亲委派,如果都加载不到,才会考虑从应用类加载器中加载,直到加载为止;
Class cls = Class.forName("ReflectTest.Movie");
// 获取类中所有public修饰的field
Field[] fs = cls.getFields();
Field[] fs2 = cls.getDeclaredFields();
for (Field field : fs2) {
// 获取属性类型
System.out.print(field.getType().getName() + "---");
// 获取属性类型简称
System.out.print(field.getType().getSimpleName() + "---");
// 获取属性名称
System.out.print(field.getName() + "---");
// 获取属性的修饰符,每个修饰符是一个数字,每个数字是修饰符的代号
int i = field.getModifiers();
// 将数字代号转成修饰符字符串
System.out.print(i + "---" + Modifier.toString(i));
System.out.println();
}
反编译field属性:
public static void test42() throws Exception {
Class cls = Class.forName("java.util.Date");
StringBuilder sb = new StringBuilder();
sb.append("public class " + cls.getSimpleName() + " {\r\n");
Field[] fs = cls.getDeclaredFields();
for (Field field : fs) {
sb.append("\t" + Modifier.toString(field.getModifiers()) + " ");
sb.append(field.getType().getSimpleName() + " ");
sb.append(field.getName() + ";\r\n");
}
sb.append("}");
System.out.println(sb);
}
给对象属性赋值的三要素:对象、属性、值;
/**
* 可以通过反射机制,和配置文件,可以灵活的获取和设置对象的属性
* 反射机制虽然让代码更复杂了,但是可以让程序更加灵活
*/
public static void test43() throws Exception {
Class cls = Class.forName("ReflectTest.Movie");
Object obj = cls.newInstance();
// 获取name属性
Field nameField = cls.getDeclaredField("name");
nameField.set(obj, "寒战");
System.out.println(nameField.get(obj));
// 获取私有属性,需要打破封装
// 缺点:可能会给不法分子留下机会,这样设置之后外部也能访问私有属性
Field directorField = cls.getDeclaredField("director");
directorField.setAccessible(true);
directorField.set(obj, "麦兆辉");
System.out.println(directorField.get(obj));
}
语法:类型... args;
可变长度参数的个数是0-N个;
可变长度参数在参数列表中必须在最后一个位置上;
可变长度参数可以当成一个数组看待,也可以传入一个数组参数;
public static void testLength(int... args) {
for (int i : args) {
System.out.println("可变长度参数" + i);
}
}
ReflectTest.Movie.testLength(10, 20, 30);
int[] arr = { 1, 2, 3, 4, 5 };
ReflectTest.Movie.testLength(arr);
反编译方法:
public static void test45() throws Exception {
Class cls = Class.forName("java.lang.String");
Method[] mts = cls.getMethods();
StringBuffer sb = new StringBuffer();
sb.append(Modifier.toString(cls.getModifiers()) + " class " + cls.getSimpleName() + "{\r\n");
for (Method method : mts) {
// 获取权限修饰符
sb.append("\t" + Modifier.toString(method.getModifiers()) + " ");
// 获取返回值类型
sb.append(method.getReturnType().getSimpleName() + " ");
// 获取方法名
sb.append(method.getName() + "(");
// 获取参数类型数组
Class[] paramTypes = method.getParameterTypes();
for (Class pt : paramTypes) {
// 获取参数类型字符串
sb.append(pt.getSimpleName() + ",");
}
// 删除指定下标位置的字符
sb.deleteCharAt(sb.length() - 1);
sb.append("){}\r\n");
}
sb.append("}\r\n");
System.out.println(sb);
}
对象获取方法的4个要素:对象、方法名、参数列表、返回值类型;
反射机制调用方法:
public static void test46() throws Exception {
Class cls = Class.forName("ReflectTest.Movie");
// 获取Method
Method mtd = cls.getMethod("buy", String.class, int.class);
Object obj = cls.newInstance();
Object retVal = mtd.invoke(obj, "3303271991", 1107);
System.out.println(retVal);
}
反射机制,让代码更具有通用性,可变化的内容都是写在配置文件中的,将来修改配置文件之后,创建的对象就不一样了,调用的方法也不同了,但是java代码不需要做任何改动,这就是反射机制的魅力;
反编译构造方法
public static void test47() throws Exception {
Class cls = Class.forName("ReflectTest.Movie");
StringBuffer sb = new StringBuffer();
sb.append(Modifier.toString(cls.getModifiers()) + " class " + cls.getSimpleName() + "{\n\t");
Constructor[] cs = cls.getDeclaredConstructors();
for (Constructor constructor : cs) {
sb.append("\t" + Modifier.toString(constructor.getModifiers()) + " " + cls.getSimpleName() + "(");
Class[] csTypes = constructor.getParameterTypes();
for (Class cst : csTypes) {
sb.append(cst.getSimpleName() + ",");
}
if (csTypes.length > 0) {
sb.deleteCharAt(sb.length() - 1);
}
sb.append("){}\n\t");
}
sb.append("}\n\t");
System.out.println(sb);
}
通过反射机制创建对象
public static void test48() throws Exception {
Class cls = Class.forName("ReflectTest.Movie");
// 调用无参构造方法
Object obj = cls.newInstance();
// 获取有参数的构造方法
Constructor cs = cls.getDeclaredConstructor(String.class, double.class, String.class);
Constructor cs2 = cls.getDeclaredConstructor(String.class, String.class, String.class, boolean.class);
// 调用构造方法创建对象
cs.newInstance("寒战2", 35.6, "买摘花");
cs2.newInstance("无间道", "刘德华", "麦兆辉", true);
Constructor cs3 = cls.getDeclaredConstructor();
cs3.newInstance();
}
public static void test49() throws Exception {
Class cls = Class.forName("java.lang.String");
// 获取父类
Class clsParent = cls.getSuperclass();
System.out.println(clsParent.getName());
// 获取所有实现的接口
Class[] ims = cls.getInterfaces();
for (Class class1 : ims) {
System.out.println(class1.getName());
}
}
注解,或者叫注释类型,也是一种引用类型,编译后生成.class文件
注解定义时的语法格式:
[修饰符列表] @interface 注解类型名{}
注解使用时的语法格式:
@注解类型名
注解可以出现在类上、方法上、属性上、变量上,形参上、枚举上等 。。。,注解还可以出现在注解类型上;
默认情况下,注解可以出现在任意位置;
@Override:这个注解只能注解方法,这个注解是给编译器参考的,和运行阶段没关系,凡是java中的方法带有这个注解的,编译器都会进行编译检查,如果这个方法不是重写父类的方法,编译器报错;
用来标注注解类型的注解,称为元注解;
下面的Targer就是元注解;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
常见的元注解:
Targer:用来标注被标注的注解可以出现在哪些位置上;
Retention:用来标注被注解的文件最终保存到哪里;
@Target(ElementType.METHOD)表示被标注的注解只能出现在方法上;
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})表示该注解可以出现在构造方法上、字段上、局部变量上、方法上、包上、模块上、参数上、类上
@Retention(RetentionPolicy.SOURCE)表示该注解只被保留在java源文件中;
@Retention(RetentionPolicy.CLASS)表示该注解被保存在class文件中;
@Retention(RetentionPolicy.RUNTIME)表示该注解被保存在class文件中,并且可以被反射机制所读取;
@Deprecated这个注解标识的元素已过时,这个注解的作用主要是向其他程序员传达一个信息,告知已过时,有更好的解决方案存在;
注解里面如果有属性的话,必须给属性赋值,如果属性有设置默认值,可以不用赋值;
public @interface MovieAnnotation {
/**
* 我们通常在注解当中可以定义属性,一下这个是MovieAnnotation当中的name属性
* 看着像方法,但我们称之为属性name
* 电影名称
*
* @return
*/
String name();
double price();
int year() default 2009;
}
//使用
@MovieAnnotation(value = "国产电影", name = "寒战2", price = 22.5)
public static void doOther() {
System.out.println("doOther");
}
如果一个注解的属性名是value,并且只有这个属性名的时候,那么该注解在使用的可以只写一个值;
public @interface MovieAnnotation {
String value();
}
@MovieAnnotation("国产电影")
注解当中的属性类型:byte,short,int,long,char,boolean,float,double,String,枚举,Class,与及以上每种数据类型的数组形式;
注解的数组用大括号:
public @interface MovieAnnotation {
String name();
int year();
String[] players();
}
@MovieAnnotation(name = "寒战2", year = 2015, players = { "郭富城", "刘家辉" })
public static void doOther() {
System.out.println("doOther");
}
//如果数组中只有一个元素,大括号可以省略
@MovieAnnotation(name = "寒战2", year = 2015, players = "郭富城")
public static void doOther2() {
System.out.println("doOther");
}
枚举类型的使用:
public @interface MovieAnnotation {
String name();
Mtype[] mts();
}
enum Mtype {
爱情,
战争,
悬疑
}
@MovieAnnotation(name = "寒战2",mts = {Mtype.爱情,Mtype.悬疑})
public static void doOther3() {}
@MovieAnnotation(name = "寒战2", mts = Mtype.爱情)
public static void doOther4() {}
//数组只有一个元素
public @interface MovieAnnotation {
Mtype[] value();
}
@MovieAnnotation({ Mtype.爱情, Mtype.悬疑 })
public static void doOther3() {}
@MovieAnnotation(Mtype.爱情)
public static void doOther4() {}
通过反射获取注解
//标注该注解只能使用在方法和类上
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })
// 标注该注解可以被反射
@Retention(RetentionPolicy.RUNTIME)
public @interface MovieAnnotation {
String name() default "电影注解咚咚咚";
int year() default 2018;
}
@Deprecated
@MovieAnnotation(name = "后天")
public class MovieAnnotationTest {}
public static void test50() throws Exception {
Class stringClass = Class.forName("ReflectTest.MovieAnnotationTest");
// 先判断类上是否有MovieAnnotation注解
if (stringClass.isAnnotationPresent(MovieAnnotation.class)) {
// 获取注解类
MovieAnnotation ma = (MovieAnnotation) stringClass.getAnnotation(MovieAnnotation.class);
System.out.println("注解:" + ma);
// 获取注解的属性值
System.out.println(ma.name());
}
// 获取方法上的注解属性
Method mtd = cls.getDeclaredMethod("doSome");
if (mtd.isAnnotationPresent(MovieAnnotation.class)) {
MovieAnnotation ma2 = mtd.getAnnotation(MovieAnnotation.class);
System.out.println(ma2.name());
System.out.println(ma2.year());
}
}
注解标识的类必须包含int id属性,否则抛出异常,demo
//该注解标识的类必须包含int id属性,否则抛出异常
//只能要来标识类
@Target(ElementType.TYPE)
// 编译后保留在class文件中,并且可以被反射
@Retention(RetentionPolicy.RUNTIME)
public @interface HasIdAnnotation {}
@HasIdAnnotation
public class TestAnnotation {
int id;
String name;
int age;
}
public static void test51() throws Exception {
Class cls = Class.forName("ReflectTest.TestAnnotation");
if (cls.isAnnotationPresent(HasIdAnnotation.class)) {
boolean flag = false;
Field[] fs = cls.getDeclaredFields();
for (Field field : fs) {
if ("id".equals(field.getName()) && "int".equals(field.getType().getSimpleName())) {
flag = true;
}
}
if (!flag) {
throw new NotHasIdException("该类必须包含int id字段");
}
}
}