笔记是根据狂神的B站视频,以及自己的理解做出来的。如果有说错的地方,望大佬能够指出,相互学习!
注解和反射
注解
- 和注释一样,注解不是程序本身,而是对程序作出解释,而注解与注释不同的点在于,注解可以被其他程序比如编译器读取
//内置注解
@Override//重写注解
@Deprecated//不推荐使用注解,可以使用但是又风险或者有更好的方式
@SuppressWarnings//“镇压”警告注解
元注解
元注解 重点需要了解 @Target @Retention
- 元注解的作用就是注解其他注解,Java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型做说明
- 4个元注解分别为:
- @Target:用于描述注解的使用范围
- @Retention:用于表示需要在什么级别保存注解信息,用于描述注解的声明周期,(SOURCE<CLASS<RUNTIME)
- @Document:说明该注解将被包含在javadoc(文档)中
- @Inherited:说明子类可以继承父类中的该注解
测试元注解
//测试元注解
@MyAnnotation
public class Test02 {
public void test(){
}
}
//定义注解
//Target 表示我们的注解可以用在哪些地方 METHOD方法 TYPE 类
@Target(value = {ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented //表示把注解生成在Javadoc中
@Inherited //表示可以被继承
@interface MyAnnotation{
}
自定义元注解
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口
public class Test03 {
//注解可以显示赋值,如果没有默认值,就必须给注解赋值
@MyAnnotation2(schools = {"贵州大学","清华大学"})
public void test(){
}
//如果注解只有一个参数可以省略参数名,或value
@MyAnnotation3("")
public void test2(){
}
}
//注解的写法
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
//注解的参数:类型+参数名() [default 默认值];
String name() default "";
int age() default 0;
int id() default -1; //如果默认值为-1,代表不存在
String[] schools();
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation3{
//如果注解只有一个值 可以直接使用value
String[] value(); //不成名的一个规范
}
反射机制
动态语言和静态语言
动态语言:
- 在运行时可以改变其结构:例如新的函数、对象甚至代码可以被引进,已有的函数可以被删除或者是其他结构上的变化。通俗来说就是运行时代码可以根据一些条件来改变自身的结构。
- 主要动态语言:Object-C、C#、JavaScript、PHP、Python等。
这里演示下 javaScript :弱语言(是比较随意的)
function f(){
var x="var a=3;var b=5;alert(a+b)";
eval(x); //执行
}
静态语言:
- 与动态语言相对应的,运行时不能改变其结构,如Java、C、C++
- Java不是动态语言,但是java可以称为是“准动态语言”。即java有一定的动态性,可以利用反射机制获得类似动态语言的特性。Java的动态性使得编程时更加灵活。
Java Reflection
-
Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API获取任何类的内部信息(比如类名,类的接口,类的方法,字段,属性…),并且能够直接操作任意对象的内部属性及方法。
Class c = Class.forName("java.lang.String")
-
加载完类之后,在堆内存中的方法区中间产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射
java 放射机制及应用
java反射优点和缺点
优点:
- 可以实现动态创建对象和编译,体现出很大的灵活性
缺点:
- 对性能有影响,使用反射基本上是一种解释操作,我们可以告诉JVM 我们希望做什么并且它满足我们的需求。这类操作总是慢于 直接执行相同的操作。
反射相关的主要API
代码演示:
这里提到的hash code:是一种编码方式,在Java中,每个对象都会有一个hashCode,Java可以通过这个hashCode来识别一个对象。
public class Test02 {
public static void main(String[] args) throws ClassNotFoundException {
//通过反射获取类的class对象
Class c1 = Class.forName("com.javacto.reflection.User");//需要全限定名
System.out.println(c1); //class com.javacto.reflection.User
Class c2 = Class.forName("com.javacto.reflection.User");
Class c3 = Class.forName("com.javacto.reflection.User");
//打印hashcode可以看出一个类在内存中只有一个Class对象,不可能有多个
//一个类被被载后,类的整个结构都会被封装在Class对象中
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
}
}
//实体类 :pojo entity
class User{
private String name;
private int id;
private int age;
/**
* 无参构造方法
*/
public User() {
}
/**
* 有参构造方法
* @param name
* @param id
* @param age
*/
public User(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
'}';
}
}
运行结果:
Class类
从这张图可以看出:可以通过对象反射出类的名称 这就是反射
- Class类的常用方法
方法名 | 说明 |
---|---|
static Class forName(String name) | 返回指定类名name对应的Class对象 |
Object newInstance() | 调用缺省构造函数,返回Class对象的一个实例 |
String getName() | 返回此Class对象所表示的实体(类、接口、数组类或者void)的名称 |
Class getSuperClass | 返回当前Class对象的父类Class对象 |
Class[] getinterfaces() | 获取当前Class对象的接口 |
ClassLoader getClassLoader() | 返回该类的加载器 |
Constructor[] getConstructors() | 返回一个包含某些Constructor对象的数组 |
Method getMothed(String name,Class… T) | 返回一个Method对象,此对象形参类型为param Type |
Fied[] getDeclaredFields() | 返回Field对象的一个数组 |
-
获得Class类的实例
a)如果已有具体的类,通过类的class属性获取,最为安全可靠且性能最高的方法。
C l a s s 变 量 = X X X . c l a s s ; Class 变量=XXX.class; Class变量=XXX.class;b)已知某个类的实例,调用此实例的getClass()方法获取Class对象。
C l a s s 变 量 = x x x . g e t C l a s s ( ) ; Class 变量=xxx.getClass(); Class变量=xxx.getClass();c)已知一个类的全名且在类路径下,可以通过Class类的静态方法forName()获取,需要处理异常ClassNotFoundException
C l a s s 变 量 = C l a s s . f o r N a m e ( " 已 知 类 的 全 限 定 名 " ) ; Class 变量=Class.forName("已知类的全限定名"); Class变量=Class.forName("已知类的全限定名");d)内置基本数据类型可以直接使用类名.Type
e)还可以用ClassLoader(之后讲解)
测试class 类的创建方式有哪些
public class Test03 {
public static void main(String[] args) throws ClassNotFoundException {
Person person=new Student();
System.out.println("这个人是:"+person.name);
//方式1:通过对象获得
Class c1=person.getClass();
System.out.println(c1.hashCode()); //460141958
//方式2:forName获得
Class c2=Class.forName("com.javacto.reflection.Student");
System.out.println(c2.hashCode()); //460141958
//方式3: 通过类名.class 获得
Class c3 = Student.class;
System.out.println(c3.hashCode()); //460141958
//---------------------------------------------------------------------------------------
//方式4: 基本内置类型的包装类都有一个 TYPE属性 类名.TYPE
Class c4 = Integer.TYPE;
System.out.println(c4); //int 默认的基本类型 (d.获得内置基本数据类型)
//获得父类类型
Class c5 = c1.getSuperclass();
System.out.println(c5); //class com.javacto.reflection.Person
}
}
//定义一个父类
class Person{
public String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
//子类1: 学生
class Student extends Person{
public Student(){
this.name="学生";
}
}
//子类2:老师
class Teacher extends Person{
public Teacher(){
this.name="老师";
}
}
运行结果:可以看到 前三种通过不同的方式获得Class对象一样(这个class对象是Student,因为对象实例化的是Student这个类)
- 哪些类型可以有Class对象?
- class:外部类、成员(成员内部类、静态内部类),局部内部类,匿名内部类。
- interface:接口
- []:数组
- enum:枚举
- annotation:注解 (也是一个类型)
- primitive type:基本数据类型
- void
所有类的Class对象
public class Test04 {
public static void main(String[] args) {
Class c1= Object.class;//类
Class c2= Comparable.class;//接口
Class c3= String[].class;//一维数组
Class c4= int[][].class;//二维数组
Class c5=Override.class;//注解
Class c6= ElementType.class;//枚举
Class c7= Integer.class;//基本数据类型
Class c8= void.class;//void (代表空类型)
Class c9= Class.class;//Class
System.out.println(c1); //class java.lang.Object
System.out.println(c2); //interface java.lang.Comparable
System.out.println(c3); //class [Ljava.lang.String;
System.out.println(c4); //class [[I
System.out.println(c5); //interface java.lang.Override
System.out.println(c6); //class java.lang.annotation.ElementType
System.out.println(c7); //class java.lang.Integer
System.out.println(c8); //void
System.out.println(c9); //class java.lang.Class
System.out.println("=======================");
//只要元素类型与维度一样,就是同一个class
int[] a=new int[10];
int[] b=new int[100];
int[][] c=new int[10][10];
System.out.println(a.getClass().hashCode());
System.out.println(b.getClass().hashCode());
System.out.println(c.getClass().hashCode());
}
}
查看运行结果:
- 注意:只要元素类型与维度一样,就是同一个class,反之 同样是int类型的数组,维度不同Class对象所打印出的hashcode不同,即:数组维度不同对应不同的Class对象。
类加载内存分析
java内存分析
了解:类的加载过程
类的加载与ClassLoader的理解
测试demo以及画图分析
public class Test05 {
public static void main(String[] args) {
A a =new A();
System.out.println(A.m);
/**
* 1.加载到内存,会产生一个对应Class对象
* 2.链接, 链接结束后 m=0
* 3.初始化
<clint>(){
System.out.println("A类静态代码块初始化");
m=300;
m=100;
}
*/
}
}
class A{
//无参构造方法
public A(){
System.out.println("A类的无参构造初始化");
}
//静态代码块
static {
System.out.println("A类静态代码块初始化");
m=300;
}
/**
* m=300
* m=100 (覆盖了上面的值)
*/
//静态变量
static int m=100;
}
基于图的分析步骤:
1.类写好后,编译成class加载到内存中(方法区可以说是特殊的堆,可以看到里面存储class相关的信息包含方法的信息)
2.在类加载的时候形成了class对象
3.准备执行main方法,这里的m是静态变量,链接段初始值为为0,这些内存都将在方法区中进行分配(栈内存空间:存放方法中的局部变量)
4.之后开始执行代码 堆内存(我的理解是只要new出来的就存在堆内存),
new A(); 产生新的对象 A类对象 (A类自己创建出来的对象)先去找到自己Class类,通过A类的具体方法进行赋值。
5.初始化,这一步是由jvm去执行的,赋值后通过 方法 初始化 详情看代码 最终结果就为m=100.
查看代码的运行结果:
额外补充:
静态代码块:用staitc声明,jvm加载类时执行,仅执行一次
构造代码块:类中直接用{}定义,每一次创建对象时执行。
执行顺序优先级:静态块,main(),构造块,构造方法。
有关 静态代码块,构造代码块,构造函数执行执行顺序查看该链接(写得很详细)
什么时候会发生初始化
类的主动应用(一定会发生类的初始化)
测试代码: 类什么时候会初始化
public class Test06 {
static {
System.out.println("Main类被加载");
}
public static void main(String[] args) throws ClassNotFoundException {
//1.主动引用
// Son son=new Son();
//反射也会产生主动引用
Class.forName("com.javacto.reflection.Son");
}
}
class Father{
static int b=2;
static {
System.out.println("父类被加载");
}
}
class Son extends Father{
static {
System.out.println("子类被加载");
}
static int m=100;
static final int M =1; //常量
}
运行效果:
可以看出:当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。且初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
什么时候不会发生初始化
类的被动引用,不会发生初始化
基于同样的代码测试如下:
public class Test06 {
static {
System.out.println("Main类被加载");
}
public static void main(String[] args) throws ClassNotFoundException {
//System.out.println("==========不会发生类的初始化===============");
//不会产生类的引用的方法
//1.通过子类引用父类的静态变量,不会导致子类初始化 因为static在链接阶段的时候已经存在了
//System.out.println(Father.b);
//2.只是一个数组 命了一个名和开辟了空间而已
//Son [] array= new Son[5];
//System.out.println(array.getClass());
//3.常量不会引起父类与子类的初始化,因为所有的常量以及静态变量,都是在链接阶段都已经赋了一个值了,初始化的时候已经存在了
//System.out.println(Son.M);
}
}
class Father{
static int b=2;
static {
System.out.println("父类被加载");
}
}
class Son extends Father{
static {
System.out.println("子类被加载");
}
static int m=100;
static final int M =1; //常量
}
1.对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,**只会触发父类的初始化而不会触发子类的初始化。**在本例中,虚拟机会先发现其父类Father还未被初始化,因此虚拟机将先初始化父类Father,而Son始终不会被初始化。
2.通过数组定义来引用类,不会触发此类的初始化
上述案例运行之后并没有任何输出,说明虚拟机并没有初始化类Son。但是,这段代码触发了另外一个名为 [Lcom.javacto.reflection.Son的类的初始化。从类名称我们可以看出,这个类代表了元素类型为Son的一维数组,它是由虚拟机自动生成的,直接继承于Object的子类,创建动作由字节码指令newarray触发。
3.常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化
常量不会引起父类与子类的初始化,因为所有的常量以及静态变量,都是在链接阶段都已经赋了一个值了,初始化的时候已经存在了
类加载器
类加载器的作用
注:类是有缓存的 是提高效率的 在加载的时间当中 如果有不需要的会垃圾回收器 (gc)回收这些class对象 。
java平台核心库:rt.jar 包
扩展类加载器 :ExtClassLoader:
系统类加载器: System Classloder ,也有的地方叫 AppClassLoader
java的运行环境jre -> lib 目录 运行所有的jar包都在这里面
测试获取加载器:
public class Test07 {
public static void main(String[] args) throws ClassNotFoundException {
//获取系统的类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println("系统类加载器:"+systemClassLoader);
//获取系统类加载的父类加载器--> 扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println("扩展类加载器:"+parent);
//获取扩展类加载器的父类加载器--> 根加载器 (c/c++ 写的 读取不到 返回 null)
ClassLoader parent1 = parent.getParent();
System.out.println("根加载器:"+parent1);
//测试当前类是哪个加载器加载的
ClassLoader classLoader = Class.forName("com.javacto.reflection.Test07").getClassLoader();
System.out.println("当前类加载器:"+classLoader);
//测试jdk 内置的类是谁加载的
classLoader=Class.forName("java.lang.Object").getClassLoader();
System.out.println("JDK内置类的加载器:"+classLoader); //根加载器加载的
}
}
运行结果:
可以看到 jdk内置类的加载器 就是java平台核心库,跟加载器加载的 因为用c++编写的的,无法直接获取 所有显示null。
扩充: 获得系统类加载器可以加加载的路径
如何获得系统类加载器可以加加载的路径
System.out.println(System.getProperty("java.class.path"));
//打印输出的结果 ,可以看到 java运行环境路径,扩展包类jar包 rt.jar包 包括我们自己写的工程,ide的jar包。当然如果我们的类不在这些地方就读取不到了,加载是有机制的
/*
D:\java\tools\jdk1.8.0_201\jre\lib\charsets.jar;
D:\java\tools\jdk1.8.0_201\jre\lib\deploy.jar;
D:\java\tools\jdk1.8.0_201\jre\lib\ext\access-bridge-64.jar;
D:\java\tools\jdk1.8.0_201\jre\lib\ext\cldrdata.jar;
D:\java\tools\jdk1.8.0_201\jre\lib\ext\dnsns.jar;
D:\java\tools\jdk1.8.0_201\jre\lib\ext\jaccess.jar;
D:\java\tools\jdk1.8.0_201\jre\lib\ext\jfxrt.jar;
D:\java\tools\jdk1.8.0_201\jre\lib\ext\localedata.jar;
D:\java\tools\jdk1.8.0_201\jre\lib\ext\nashorn.jar;
D:\java\tools\jdk1.8.0_201\jre\lib\ext\sunec.jar;
D:\java\tools\jdk1.8.0_201\jre\lib\ext\sunjce_provider.jar;
D:\java\tools\jdk1.8.0_201\jre\lib\ext\sunmscapi.jar;
D:\java\tools\jdk1.8.0_201\jre\lib\ext\sunpkcs11.jar;
D:\java\tools\jdk1.8.0_201\jre\lib\ext\zipfs.jar;
D:\java\tools\jdk1.8.0_201\jre\lib\javaws.jar;
D:\java\tools\jdk1.8.0_201\jre\lib\jce.jar;
D:\java\tools\jdk1.8.0_201\jre\lib\jfr.jar;
D:\java\tools\jdk1.8.0_201\jre\lib\jfxswt.jar;
D:\java\tools\jdk1.8.0_201\jre\lib\jsse.jar;
D:\java\tools\jdk1.8.0_201\jre\lib\management-agent.jar
D:\java\tools\jdk1.8.0_201\jre\lib\plugin.jar;
D:\java\tools\jdk1.8.0_201\jre\lib\resources.jar;
D:\java\tools\jdk1.8.0_201\jre\lib\rt.jar;
D:\java\project\ideaPro\Test\ProjectAll\out\production\注解和放射;
D:\java\常用工具\ideaIU-2019.3.2.win\lib\idea_rt.jar
*/
了解什么是双亲委派机制:
当某个类加载器需要加载某个.class
文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
双亲委派机制的作用
1、防止重复加载同一个.class
。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
2、保证核心.class
不能被篡改。通过委托方式,不会去篡改核心.clas
,即使篡改也不会去加载,即使加载也不会是同一个.class
对象了。不同的加载器加载同一个.class
也不是同一个Class
对象。这样保证了Class
执行安全。
创建运行时类的对象
获取运行时类的完整结构
通过反射获取运行时类的完整结构
- Field(字段)、Method(方法)、Constructor(构造器)、Superclass(父类)、Interface(接口)、Annotation(注解)
- 实现的全被接口
- 所继承的父类
- 全部的构造器
- 全部的方法
- 全部的Field
- 注解
- 。。。
操作很简单,总结只需要注意:
默认 只能找到 public 字段, 属性,方法 。。。。。
关键字:Declared 能找到全部的 字段, 属性,方法 。。。。。
//获得类的信息
public class Test08 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class c1 = Class.forName("com.javacto.reflection.User");
System.out.println("=========获得类的名字=============");
//类的名字
System.out.println(c1.getName());//包名+类名
System.out.println(c1.getSimpleName());//类名
/*
打印输出结果
com.javacto.reflection.User
User
*/
System.out.println("==============获得类的属性===================");
//获得类的属性
Field[] field1=c1.getFields();//只能找到public属性
for (Field f :field1) {
System.out.println(f); //因为没有定义public属性所以为无
}
Field[] field2=c1.getDeclaredFields();//找到全部的属性
for (Field f :field2) {
System.out.println(f);
}
/*
输出结果
private java.lang.String com.javacto.reflection.User.name
private int com.javacto.reflection.User.id
private int com.javacto.reflection.User.age
*/
//获得指定的属性
Field name=c1.getDeclaredField("name");
System.out.println("指定:"+name);
/*
输出结果
指定:private java.lang.String com.javacto.reflection.User.name
*/
//获得类的方法
System.out.println("==============获得类的方法===================");
Method[] methods=c1.getMethods();//获得本类及其父类的全部public方法
for (Method method:methods) {
System.out.println("正常的:"+method);
}
methods=c1.getDeclaredMethods();//获得本类的所有方法,包括私有的 不包括父类
for (Method method:methods) {
System.out.println("DeclaredMethods:"+method);
}
/*
打印输出结果
正常的:public java.lang.String com.javacto.reflection.User.toString()
正常的:public java.lang.String com.javacto.reflection.User.getName()
正常的:public int com.javacto.reflection.User.getId()
正常的:public void com.javacto.reflection.User.setName(java.lang.String)
正常的:public int com.javacto.reflection.User.getAge()
正常的:public void com.javacto.reflection.User.setId(int)
正常的:public void com.javacto.reflection.User.setAge(int)
正常的:public final void java.lang.Object.wait() throws java.lang.InterruptedException
正常的:public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
正常的:public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
正常的:public boolean java.lang.Object.equals(java.lang.Object)
正常的:public native int java.lang.Object.hashCode()
正常的:public final native java.lang.Class java.lang.Object.getClass()
正常的:public final native void java.lang.Object.notify()
正常的:public final native void java.lang.Object.notifyAll()
DeclaredMethods:public java.lang.String com.javacto.reflection.User.toString()
DeclaredMethods:public java.lang.String com.javacto.reflection.User.getName()
DeclaredMethods:public int com.javacto.reflection.User.getId()
DeclaredMethods:public void com.javacto.reflection.User.setName(java.lang.String)
DeclaredMethods:private void com.javacto.reflection.User.test()
DeclaredMethods:public int com.javacto.reflection.User.getAge()
DeclaredMethods:public void com.javacto.reflection.User.setId(int)
DeclaredMethods:public void com.javacto.reflection.User.setAge(int)
*/
//获得指定方法只需要在()中添加参数(方法名,方法参数)
//添加参数 实则是因为考虑到了 重载
System.out.println("============获得指定方法=============");
Method getName = c1.getMethod("getName", null);
Method setName = c1.getMethod("setName", String.class);
System.out.println(getName);
System.out.println(setName);
/*
打印输出结果
public java.lang.String com.javacto.reflection.User.getName()
public void com.javacto.reflection.User.setName(java.lang.String)
*/
System.out.println("=============获得指定的构造器===================");
//获得指定构造器
Constructor[] constructors=c1.getConstructors();//获得public方法
for (Constructor constructor : constructors) {
System.out.println("public:"+constructor);
}
constructors=c1.getDeclaredConstructors();//获得本类所有方法
for (Constructor c :constructors) {
System.out.println("全部:"+c);
}
//获得指定构造器 (String name, int id, int age)
Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
System.out.println("指定构造器:"+declaredConstructor);
/*
打印输出结果:
public:public com.javacto.reflection.User(java.lang.String,int,int)
public:public com.javacto.reflection.User()
全部:public com.javacto.reflection.User(java.lang.String,int,int)
全部:public com.javacto.reflection.User()
指定构造器:public com.javacto.reflection.User(java.lang.String,int,int)
*/
}
}
有了Class对象,能做什么
-
创建类的对象,调用Class对象的newInstance()方法
-
必须要有一个无参数的构造器
-
类的构造器的访问权限要够
-
-
通过获取Class对象的构造器创建
-
通过Class对象的getDeclaredConstructor(所需要的参数 Type.class) 方法获取本类指定参数类型的构造器
-
向构造器传入一个对象数组进去,里面包含此构造器所需要的各个参数
-
通过Constructor实例化对象
-
通过反射动态创建对象测试
public class Test09 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
//获得Class对象
Class c1 = Class.forName("com.javacto.reflection.User");
//构造一个对象
/* User user = (User)c1.newInstance(); //本质上调用了类的无参构造器
System.out.println(user); //User{name='null', id=0, age=0}*/
//通过构造器创建对象
Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
User user2 = (User) constructor.newInstance("小明", 1, 18);
System.out.println(user2); //User{name='小明', id=1, age=18}
//通过反射调用普通方法
User user3= (User) c1.newInstance();
//通过反射获取一个方法
Method setName = c1.getMethod("setName", String.class);
//invoke: 激活的意思 (对象, "方法需要的参数")
setName.invoke(user3,"小明3");
System.out.println(user3.getName());
System.out.println("========通过反射操作属性===========");
//通过反射操作属性
User user4= (User) c1.newInstance();
Field name = c1.getDeclaredField("name");
//不能直接操作私有属性, 我们需要关闭程序的安全检查,属性或者方法的 setAccessible(true)
name.setAccessible(true);
name.set(user4,"小明4"); //修改属性值
System.out.println(user4.getName());//setAccessible 默认为false 如果没有关闭将会报没有访问private的权限
//can not access a member of class com.javacto.reflection.User with modifiers "private"
}
}
执行结果:
调用指定的方法 invoke
invoke 激活的意思
(对象,“方法所需要的参数”)
setAccessible
启动和禁用安全检查的开关,默认为false (启动)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QJM02udB-1608384690640)(C:\Users\Cyj\AppData\Roaming\Typora\typora-user-images\image-20201218010740137.png)]
分析性能问题
public class Test10 {
//普通方式调用
public static void test01(){
User user=new User();
//开始时间
long starTime=System.currentTimeMillis();
//模拟10亿次不断获取名字所需要的时间
for (int i = 0; i < 1000000000; i++) {
user.getName();
}
//结束时间
long endTime=System.currentTimeMillis();
System.out.println("普通方式执行10亿次:"+(endTime-starTime)+"ms");
}
//反射方式调用
public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
Class c1 = user.getClass();
//获取指定方法
Method getName = c1.getDeclaredMethod("getName", null);
//开始时间
long starTime = System.currentTimeMillis();
//模拟10亿次不断获取名字所需要的时间
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user, null);
}
//结束时间
long endTime = System.currentTimeMillis();
System.out.println("反射方式执行10亿次:"+(endTime-starTime)+"ms");
}
//反射方式调用 关闭检测
public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
Class c1 = user.getClass();
//获取指定方法
Method getName = c1.getDeclaredMethod("getName", null);
getName.setAccessible(true);
//开始时间
long starTime = System.currentTimeMillis();
//模拟10亿次不断获取名字所需要的时间
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user, null);
}
//结束时间
long endTime = System.currentTimeMillis();
System.out.println("关闭检测执行10亿次:"+(endTime-starTime)+"ms");
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
test01();
test02();
test03();
//如果反射调用次数多的话,可以关闭这个检测 提高程序的一个效率
}
}
执行结果:
普通方式执行最快,
反射会比正常的效率慢,
关闭检测执行改善了反射调用效率问题
- 注意:其中setAccessible(true)方法调用后会关闭对应属性、方法的安全检查,但会改善反射调用的效率问题
如果反射调用频率高的话,可以关闭这个检查 以便提高程序的一个效率
获取泛型信息
-
反射操作泛型
-
Java采用泛型擦除的机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的问题,但是,一旦编译完成,所有和泛型有关的类型全部擦除。
思考该怎么获得? (之前有说过,类加载的时候就产生了Class对象,故class对象里面应该是有保留的)
-
为了通过反射操作这些类型,Java新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。
类型 | 说明 |
---|---|
ParameterizedType | 表示一种参数化类型,比如Collection< String > |
GenericArrayType | 表示一种元素类型是参数化类型或者类型变量的数组类型 |
TypeVariable | 是各种类型变量的公共父接口 |
WildcardType | 代表一种通配符类型表达式 |
通过反射获取泛型 扩展了解
public class Test11 {
/**
* 通过泛型传参
* @param map
* @param list
*/
public void test01(Map<String,User> map, List <User> list){
System.out.println("test01");
}
/**
* 通过泛型返回值
* @return
*/
public Map<String,User> test02(){
System.out.println("test02");
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
//获得参数类型
Method method = Test11.class.getMethod("test01", Map.class, List.class);
//获取参数类型 即Map和 List
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println("1:"+genericParameterType);
//判断genericParameterType参数类型 是否属于 ParameterizedType 参数化类型
if (genericParameterType instanceof ParameterizedType){
//如果属于参数化类型,获得他的真实类型 getActualTypeArguments
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
//再次输出真实的泛型信息
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("2:"+actualTypeArgument);
}
}
}
//获得返回值类型
method = Test11.class.getMethod("test02",null);
Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType){
//如果genericReturnType返回值类型属于参数化类型,获得他的真实类型 getActualTypeArguments
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
//再次输出真实的泛型信息
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("3:"+actualTypeArgument);
}
}
}
}
查看结果: 得到泛型
获取注解信息
反射操作注解
练习反射操作注解
public class Test12 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1 = Class.forName("com.javacto.reflection.Student2");
//通过反射获取注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//获得注解的value的值 获取指定注解值
MyTable myTable =(MyTable) c1.getAnnotation(MyTable.class);
String value = myTable.value();
System.out.println(value);
//获得类指定的注解
System.out.println("=====获得类指定的注解======");
Field f= c1.getDeclaredField("name");
MyField annotation = f.getAnnotation(MyField.class);
System.out.println(annotation.columnName());
System.out.println(annotation.type());
System.out.println(annotation.length());
}
}
@MyTable("db_students")
class Student2{
@MyField(columnName = "db_id",type = "int",length = 10)
private int id;
@MyField(columnName = "db_age",type = "int",length = 10)
private int age;
@MyField(columnName = "db_name",type = "varchar",length = 50)
private System name;
public Student2() {
}
public Student2(int id, int age, System name) {
this.id = id;
this.age = age;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public System getName() {
return name;
}
public void setName(System name) {
this.name = name;
}
@Override
public String toString() {
return "Student2{" +
"id=" + id +
", age=" + age +
", name=" + name +
'}';
}
}
//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyTable{
String value();
}
//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyField{
String columnName(); //列名
String type(); //类型
int length(); //长度
}
执行结果: