注解、反射、Java内存分析
一、注解
什么是注解?
注解(Annotation)和注释(Comment)很类似,只不过注释是给程序员看的,帮助程序员理解程序代表的意思和逻辑,而注解既是给程序员看的,又是给程序看的比如编译器,程序对不同的注解做出与之相应的动作。
Java语言自带的几个基本注解
@Override:修饰方法,表示该方法是对父类方法的重写,编译器会检查该方法的定义方式,保证和父类的方法相同。
@Deprecated:修饰属性,方法,类,表示这些东西已经过时了,有更好的替代方式或者很危险,虽然不推荐但还是可以编译并执行。
@SuppressWarnings:用于抑制编译时的警告信息,后面可以接一些参数:
比如:
@SuppressWarnings(“all”)
@SuppressWarnings(“unchecked”)
@SuppressWarnings({value=“unchecked”,”deprecation“})
注解的使用
//无参数的
@Override
public void test(){}
//有参数的
@SuppressWarnings("all")
class Test{
int i;
...
}
元注解
元注解(meta-anotation)就是对注解进行注解,为注解提供一些说明。
比如:
@Target:描述注解的使用范围
需要的参数:
ElementType.TYPE、ElementType.FIELD、ElementType.METHOD、ElementType.PARAMETER等等。
@Retention:描述注解的生命周期。编译时存在,还是类加载存在,还是运行时存在。(SOURCE<CLASS<RUNTIME)
需要的参数:
RetentionPolicy.SOURCE、RetentionPolicy.CLASS、RetentionPolicy.RUNTIME
@Document:说明该注解将包含在javadoc中。
@Inherited:说明子类可以继承父类的该注解。
自定义注解
//注解的使用
@MyAnnotation01(name = "ace")
public class Test01{
}
//元注解
@Target({ElementType.METHOD,ElementType.TYPE}) //表示我们的注解只能用在方法、类型上,类也是一个类型。
@Retention(RetentionPolicy.RUNTIME) //表示我们的注解运行时任然有效,通常都是使用这个。
//自定义注解
@interface MyAnnotation01{
//注解的参数,参数的数据类型只能是基本数据类型,Class,String,Enum
String name();
//参数可以有默认值
int sex() default 1;
}
//注解的使用
@MyAnnotation02({1,2,3})
public class Test01{
}
//元注解
@Target({ElementType.METHOD,ElementType.TYPE}) //表示我们的注解只能用在方法、类型上,类也是一个类型。
@Retention(RetentionPolicy.RUNTIME) //表示我们的注解运行时任然有效,通常都是使用这个。
//自定义注解
@interface MyAnnotation02{
//当参数只有一个的时候,推荐使用value做参数名,这样使用的时候可以省略参数名。
int[] value();
}
二、反射
什么是反射机制?
反射(Reflection)就如同照镜子一样,照镜子能得到我们清晰、完整的模样,反射机制能得到一个类的完整结构,我们就可以操作任意该类对象的所有属性和方法。
Class类
a. 我们通过反射机制获得的是一个Class类对象,该类保存了一个类的完整结构。
b. Class在类被加载的时候创建,且一个类只有一个Class对象,所有类实例的Class对象都是相同的。
注意是Class,而不是class,小写的class是关键字
1. 哪些类型可以有Class对象
public class Test {
public static void main(String[] args) {
//void
Class c0 = void.class; //void
//基本数据类型
Class c1 = int.class; //int
//包装类
Class c2 = Integer.class; //java.lang.Integer
//一维数组
Class c3 = int[].class; //class [I
//二维数组
Class c4 = String[][].class; //class [[Ljava.lang.String;
//类
Class c5 = Object.class; //class java.lang.Object
//接口
Class c6 = Comparable.class; //interface java.lang.Comparable
//枚举
Class c7 = ElementType.class; //class java.lang.annotation.ElementType
//注解
Class c8 = Override.class; //interface java.lang.Override
Class c10 = Class.class; //class java.lang.Class
}
}
2. 获取Class类的实例
public class Test02 {
public static void main(String[] args) throws ClassNotFoundException {
//通过类的实例获取该类的Class实例
A a = new A();
Class classA01 = a.getClass();
System.out.println(classA01.hashCode());
//若知道类名,则直接通过类属性class获取该类的Class实例
Class classA02 = A.class;
System.out.println(classA02.hashCode());
//若知道类的全名(包名+类名)则可以通过Class的类方法forName获取该类的Class实例
Class classA03 = Class.forName("com.ace.test.A");
System.out.println(classA03.hashCode());
//基本数据类型的包装类的Class实例的获取可以通过包装类中的TYPE属性获取
Class classInt = Integer.TYPE;
System.out.println(classInt.hashCode());
//通过子类的Class实例获取父类的Class实例
Class classB = classA01.getSuperclass();
}
}
class A extends B{
private int a;
private void test(){
}
}
class B {
int b;
}
3. Class类常用方法
通过反射机制获取类的信息
//获取类的信息
public class Test06 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class class01 = Class.forName("com.ace.test.Person");
//获取类的名字
System.out.println(class01.getName()); //包名 + 类名 com.ace.test.Person
System.out.println(class01.getSimpleName()); //类名 Person
//获取类的属性
Field[] fields01 = class01.getFields(); //只能得到public,其他权限都不可以
Field[] fields02 = class01.getDeclaredFields(); //得到全部权限的属性
//获得指定的属性
Field field01 = class01.getField("id");
Field field02 = class01.getDeclaredField("age");
//获得方法
Method[] methods01 = class01.getMethods();
Method[] methods02 = class01.getDeclaredMethods();
//获得指定的方法,需要传入方法名和形参类型的Class实例对象
Method method01 = class01.getMethod("test01", int.class, int.class);
Method method02 = class01.getDeclaredMethod("test02",null); //无参数可以填null
//获得构造器
Constructor[] constructors = class01.getConstructors();
Constructor[] constructors1 = class01.getDeclaredConstructors();
//获得指定的构造器
Constructor constructor = class01.getConstructor(null);
Constructor constructor1 = class01.getDeclaredConstructor(int.class, String.class);
}
}
class Person{
public int id;
private String name;
protected int age;
public Person() {
}
private Person(int id, String name) {
this.id = id;
this.name = name;
}
private void test01(int x, int y){
}
void test02(){
}
}
通过反射机制调用构造器、方法、属性
public class Test07 {
//通过反射动态的创建对象
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class classUser = Class.forName("com.ace.test.User");
//调用newInstance()方法,相当于调用了类的无参构造器
User user1 = (User) classUser.newInstance();
//通过反射机制获取本类构造方法,用这个方法去创建对象
Constructor constructorUser = classUser.getDeclaredConstructor(String.class, String.class);
User user2 = (User) constructorUser.newInstance("ace123","123");
//调用普通的方法
Method method1 = classUser.getDeclaredMethod("test02",null);
//当调用的方法是私有的时候,需要关闭程序的访问安全检查,Method、Constructor、Field都有这个方法,关闭安全检查可以提高 程序的运行效率
method1.setAccessible(true);
method1.invoke(user1,null); //将该方法授权给user1对象使用,并传入参数的Class对象
//操作属性
Field field = classUser.getDeclaredField("name");
field.setAccessible(true);
field.set(user2,"ace"); //设置属性的值
String userName = (String) field.get(user2); //获得属性的值
System.out.println(userName);
}
}
class User{
private String name;
private String passWord;
public User() {
}
public User(String name, String passWord) {
this.name = name;
this.passWord = passWord;
}
public void test01(){
System.out.println("调用了test01()");
}
private void test02(){
System.out.println("调用了test02()");
}
}
通过反射机制操作泛型
Java采用泛型擦除机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据安全性和免去强制类型转换问题,但是一旦编译完成,所有和泛型相关的信息全部擦除。
为了通过反射操作这些类型,Java新增了几种类型来代表不能归到Class类中的类型但是又和原始类型齐名的类型。
- ParameterizedType:表示一种参数化的类型。
- GenericArrayType:表示一种元素类型是参数化类型或者类型变量的类型。
- TypeVarible:各种类型变量的公共父接口。
- WildcardType:表示一种通配符类型表达式。
public class Test08 {
public Set<Integer> test01(Map<Integer,String> map, List<String> list){
System.out.println("调用了test01()");
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
Method method = Test08.class.getMethod("test01", Map.class, List.class);
//获得参数类型
Type[] genericParameterTypes = method.getGenericParameterTypes();
/*for (Type genericParameterType : genericParameterTypes) {
System.out.println(genericParameterType);
输出:
java.util.Map<java.lang.Integer, java.lang.String>
java.util.List<java.lang.String>
}*/
for (Type genericParameterType : genericParameterTypes) {
if (genericParameterType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
/*
输出:
class java.lang.Integer
class java.lang.String
class java.lang.String
*/
}
}
}
//获得返回值类型
Type genericReturnType = method.getGenericReturnType();
System.out.println(genericReturnType); //输出java.util.Set<java.lang.Integer>
if(genericReturnType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument); //输出class java.lang.Intege
}
}
}
}
通过反射操作注解
什么是对象映射关系?
public class Test09 {
public static void main(String[] args) throws ClassNotFoundException {
Class c = Class.forName("com.ace.test.Student");
//直接通过反射获得注解,只能获得类外部的注解
Annotation[] annotations = c.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation); //@com.ace.test.TableAnno(value=t_student)
}
//指定注解的类型,获得注解的参数值,也只能获取到类外部的注解
TableAnno TableAnno = (TableAnno) c.getAnnotation(TableAnno.class);
//获取注解的参数值
String value = TableAnno.value();
System.out.println(value); //t_student
//获取类内部的注解
//先获取到被注解的元素,如Field、Method等等,再获取该元素的某个注解,就可以获取到注解的参数值了
Field[] Fields = c.getDeclaredFields();
ColumnAnno columnAnno;
String name , type;
int length;
for (Field field : Fields) {
//获取所有属性指定的注解的参数值
columnAnno = field.getAnnotation(ColumnAnno.class); //指定获取该属性的哪个注解
name = columnAnno.columnName();
type = columnAnno.type();
length = columnAnno.length();
System.out.println(name + type + length);
/*
学号int10
姓名varchar255
年龄int10
*/
}
}
}
@TableAnno("t_student")
class Student{
@ColumnAnno(columnName = "学号",type = "int",length = 10)
int num;
@ColumnAnno(columnName = "姓名",type = "varchar",length = 255)
String name;
@ColumnAnno(columnName = "年龄",type = "int",length = 10)
int age;
}
//类名的注解,类名和表名对应
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface TableAnno{
String value();
}
//属性的注解,属性和字段对应
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@interface ColumnAnno{
String columnName(); //列名
String type(); //字段类型
int length(); //字段长度
}
三、Java内存分析
简易JVM内存模型
深入理解JVM内存模型
类加载过程
I. 加载:将类的字节码文件.class读入内存,并为之创建一个Class对象,此过程由类加载器ClassLoader完成。
II. 链接:验证(保证安全)–>准备(为静态变量分配内存并为其设置默认值)–>解析(将虚拟机常量池中的符号引用替换为直接 引用)
II. 初始化:执行类构造器<clinit>()方法。如果其父类还未初始化,则触发父类的初始化。
注意:类构造器是构造类信息的,不是构造该类对象的构造器,是编译器在编译阶段自动收集类中的类变量赋值语句,和静态代 码块中的语句合成而来的。
public class Test03 {
public static void main(String[] args) {
C c = new C();
System.out.println(C.m);
/**
* 初始化过程:
* 1.加载:加载到内存,产生一个Class对象。
* 2.链接:链接结束后,m = 0;
* 3.初始化:
* 相当于
* <clinit>(){
* System.out.println("C类静态代码块初始化");
* m =10;
* m=30
* }
* 所以最后m = 30
*/
}
}
class C{
static {
System.out.println("C类静态代码块初始化");
m =10;
}
static int m = 30;
public C() {
System.out.println("C类无参构造初始化");
}
}
什么时候一定会发生类的初始化?(主动引用)
I. main方法所在类
II. new一个类的对象
III. 调用类的静态属性(static final常量除外)和静态方法
VI. 对类进行反射
V. 当初始化一个类的时候,若父类未初始化,子类会先初始化父类。
什么时候不会发生类的初始化?(被动引用)
I. 通过数组定义类引用
II. 调用类中的常量,因为常量在链接阶段就存入调用类中的常量池中了。
III. 调用静态属性和静态方法的时候,只有真正声明这个静态成员的类才会被初始化。比如通过子类使用父类的静态属性,子类不会初始化。
public class Test04 {
static {
System.out.println("main方法所在类被加载!");
}
public static void main(String[] args) {
//一定会初始化,一个类只会被加载一次
Father father = new Father();
Class c1 = Father.class;
//不会初始化
Son[] sons = new Son[5];
System.out.println(Son.a);
}
}
class Father{
static int a = 5;
static {
System.out.println("父类被加载!");
}
}
class Son extends Father{
static {
System.out.println("子类被加载!");
}
}
/**
* 结果:
* main方法所在类被加载!
* 父类被加载!
* 5
*/
类加载器
引导类加载器(BootstapClassLoader):负责加载Java核心类库。该加载器无法直接获取。
扩展类加载器(ExtensionClassLoader或者ExtClassLoader):负责jre/lib/ext目录下的jar包。
系统类加载器(SystemClassLoader或者AppClassLoader):负责java -classpath或-D java.class.path所指目录下的jar包,是最常见的类加载器。
public class Test05 {
public static void main(String[] args) throws ClassNotFoundException {
//获取系统类加载器
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
System.out.println(classLoader);
//获取系统类加载器的父类加载器-->扩展类加载器
ClassLoader classLoader1 = classLoader.getParent();
System.out.println(classLoader1);
//获取扩展类加载器的父类加载器-->根类加载器
ClassLoader classLoader2 = classLoader1.getParent();
System.out.println(classLoader2); //获取不到
//测试当前类是由哪个类加载器加载的
ClassLoader classLoader3 = Class.forName("com.ace.test.Test05").getClassLoader();
System.out.println(classLoader3);
//测试jdk的内置类是由哪个类加载器加载的
ClassLoader classLoader4 = Class.forName("java.sql.Connection").getClassLoader();
System.out.println(classLoader4); //根加载器,获取不到
//获取系统加载器可以加载到的路径
System.out.println(System.getProperty("java.class.path"));
}
}
双亲委派机制
java.lang.ClassLoader中的loadClass方法源码:
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
示意图:
这样设计的好处?
如果有人想替换系统级别的类:String、Object等等。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap ClassLoader加载过了,因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader,所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。