这里写目录标题
注解
从JDK5开始,Java增加对元数据的支持,也就是注解,注解与注释是有一定区别的,可以把注解理解为代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。
内置注解
在java中有5个内置注解,如下所示:
- 基本注解
注解名 | 作用 |
---|---|
@Override | 定义在java.lang.Override中,覆盖父类方法。 |
@Deprecated | 定义在java.lang.Deprecated中,此注释可用于修饰方法、属性、类,表示不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择 |
@SuppressWarning | 定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息 |
- 元注解
元注解的作用就是用来负责注解其他注解,java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型作说明
注解名 | 作用 |
---|---|
@Target | 用于描述注解的使用范围 |
@Retention | 表示需要在什么级别保存该注释信息,用于描述注解的生命周期 |
@Documented | 表示需要在什么级别保存该注释信息,用于描述注解的生命周期 |
@Inherited | 说明子类可以继承父类中的该注解 |
- 自定义注解
1.@interface用来声明一个注解,格式 public @inteface 注解名 { 参数}
2.注解的参数格式为:参数名+()。
3.可以通过default来声明参数的默认值。
4.如果没有默认值,可以显示赋值。
5.如果只有一个参数成员,一般参数名为value
注解元素必须要有值,我们定义注解元素时,经常使用空字符串、0作为默认值,-1表示不存在。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class test01 {
// 注解可以显示赋值 如果没有默认值,就必须给注解赋值
@MyAnnotation01(name = "ss",id = 12)
public void test(){
}
@MyAnnotataion02("kkk")
public void test02(){}
}
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation01{
String name() default "";
int age() default -1;//默认值为-1,表示不存在
int id();
String school[] = {"PekingU","TshinghuaU"};
}
@interface MyAnnotataion02{
String value();
}
反射
动态语言
是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。
主要动态语言:Object-C、JavaScript、PHP、Python、Erlang。
静态语言
与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++、C#。因为反射机制,java被视为准动态语言。
PS:C#不是动态语言,但是MS有将.NET支持动态语言的趋势,3.0吸收了一定动态特征,比如 匿名函数,临时类型,临时变量等.
反射机制的引入
反射是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
- 正常方式:引入需要的 “ 包类 ” 名称 ——> 通过 new 实例化 ——> 取得实例化对象
- 反射方式:实例化对象 ——> getClass() 方法 ——> 得到完整的 “包类” 名称
Class c = Class.forName("java.lang.String")
- 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息.我们可以通过这个对象看对类的结构. 这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为: 反射。
反射机制的优缺点
- 优点: 可以动态创建对象和编译, 体现出很大的灵活性
- 缺点: 对性能有影响. 使用反射基本上是一种解释操作,我们可以告诉JVM, 我们希望做什么并且它满足我们的要求. 这类操作总是慢于直接执行相同的操作 。
Java 反射机制提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
反射机制API
1. 获取反射对象
package com.my.zhujie;
public class reflection {
public static void main(String[] args) throws ClassNotFoundException {
// 通过反射获取类的Class对象
Class c1 = Class.forName("com.my.zhujie.User");
System.out.println(c1);
Class c2 = Class.forName("com.my.zhujie.User");
Class c3 = Class.forName("com.my.zhujie.User");
Class c4 = Class.forName("com.my.zhujie.User");
// 一个类在内存中只有一个class对象
// 一个类被加载后,类的整个结构都会被封装在class对象中
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
System.out.println(c4.hashCode());
}
}
// 实体类 : pojo, entity
class User{
private String name;
private int id;
private int age;
public User() {
}
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 com.my.zhujie.User
1975012498
1975012498
1975012498
*/
2.得到Class类的几种方式
//1、通过对象调用 getClass() 方法来获取,通常应用在:比如你传过来一个 Object
// 类型的对象,而我不知道你具体是什么类,用这种方法
Person p1 = new Person();
Class c1 = p1.getClass();
//2、直接通过 类名.class 的方式得到,该方法最为安全可靠,程序性能更高
// 这说明任何一个类都有一个隐含的静态成员变量 class
Class c2 = Person.class;
//3、通过 Class 对象的 forName() 静态方法来获取,用的最多,
// 但可能抛出 ClassNotFoundException 异常
Class c3 = Class.forName("com.ys.reflex.Person");
Class类
上面的方法返回值的类型是一个 Class 类,此类是 Java 反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称
对于每个类而言,JRE 都为其保留了一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个结构(class / interface / enum / annotation / primitive type/void / [])
- Class 本身也是一个类
- Class 对象只能由系统建立对象
- 一个加载的类在 JVM 中只会有一个 Class 实例
- 一个 Class 对象对应的是一个加载到 JVM 中的一个 .class 文件
- 每个类的实例都会记得自己是由哪个 Class 实例所生成
- 通过 Class 可以完整地得到一个类中的所有被加载的结构
- Class 类是 Reflection 的根源,针对任何你想动态加载、运行的类、唯有先获得响应的 Class 对象
static Class forName(String name) | 返回指定类名 name 的 Class 对象 |
---|---|
Object newInstance() | 调用缺省构造函数,返回 Class 对象的一个实例 |
getName() | 返回此 Class 对象所表示的实体(类、接口、数组类、或 void)的名称 |
Class getSuperClass() | 返回当前 Class 对象的父类的 Class 对象 |
Class[] getInterfaces() | 获取当前 Class 对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Constructor[] getConstructors() | 返回一个包含某些 Constructor 对象的数组 |
Method getMethod(String name, Class… T) | 返回一个 Method 对象,此对象的形参类型为 paramType |
Field[] getDeclaredFields() | 返回 Field 对象的一个数组 |
//获得类完整的名字
String className = c2.getName();
System.out.println(className);//输出com.ys.reflex.Person
//获得类的public类型的属性。
Field[] fields = c2.getFields();
for(Field field : fields){
System.out.println(field.getName());//age
}
//获得类的所有属性。包括私有的
Field [] allFields = c2.getDeclaredFields();
for(Field field : allFields){
System.out.println(field.getName());//name age
}
//获得类的public类型的方法。这里包括 Object 类的一些方法
Method [] methods = c2.getMethods();
for(Method method : methods){
System.out.println(method.getName());//work waid equls toString hashCode等
}
//获得类的所有方法。
Method [] allMethods = c2.getDeclaredMethods();
for(Method method : allMethods){
System.out.println(method.getName());//work say
}
//获得指定的属性
Field f1 = c2.getField("age");
System.out.println(f1);
//获得指定的私有属性
Field f2 = c2.getDeclaredField("name");
//启用和禁用访问安全检查的开关,值为 true,则表示反射的对象在使用时应该取消 java 语言的访问检查;反之不取消
f2.setAccessible(true);
System.out.println(f2);
//创建这个类的一个对象
Object p2 = c2.newInstance();
//将 p2 对象的 f2 属性赋值为 Bob,f2 属性即为 私有属性 name
f2.set(p2,"Bob");
//使用反射机制可以打破封装性,导致了java对象的属性不安全。
System.out.println(f2.get(p2)); //Bob
//获取构造方法
Constructor [] constructors = c2.getConstructors();
for(Constructor constructor : constructors){
System.out.println(constructor.toString());//public com.ys.reflex.Person()
}
哪些类型可以用Class对象?
- 类、接口、一维数组、二维数组、注解、枚举、基本数据类型、void、Class。
package com.my.zhujie;
import java.lang.annotation.ElementType;
public class test03 {
public static void main(String[] args) {
Class objectClass = Object.class; // 类
Class comparableClass = Comparable.class; // 接口
Class aClass = String[].class; // 一维数组
Class aClass1 = int[][].class; // 二维数组
Class overrideClass = Override.class; // 注解
Class elementTypeClass = ElementType.class; // 枚举
Class integerClass = Integer.class; // 基本数据类型
Class voidClass = void.class; // void
Class classClass = Class.class; // Class
int[] a =new int[10];
int[] b =new int[100];
System.out.println(objectClass);
System.out.println(comparableClass);
System.out.println(aClass);
System.out.println(aClass1);
System.out.println(overrideClass);
System.out.println(elementTypeClass);
System.out.println(integerClass);
System.out.println(voidClass);
System.out.println(classClass);
System.out.println(a.getClass().hashCode() == b.getClass().hashCode());
}
}
/*class java.lang.Object
interface java.lang.Comparable
class [Ljava.lang.String;
class [[I
interface java.lang.Override
class java.lang.annotation.ElementType
class java.lang.Integer
void
class java.lang.Class
true*/
java内存分析
类的加载过程
- 加载:将 class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的 java.lang.Class 对象,此过程由类加载器完成
- 链接:将 Java 类的二进制代码合并到 JVM 的运行状态之中的过程
- 验证:确保加载的类信息符合 JVM 规范,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
- 初始化(JVM工作):
- 执行类构造器< clint>() 方法的过程,类构造器< clint>() 方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的(类构造器是构造类信息的,不是构造该类对象的构造器)
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
- 虚拟机会保证一个类的< clint>() 方法在多线程环境中被正确加锁和同步
类加载code
//类加载
public class ClassLoader {
public static void main(String[] args) {
A a = new A();
System.out.println(A.m);
/**
* 1. 加载到内存,会产生一个类对应Class对象
* 2. 链接,连接结束后m=0
* 3. 初始化
* <clinit>(){
* System.out.println("A类静态代码块初始化");
* m = 300;
* m = 100;
* }
*/
}
}
class A {
static {
System.out.println("A类静态代码块初始化");
m = 300;
}
static int m = 100;
public A() {
System.out.println("A类无参构造初始化");
}
}
示意图如下:
类什么时间会初始化?
类初始化代码
//测试类什么时候会初始化
public class ActiveReference {
static {
System.out.println("Main类被加载");
}
public static void main(String[] args) throws ClassNotFoundException {
// 1. 主动调用
//Son son = new Son();
//res:
/**Main类被加载
父类被加载
子类被加载**/
// 2.反射也会产生主动引用
//Class.forName("cn.my.reflection.Son");
//res:
/**Main类被加载
父类被加载
子类被加载**/
//3.不会产生类的引用的方法:类的被动初始化
//System.out.println(Son.b);//子类调用父类的常
//量或者方法,不会加载子类
//res:
/**Main类被加载
父类被加载
2
**/
//
//Son[] array = new Son[5];
//数组只是名字+一片空间而已,不会加载类
//res:
/**Main类被加载
**/
//System.out.println(Son.a);
//res:
/**Main类被加载
1
因为所有的常量在链接阶段就调用常量池中了
**/
}
}
class Father {
static final int b = 2;
static {
System.out.println("父类被加载");
}
}
class Son extends Father {
static {
System.out.println("子类被加载");
m = 100;
}
static int m = 300;
static final int a = 1;
}
类加载器
获取类的信息
方法:
- 获取当前对象的Class
Class c1 = Class.forName(“cn.doris.reflection.User”);- 获得类的名字
c1.getName();// 获得包名 + 类名
c1.getSimpleName();// 获得类名- 获得类的属性
c1.getFields();//只能找到public属性
c1.getDeclaredFields();//找到全部的属性
c1.getDeclaredField(“name”); //获得指定属性的值- 获得类的方法
c1.getMethods(); //获得本类及父类的全部public方法
c1.getDeclaredMethods(); //获得本类的所有方法
c1.getMethod(“getName”, null);//获得指定的方法- 获得类的构造器
c1.getConstructors();
c1.getDeclaredConstructors();
c1.getDeclaredConstructor(String.class, int.class, int.class);//获得指定的构造器
获取了Class对象,能做什么?