类加载机制
类加载机制当调用某个类时,系统会通过加载,连接,初始化三个步骤来对该类进行初始化操作。
加载
-
加载是指将类的字节码文件(.class)加载到内存中,并为之创建一个java.lang.Class对象,称为字节码对象。
-
加载过程由类加载器控制完成,类加载器通常由Jvm提供,也可以通过继承classloader自定义类加载器。
-
当加载结束,类的二进制数据会合并到Jre中。
连接
-
验证:检测被加载类的内部结构是否正确。
-
准备:负责为类的static变量分配内存,并设置默认值。
-
解析:把类的二进制数据中的符号引用,替换为直接引用。
初始化
-
在此阶段,Jvm负责对类进行初始化,主要是对static变量进行初始化。
-
如果类中有初始化语句(静态代码块),则系统依次执行这些初始化语句。
-
如果该类的直接父类还未被初始化,则先初始化其父类。
-
如果该类还未被加载和连接,则先加载并连接该类。
反射
反射是指获得类的元数据的过程,在运行时期,动态的获取某一个类中的成员信息(构造器,方法,字段,内部类,接口,父类等),反射损耗性能。
-
当字节码文件加载到内存时,Jvm会对其进行解刨,分析其成员,创建对应的Class对象,将该字节码文件的信息全部存储到Class对象中。
-
通过操作该Class对象,就可以获取使用该字节码文件的信息。
获取Class的三种方式
public static void demo() throws ClassNotFoundException {
// 1.通过完整类名获取Class,推荐这种
Class clz = Class.forName("domain.PersonDemo");
// 2.通过类来获取
Class clz1 = PersonDemo.class;
// 3.通过对象来获取
Class clz2 = new PersonDemo().getClass();
}
public class PersonDemo {
private String name;
private int age;
public PersonDemo() {
}
// 私有构造函数
private PersonDemo(String name) {
this.name = name;
}
public PersonDemo(String name, int age) {
this.name = name;
this.age = age;
}
public void test() {
System.out.println("获取公共方法");
}
private void test(String string) {
System.out.println("获取私有方法");
}
@Override
public String toString() {
return "PersonDemo{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
1.获取构造方法
public static void demo() throws ClassNotFoundException {
Class clz = Class.forName("domain.PersonDemo");
// 获取一个类所有非私有的构造方法
Constructor[] cons = clz.getConstructors();
for (Constructor con : cons) {
System.out.println(con);
}
// 获取一个类所有构造法方法(包括私有)
Constructor[] declaredCons = clz.getDeclaredConstructors();
for (Constructor con : declaredCons) {
System.out.println(con);
}
}
通过Class获取单个构造方法,生成对象
public static void demo() throws Exception {
Class clz = Class.forName("domain.PersonDemo");
// 获取单个构造方法(不能取出私有构造方法)
// 传入一个可变参数,对应构造方法的参数类型
Constructor constructor = clz.getConstructor(String.class, int.class);
// 通过构造方法创建对象
PersonDemo personDemo = (PersonDemo) constructor
.newInstance("公共构造函数", 18);
System.out.println(personDemo);
// 获取单个构造方法(包括私有构造方法)
Constructor declaredConstructor = clz.getDeclaredConstructor(String.class);
//暴力反射
declaredConstructor.setAccessible(true);
PersonDemo personDemo1 = (PersonDemo) declaredConstructor
.newInstance("私有构造函数");
System.out.println(personDemo1);
}
2.获取方法(Method)
public static void demo() throws Exception {
Class clz = Class.forName("domain.PersonDemo");
// 获取所有公共方法(包括父类继承下来的)
Method[] methods = clz.getMethods();
for (Method method : methods)
System.out.println(method);
// 获取所有方法(不包括父类继承下来的)
Method[] declaredMethods = clz.getDeclaredMethods();
for (Method method : declaredMethods)
System.out.println(method);
}
获取单个方法,调用
public static void demo() throws Exception {
Class clz = Class.forName("domain.PersonDemo");
PersonDemo personDemo = new PersonDemo();
// 获取单个方法,传入参数要获取的方法名,和对应参数类型
Method method = clz.getMethod("test", null);
//调用,传入参数方法的调用对象,方法所需要的参数
method.invoke(personDemo, null);
Method declaredMethod = clz.getDeclaredMethod("test", String.class);
declaredMethod.setAccessible(true);
declaredMethod.invoke(personDemo, "私有方法参数");
//静态的调用传null即可
//method.invoke(null, "测试参数");
//数组类型
//clz.getDeclaredMethod("test", String[].class);
}
获取成员变量
public static void demo() throws Exception {
Class clz = Class.forName("domain.PersonDemo");
PersonDemo personDemo = new PersonDemo();
Field[] fields = clz.getFields();
for (Field field : fields) {
System.out.println(field);
}
//获取私有的
Field[] declaredFields = clz.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println(field);
}
//获取单个私有属性
Field field = clz.getDeclaredField("name");
field.setAccessible(true);
field.set(personDemo, "给私有属性赋值");
System.out.println(personDemo);
}
获取静态成员
public class PersonDemo {
public PersonDemo() {
}
private static String name;
private void setName(String name) {
this.name = name;
}
public static String getName() {
return name;
}
}
public static void demo() throws Exception {
Class clz = Class.forName("domain.PersonDemo");
//当构造方法为public且无参时可以用此直接构建对象
PersonDemo personDemo = (PersonDemo) clz.newInstance();
Field name = clz.getDeclaredField("name");
name.setAccessible(true);
//因为是静态的可以不传对象
name.set(null,"为静态属性赋值");
Method method = clz.getDeclaredMethod("setName", String.class);
method.setAccessible(true);
method.invoke(personDemo,"更改赋值");
System.out.println(PersonDemo.getName());
}
获取数组(可变参数)
public class PersonDemo {
public PersonDemo() {
}
public void getStringArr(String... arrs) {
System.out.println(Arrays.toString(arrs));
}
public void getIntArr(int[] intarr, int i) {
System.out.println(Arrays.toString(intarr) + ":" + i);
}
}
public static void demo() throws Exception {
Class clz = Class.forName("domain.PersonDemo");
PersonDemo personDemo = (PersonDemo) clz.newInstance();
Method getStringArr = clz.getMethod("getStringArr", String[].class);
getStringArr.invoke(personDemo,
new Object[]{ //用一个Object包裹
new String[]{"1", "2", "3", "4", "5"}});
Method intArr = clz.getMethod("getIntArr", int[].class, int.class);
intArr.invoke(personDemo,
new Object[]{
new int[]{5, 4, 3, 2}, 1});
}
Class常用API
public class Demo {
public static void main(String[] args) {
String name = Demo.class.getName();
System.out.println("获取完整类名:" + name);
String simpleName = Demo.class.getSimpleName();
System.out.println("获取类名:" + simpleName);
Class object = Demo.class.getSuperclass();
System.out.println("获取父类:" + object);
Class[] IClass = Demo.class.getInterfaces();
System.out.println("获取接口:" + Arrays.toString(IClass));
int modifier = Demo.class.getModifiers();
System.out.println("获取修饰符:" + modifier);
String packageName = Demo.class.getPackage().getName();
System.out.println("获取包名:" + packageName);
}
}
加载资源路径
public class Demo {
public static void main(String[] args) throws IOException {
test2();
}
//方式1使用绝对路径
public static void test1() throws IOException {
Properties properties = new Properties();
properties.load(new FileInputStream("E:\\AndroidFile\\Repeat\\resource\\db.properties"));
System.out.println(properties);
}
//方式2使用相对路径,相对于classPath字节码输出的目录即bin目录,常用
public static void test2() throws IOException {
Properties properties = new Properties();
//使用ClassLoader类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
InputStream inputStream = classLoader.getResourceAsStream("db.properties");
properties.load(inputStream);
System.out.println(properties);
}
//方式3使用相对路径,相对于当前加载资源文件的字节码路径
public static void test3() throws IOException {
Properties properties = new Properties();
//这里需要注意用Class.getResourceAsStream同ClassLoader的路径不一样
InputStream inStream = Demo.class.getResourceAsStream("db.properties");
properties.load(inStream);
System.out.println(properties);
}
}
根据配置文件生成类
//定义接口规范
public interface IWork {
void work();
}
//接口实现类
public class Key implements IWork {
@Override
public void work() {
System.out.println("Load Key");
}
}
public class Mouse implements IWork {
@Override
public void work() {
System.out.println("Load Mouse");
}
}
//配置文件
Key=完整类名
Mouse=完整类名
public class Computer {
//声明配置文件
private static Properties properties = new Properties();
static {
//获取相对路径bin(classPath)
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
InputStream inStream = classLoader.getResourceAsStream("load.properties");
try {
properties.load(inStream);
//获取包名,反射生成类
Collection<Object> values = properties.values();
for (Object item : values) {
String name = (String) item;
Class<?> className = Class.forName(name);
IWork work = (IWork) className.newInstance();
work(work);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void work(IWork work) {
work.work();
}
}
public class Run {
public static void main(String[] args) {
Computer computer = new Computer();
}
}
注解
从JDK5开始,Java开始支持元数据Annotation(元数据即描述数据的数据),通常用来为程序元素(类,方法,成员变量等)设置元数据。
注解是一种特殊的接口
public @interface Override {
}
常见注解
@Override 标记重写的父类方法
@Deprecated 标记过时,不推荐使用的方法和类
@SuppressWarings 抑制编译器发出的警告,仅仅是看不到警告(问题依然存在)
@SuppressWarings("all") 抑制所有的警告
@SafeVarargs 抑制堆污染发出的警告,同时出现可变参数和泛型。
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
元注解
自定义注解时,用来贴在注解上的注解,用来制定自定义注解的使用范围。
@Retention 决定注解可以保存到哪个时期。
@Target 决定了该注解可以贴在什么地方
注解的使用
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
public @interface Demo {
//抽象方法 属性
}
@Retention(RetentionPolicy.RUNTIME)
注解的有效期有三个,封装在枚举:RetentionPolicy类中。
RetentionPolicy.SOURCE
表示注解只存在于源文件中,不会被编译到字节码中。
RetentionPolicy.CLASS
表示注解会被编译到字节码中,但是JVM不加载注解。
RetentionPolicy.RUNTIME
表示注解会被编译到字节中,随着字节码加载进JVM,可以通过反射读取。
@Target({ElementType.TYPE})
决定了该注解可以贴在什么地方
ElementType.PACKAGE:贴在包上(极少使用)
ElementType.ANNOTATION_TYPE:贴在注解上
ElementType.TYPE: 贴在类、接口或枚举上
ElementType.CONSTRUCTOR:贴在构造方法上
ElementType.METHOD:贴在方法上
ElementType.FIELD:贴在字段上(包括枚举常量)
ElementType.LOCAL_VARIABLE:贴在局部变量上
ElementType.PARAMETER: 贴在参数上
@Documented 表示注解会被javadoc指令编辑到API中
@Inherited 表示注解会遗传给子类
注解的使用
//自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
public @interface AnnotationDemo {
int level() default 1;
String info();
String[] data();
}
//使用注解
@AnnotationDemo(level = 1, info = "注解演示", data = {"data1", "data2"})
class Emp {
}
//获取注解属性
public static void demo() throws ClassNotFoundException {
//通过反射找到注解依附的类
Class clz = Class.forName("domain.Emp");
//判断是否有这个类型的注解
if (clz.isAnnotationPresent(AnnotationDemo.class)) {
//获取注解
AnnotationDemo anno = (AnnotationDemo)
clz.getAnnotation(AnnotationDemo.class);
//获取属性
System.out.println(anno.level());
System.out.println(anno.info());
System.out.println(Arrays.toString(anno.data()));
}
}
动态代理
interface IDataInfo {
int add(int i, int j);
int sub(int i, int j);
}
class DataInfoImpl implements IDataInfo {
@Override
public int add(int i, int j) {
return i + j;
}
@Override
public int sub(int i, int j) {
return i - j;
}
}
public static void demo() {
DataInfoImpl dataInfoImple = new DataInfoImpl();
IDataInfo proxy = (IDataInfo) Proxy.newProxyInstance(
dataInfoImple.getClass().getClassLoader(),
new Class[]{IDataInfo.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("在代理真实对象前我们可以添加一些自己的操作");
//当代理对象调用真实对象的方法时,
//会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
Object res = method.invoke(dataInfoImple, args);
System.out.println(res.toString());
System.out.println("在代理真实对象后我们可以添加一些自己的操作");
return res;
}
});
proxy.add(1, 1);
}
网友评论