1.类加载
1.1类的加载
当一个类第一次被使用时,会被加载到方法区,一个类只会被加载一次。
1.2类的加载时机
-
创建类的实例。
-
调用类的静态变量,或者为静态变量赋值。
-
调用类的静态方法。
-
使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。
-
初始化某个类的子类。
-
直接使用java.exe命令来运行某个主类。
public class Test01 { public static void main(String[] args) throws ClassNotFoundException { //1. 创建类的对象。 Student s = new Student(); //2. 调用类的静态变量,或者为静态变量赋值。 Student.s = "abc"; //3. 调用类的静态方法。 Student.method(); //4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。 Class c = Class.forName("com.itheima_01.Student"); //5. 初始化某个类的子类。 SmallStu ss = new SmallStu(); //6. 直接使用java.exe命令来运行某个主类。 有主方法的类会直接被加载 } }
1.3类加载器
类加载器就是把类加载到内存的工具。不同的类加载器用来加载不同类型的类。
- 启动类加载器(Bootstrap ClassLoader)
- 扩展类加载器(Extension ClassLoader) 在JDK9以后它变成了平台类加载器PlatformClassLoader
- 应用程序类加载器(Application ClassLoader)
- 关系:
- Application 的父亲是 Extension 的父亲是 Bootstrap
2.反射【重要】
2.1反射的概念
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;
这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
2.2获取字节码对象的三种方式
1.使用类名调用.class属性(.class不是只有类能调用)
2.使用一个类的对象调用.getClass()方法
3.使用Class类的静态方法forName(“类全名”)
//获取字节码对象的三种方式
//1.使用类名调用.class属性(.class不是只有类能调用)
Class c1 = String.class;
//2.使用一个类的对象调用.getClass()方法
Class c2 = "abd".getClass();
//3.使用Class类的静态方法forName("类全名")
Class c3 = Class.forName("java.lang.String");
System.out.println(c1 == c2);//true
System.out.println(c2 == c3);//true
2.3反射操作构造方法
-
反射获取构造方法
- Class类的方法:
- getConstructor(Class… c) 可以获取某一个构造方法(公共的)
- getConstructors() 可以获取所有的构造方法(公共的)
- Class类的方法:
-
反射执行构造方法
-
Constructor类的方法:
- newInstance(Object… obj) : 执行当前构造方法创建对象。
Object… obj 表示的是创建对象时的实际参数。
- newInstance(Object… obj) : 执行当前构造方法创建对象。
import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class Test02 { public static void main(String[] args) throws Exception{ //获取类的字节码对象 Class c = Class.forName("com.itheima_02.Student"); //- getConstructors() 可以获取所有的构造方法(公共的) //获取学生类的所有公共构造方法 //Constructor[] cons = c.getConstructors(); //遍历数组 //for (Constructor con : cons) { // System.out.println(con); //} //- getConstructor(Class... c) 可以获取某一个构造方法(公共的) //获取空参构造 Constructor con1 = c.getConstructor(); //获取有参构造 Constructor con2 = c.getConstructor(String.class,int.class); //newInstance(Object... obj) : 执行当前构造方法创建对象。 //使用空参构造创建对象 Object o1 = con1.newInstance(); System.out.println(o1); //o1就是一个空参创建的学生对象,显示的是父类Object,可以向下转型 //使用有参构造创建对象 Object o2 = con2.newInstance("石原里美",28); System.out.println(o2); //上面的反射和下面的创建对象功能一模一样,为什么要反射呢?一会说。 //Student stu = new Student("石原里美",28); } }
-
2.4反射操作成员方法
-
反射获取成员方法
-
Class类的方法:
-
getMethod(String name, Class… c) :获取一个成员方法(公共的)
String name 表示方法名称
Class… c 表示的是方法的参数的类型
-
getMethods() : 获取类中的所有方法(公共的)
-
-
-
反射执行成员方法
-
Method类的方法:
-
Object invoke(Object obj , Object… o) :让方法执行
第一个参数表示执行的对象
第二个参数表示方法的实际参数
返回值表示方法的实际返回值
-
import java.lang.reflect.Method; public class Test04 { public static void main(String[] args) throws Exception{ //反射获取成员方法并执行 //获取类的字节码对象 Class c = Class.forName("com.itheima_02.Student"); //- getMethods() : 获取类中的所有方法(公共的) //Method[] ms = c.getMethods(); //遍历数组 //for (Method m : ms) { // System.out.println(m); //} //- getMethod(String name, Class... c) :获取一个成员方法(公共的) //获取睡觉方法 Method m1 = c.getMethod("sleep"); //获取吃饭方法 Method m2 = c.getMethod("eat", String.class); //Object invoke(Object obj , Object... o) :让方法执行 //第一个参数是对象,第二个参数是方法的实际参数,返回值是方法的实际返回值 //创建对象 Student s = new Student("石原里美",28); // 返回值 = s.sleep(参数); //执行睡觉方法 m1.invoke(s); //执行吃饭方法 m2.invoke(s, "汉堡"); //需求:通过反射调用toString() //获取方法 Method m3 = c.getMethod("toString"); //执行方法 Object o3 = m3.invoke(s); System.out.println("o3是toString方法的返回值" + o3); //上面的三行代码相当于 //String s3 = s.toString(); //System.out.println(s3); //为什么要反射?一会说。 } }
-
2.5暴力反射
反射正常情况下遵从java权限修饰的规则。
如果使用暴力反射就可以打破权限修饰符的规则。
写法: 在所有的获取方法之间加declared词就可以了,然后调用setAccessable(true)就表示可以访问了。
暴力反射一般不要用。
import java.lang.reflect.Constructor;
public class Test03 {
public static void main(String[] args) throws Exception{
//获取类的字节码对象
Class c = Class.forName("com.itheima_02.Student");
//获取有参构造
Constructor con2 = c.getDeclaredConstructor(String.class,int.class);
//设置可以访问
con2.setAccessible(true);
//使用有参构造创建对象
Object o2 = con2.newInstance("石原里美",28);
System.out.println(o2);
}
}
2.6反射操作成员变量【了解】
import java.lang.reflect.Field;
public class Test05 {
public static void main(String[] args) throws Exception{
//获取字节码对象
Class c = Class.forName("com.itheima_02.Student");
//获取成员变量
//变量是私有的所以不能这么获取
//Field f1 = c.getField("age");
//System.out.println(f1);
//如果获取需要暴力反射
Field f2 = c.getDeclaredField("age");
System.out.println(f2);
//类中的成员变量因为遵从面向对象的语法,都会使用private修饰
//不让在外界直接使用
}
}
2.7反射的作用案例演示
反射是框架的灵魂。
反射代码虽然复杂,但是可以配合配置文件,代码不需要修改,把灵活修改的部分放在配置文件中。
-
案例演示
-
配置文件
className=com.itheima_03.Cat methodName=sleep
-
代码
import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Properties; public class Test02使用反射 { public static void main(String[] args) throws Exception { //Properties配置文件 Properties prop = new Properties(); //读取文件的键值对 prop.load(new FileReader("day14\\abc.properties")); //根据键获取值 String cn = prop.getProperty("className"); String mn = prop.getProperty("methodName"); //System.out.println(cn); //com.itheima_03.Dog //System.out.println(mn); //eat //反射获取字节码对象 Class c = Class.forName(cn); //字节码获取构造方法 Constructor con = c.getConstructor(); //创建对象 Object o = con.newInstance(); //o其实是一个子类对象 //获取方法 Method m = c.getMethod(mn); //执行方法 m.invoke(o); } }
-
3.注解
3.1概述
注解也是类的一个组成部分。
注解用@来使用。
3.2注解的作用
-
生成帮助信息
@auther 作者 @param 方法参数的介绍 @return 方法返回值的介绍
-
编译检查
@Override 检查方法是否是重写方法 @FunctionalInterface 检查接口是否是函数式接口
-
功能型
@Test 单元测试
4.自定义注解
4.1定义格式
对于对象,应该先定义类再创建对象
先定义类型:
public class Student{
}
再创建对象:
Student s = new Student();
对于注解,应该先定义注解再使用@
定义注解:
public @interface AAA{
}
使用注解:
@AAA
4.2注解的属性
-
属性的格式
数据类型 属性名();
-
数据类型
基本数据类型 String Class 枚举[枚举] 注解 以上类型的数组形式
-
示例
//注解 public @interface AAA { //定义属性 int a(); //基本数据类型 String b(); //字符串 Class c(); //Class类型 BBB d(); //注解类型 int[] arr(); //数组形式 String[] srr(); } 给属性赋值: @AAA(a=123,b="美美",c=String.class,d=@BBB,arr={11,22},srr={"abc","def"})
4.3注意事项【重要】
- 如果属性有默认值,那么就可以不赋值。
int age() default 18;
- 如果只有一个属性需要赋值,且属性名字叫value,那么可以直接赋值不需要写键值对。
- 如果数组形式的属性赋值时只有一个元素,那么可以省略大括号。
4.4元注解
元注解就是作用在注解上面的注解。
@Target
表示注解可以作用的位置
ElementType.TYPE 作用在类上
ElementType.METHOD 作用在方法上
ElementType.Field 作用在成员变量上
还有很多取值..
如果不写这个元注解,注解就能加在任意位置
@Retention
表示注解可以存活阶段
RetentionPolicy.SOURCE 存活到源码阶段 .java
RetentionPolicy.CLASS 存活到编译后阶段 .class
RetentionPolicy.RUNTIME 存活到运行阶段 运行程序
如果不写这个元注解,注解会存活到CLASS阶段
4.5注解完成测试框架
MyApp是自己的一个类,类中有多个方法,我们想测试这些方法有没有异常。
写了一个测试框架,如果要对方法进行测试,只需要给方法加上@Test注解即可。
import java.lang.annotation.Target;
import java.util.ArrayList;
public class MyApp {
//模拟开发过程中自己写了很多方法
//不知道方法有没有bug 进行方法的测试
@Test
public void method01(){
String s = "abc";
s.charAt(10);
}
@Test
public void method02(){
String s = null;
s.charAt(10);
}
@Test
public void method03(){
int[] arr = {11,22,33,44};
arr[3] = 100;
}
public void method04(){
ArrayList<String> list = new ArrayList<>();
list.add("abc");
list.add("def");
for (String s : list) {
list.add("qwe");
}
}
}
//让注解存活到运行阶段
@Retention(RetentionPolicy.RUNTIME)
//让注解只能作用在方法上
@Target(ElementType.METHOD)
public @interface Test {
}
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Test测试框架 {
public static void main(String[] args) throws IOException {
//获取MyApp类的字节码对象
MyApp m = new MyApp();
Class c = m.getClass();
//创建流
FileWriter fw = new FileWriter("day14\\bug.txt");
//获取类的所有方法getMethods()
Method[] methods = c.getMethods();
//遍历数组
for (Method method : methods) {
//判断方法上有没有加Test这个注解
if(method.isAnnotationPresent(Test.class)) {
//如果加了注解,让这个方法执行
try {
method.invoke(m);
} catch (Exception e) {
//如果出现了异常,把异常信息输出到文件中
//获取产生反射异常的原因
Throwable throwable = e.getCause();
//获取异常的类型和信息
String s = throwable.toString();
//把信息输出到文件中
//method.getName()是获取方法名
fw.write(method.getName() + "()方法中出现了" + s + "异常");
//换行
fw.write("\r\n");
fw.write("-----------------------------------");
fw.write("\r\n");
}
}
}
//关流
fw.close();
}
}
5.动态代理【重要】
5.1作用
动态代理可以在程序的运行期间,使用代理类型对原有类的方法进行增强。
5.2使用前提
类必须要有实现接口,因为代理类和原有类需要实现同样的接口。
5.3案例:不允许调用集合删改方法
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Test01 {
public static void main(String[] args) {
//创建集合对象
ArrayList<String> list = new ArrayList<>();
list.add("石原里美");
list.add("桥本环奈");
list.add("马冬梅");
//不允许调用集合的删改方法
//不能去ArrayList这个类中做修改,使用动态代理生成一个不能删除修改的集合对象
//获取类加载器
ClassLoader classLoader = list.getClass().getClassLoader();
//获取类的所有结果
Class[] is = list.getClass().getInterfaces();
//匿名内部类
InvocationHandler ih = new InvocationHandler() {
//在使用代理对象调用任何方法时,都会执行invoke方法
/*
返回值:
代表执行方法之后的结果
参数:
Object :第一个参数代表代理对象本身,不用
Method :第二个参数代表代理对象调用的方法
Object[]: 第三个参数代表调用方法时传入的实参
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取方法名
String name = method.getName();
//判断是不是删改方法
if(name.equals("remove") || name.equals("set")) {
//如果是删改方法不让执行直接产生一个异常
throw new RuntimeException("不能执行"+name+"方法");
}else {
//如果不是删改方法让他执行
Object o = method.invoke(list, args);
return o;
}
}
};
/*
返回值:
代表返回了一个代理对象
参数:
ClassLoader 第一个参数代表类加载器
Class 第二个参数原有类的接口的字节码对象
InvocationHandler 第三个参数使用匿名内部类表示操作方式
*/
List<String> list2 = (List<String>)Proxy.newProxyInstance(classLoader,is,ih);
list2.add("什么冬梅");
System.out.println(list);
int size = list2.size();
System.out.println(size); //4
list2.remove("马冬梅"); //出现异常
}
}
5.4案例:集合只允许添加四个字的字符串
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
//去掉警告
@SuppressWarnings("all")
public class Test02 {
public static void main(String[] args) {
//创建集合对象
ArrayList<String> list = new ArrayList<>();
list.add("石原里美");
list.add("桥本环奈");
//集合只允许添加四个字的字符串
List<String> list2 = (List<String>) Proxy.newProxyInstance(list.getClass().getClassLoader(), list.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//使用代理对象调用任何方法都会执行invoke方法
//可以在这个方法中做出需求
//判断方法是不是添加方法
String name = method.getName();
if(name.equals("add")){
//如果是添加方法,判断参数是不是四个字
String s = (String)args[0];
if(s.length() != 4){
//如果是添加方法并且参数不是四个字,就不让执行(产生异常)
throw new RuntimeException("只能添加4个字的字符串");
}
}
//如果是其他情况就可以正常执行
Object o = method.invoke(list, args);
return o;
}
});
//list2.add("柳岩"); 报错
list2.add("新垣结衣");
System.out.println(list2);
int size = list2.size();
System.out.println(size);
}
}
6.Lombok
Lombok是idea的一个插件,插件就是扩展功能。
在使用Lombok时需要做两件事情:
1.让idea下载插件(只需要在第一次下载,以后就不用下载了)
2. 导入Lombok的jar包(每次使用都需要导jar包)
功能:
帮实体类生成构造方法、setget方法、toString、equals等固定方法。
代码:
import lombok.*;
@Data //set get toString equals hashcode
@NoArgsConstructor //空参构造
@AllArgsConstructor //有参构造
//@ToString
//@Setter
//@Getter
//@EqualsAndHashCode
public class Student {
private String name;
private int age;
}