Java基础--注解和反射
注解(Annotation)
---区分注解和注释:注解(Annotation)---不仅能给人看,还能给程序看,解释
注释(comment)---对程序作出解释,给人看
注解入门
- 作用:对程序作出解释;可以被其他程序(如编译器)读取
- 格式:“@注释名”,还可以添加一些参数值
- 使用:可以附加在包、方法、类、变量等上面,相当于给他们添加了额外的辅助信息;可以通过反射机制编程实现对这些元数据的访问
内置注解
@Override //重写
public String toString(){
return super.toString();
}
@Deprecated //不推荐使用,但可以使用
public static void test(){
System.out.println("Deprecated ");
}
@SuppressWarnings("all") //镇压警告
public void test0(){
List list = new ArrayList<>();
}
自定义注解,元注解
--元注解:负责注解其他注解,对其他注解进行说明
@Target(描述注解的适用范围) @Retention(表示注解在什么地方有效:源码级别,运行时级别--runtime>class>source) @Document(说明该注解将被包含在javadoc中) @Inherited(子类可以继承父类中的该注解)
--自定义注解
使用@interface自定义注解---
//定义元注解,在类、方法上适用,运行时有效
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface Myannotation{
//注解的参数:参数类型+参数名();
String name() ;
String[] schools() default {"山西大学","清华大学"};
}
public class note8 {
//如果没有默认值,必须显式赋值,有默认值,可不必赋值
@Myannotation(name = "zxy")
public void test(){
}
反射
Java不是动态语言,但Java可以称为“准动态语言”,即有一定的动态性,我们利用反射机制获取类似动态语言的特性。
反射机制概述
- 允许程序在执行期借助于Reflection API取得任何类的内部信息,并直接操作任意对象的内部属性及方法。
- 加载完类后,在堆内存的方法区就产生了一个Class对象(一个类只有一个Class对象),这个对象包含了完整的类的结构信息,通过这个对象看到类的结构。
- 反射的优点:可以实现动态创建对象和编译,很大的灵活性;缺点:对性能有影响
获取Class实例
Person person = new Student();
System.out.println("这个人是"+person.name);
//方式一:通过对象获得
Class c1 = person.getClass();
System.out.println(c1.hashCode()); //hashcode证明三种方式打印的是一个东西
//方式二:forName()获得
Class c2 = Class.forName("com.zhang.reflection.Student"); //这里是全类名
System.out.println(c2.hashCode());
//方式三:通过类名.class获得
Class c3 = Student.class;
System.out.println(c3.hashCode());
哪些类型可以有Class对象:
-
class:外部类,成员内部类,局部内部类,匿名内部类
-
接口interface
-
数组
-
枚举enum
-
注解annotation
-
基本数据类型
-
void
Class c1 = Object.class; //类 Class c2 = Comparable.class; //Comparable是一个排序接口 Class c3 = String[].class; //一维数组 Class c4 = int[][].class; //二维数组 Class c5 = Override.class; //注解类型 Class c6 = ElementType.class; //枚举类型 Class c7 = Integer.class; //基本数据类型 Class c8 = void.class; //空类型 Class c9 = Class.class;
注意:只要元素类型维度都一样,获取的Class对象就一样(长度为10的一维数组和长度为100的一维数组)
类的加载与ClassLoader
类的加载过程
首先:类的加载--将类的Class文件读入内存,生成一个代表这个类的java.lang.Class对象(也就是说,中我们无法创建这个对象,是系统生成的,我们只能获取这个Class对象)
第二:链接--将Java类的二进制代码合并到JVM运行状态之中;这一阶段会为类变量(static)分配内存并设置类变量默认初始值,在方法区分配;
最后:初始化--执行类构造器(JVM做的事)---执行clinit()方法,编译器自动收集类中所有变量的赋值动作和静态代码块中的语句合并产生的;
什么时候发生类的初始化
-
类的主动引用(一定发生类的初始化)
--虚拟机启动,先初始化main方法所在类;
new一个对象时会初始化;
调用静态成员(除fiinal常量)和静态方法时会初始化;
使用java.lang.reflect包的方法对类进行反射调用时会初始化;
当初始化一个类,若父类没有初始化,会先初始化父类
-
类的被动引用(不会发生)
--数组定义类引用;引用常量不会触发初始化(常量在链接阶段已经存入常量池)
--子类调用父类的静态方法,子类并不会被初始化
类加载器
类加载的作用:把类装在进内存。 将class文件字节码内容加载到内存中,并将静态数据转换成方法区运行时的数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
类缓存:
//获取系统类的加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
//获取系统类加载器的父亲加载器->扩展类加载器
ClassLoader parent= systemClassLoader.getParent();
System.out.println(parent);
//获取扩展类加载器的父类加载器-->根加载器--无法直接获取
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);
//测试当前类是哪个加载器加载的
ClassLoader classLoader = Class.forName("com.zhang.reflection.test03").getClassLoader();
System.out.println(classLoader);
//如何获取系统类加载器可以加载的路径
System.out.println(System.getProperty("java.class.path"));
创建运行时类的对象
获取运行时类的完整结构
//获得Class对象
Class c1 = Class.forName("com.zhang.reflection.User");
//获得类的名字
System.out.println(c1.getName());
System.out.println(c1.getSimpleName()); //获得类名
//获得类的属性
Field[] fields = c1.getFields(); //只能找到public属性
fields = c1.getDeclaredFields(); //找到全部属性
for (Field field :fields){
System.out.println(field);
}
//获得类的方法
Method[] methods = c1.getMethods(); //获得本类及其父类的全部public方法
for(Method method :methods){
System.out.println("正常的:"+method);
}
methods = c1.getDeclaredMethods(); //获得本类的所有方法
//获得构造器
Constructor[] constructors = c1.getConstructors(); //获得本类的public方法
for (Constructor constructor : constructors) { //遍历输出
System.out.println(constructor);
}
constructors = c1.getDeclaredConstructors(); //获得本类的全部方法
有了Class对象,能做什么?
--创建类的对象:调用Class对象的newInstance()方法【类必须有一个无参数的构造器】
User user1 = (User)c1.newInstance(); //本质是调用了类的无参构造
若没有无参构造器怎么弄?
//通过构造器创建对象(构造器传参)
Constructor constructor = c1.getDeclaredConstructor(String.class,int.class,int.class); //获得构造器
User user2 = (User)constructor.newInstance("zxy",001,18);
通过反射调用类中的方法
--调用方法:获取类,创建类的对象;通过Class类的getMethod()方法取得一个Method对象,设置类型;通过invoke传参进行调用(实例化-->找到方法-->调用)
User user3 = (User)c1.newInstance();
Method setName = c1.getDeclaredMethod("setName",String.class);
//invoke(对象,"方法的值")--激活的意思
setName.setAccessible("true"); //关闭安全检测,即可调用私有方法
setName.invoke(user3,"zxy");
System.out.println(user3.getName());
通过反射操作属性
--获取类-->创建类的对象-->通过getField()方法取得一个对象,通过set设置值;
User user4 = (User)c1.newInstance();
Field name = c1.getDeclaredField("name");
name.setAccessible(true); //不能操作私有属性,关闭安全检测即可
name.set(user4,"张晓阳");
System.out.println(user4.getName());
注意:操作私有属性、私有方法需要打开权限。加一句:setName.setAccessible("true");
--setAccessible
- Method和Field,Constructor对象都有setAccessible()方法
- setAccessible作用是启动和禁用访问安全检查的开关
- 参数值为true表示取消访问检查,使得私有成员也可以访问
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本