Java高级
枚举
枚举是Java中一种特殊类型,一般用来做信息的标记和分类
在不是枚举的情况下,也可以实现信息的标记和分类:
package _enum;
public class EnumDemo01 {
public static void main(String[] args) {
useSeason(Season.AUTUMN);
}
public static void useSeason(String string) {
switch (string) {
case Season.SPRING -> System.out.println("春天");
case Season.SUMMER -> System.out.println("夏天");
case Season.AUTUMN -> System.out.println("秋天");
case Season.WINTER -> System.out.println("冬天");
}
}
}
class Season {
public static final String SPRING = "SPRING";
public static final String SUMMER = "SUMMER";
public static final String AUTUMN = "AUTUMN";
public static final String WINTER = "WINTER";
// 私有构造函数,防止实例化
private Season() {}
public static String[] getAllSeasons() {
return new String[]{SPRING, SUMMER, AUTUMN, WINTER};
}
}
使用枚举技术优化代码:
package _enum;
public class EnumDemo01 {
public static void main(String[] args) {
useSeason(Season.AUTUMN);
}
public static void useSeason(Season season) {
switch (season) {
case SPRING -> System.out.println("春天");
case SUMMER -> System.out.println("夏天");
case AUTUMN -> System.out.println("秋天");
case WINTER -> System.out.println("冬天");
}
}
}
enum Season {
SPRING, SUMMER, AUTUMN, WINTER;
}
枚举入参严谨,提示性更好,代码优雅
枚举定义格式:
修饰符 enum 枚举类名 {
枚举项1, 枚举项2, 枚举项3...
}
枚举的特点
- 每一个枚举项其实就是该枚举类的一个对象
- 通过枚举类名去访问指定的枚举项
- 所有枚举类都是Enum的子类
- 枚举也是类,可以定义成员变量
- 枚举类的第一行上必须是枚举项,最后一个枚举项的分号是可以省略的。但如果枚举类有其他东西,就不能省略(推荐不省略)
- 枚举类可以有构造器,但必须是private的,它默认也是private
- 枚举类也可以有抽象方法,但枚举项必须重写该方法
类加载器
主要作用就是将类的字节码载入方法区中
加载时机:用到就加载。比如创建对象、调用类的静态成员、初始化继承体系、使用反射加载类的字节码
加载过程:加载、链接、初始化
第一步加载:
- 通过包名 + 类名,获取这个类,准备用流进行传输
- 将类加载到内存中
- 加载完毕创建一个class对象
第二步链接:
- 验证:验证类是否符合JVM规范,安全性检查
- 准备:为static变量分配空间,设置默认值
- 解析:将常量池中的符号引用解析为直接引用
``` package classloader;
public class ClassLoaderDemo01 { public static void main(String[] args) { ClassLoader classLoader = B.class.getClassLoader(); // 代码执行到这,表示: // 1. B类的字节码被加载到方法区,并完成链接(验证、准备、解析) // 2. C类的字节码也会被加载到方法区 // 3. 此时B和C都只是类结构,没有创建任何实例对象 // 4. B类中对C的引用是符号引用,尚未解析为直接引用
B b = new B();
// 当创建B对象时: // 1. 在堆中为B实例分配内存 // 2. 执行B的实例初始化 // 3. 在初始化过程中,执行 C c = new C(),创建C的实例 // 4. 此时C的实例才在堆中分配内存,有了实际的地址值 } }
class B { C c = new C(); }
class C {
} ```
第三步初始化:
- 初始化:根据程序员程序编码制定的主观计划去初始化类变量和其他资源
- 可以理解为将类中的静态成员变量赋上真实的值
类加载器的分类
- Bootstrap class loader:启动类加载器,虚拟机内置类加载器,通常表示为null。因为这个加载器是利用C++实现,所以获取到的只能是null
- Platfrom class loader:平台类加载器,负责加载JDK中一些特殊的模块。负责加载lib/modules内部的类
- 平台类加载器是从JDK9版本之后出现的
- JDK9版本之前是Extension Class Loader,扩展类加载器。负责加载jre/lib/ext目录下的类
- Application class loader:应用程序类加载器。负责加载自己写的类
- 自定义类加载器:上级为Application,目前不做讲解
| 方法 | 功能 |
|---|---|
| public Classloader getClassLoader() | 获取该类的 类加载器对象 |
示例:
package classloader;
public class ClassLoaderDemo {
public static void main(String[] args) {
// Boostrap类加载器
ClassLoader classLoader1 = String.class.getClassLoader();
System.out.println(classLoader1); // null
// Application类加载器
ClassLoader classLoader2 = ClassLoaderDemo.class.getClassLoader();
System.out.println(classLoader2); // jdk.internal.loader.ClassLoaders$AppClassLoader@36baf30c
// 获取上下级关系
System.out.println(classLoader2.getParent()); // jdk.internal.loader.ClassLoaders$PlatformClassLoader@b4c966a
System.out.println(classLoader2.getParent().getParent());// jdk.internal.loader.ClassLoaders$PlatformClassLoader@b4c966a
}
}
Application类加载器的上级:Platform类加载器
Platform类加载器的上级:Bootstrap类加载器
双亲委派模式
- 如果一个类加载器收到了类加载请求,它并不会自己先去加载
- 而是把这个请求委托给父类的加载器去执行
- 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归
- 请求最终将到达顶层的启动类加载器
- 如果父类加载器可以完成类加载任务,就成功返回
- 但父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式
- 这种加载模式的好处在于避免类的重复加载

反射
- 框架技术的灵魂
- 在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法。对于任意一个对象,都能够调用它的任意属性和方法
- 这种动态获取信息以及动态调用对象方法的功能成为Java语言的反射机制
获取类的字节码对象
- Class.forName(“全类名”);
- 类名.class
- 对象.getClass();
示例:
package reflect;
import pojo.Student;
public class ReflectDemo01 {
public static void main(String[] args) throws ClassNotFoundException {
// 1. Class.forName("全类名");
Class<?> class1 = Class.forName("pojo.Student");
// 2. 类名.class
Class<Student> class2 = Student.class;
// 3. 对象.getClass();
Student s = new Student();
Class<? extends Student> class3 = s.getClass();
System.out.println(class1); // class pojo.Student
System.out.println(class2); // class pojo.Student
System.out.println(class3); // class pojo.Student
System.out.println((class1 == class2) && (class2 == class3)); // 结果为true,因为字节码文件唯一
}
}
反射类中的构造方法
| 方法 | 功能 |
|---|---|
| Constructor<?>[] getConstructors() | 返回所有公共构造方法对象的数组 |
| Constructor<?>[] getDeclaredConstructors() | 返回所有构造方法对象的数组 |
| Constructor |
返回单个公共构造方法对象 |
| Constructor |
返回单个构造方法对象 |
示例:
package reflect;
import java.lang.reflect.Constructor;
import java.util.Arrays;
public class ReflectDemo02 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
// 获取类的字节码对象
Class<?> class1 = Class.forName("pojo.Student");
// 反射构造方法对象,批量反射
Constructor<?>[] constructors1 = class1.getConstructors();
Constructor<?>[] constructors2 = class1.getDeclaredConstructors();
System.out.println(Arrays.toString(constructors1));
// [public pojo.Student(), public pojo.Student(java.lang.String,int)]
System.out.println(Arrays.toString(constructors2));
// [public pojo.Student(), private pojo.Student(int), private pojo.Student(java.lang.String), public pojo.Student(java.lang.String,int)]
// 反射构造方法对象,单独反射
Constructor<?> constructor1 = class1.getConstructor(String.class, int.class); // 对应构造方法的参数,提供参数的字节码对象
System.out.println(constructor1); // public pojo.Student(java.lang.String,int)
Constructor<?> constructor2 = class1.getDeclaredConstructor(String.class);
System.out.println(constructor2); // private pojo.Student(java.lang.String)
}
}
通过构造方法对象进行实例化:
package reflect;
import pojo.Student;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ReflectDemo03 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<Student> class1 = Student.class;
Constructor<Student> constructor1 = class1.getConstructor(String.class, int.class);// 这是公共的构造方法
// ---------------------------------------------------------------------------- //
Student student1 = constructor1.newInstance("张三", 18);
System.out.println(student1); // Student{name='张三', age=18}
// ---------------------------------------------------------------------------- //
Constructor<Student> constructor2 = class1.getDeclaredConstructor(String.class); // 这是私有的构造方法
// Student student2 = constructor2.newInstance("张三"); // 报错:IllegalAccessException
// System.out.println(student2);
constructor2.setAccessible(true); // 取消权限检查
Student student2 = constructor2.newInstance("张三");
System.out.println(student2); // Student{name='张三', age=0}
}
}
newInstance:实例化对象的方法
setAccessible:设置为true表示取消权限检查
反射类中的成员变量
| 方法 | 功能 |
|---|---|
| Field[] getFields() | 返回所有公共成员变量对象的数据 |
| Field[] getDeclaredFields() | 返回所有成员变量对象的数组 |
| Field getField(String name) | 返回单个公共成员变量对象 |
| Field getDeclaredField(String name) | 返回单个成员变量对象 |
示例:
package reflect;
import pojo.Student;
import java.lang.reflect.Field;
import java.util.Arrays;
public class ReflectDemo04 {
public static void main(String[] args) throws NoSuchFieldException {
Class<Student> class1 = Student.class;
Field[] fields1 = class1.getFields();
System.out.println(Arrays.toString(fields1)); // [public int pojo.Student.id]
Field[] fields2 = class1.getDeclaredFields();
System.out.println(Arrays.toString(fields2)); // [public int pojo.Student.id, private java.lang.String pojo.Student.name, private int pojo.Student.age]
Field name = class1.getField("id");
Field age = class1.getDeclaredField("age");
System.out.println(name); // public int pojo.Student.id
System.out.println(age); // private int pojo.Student.age
}
}
Field类的设置和获取方法:
| 方法 | 说明 |
|---|---|
| void set(Object obj,Object value) | 赋值 |
| Object get(Object obj) | 获取值 |
示例:
package reflect;
import pojo.Student;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
public class ReflectDemo05 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
Class<Student> studentClass = Student.class;
// 利用反射创建对象
Student student1 = studentClass.getConstructor().newInstance();
Student student2 = studentClass.getConstructor().newInstance();
// 获取成员变量对象
Field name = studentClass.getDeclaredField("name");
Field age = studentClass.getDeclaredField("age");
// 取消权限检查
name.setAccessible(true);
age.setAccessible(true);
// 赋值操作
name.set(student1,"张三");
age.set(student1,18);
name.set(student2,"李四");
age.set(student2,20);
// 打印对象信息
System.out.println(student1); // Student{name='张三', age=18}
System.out.println(student2); // Student{name='李四', age=20}
// 获取成员变量的值
String s1 = (String) name.get(student1);
String s2 = (String) name.get(student2);
System.out.println(s1); // 张三
System.out.println(s2); // 李四
}
}
反射建议:禁止暴力反射操作
反射类中的成员方法
| 方法 | 功能 |
|---|---|
| Method[] getMethods() | 返回所有公共成员方法对象的数组,包括继承的 |
| Method[] getDeclaredMethods() | 返回所有成员方法对象的数组,不包括继承的 |
| Method getMethod(String name, Class<?>… parameterTypes) | 返回单个公共成员方法对象 |
| Method getDeclaredMethod(String name, Class<?>… parameterTypes) | 返回单个成员方法对象 |
- Method类用于执行方法的方法
| 方法 | 功能 |
|---|---|
| Object invoke(Object obj, Object… args) | 运行方法 |
示例:
package reflect;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
public class ReflectDemo06 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<Zi> class1 = Zi.class;
// 获取成员方法列表
Method[] methods1 = class1.getMethods();
System.out.println(Arrays.toString(methods1)); // 很长的一条数组,因为继承了Fu和Object,所以会获取到它们的所有方法
Method[] methods2 = class1.getDeclaredMethods();
System.out.println(Arrays.toString(methods2)); // [public void reflect.Zi.count(int[]), private void reflect.Zi.printHello(double), private void reflect.Zi.printHello(int)]
// 获取指定成员方法
Method count = class1.getMethod("count", int[].class);// 第二个以及以后的参数用于区分重载方法
Zi zi = class1.getConstructor().newInstance(); // 创建调用方法的对象
count.invoke(zi,new int[]{1,2,3,4,5}); // 调用方法
}
}
class Fu {
public void show() {
System.out.println("Fu Show ...");
}
}
class Zi extends Fu{
public Zi() {};
public void count(int... nums) {
int cnt = 0;
for (int i = 0; i < nums.length; i++) {
cnt += nums[i];
}
System.out.println(cnt);
}
private void printHello(int i) {
System.out.println("Hello");
}
private void printHello(double i) {
System.out.println("Hello");
}
}
反射练习
伪泛型
需求:请向一个Integer的集合,添加一个String字符串
思路:Java中的泛型是假的,只在编译的时候有效
示例:
package reflect;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
public class ReflectTest01 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,1,2,3,4,5);
Class<? extends ArrayList> class1 = list.getClass();
Method add = class1.getMethod("add", Object.class);
add.invoke(list,"你好");
System.out.println(list); // [1, 2, 3, 4, 5, 你好]
}
}
综合练习
需求:
编写三个任意的Javabean类:Student、Teacher、Worker
属性均为name、age
方法要求:
- Student里面写学习study和吃饭eat的方法(方法无参返回)
- Teacher里面写上课teach和吃饭eat的方法(方法无参返回)
- Worker里面写工作work和睡觉sleep的方法
本地新建配置文件properties
属性:className=Student类的全类名,methodName=study
在测试类中读取properties文件中的两个属性值
利用反射创建学生类的对象,并调用方法
修改配置文件,不修改代码,运行老师类中的上课方法
修改配置文件,不修改代码,运行工人类中的工作方法
示例:
package reflect;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Properties;
public class Test {
public static void main(String[] args) throws Exception {
Properties prop = new Properties();
try (FileInputStream fis = new FileInputStream("Day06\\config.properties")) {
prop.load(fis);
} catch (IOException e) {
e.printStackTrace();
}
// 获取字节码对象
Class<?> class1 = Class.forName(prop.getProperty("className"));
// 创建对象
Object o = class1.
getConstructor(String.class,int.class).
newInstance(prop.getProperty("name"),Integer.parseInt(prop.getProperty("age")));
// 获取成员方法对象
Method method = class1.getMethod(prop.getProperty("methodName"));
// 调用方法
method.invoke(o);
}
}
config.properties:
className=reflect.Worker
methodName=work
name=Alice
age=28
注解
- Annotation表示注解,是JDK1.5的新特性。主要作用:对程序进行标注
- 理解:注释是给人看的,注解是给JVM虚拟机看的。通过注解可以给类增加额外的信息。编译器或JVM可以根据注解来完成对应的功能。
JDK中常见的注解:
- @Override:表示方法的重写
- @Deprecated:表示修饰的方法已经过时
- @SuppressWarning(“all”):压制警告
注解常用位置:
- 类
- 方法
自定义注解
- 自定义注解单独存在意义不大,一般会跟反射结合起来使用
// 格式
public @interface 注解名称 {
public 属性类型 属性名() default 默认值;
}
// 示例
public @interface Anno {
String show() default "show...";
}
属性类型:
- 基本数据类型
- String
- Class
- 注解
- 枚举
- 以上类型的一维数组

当属性不存在默认值时,使用注解需要指定属性值:
package anno;
@MyAnno02(show = "Hello")
public class MyAnnoDemo01 {
@MyAnno02(show = "World",test = "CBA")
public static void main(String[] args) {
method();
}
@MyAnno03("Test") // 如果属性名为value,可以省略
public static void method() {
System.out.println("method");
}
}
@interface MyAnno02 {
String show();
String test() default "ABC";
}
@interface MyAnno03 {
String value();
}
- 在使用注解时,如果注解的属性没有给出默认值,需要手动给出
- @Anno(name=”张三”)
- 如果数组中只有一个属性值,在使用时
{}是可以省略的- 如果只有一个属性名字为value没有赋值,使用时直接给出值,不需要写属性名
自定义测试注解
需求:自定义一个@Test,用于指定类的方法上。如果类中的某个的方法上使用了该注解,就执行该方法
示例:
package anno;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MyTestDemo01 {
public static void main(String[] args) throws Exception {
Class<MethodDemo> clazz = MethodDemo.class;
MethodDemo methodDemo = clazz.getConstructor().newInstance();
// 获取该类中所有成员方法对象
Method[] methods = clazz.getMethods();
for (Method method : methods) {
// 判断该方法是否存在注解
if (method.isAnnotationPresent(Test.class)) {
method.invoke(methodDemo);
}
}
}
}
@interface Test {}
class MethodDemo {
public MethodDemo() {};
@Test
public void show() {
System.out.println("show");
}
@Test
public void print() {
System.out.println("print");
}
public void method() {
System.out.println("method");
}
}
测试类代码没有问题,但是执行后没有调用show和print方法
主要是因为注解存在生命周期:源代码阶段、字节码阶段、运行阶段,默认是处于字节码阶段
但是反射是在运行阶段的进行操作,所以无法正确识别到注解
isAnnotationPresent():查看是否存在对应注解,参数为注解的字节码对象
元注解
- 元注解就是用在注解上的注解
| 元注解名 | 说明 |
|---|---|
| @Target | 指定了注解能在哪里使用 |
| @Retention | 可以理解为保留时间(生命周期) |
@Target:用来标识注解使用的位置,如果没有使用该注解表示,则自定义的注解可以使用在任意位置

@Retention:用来标识注解的生命周期(有效范围)

将自定义测试注解案例进行修改:
package anno;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MyTestDemo01 {
public static void main(String[] args) throws Exception {
Class<MethodDemo> clazz = MethodDemo.class;
MethodDemo methodDemo = clazz.getConstructor().newInstance();
// 获取该类中所有成员方法对象
Method[] methods = clazz.getMethods();
for (Method method : methods) {
// 判断该方法是否存在注解
if (method.isAnnotationPresent(Test.class)) {
method.invoke(methodDemo);
}
}
}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}
class MethodDemo {
public MethodDemo() {};
@Test
public void show() {
System.out.println("show");
}
@Test
public void print() {
System.out.println("print");
}
public void method() {
System.out.println("method");
}
}
现在就可以正确调用show和print方法了
动态代理
- 动态代理是对象的另外一种创建方式,这种方式可以在不修改源码的情况下,增强方法
- 动态代理就是在运行时生成代理类,无需手动为每个目标类编写代理类
java.lang.reflect.Proxy类:提供了使用动态代理方式创建对象的API
| 方法 | 说明 |
|---|---|
| static Object newProxyInstance( ClassLoader loader, Class<?>[] interfaces, InvocationHandler h ) | 参数一: 用于指定用哪个类加载器,去加载生成的实现类 参数二: 指定接口,这些接口用于指定生成的实现类中有什么 也就是有哪些方法 参数三: 用来指定生成的实现类中不同方法的实现方案 |
如果目标类的方法不是通过接口实现的,JDK动态代理(Proxy.newProxyInstance)无法直接使用
代理
类似于通过一个中间类对源方法进行增强

SaleImpl这个类就是中间类(代理类)
不过每当有用户需要进行新的方法增强时,就得重新编写另一份专门的代理类
这种代理方式被称为静态代理
示例
package proxy;
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 ProxyDemo01 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
// 参数一:应用程序加载器
// 参数二:List字节码对象数组
// 参数三:对具体方法的增强方案
List<Integer> o = (List<Integer>) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{List.class}, new InvocationHandler() {
// proxy:生成的代理对象,一般不直接在内部使用
// method:所有代理的方法
// args:调用方法的时候,传入的实际参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
long begin = 0, end = 0;
if ("add".equals(name)) {
begin = System.currentTimeMillis();
Object invoke = method.invoke(list, args);
end = System.currentTimeMillis();
System.out.println("耗时:" + (end - begin));
return invoke;
}
return null;
}
});
o.add(111);
o.add(222);
o.add(333);
System.out.println(list);
}
public static void showClassLoader() {
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader); // jdk.internal.loader.ClassLoaders$AppClassLoader@36baf30c
}
}
实现动态代理的步骤较为复杂,不需要会熟练使用,但一定要认真理解
但在Spring框架中,有面向切面编程(AOP),其中已经对动态代理进行了封装
小结
- 动态代理是对象的另外一种创建方式,这种方式可以在不修改源码的情况下,增强方法
- 代理对象可以在原有功能的基础上进行增强
- 动态代理在运行时动态的生成代理类,无需手动为每个目标类编写代理类
- 使用Proxy中的newProxyInstance()方法创建代理对象