1. 注解(Annotation)
1. 什么是注解
-
Annotation是从JDK5.0开始引入的新技术。
-
Annotation的作用 :
- 不是程序本身,可以对程序作出解释。(这一点和注释(comment)没什么区别)
- 可以被其他程序(比如:编译器等)读取。
-
Annotation的格式:
注解是以"@注释名"在代码中存在的, 还可以添加一些参数值,例如:@SuppressWarnings(value=“unchecked”).
-
Annotation在哪里使用 ?
- 可以附加在类、方法、变量、参数和包上面,
- Java 标注可以通过反射获取标注内容。
- 在编译器生成类文件时,标注可以被嵌入到字节码中。
- Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。
2. 内置注解
-
@Override : 检查该方法是否是重写方法。
-
@Deprecated :用于修辞方法,属性,类,标记已过时。如果使用该方法,会报编译警告。
-
@SuppressWarnings : 指示编译器去忽略注解中声明的警告。
-
@SafeVarargs : Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。对于非static或非final声明的方法,不适用,会编译不通过。
-
@Functionallnterface :
-
注解只能标记在"有且仅有一个抽象方法"的接口上。
-
接口体内只能声明常量字段和抽象方法,并且被隐式声明为public,static,final。
-
接口里面不能有私有的方法或变量。
-
public class Demo extends Object{
@Override//重写方法
public String toString() {
return super.toString();
}
@Deprecated//表示弃用方法
public static void test(){
}
@SuppressWarnings("all")//抑制警告
public static void test01(){
int age;
}
private S[] args;
//构造函数可以使用@SafeVarargs标记
@SafeVarargs
public Demo(S... args){
this.args = args;
}
public static void main(String[] args) {
test();
test01();
}
}
@FunctionalInterface
class interfase Demo2{
public void add();
}
3. 元注解
- @interface:声明是一个注解
- @Target :用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
取值 | 注解使用范围 |
---|---|
METHOD | 可用于方法上 |
TYPE | 可用于类或者接口上 |
ANNOTATION_TYPE | 可用于注解类型上(被@interface修饰的类型) |
CONSTRUCTOR | 可用于构造方法上 |
FIELD | 可用于域上 |
LOCAL_VARIABLE | 可用于局部变量上 |
PACKAGE | 用于记录java文件的package信息 |
PARAMETER | 可用于参数上 |
- @Retention :表示需要在什么级别保存该注释信息,用于描述注解的生命周期。参数的类型是RetentionPolicy
- (SOURCE < CLASS < RUNTIME)
- @Document:说明该注解将被包含在javadoc中
- @Inherited: 说明子类可以继承父类中的该注解
import java.lang.annotation.*;
@MyAnnotation
public class Demo02 {
void test(){
}
}
//定义一个注解
//Target 表示我们的注解可以用在哪些地方.
@Target(value = {ElementType.METHOD, ElementType.TYPE})
//Retention表示我们的注解在什么地方还有效。
// runtime>class>sources
@Retention(value = RetentionPolicy.RUNTIME)
//Documented表示是否将我们的注解生成在Javadoc中
@Documented
//Inherited子类可以继承父类的注解
@Inherited
@interface MyAnnotation{ }
4. 自定义注解
- 使用@interface自定义注解时,自动继承了java.lang .annotation.Annotation接口
- 分析:
- @ interface用来声明一个注解,格式: public @ interface注解名{定义内容}
- 其中的每一个方法实际 上是声明了一个配置参数.
- 方法的名称就是参数的名称.
- 返回值类型就是参数的类型(返回值只能是基本类型,Class , String , enum ).
- 可以通过default来声明参数的默认值
- 如果只有一个参数成员, 一般参数名为value
- 注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值.
import java.lang.annotation.*;
public class Demo03 {
public static void main(String[] args) {
}
//注解可以显示赋值,如果没有默认值 ,我们就必须给注解赋值
@MyAnnotation2(name = "赵大宝",age = 12)
public void test(){}
@MyAnnotation3("baobao")//参数只有一个,且参数名为value
public void test1(){}
}
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
//注解的参数:参数类型+参数名();
String name() default "";
int age() default 0;
int id() default -1;// 如果默认值为-1,代表不存在。
String[] schools() default {"清华大学,辽宁大学"}
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation3{
//只有一个参数时,参数名为value时,使用时不需参数名
String value();
}
2. 静态VS动态语言
-
动态语言
- 是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。
- 主要动态语言: Object、C、C#、 JavaScript、 PHP、Python等。
-
静态语言
- 与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、 C、C++。
- Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一 定的动态性,我们可以利用反射机制获得类似动态语言的特性。Java的动态性让编程的时候更加灵活!
3. 反射(Reflection)
1. 什么是反射
- Reflection (反射)是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能***直接操作任意对象的内部属性及方法***。
Class C= Class.forName("java.lang String");
- 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。
- 可以理解为:不管实例化多少对象,对象生成指向的都是同一个Class。
public class Demo {
public static void main(String[] args) throws ClassNotFoundException {
//方式一:实例化
Demo demo = new Demo();
Class c1 = demo.getClass();
Demo demo2 = new Demo();
Class c2 = demo2.getClass();
//方式一:根据包名路径
Class c3 = Class.forName("com.Demo");
//方式三:通过类名.class
Class c4=Demo.class;
System.out.println(c1.hashCode());
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
System.out.println(c4.hashCode());
/*通过反射获取类的Class对象,
*一个类只有一个Class对象,所以c1,c2,c3,c4的hashcode相同
*一个类被加载后,整个类的结构都会被封装在Class对象中
*/
}
}
2. 提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法:
- 在运行时处理注解
- 生成动态代理
3. 优点和缺点
- 优点:可以实现动态创建对象和编译,体现出很大的灵活性
- 缺点:对性能有影响,使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于直接执行相同的操作。
4. 主要API
-
java.lang.Class:代表一个类
-
java.lang.reflect.Method :代表类的方法
-
java.lang.reflect.Field :代表类的成员变量
-
java.lang.reflect.Constructor :代表类的构造器
4. Class类
1. 什么是Class类
- 在Object类中定义了以下的方法,此方法将被所有子类继承
public final Class getClass()
- 以上的方法返回值的类型是一 个Class类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。
- 对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。
- 对于每个类而言,JRE都为其保留一个不变的Class类型的对象。一个Class对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void/[)的有关信息。
-
Class本身也是一个类
-
Class 对象只能由系统建立对象
-
一个加载的类在JVM中只会有一 个Class实例
-
一个Class对象对应的是一个加载到JVM中的一个.class文件
-
每个类的实例都会记得自己是由哪个Class实例所生成
-
通过Class可以完整地得到一个类中的所有被加载的结构
-
Class类 是Reflection的根源,针对任何你想动态加载、运行的类) 唯有先获得相应的Class对象
2. 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对象的一个数组 |
3. 获得Class类的实例
- 如果已有具体的类,通过类的class属性获取,最为安全可靠且性能最高的方法。(Class xxx=XXX.class;)
- 已知某个类的实例,调用此实例的getClass()方法获取Class对象。(Class xxx=xxx1.getClass();)
- 已知一个类的全名且在类路径下,可以通过Class类的静态方法forName()获取,需要处理异常ClassNotFoundException(Class xxx=ClassforName(“yyy.XXX”);)
- 内置基本数据类型可以直接使用类名.Type
- 还可以用ClassLoader(之后讲解)
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Person s1 = new Student();
//方式一:通过对象获取
Class c1 = s1.getClass();
System.out.println(c1.hashCode());
//方式二:通过forname获取
Class c2 = Class.forName("com.Student");
System.out.println(c2.hashCode());
//通过类名.class获得
Class c3 = Student.class;
System.out.println(c3.hashCode());
//方式四:基本内置类型的包装类都有一个TYPE属性
Class c4 = Integer.TYPE;
System.out.println(c4);
//获得父类类型
Class c5 = c1.getSuperclass();
System.out.println(c5);
}
}
class Person{...}
class Student extends Person{...}
4. 哪些类型有Class对象?
- class: 外部类成员(成员内部类,静态内部类),局部内部类,匿名内部类。
- interface: 接口
- []:数组
- enum:枚举
- annotation:注解@interface
- primitive type:基本数据类型
- void
import java.lang.annotation.ElementType;
public class Demo {
public static void main(String[] args) {
Class c1 = Object.class; //类 .
Class c2 = Comparable.class; //接口
Class c3 = String[].class; //- -维数组
Class c4 = int[][].class; //二维数组
Class c5 = ElementType.class; //枚举
Class c6 = Override.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);//class java.lang.annotation.ElementType
System.out.println(c6);//interface java.lang.Override
System.out.println(c7);//class java.lang.Integer
System.out.println(c8);//void
System.out.println(c9);//class java.lang.Class
//只要元素类型与维度一样,就是同一个Class.
int[] a = new int[10];
int[] b = new int[100];
System.out.println(a.getClass().hashCode());//1163157884
System.out.println(b.getClass().hashCode());//1163157884
}
}
注意:只要元素类型与维度一样,就是同一个Class.
5. 内存分析
6. 类的加载过程
1. 加载
将class字节码文件加载到内存中,并将这些数据转换成方法区中的运行时数据(静态变量、静态代码块、常量池等),在堆中生成一个Class类对象代表这个类(反射原理),作为方法区类数据的访问入口。
2. 链接
将Java类的二进制代码合并到JVM的运行状态之中。
-
验证
确保加载的类信息符合JVM规范,没有安全方面的问题。 -
准备
正式为类变量(static变量)分配内存并设置类变量初始值的阶段**(发生在初始化前,基本数据类型为0 引用类型为null,类变量常量的初值即为代码中设置的值,final static tmp = 456, 那么该阶段tmp的初值就是456)**,这些内存都将在方法区中进行分配。注意此时的设置初始值为默认值,具体赋值在初始化阶段完成。
-
解析
虚拟机常量池内的符号引用替换为直接引用(地址引用)的过程。两个重点:
- 符号引用。即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,一个类的相关信息。
- 直接引用。可以理解为一个内存地址,或者一个偏移量。比如类方法,类变量的直接引用是指向方法区的指针;而实例方法,实例变量的直接引用则是从实例的头指针开始算起到这个实例变量位置的偏移量
- 举个例子来说,现在调用方法hello(),这个方法的地址是1234567,那么hello就是符号引用,1234567就是直接引用。
在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。
3. 初始化
这个阶段主要是对类变量初始化,是执行类构造器的过程。换句话说,只对static修饰的变量或语句进行初始化。
-
初始化阶段是执行类构造器< clinit>()方法的过程。类构造器< clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和**静态语句块(static块)**中的语句合并产生的。
-
当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先初始化其父类。
-
虚拟机会保证一个类的< clinit>()方法在多线程环境中被正确加锁和同步。
package com;
public class Demo {
public static void main(String[] args) {
A a=new A();
System.out.println("初始化完 m 打印的值"+A.m);//100
System.out.println("初始化完 n 打印的值"+A.n);//300
/*
1.加载到内存,产生一个类对应Class对象
2.链接,链接结束后m=0
3.初始化
<clinit>(){
按顺序收集所有静态语句
System. out . println( "A类静态代码块初始化") ;
m = 300;
m=100;
}
m=100
*/
}
}
class A{
static int m ;
static{
System.out.println("静态代码块初始化");
System.out.println("类变量 m 分配的初始值"+m);
m=100;
System.out.println(m);
n=100;
}
static int n = 300;
public A(){
System.out.println("无参构造初始化");
}
}
静态代码块初始化
类变量 m 分配的初始值0
100
无参构造初始化
初始化完 m 打印的值100
初始化完 n 打印的值300
static变量的初始化是从上往下
7. 程序初始化顺序
- 父类的静态变量
- 父类的静态代码块
- 子类的静态变量
- 子类的静态代码块
- 父类的非静态变量
- 父类的非静态代码块
- 父类的构造方法
- 子类的非静态变量
- 子类的非静态代码块
- 子类的构造方法
public class Demo {
public static void main(String[] args) {
B b = new B();
}
}
class A{
static String str1 = "父类A的静态变量";
String str2 = "父类A的非静态变量";
static {
System.out.println("执行了父类A的静态代码块");
}
{
System.out.println("执行了父类A的非静态代码块");
}
public A(){
System.out.println("执行了父类A的构造方法");
}
}
class B extends A{
static String str1 = "子类B的静态变量";
String str2 = "子类B的非静态变量";
static {
System.out.println("执行了子类B的静态代码块");
}
{
System.out.println("执行了子类B的非静态代码块");
}
public B(){
System.out.println("执行了子类B的构造方法");
}
}
执行了父类A的静态代码块
执行了子类B的静态代码块
执行了父类A的非静态代码块
执行了父类A的构造方法
执行了子类B的非静态代码块
执行了子类B的构造方法
8. 类的引用
1. 类的主动引用
一定会发生类的初始化
- 当虚拟机启动,先初始化main方法所在的类
- new一个类的对象
- 调用类的静态成员(除了final常量)和静态方法
- 使用java.lang.reflect包的方法对类进行反 射调用
- 当初始化一个类, 如果其父类没有被初始化,则先会初始化它的父类
2. 类的被动引用
不会发生类的初始化
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化
- 通过数组定义类引用,不会触发此类的初始化
- 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
public class Demo {
static {
System.out.println("main类被加载");
}
public static void main(String[] args) throws ClassNotFoundException {
//1.主动引用
//Son son = new Son();
/*
* 结果:
* main类被加载
* 父类被加载
* 子类被加载
* */
//反射也会产生主动引用
//Class.forName ("com.reflection.Son");
/*
* 结果:
* main类被加载
* 父类被加载
* 子类被加载
* */
//不会产生类的引用的方法
//System.out.println(Son.b);
/*
* 结果:
* main类被加载
* 父类被加载
* 2
* */
//Son[] array = new Son[5];
/*结果:main类被加载*/
System.out.println(Son.M) ;
/*
* 结果:
* main类被加载
* 1
* */
}
}
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;
}
9. 类加载器
1. 类加载的作用
将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang(Class对象)作为方法区中类数据的访问入口。
2. 类缓存
标准的JavaSE类加载器可以按要求查找类,但一旦某一类被加载到类加载器中,它将维持加载(缓存) 一段时间。不过JVM垃圾回收机制可以回收这些Class对象
3. 类加载器的分类
-
引导类加载器(bootstrap class loader)
- 是用原生代码(C语言)来实现的,是JVM自带的类加载器,用来加载 Java 的核心库(JAVA_HOME/jre/lib/rt.jar,sun.boot.class.path路径下的内容)。并不继承自 java.lang.ClassLoader,无法直接获取。
- 加载扩展类和应用程序类加载器。并指定他们的父类加载器。
-
扩展类加载器(extensions class loader)
- 用来加载 Java 的扩展库(JAVA_HOME/jre/ext/*.jar,或java.ext.dirs路径下的内容) 。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java类。
- 由sun.misc.Launcher$ExtClassLoader实现。
-
应用程序类加载器(application class loader)
- 它根据 Java 应用的类路径(classpath,java.class.path 路径下的内容)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。
- 由sun.misc.Launcher$AppClassLoader实现。
-
自定义类加载器
- 开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。
public static void main(String[] args) throws ClassNotFoundException {
//获取系统的类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//获取系统类加载器的父类加载器-->扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);//sun.misc.Launcher$ExtClassLoader@4554617c
//获取扩展类加载器的父类加载器- ->根加载器(C/c++)
ClassLoader grantparent = parent.getParent();
System.out.println(grantparent);//null
//测试当前类是哪个加载器加载的
ClassLoader classLoader = Class.forName("com.reflection.Demo06").getClassLoader();
System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//测试JDK内置的类是谁加载的
ClassLoader classLoader1 = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader1);//null
//如何获得系统类加载器可以加载的路径
System.out.println(System.getProperty("java.class.path"));
/*
C:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\deploy.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\dnsns.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jaccess.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\localedata.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\nashorn.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunec.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\zipfs.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\javaws.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfxswt.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\management-agent.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\plugin.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar;
D:\IT\workspase\Test\target\classes;
D:\IT\software\IntelliJ IDEA 2019.3.1\lib\idea_rt.jar
*/
}
10. 双亲委托机制
某个特定的类加载器接收到类加载的请求时,会将加载任务委托给自己的父类,直到最高级父类引导类加载器(bootstrap class loader),如果父类能够加载就加载,不能加载则返回到子类进行加载。如果都不能加载则报错。ClassNotFoundException
双亲委托机制是为了保证 Java 核心库的类型安全。这种机制保证不会出现用户自己能定义java.lang.Object类等的情况。例如,用户定义了java.lang.String,那么加载这个类时最高级父类会首先加载,发现核心类中也有这个类,那么就加载了核心类库,而自定义的永远都不会加载。
11. 运行时类的完整结构
反射获取运行时类的完整结构Field、Method, Constructor、 Superclass、 Interface、 Annotation
- 实现的全部接口
- 所继承的父类
- 全部的构造器
- 全部的方法
- 全部的Field
- 注解
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Demo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class c1 = Class.forName("com.User");
//获得包名+类名
//User user = new User();
//Class c2 = user.getClass();
//获得类的信息
System.out.println("获得包名+类名: "+c1.getName());//获得包名+类名
System.out.println("获得类名: "+c1.getSimpleName());//获得类名
//获得类的属性
System.out.println("===========获取类的公开属性和父类的公开属性============");
Field[] fields = c1.getFields();//获取类的公开属性和父类的公开属性
for (Field field : fields) {
System.out.println(field);
}
System.out.println("===========获取类的任何属性============");
fields = c1.getDeclaredFields();//获取类的任何属性
for (Field field : fields) {
System.out.println(field);
}
System.out.println("===========获得指定属性的值============");
//获得指定属性的值
Field id = c1.getField("id");
System.out.println("类的公开属性: "+id);
//获得指定属性的值
Field name = c1.getDeclaredField("name");
System.out.println("获得指定属性的值: "+name);
//获得类的方法
System.out.println("===========获得本类和父类的所有public方法==============");
Method[] methods = c1.getMethods();//获得本类和父类的所有public方法
for (Method method : methods) {
System.out.println("methods: " + method);
}
System.out.println("============获得本类的所有方法=============");
Method[] decmethods = c1.getDeclaredMethods();//获得本类的所有方法
for (Method method : decmethods) {
System.out.println("decmethods: " + method);
}
//获得指定方法
//需要传参数的原因:存在重载,参数可找到指定的方法
System.out.println("============获得指定方法=============");
Method getName = c1.getMethod("getName", null);
Method setName = c1.getMethod("setName", String.class);
System.out.println("getName: "+getName);
System.out.println("setName: "+setName);
//获得构造器
System.out.println("============获得public构造器=============");
Constructor[] constructors = c1.getConstructors();
for (Constructor constructor : constructors) {
System.out.println("getConstructors: " + constructor);
}
System.out.println("===========获得所有构造器==============");
Constructor[] constructors1 = c1.getDeclaredConstructors();
for (Constructor constructor : constructors1) {
System.out.println("getDeclaredConstructors: " + constructor);
}
System.out.println("==============获得指定的构造器===========");
//获得指定的构造器
Constructor getDeclaredConstructor = c1.getDeclaredConstructor(String.class, String.class);
System.out.println("指定构造器: " + getDeclaredConstructor);
}
}
class User {
public String id;
private String name;
public User() {
}
private User(String id) {
this.id = id;
}
public User(String id, String name) {
this.id = id;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
protected void add(){
}
}
11. 有了Class对象能做什么?
1. 创建类的对象
-
无参数的构造器
- 调用Class对象的newInstance()方法
-
有参数的构造器
- 通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类型的构造器
- 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
- 通过Constructor实例化对象
2. 调用指定的方法
通过反射,调用类中的方法,通过Method类完成。
-
通过Class类的getMethod(String name,Clas…parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型。
-
之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息。
3. invoke
Object invoke(Object obj, Object ... args)
- Object对应原方法的返回值,若原方法无返回值,此时返回null
- 若原方法若为静态方法,此时形参0bject obj可为null
- 若原方法形参列表为空,则Object[] args为null
4. setAccessible
- Method和Field、Constructor对象都有setAccessible()方法。
- setAccessible作用是启动和禁用访问安全检查的开关。
- 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
- 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
- 使得原本无法访问的私有成员也可以访问
- 参数值为false则指示反射的对象应该实施Java语言访问检查
public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
//获得class对象
Class c1 = Class.forName("com.User");
//创建一个对象
System.out.println("==============无参构造器创建一个对象==============");
User user = (User)c1.newInstance();//本质是调用了类的无参构造器
System.out.println(user);
//通过构造器创建对象
System.out.println("=============有参构造器创建一个对象===============");
Constructor constructor = c1.getDeclaredConstructor(String.class,String.class);
User user2 = (User)constructor.newInstance("1","张三");
System.out.println(user2);
System.out.println("id: "+user2.id+" , name: "+user2.getName());
//通过反射调用普通方法
//通过反射获取一个方法
System.out.println("============通过反射调用普通方法================");
//Method getName = c1.getDeclaredMethod("getName",null);
Method setName = c1.getDeclaredMethod("setName",String.class);
//invoke:激活的意思
//参数:对象,方法参数的值
setName.invoke(user,"立良");
System.out.println(user.getName());
System.out.println("============通过反射调用private方法================");
//通过反射操作属性
User user3 = (User)c1.newInstance();
//通过Class对象获取对应属性信息
Field name = c1.getDeclaredField("name");
//不能直接操作私有属性,我们需要关闭程序的安全监测,属性或方法的setAccessible(true)
name.setAccessible(true);
name.set(user3,"小宝");//找到实例变量属性并设置值 静态属性时第一个参数Object为null
System.out.println(user3.getName());
}
12. 反射操作泛型
-
Java采用泛型擦除的机制来引入泛型, Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题,但是, 一旦编译完成,所有和泛型有关的类型全部擦除
-
为了通过反射操作这些类型, Java新增了ParameterizedType , GenericArrayType ,TypeVariable和WildcardType几种类型来代表不能被归一-到Class类中的类型但是又和原始类型齐名的类型.
- ParameterizedType :表示- -种参数化类型,比如Collection
- GenericArrayType :表示一种元素类型是参数化类型或者类型变量的数组类型
- TypeVariable :是各种类型变量的公共父接口
- WildcardType :代表-种通配符类型表达式
-
参数范型 : getGenericParameterTypes
public void test01(Map<String, User> map, List<User> list) {
System.out.println("test01");
}
public Map<String, User> test02() {
System.out.println("test02");
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
System.out.println("----------参数范型-----------");
Method method = Demo.class.getMethod("test01", Map.class, List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println("参数范型" + genericParameterType);
if (genericParameterType instanceof ParameterizedType) {
Type[] actualTypeAnguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeAngument : actualTypeAnguments) {
System.out.println("实际参数范型" + actualTypeAngument);
}
}
}
System.out.println("----------返回值范型-----------");
Method method1 = Demo.class.getMethod("test02", null);
Type getGenericReturnType = method1.getGenericReturnType();
if (getGenericReturnType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) getGenericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("返回值范型" + actualTypeArgument);
}
}
}
----------参数范型-----------
参数范型java.util.Map<java.lang.String, com.User>
实际参数范型class java.lang.String
实际参数范型class com.User
参数范型java.util.List<com.User>
实际参数范型class com.User
----------返回值范型-----------
返回值范型class java.lang.String
返回值范型class com.User
13. 反射操作注解
public class Demo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1 = Class.forName("com.User");
//通过反射获取注解
System.out.println("---------通过反射获取注解----------");
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);//@com.reflection.Table(value=db_user)
}
System.out.println("---------获得注解中的value值----------");
//获得注解中的value值
Table table = (Table)c1.getAnnotation(Table.class);
String value = table.value();
System.out.println(value);//db_user
System.out.println("---------获得类指定的注解----------");
//获得类指定的注解
Field f = c1.getDeclaredField("name");
annotations = f.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);//@com.Filed(columnName=db_name, type=varchar, length=3)
}
Filed annotation=f.getAnnotation(Filed.class);
System.out.println(annotation.columnName());//db_name
System.out.println(annotation.length());//3
System.out.println(annotation.type());//varchar
}
}
//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table {
String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Filed{
String columnName();
String type();
int length();
}
@Table("db_user")
class User {
@Filed(columnName = "db_id",type = "int",length = 10)
private int id;
@Filed(columnName = "db_age",type = "int",length = 10)
private int age;
@Filed(columnName = "db_name",type = "varchar",length = 3)
private String name;
public User(){}
public User(int id, int age, String 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 String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
14. 线程上下文类加载器
-
这个类加载器可以通过java.lang.Thread类的setContextClassLoaser()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。
-
有了线程上下文类加载器,也就是父类加载器请求子类加载器去完成类加载的动作(即,父类加载器加载的类,使用线程上下文加载器去加载其无法加载的类),这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,实际上已经违背了双亲委派模型的一般性原则。
//mysql获取数据库连接的:
// 加载Class到AppClassLoader(系统类加载器),然后注册驱动类
// Class.forName("com.mysql.jdbc.Driver").newInstance();
String url = "jdbc:mysql://localhost:3306/testdb";
// 通过java库获取数据库连接
Connection conn = java.sql.DriverManager.getConnection(url, "name", "password");
视频教程[狂神说B站]: https://www.bilibili.com/video/BV1p4411P7V3