参考文献:https://www.bilibili.com/video/BV1p4411P7V3
参考文献:https://blog.csdn.net/codeyanbao/article/details/82875064
类的加载过程:
Java的内存分析
堆
- 存放new的对象和数组
- 可以被所有的线程共享,不会存放别的对象引用
-
栈
- 存放基本变量类型(会包含这个基本类型的具体数值)
- 引用对象的变量(会存放这个引用在堆里边的地址)
-
方法区(Java 1.8 中已经没有这个概念了)
- 可以被所有的县成功向
- 包含了所有的Class和static变量
-
类加载过程
- 当程序主动使用某个类时,如果该类还没有被加载到内存中,系统会通过如下三个步骤来对该类进行初始化。
- 类的加载 Load -> 类的链接 Link -> 类的初始化 initialize
-
双亲委派机制
类在加载过程中,是有很多个加载器的,顺序依次是:
CustomClassLoader < AppClassLoader < ExtClassLoader < BootStrapClassLoader
BootStrapClassLoader为根加载器,使用C的最基础的加载功能。
通常在使用一个类的时候,会启动最底层的加载器,然后查看子类是否加载过,一层一层传递到顶层,如果都没有加载过,在由顶层判断是否可以加载,一层层传递到底层。
注解
-
注解Annotation的概念
1. 注解不是程序本身,可以对程序做解释,并且可以通过JDT来生成代码影响程序。
如:@override 不会去影响原始代码的功能,但是会在编译时生成代码检测是否为覆盖父类方法的写法。
2. 注解的使用范围:
package,class,filed,method
3. 注解有一定的约束,所以必须按照一定的格式使用
内置注解
Override:定义在java.lang.Overrride中,此注解表示一个方法声明打算重写父类中的一个方法声名。
Deprecated:定义在java.lang.Deprecated中,表示不推荐使用这个field,class,method,其他程序调用该注解修饰的元素时会给与不推荐使用的提示。
SuppressWarnings:定义在java.lang.SuppressWarnings中,写在class,method中用来抑制编译时的警告信息。需要添加部分参数才可以使用:
SuppressWarning("all")
SuppressWarning("unchecked")
SuppressWarning(value={"unchecked","deprecation"})
....
元注解
元注解的作用是负责注解其他注解,自定义注解时需要使用元注解。
Java定义了4个标准的meta-annotation类型:
@Target:描述注解的使用范围
@Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期( Source < Class < Runtime )
@Document:说明该注解将被包含在javaDoc中
@Inherited:说明子类可以继承父类中的该注解
实例代码:
//自定义一个注解,类前需要使用@interface表示这个是一个注解
//使用位置:类方法
@Target(value={ElementType.TYPE,ElementType.METHOD})
//使用生效范围:运行时注解生效runtime>class>sources
@Retention(RetentionPolicy.RUNTIME)
//该注解被写入到JavaDoc中
@Documented
//改注解可以被子类继承
@Inherited
@interface MyAnnotation{
}
@Target(value={ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@interface MyAnnotation2{
//注解的参数:参数类型+参数名()
//如果增加了default表示可以不写,后边自动给默认值
Stringname() default"";
intage();
String[] schools() default{"school1","school2"};
}
反射
- 反射的概念
Reflection是Java被视为动态语言的关键,反射机制允许程序在执行期借助Refection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
Class s = Class.forName("java.lang.String")
- 类的信息
类加载完成之后,内存中会出现一个Class类型的对象,这个对象里包含了预置类型的所有基本信息。我们可以通过这个对象看到类内的结构,可以获得如下信息:
- 运行时判断一个对象所属的类
- 运行时判断一个类所具有的成员变量和方法
- 运行时获取泛型信息
- 运行时调用一个对象的成员变量和方法
- 运行时处理注解
- 生成动态代理(AOP中常用)
- 类的equal和==
一个类,在JVM中只能生成一个.class类,无论被实现了多少次。这个Class相当于是一个模板。
public static void main(String[] args) {
String a = "abc";
String b = "xyz";
Class class1 = a.getClass();
Class class2 = b.getClass();
if (class1.equals(class2)) {
System.out.println("class1 equals class2");
}
if (class1 == class2) {
System.out.println("class1 == class2");
}
}
会输出class1和class2一样
- 获取Class的方式
- 通过已经实例化后的对象,使用getClass方法获得
- 通过类的全路径,使用forname获得
注意:如果出现多态情况,要根据实际开启内存的类型来确定Class获取的类型
public class ReflectTest2{
public static void main(String[]args) throws ClassNotFoundException{
Personperson=newStudent();
//方式一:通过对象获得
Classc1=person.getClass();
System.out.println(c1.hashCode());
//方式二:通过forname方式获得
Classc2=Class.forName("reflection.Person");
System.out.println(c2.hashCode());
//方式三:通过.class获得
Classc3=Student.class;
System.out.println(c3.hashCode());
//方式四:通过内置类型的包装类都有一个Type属性获得(得提前设定)
Classc4=Integer.TYPE;
System.out.println(c4.hashCode());
//方式五:在获取的class基础上,获得父类的类型
Classc5=c1.getSuperclass();
System.out.println(c5.hashCode());
}
}
class Person{
public String name;
public Person(){
}
}
class Student extends Person{
public Student(){
this.name="student";
}
}
- 动态创建对象(使用反射创建对象)
- 创建对象:使用newInstance()方法进行类实例化
- 执行方法:通过getMethod得到Method类型的方法,之后通过invoke对象进行调用(private方法需要显示调用setAccessible(true)方法)
- 调用常量:通过调用getDeclaredField()获取常量信息,之后使用get()和set()方法就可以获取和设置属性的值
/**
*通过反射构建一个类,并使用类里的数据
*
*@paramargs
*@throwsClassNotFoundException
*/
@SneakyThrows
public static void main(String[] args) throws ClassNotFoundException{
Classc1=Class.forName("reflection.User");
//通过反射创建一个类
Useruser=(User)c1.newInstance();
//通过反射,获得一个方法invoke激活这个方法,并且只调用这个方法,
MethodsetName=c1.getDeclaredMethod("setName",String.class);
setName.invoke(user,"peter");
//通过反射,获得一个field
Fieldname=c1.getDeclaredField("name");
name.set(user,"tony");
}
注意,操作的时候不能对private field进行操作。如果必须要进行操作,需要将field的属性设置成true
name.setAccessible(true),开启对私有类型的检测的权限。
增加这个访问权限,本质是减少了检测method和field的权限类型,所以增加了可访问性,可以提高效率。
- 反射操作泛型(软件分析,通过反射来获取方法中泛型中的类型)
通过反射的方式获取方法中的泛型的类型。
需要将方法提取出来,获取genericParameterTypes,之后将genericParameterTypes转换为ParameterTypes,调用ActualTypeArguments获取实际的类型。
其实和软件分析取类型是一样的方式。
// 参数的类型
public void method1 (Map<String,User>map, List<User>list){
System.out.println("method1");
}
public void getmethodParamType() throws NoSuchMethodException{
Methodmethod=ReflectTest4.class.getDeclaredMethod("method1",Map.class,List.class);
Type[] genericParameterTypes=method.getGenericParameterTypes();//获取方法的参数类型
for(TypegenericParameterType:genericParameterTypes){
System.out.println("#"+genericParameterType);
if(genericParameterTypeinstanceofParameterizedType){
Type[] types=((ParameterizedType)genericParameterType).getActualTypeArguments();
for(TypetypeResult:types){
System.out.println("@"+typeResult);
}
}
}
}
// 返回类型的查询
public Map<String,User>method2(){
System.out.println("method2");
return null;
}
public void getMethodReturnType() throws NoSuchMethodException{
Methodmethod=ReflectTest4.class.getDeclaredMethod("method2");
Typegeneric ReturnType=method.getGenericReturnType();
if(genericReturnTypeinstanceofParameterizedType){
Type[] types=((ParameterizedType)genericReturnType).getActualTypeArguments();
for(Typetype:types){
System.out.println("ReturnType:"+type);
}
}
}
- 反射操作注解
类似于获取method参数类型
- 使用Class class = Class.forName()获取类型
- 调用class的getAnnotation,来获取注解信息
- 获取注解之后在继续根据实际需要的东西取具体的值
注解可以通过反射操作源代码,程序可以通过反射获取注解内的信息从而实现数据库相关的操作。
- 反射的效率
使用反射方法时,需要注意反射调用的效率,通常情况下直接调用方法的效率最高,反射调用(去除检查)的效率其次,直接调用反射方法的效率最低。
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
Class c1 = user.getClass();
long startTime1 = System.currentTimeMillis();
for (int i = 0; i < Integer.MAX_VALUE; i++) {
user.getAge();
}
System.out.println("直接调用时间:" + (System.currentTimeMillis() - startTime1) + "ms");
Method method = c1.getDeclaredMethod("getAge");
long startTime2 = System.currentTimeMillis();
for (int i = 0; i < Integer.MAX_VALUE; i++) {
method.invoke(user, null);
}
System.out.println("反射取方法的时间:" + (System.currentTimeMillis() - startTime2) + "ms");
Method method3 = c1.getDeclaredMethod("getAge");
method3.setAccessible(true);
long startTime3 = System.currentTimeMillis();
for (int i = 0; i < Integer.MAX_VALUE; i++) {
method3.invoke(user, null);
}
System.out.println("反射取方法的时间(取消检测):" + (System.currentTimeMillis() - startTime3) + "ms");
}
直接调用时间:3ms
反射取方法的时间:2656ms
反射取方法的时间(取消检测):2524ms
反射效率低的原因:method invoke方法会对参数做封装和解封;检查方法的可见性; 校验参数;
反射取消检测效率高是因为少了检查方法的可见性一个环节。