一、注解
1.内置注解
1.1 Override
重写超类方法的注解
只能用于修饰方法
表示一个方法声明,打算重写超类中的另一个方法
1.2 Depecated
不推荐使用的
可用于修饰方法、属性、类
表示不鼓励程序员使用这样的元素
1.3 SuppressWarnings
镇压警告
用来抑制编译时的警告信息
使用时,需要参数
2.元注解
2.1 作用
负责注解其他注解的,被用来提供对其他annotation类型作说明
java定义了四个标准的meta-annotation类型
2.2 包括
-
Target
描述注解的使用范围,在value里描述 即被描述的注解可以用在什么地方,如类上、方法上、属性上等
-
Retention
需要在什么级别保存该注解信息,用于描述注解的声明周期 即注解在什么时候有效 runtime>class>source
-
Documented
将该被描述注解生成在JAVADoc中
-
Inherited
子类可以继承父类的注解
2.3 定义一个注解
//表示该注解可以用在方法上和类上
@Target(value = {ElementType.METHOD,ElementType.TYPE})
//表示该注解在运行时有效
@Retention(value = RetentionPolicy.RUNTIME)
//表示将该注解生成在javadoc中
@Documented
//表示被该注解修饰的类的子类可以继承该注解
@Inherited
@interface MyAnnotation
{
}
3.自定义注解
注解:@interface用来声明一个注解
注解的参数:参数类型+参数名();
@Target(value="ElementType.TYPE")//类上
@Retention(value="RetentionPolicy.RUNTIME")
@interface MyAnnotation
{
String value();
}
二、反射
1. 静态语言和动态语言
1.1 动态语言
是一类在运行时可以改变其结构的语言
即运行时代码可以根据某些条件改变自身结构
1.2 静态语言
运行时结构不可变的语言就是静态语言
java不是动态语言,但java可以被称为准动态语言,即java有一定的动态性
我们可以利用反射机制获得类似动态语言的特性
2. 简介
反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法
加载完类之后,在堆内存的方法区就产生了一个Class类型的对象
一个类只有一个对象,该对象包含了完整的类的结构信息,我们可以通过该对象看到类的结构
正常情况下:
- 引入需要的包类名称
- 通过new实例化
- 取得实例化对象
反射情况下:
- 实例化对象
- getClass()拿到一个类对象
- 得到完整的包名称
3. 优缺点
3.1 优点
可实现动态创建对象和编译,体现出很大的灵活性
3.2 缺点
对性能有影响,要告诉JVM我们希望什么,会比较慢
3.3 功能
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
生成动态代理
4. Class类
在Object类中。定义了一个getClass()方法,该方法可以被所有子类继承
public final Class getClass()
对于每个类而言,JRE都为其保留一个不变的Class类型对象
一个Class类对象包含了特定的某个结构的信息,如类的属性、方法、构造器、实现的接口等
Class对象只能由系统创建,一个加载的类在JVM中会有一个实例
每个类的实例都会记得自己是由哪个Class实例所生成的
Class对象只能由系统创建
5. 获取Class类的实例
5.1 用类获取,通过class属性
Class clazz = Person.class;//安全可靠,性能高
5.2 用某个类的实例获取,通过getClass()方法
Class clazz = person.getClass();
5.3 用类的全限定路径名,通过Class的静态方法forName()
Class clazz = Class.forName("com.xk.pojo.Student");
5.4 内置的基本数据类型
类名.TYPE
Class clazz = Integer.TYPE;
5.5 获得父类类型
Class clazz = student.getClass().getSuperClass();
5.6 测试
//1.用类的属性,用过类
Class c1 = Student.class;
//2.用类的方法,通过实例
Student student = new Student();
Class c2 = student.getClass();
//3.用Class类的静态方法
Class c3 = Class.forName("com.xk.pojo.Student");
//4.基本数据类型
Class c4 = Integer.class;
//5.获得父类
Class c5 = student.getClass().getSuperclass();
6. java内存分析
堆
存放new出来的对象和数组
可以被所有线程共享
栈
存放基本类型变量(包含其数值)、引用对象的变量(这个引用在堆里的具体地址)
方法区
包含所有的class和static变量
可被所有线程共享
7. 类加载的过程
7.1 步骤
当程序主动使用某个类时,若该类还未被加载到内存中,系统会通过三个步骤来对该类的初始化
-
类加载
将类的class文件,即字节码文件读入内存,并创建一个java.lang.Class对象
该过程由类加载器完成
-
类的链接
将类的二进制数据合并到jre中
-
类的初始化
由JVM负责
7.2 详解
7.2.1 加载
类的class文件加载到内存,将静态数据转换成方法区的运行时数据结构,生成一个Class对象
7.2.2 链接
将类的二进制代码合并到JVM的运行状态之中
包括验证、准备、解析
- 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
- 准备:正式为类static变量分配内存并设置类变量默认初始值的阶段,这些内存都在方法区中分配
- 解析:虚拟机常量池内的符号引用替换为直接引用,即由常量名变为地址
7.2.3 初始化
执行类构造器()方法的过程
类构造器()方法是由编译期自动类中所有变量的赋值动作和静态代码块的语句合并产生的
即该方法是给所有变量赋值并执行静态代码块
类构造器是构造该类的信息,而不是该类对象的构造器
当初始化一个类时,如果其父类还没有进行初始化,则需要先触发其父类
8. 类加载过程的内存分析
8.1例子
public class Test
{
public static void main(String[] args) {
A a=new A();
System.out.println(a.m);//300
}
}
class A
{
static
{
m=300;
}
static int m=100;
public A()
{
System.out.println("无参构造");
}
}
8.2 内存分析
8.3 过程分析
8.3.1 类加载
- 将静态数据转换成方法区的运行时数据,见图最右边的方法区,方法区中存在了Test类和A类的数据
- 生成一个代表该类的Class对象,见图中间,生成两个Class对象,分别代表Test和A
8.3.2 链接
-
验证
-
准备:
为类变量分配内存:见图最左边的下方:Main()方法、m
设置默认值:m=0
-
解析
8.3.3 初始化
类中所有变量的赋值动作和静态代码块
new A()
m=100
m=300
8.4 主动引用
8.4.1 什么时候发生类初始化
类的主动引用时!!
8.4.2 测试:
public class TestA
{
static
{
System.out.println("Main方法所在的类被加载");
}
public static void main(String[] args) throws ClassNotFoundException {
//1.主动引用
Son son=new Son();
//2.反射,也会引起主动引用
Class.forName("com.xk.test.Son");
}
}
class Father
{
static int b=2;
static
{
System.out.println("父类被加载");
}
}
class Son extends Father
{
static
{
System.out.println("子类被加载");
}
}
8.4.3 结果:
Main方法所在的类被加载
父类被加载
子类被加载
8.4.4 结论:
1. 类只会被加载一次
2. 主动创建类和反射都会引起类加载
8.5 被动引用
不会发生类的初始化
8.5.1 当访问一个静态域时
只有真正声明这个域的类才会被初始化
如:通过子类引用父类的静态变量,只有父类会被初始化,子类不会
8.5.2 通过数组定义类引用时
不会出发此类的初始化
8.5.3 引用常量时
常量在链接阶段就存入调用类的常量池中就好了