狂神说Java笔记--反射和注解部分笔记
Posted 小智RE0
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了狂神说Java笔记--反射和注解部分笔记相关的知识,希望对你有一定的参考价值。
传送门==>B站遇见狂神说–反射和注解
笔记和练习只是跟着视频整理的;但是有的知识点并没有整理进来.
ml
1.什么是注解
Annotation: 从JDK5开始,Java增加对元数据的支持,也就是注解,注解与注释是有一定区别的,可以把注解理解为代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。
- 不是程序本身,只是向程序作出解释,这一点与注释(comment)没太大区别
- 可以被其他程序(例如:编译器)读取
注解的格式: 以 @注释名 在代码中;还可以添加参数值,例如
@SuppressWarnings(value=“unchecked”).
注解的使用位置 :可附加在package,class,method,field…;可通过反射机制编程实现对这些元数据的访问.
例如: 之前经常用到的重写方法注解 @Override
在学习拉莫达表达式时见过的 函数式接口注解 @FunctionalInterface
已弃用的方法/类/包: 弃用标识注解 @Deprecated
2.内置注解
@Override 定义在java.lang.Override 中,仅适用于注释方法;表示该方法是重写超类中的一个对应方法声明.
@Deprecated 定义在java.lang.Deprecated 中,可注释 方法,属性,类, 标示已过时(弃用);一般被该注解注释的元素中间会有一条线划过.;只是不推荐使用,而不是不能使用.
@SuppressWarnings 定义在java.lang.SuppressWarnings中,被该注解修饰的元素以及该元素的所有子元素取消显示编译器警告,例如修饰一个类,那他的字段,方法都是显示警告;
String[] value()----表示使用时需要指定参数;
案例
@SuppressWarnings(value={“deprecation”, “unchecked”}) 表示不再建议使用警告"和"未检查的转换时的警告"不再进行检查。
参数字段 | 说明 |
---|---|
deprecation | 使用了不赞成使用的类或方法时的警告 |
unchecked | 执行了未检查的转换时的警告 |
fallthrough | 当 Switch 程序块直接通往下一种情况而没有 Break 时的警告。 |
path | 在类路径、源文件路径等中有不存在的路径时的警告。 |
serial | 当在可序列化的类上缺少 serialVersionUID 定义时的警告。 |
finally | 任何 finally 子句不能正常完成时的警告。 |
all | 关于所有情况的警告。 |
3.元注解
元注解是负责为其他注解做注解;位于java.lang.annotation包下;
@Target:描述注解的使用范围(作用域)
参数: TYPE —标注"类、接口(包括注释类型)或枚举声明"。FIELD—标注"字段声明"。
METHOD —标注"方法"。PARAMETER —标注"参数"。CONSTRUCTOR —标注"构造方法"。
LOCAL_VARIABLE —标注"局部变量"。
@Retention 表示需要在什么级别保存该注释信息;用于描述注解的声明周期(SOURCE(源码级别
)<CLASS(class级别
)<RUNTIME运行时级别
) ;一般在自定义注解时用runtime;
SOURCE:仅在源文件中,当Java文件编译为class文件的时,注解不能使用;
CLASS:注解被保留到class文件,当虚拟机加载class文件,就不能使用;
RUNTIME:虚拟机加载class文件之后,还可使用该注解
@Document 说明该注解会被包含在javadoc文档;
@Inherited 表示子类可以继承父类的这个注解
4.自定义注解
@interface 使用自定义注解 ,自动继承java.lang.annotation.Annotation接口
- @interface用来声明一个注解,格式为:public @interface 注解名{定义内容}
- 注解使用参数时; 参数类型+参数名( );
- 在使用注解,进行赋值时,可以不在乎顺序;因为有参数名
- 每个方法实际上声明了一个配置参数;
- 方法的名称就是参数名称;
- 返回值类型就是参数类型(基本类型学;Class,String,enum);
- 可通过default来声明参数的默认值;经常使用空字符串或0作为默认值;
如果默认值为-1,表示不存在;
- 若定义注解时;只有一个参数成员;建议参数名为value( ),那么在使用这个注解时,参数赋值还能省略value()指向;
- 注解元素要有值,不然就写上默认值.
练习自定义注解
public class MyClass {
//使用自定义的注解;
//使用注解进行参数赋值时,没有顺序;
@MyAnnotation(id = 100,age = 21,name = "小智",course = {"Java"})
public void myMethod(){
}
//案例;这里使用定义了一个参数的那个注解;参数赋值还能省略value()指向;
@MyAnnotation1("小智")
public void myMethod1(){
}
}
//自定义注解;
//@Target:注解的作用域;
//@Retention:注解的声明周期;
@Target(value = {ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation{
//注解使用参数时; 参数类型,参数名();
// 还可以用default加默认值; (一般默认值用空字符串或0);
//如果说,这里没有声明默认值,那么在使用时注解,必须要加参数赋值;
String name() default "";
int age() default 0;
//若默认值为 -1;则表示不存在;
int id() default -1;
//还可以是数组类型的;
String[] course() default {"C++"};
}
//自定义注解;若只有一个参数,建议参数名使用value();
@Target(value = {ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation1{
//那么在使用这个注解时,参数赋值还能省略value()指向;
String value();
}
5.反射概述
Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。
动态语言
在运行时可以改变其结构的语言:例如新的函数,对象,甚至是代码都可被引进;已经存在的函数可以被删除或者其他的结构改变; 也就是在运行时的代码可以根据某些条件改变自身结构;
主要的动态语言:Object_C;C#,php,Python,javascript;
静态语言
运行时结构不可变的语言,例如Java,C,C++;
虽然Java不是动态语言,但 Java作为 准动态语言
;有动态性,可使用反射机制获得类似动态语言的特性;
java reflection
反射(reflection)使得Java变为类似动态语言;反射机制允许程序在执行期借助于反射API获取任何类的内部信息,并且可以直接操作任意对象的内部属性以及方法.
Class c=Class.forName(“java.lang.String”)
一个类在内存中只有一个Class对象;一个类被加载后,整个结构都会被封装到Class对象中
加载类之后,在堆内存的方法区中产生了一个Class类型的对象
(注意一个类只有一个Class对象);此对象包含了完整的类结构信息;可以通过这个对象看到类的结构.
正常方式: 引入需要的"包类"名称
—>通过new实例化
—>取得实例化对象
反射方式: 实例化对象
—>getClass( )方法
—>得到完整的"包类"名称
反射通俗来讲就是通过对象反射求出类的名称.
6.获得反射对象
Java反射机制提供的功能
(1)在运行时判断任意一个对象所属的类; (2)在运行时构造任意一个类的对象; (3)在运行时(判断/调用)任意一个类所具有的成员变量和方法; (4) 在运行时获取泛型信息; (5)在运行时可处理注解,(6)可以生成动态代理;
Java反射的优缺点
优点
:可实现动态创建对象和编译
缺点
:对于性能有影响;使用反射基本上是一种解释操作;总是慢于直接执行相同的操作.
java.lang.Class
:代表一个类;
java.lang.reflect.Method
:代表类的方法
java.lang.reflect.Field
:代表类的成员变量
java.lang.reflect.Constructor
:代表类的构造器
练习:
public class MyClass {
public static void main(String[] args) throws ClassNotFoundException {
//通过反射获取实体类的Class对象;
Class<?> name1 = Class.forName("com.xiaozhi.day05_javareflection.User");
//
System.out.println(name1);//输出为: class com.xiaozhi.day05_javareflection.User
//一个类在内存中只有一个Class对象;一个类被加载后,整个结构都会被封装到Class对象中;
Class<?> name2 = Class.forName("com.xiaozhi.day05_javareflection.User");
Class<?> name3 = Class.forName("com.xiaozhi.day05_javareflection.User");
Class<?> name4 = Class.forName("com.xiaozhi.day05_javareflection.User");
//测试,输出他们的hashcode值;相等;
System.out.println(name1.hashCode());
System.out.println(name2.hashCode());
System.out.println(name3.hashCode());
System.out.println(name4.hashCode());
}
}
//创建一个实体类;
class User{
String name;
int age;
int id;
public User(String name, int age, int id) {
this.name = name;
this.age = age;
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\\'' +
", age=" + age +
", id=" + id +
'}';
}
}
7.获取Class类的方式
Class类:
对象反射后可得到的信息: 类的属性,方法和构造器,这个类实现的接口;
对于每个类;jre都保留了一个不变的Class类型的对象; 一个Class对象包含了特定的结构(class/interface/enum/annotation/primitive type/void[])有关信息
Class作为一个类,它只能由系统创建对象;
JVM中一个加载的类只有一个Class实例
;
而一个Class对象对应的是一个加载到JVM中的一个.class字节码文件;
每个类的实例都会标记自己的Class实例来源;
通过Class可完整得到一个类中的所有被加载结构;
常用方法 | 注释 |
---|---|
static Class.forName() | 返回指定name的Class对象 |
getName() | 返回Class对象表示的实体(类,接口,数组类,void[] )的名称 |
Object newInstance() | 调用缼省构造函数,返回Class对象实例 |
Class getSuperClass() | 返回当前Class对象的父类Class对象 |
Class[] getinterfaces() | 返回当前Class对象的接口 |
ClassLoader getClassLoader() | 获取类的类加载器 |
Constructor[]getConstructors() | 返回包含Constructor对象的数组 |
Method getMethod(String name,Class…T) | 返回一个Method对象,形参为paramType |
Field[] getDeclaredFields() | 返回Field对象的一个数组 |
如何获取Class类的对象呢;
(1)若已知实体类;可通过类的class属性
获取,安全可靠,性能高;
Class Class对象=实体类.class;
(2)已知实体类的实例,调用实例的getClass()方法
获取Class对象;
Class Class对象=实体类对象.getClass();
(3)已知类的全类名,且在该类的类路径在,可使用Class类的静态方法forName()
获取,但是会抛出异常
ClassNotFoundException
Class Class对象=Class.forName(“类的路径”);
(4)内置基本数据类型可以直接用类名.Type
练习:
public class Demo{
public static void main(String[] args) throws ClassNotFoundException {
Person person=new Student();
System.out.println("他说=>"+person.name);//他说=>我是学生
//获取Class类;
//1.通过对象获取;
Class c1 = person.getClass();
System.out.println(c1);//class com.xiaozhi.day06_studyclass.Student
System.out.println(c1.hashCode());//460141958
//2.通过类路径获取;
Class c2= Class.forName("com.xiaozhi.day06_studyclass.Student");
System.out.println(c2);//class com.xiaozhi.day06_studyclass.Student
System.out.println(c2.hashCode());//460141958
//3.通过类名.class获取;
Class c3 = Student.class;
System.out.println(c3);//class com.xiaozhi.day06_studyclass.Student
System.out.println(c3.hashCode());//460141958
//可获取父类的类型;
Class superclass = c1.getSuperclass();
System.out.println("父类 "+superclass);//父类 class com.xiaozhi.day06_studyclass.Person
//4.内置基本数据类型可以直接用类名.Type
Class<Character> type = Character.TYPE;
System.out.println(type); //char
}
}
//创建一个Person类;
class Person{
String name;
public Person(String name) {
this.name = name;
}
public Person() {
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\\'' +
'}';
}
}
//创建学生类;
class Student extends Person{
public Student() {
this.name="我是学生";
}
}
8.所有类型的Class对象
具有Class对象的;
class(外部类,常用内部类,静态内部类,局部内部类,匿名内部类
); interface(接口
); [ ]
(数组);enum(枚举
);
annotation(注解
) ;primitive type(基本数据类型
) ;void.
对于数组;只要数组的类型维度一样;Class就一样;
练习:
public class Demo {
public static void main(String[] args) {
//类;
Class c1=String.class;
System.out.println(c1);//class java.lang.String
//接口;
Class c2=Runnable.class;
System.out.println(c2);//interface java.lang.Runnable
//一维数组;
Class c3=String[].class;
System.out.println(c3);//class [Ljava.lang.String;
//二维数组;
Class c4=String[][].class;
System.out.println(c4);//class [[Ljava.lang.String;
//注解;
Class c5=Target.class;
System.out.println(c5);//interface java.lang.annotation.Target
//枚举类型;
Class c6= ElementType.class;
System.out.println(c6);//class java.lang.annotation.ElementType
//void;
Class c7=void.class;
System.out.println(c7);//void
//基本数据类型;
Class c8=int.class;
System.out.println(c8);//int
System.out.println("===============");
//关于数组;
//只要数组的类型维度一样;Class就一样;
int[] arr0=new int[10];
int[][] arr1=new int[1][2];
int[][] arr2=new int[10][100];
System.out.println(arr0.getClass().hashCode());//460141958
System.out.println(arr1.getClass().hashCode());//1163157884
System.out.println(arr2.getClass().hashCode());//1163157884
}
}
9.类加载内存分析
JAVA内存简化;
堆:存放 new 的对象和数组
可被所有线程共享,不会存放别的对象引用
栈:存放基本变量类型(包含这个基本类型的具体数值);以及引用对象的变量(存放的是 引用 在 堆中的具体地址)
方法区:存放 class和static变量;
可以被所有的线程共享
类加载过程;
当程序主动使用某个类时,若该类还未被加载到内存中,则系统会通过 加载–> 链接–> 初始化.进行初始化;
加载(Load): 将类的class字节码文件读入内存,将这些静态数据转换为方法区的运行时数据结构,且创建一个java.lang.Class对象代表这个类;由类加载器完成;
链接(link): 将类的二进制数据合并到jvm的运行状态;
(1)首先确保加载的类信息符合jvm规范; (2)正式在方法区中为类变量分配内存且设置类变量默认初始值;(3)虚拟机常量池内的符号引用(常量名) 替换 为直接引用地址.
初始化(initialize):执行类构造器
<clinit>()
方法的过程.
(1)类构造器<clinit>
()方法是由编译期自动收集类中所有的类变量的赋值行为和静态代码块中的语句合并产生.(类构造器是构造类信息,不是用来构造该类对象的)
(2)当初始化类时,若发现其父类还未初始化,先触发父类初始化;
(3)虚拟机确保类的<clinit>
()方法在多线程环境中会加锁同步
练习
public class Demo {
public static void main(String[] args) {
//创建Demo2对象;
Demo2 demo2=new Demo2();
System.out.println(demo2.a);
/*
首先加载 内存;产生一个类对应Class对象;
链接; 结束后 a=0;
然后初始化; <clinit>(){
System.out.println("Demo2的静态代码块初始化");
a=200;
a=10;
}
所以输出的a=10;
*/
}
}
class Demo2{
static {
System.out.println("Demo2的静态代码块初始化");
a=200;
}
static int a=10;
public Demo2() {
System.out.println("Demo2的构造方法初始化");
}
}
10.分析类的初始化
类的主动引用(会发生类的初始化)
(1)当虚拟机启动,先初始化main方法所在的类; (2)new 类的对象 ;
(3)调用类的静态成员(final常量除外
)或者静态方法;
(4)使用java.lang.reflect包的方法对类进行反射调用;
(5)继承关系中,先初始化父类;
类的被动引用(不会发生类的初始化)
(1)当访问静态域时,只有真正声明此静态域的类才被初始化;
例如:通过子类引用父类的静态变量,并不会导致子类初始化;
(2)通过数组定义的类引用,不会触发此类的初始化;
(3)引用常量不会触发此类的初始化,这是由于常量在链接
时就存入调用类的常量池中
练习
主动引用;创建子类时,先加载的是父类构造初始化;
public class Demo {
static {
System.out.println("main方法被加载");
}
public static void main(String[] args) {
//首先是主动引用;创建子类时,先加载的是父类构造初始化;
Son son=new Son();
}
}
//父类;
class Father{
static int a=10;
static {
System.out.println("父类被加载");
}
}
//子类;
class Son extends Father{
static {
System.out.println("子类被加载");
s=100;
}
static int s=10;
final static int M=2000;
}
输出:
main方法被加载
父类被加载
子类被加载
注释掉;使用反射机制;也是主动引用.
//使用反射机制;
Class.forName("com.xiaozhi.day08_initclass.Son");
输出
main方法被加载
父类被加载
子类被加载
注释掉;试试 被动引用;子类去调用父类的静态属性;
System.out.println(Son.a);
输出:
main方法被加载
父类被加载
10
注释掉;试试 被动引用,创建数组
//被动引用,创建数组;
Father[] f=new Father[15];
输出
main方法被加载
注释掉,试试被动引用,调用子类的常量
//被动引用; 调用常量;
System.out.println(Son.M);
输出
main方法被加载
2000
11.类加载器
类缓存:标准的JavaSe类加载器可按要求查找类,一旦某个类被加载到类加载器中,将维持加载(缓存一段时间),但JVM可以回收这些Class对象.
以上是关于狂神说Java笔记--反射和注解部分笔记的主要内容,如果未能解决你的问题,请参考以下文章源程序(.java文件)
—>Java编译器
—>字节码(.cl