enum,java代理,反射

一、enum,枚举类

什么是枚举类:枚举类是Java中一种特殊的数据类型,用来表示一组常量,比如,一年的12个月,一周的7天。

以一周的七天为例,如果我们不用枚举类那么一般会这样写

public class DayDemo {

    public static final int MONDAY =1;

    public static final int TUESDAY=2;

    public static final int WEDNESDAY=3;

    public static final int THURSDAY=4;

    public static final int FRIDAY=5;

    public static final int SATURDAY=6;

    public static final int SUNDAY=7;

}

但是这样写就显得非常的冗余,所以我们能使用枚举类直接让我们的这一组常量变得更加简洁,高效。

使用的格式:(不同的枚举变量之间用逗号隔开,如果想在类中声明方法则需要在最后一个枚举变量后加上分号)

//枚举类型,使用关键字enum
enum Day {
    MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

 

其中ordinal()方法返回的是枚举变量在枚举类型中的申明顺序,从0开始。compareTo()返回的是枚举变量与指定对象的差,name()方法与toString()的用法几乎一致返回的是字符串

String s = "FRIDAY";
Day day = Day.FRIDAY;
System.out.println(s.equals(day)); //false
System.out.println(s.equals(day.name())); //true

 当我们需要查看枚举类中所有的枚举变量时可以用枚举数组把枚举内的枚举变量全都放进去,

Day[] days = Day.values()

但是如果向上转型为Enum之后values()方法会失效,那么我们就可以用Class对象来获取所有的枚举变量;

Enum e = Day.FRIDAY;
System.out.println(Arrays.toString(e.values()));//Enum没有values()方法
System.out.println(Arrays.toString(e.getClass().getEnumConstants()));
 enum添加方法与构造函数

还是以一周七天为例,我们对枚举变量的命名一般是英语,但是万一有人不认识让你把每个英语的中文打印出来,那我们直接switch-case一个一个输出不就好了吗?


public class EnumDemo {

    public static void printName(Day day){
        switch (day){
            case MONDAY: //无需使用day进行引用
                System.out.println("星期一");
                break;
            case TUESDAY:
                System.out.println("星期二");
                break;
            case WEDNESDAY:
                System.out.println("星期三");
                break;
        }
    }

    public static void main(String[] args){
        printName(day.MONDAY);
    }
}

如果只有七个case还好,但是如果有一天我们的后端负责人说又有个需求要写,这个需求里的枚举类里有三千个枚举变量怎么办?两个办法,一解决后端负责人,二、解决需求。今天我们先学学第二个,这时候就要用到枚举类的构造函数与方法;

public class edemo {
    enum day {
        MONDAY("星期一"),
        TUESDAY("星期二"),
        WEDNESDAY("星期三"),
        THURSDAY("星期四"),
        FRIDAY("星期五"),
        SATURDAY("星期六"),
        SUNDAY("星期日");

        private String name;

        day(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }

    public static void main(String[] args) {
        System.out.println(day.MONDAY.getName());//打印出星期一
    }
}

同样的,enum类里的也可以进行方法的重写,以及抽象方法的定义;
但是因为Java是单继承的,所以enum继承Enum就不能再继承其它的,但是他是可以实现多个接口的;

如果想了解更多可以看这篇文章Java枚举类

二、Java代理模式

1.什么是代理模式?

代理模式( Proxy ),给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。 通常会通过代理对象来为原对象添加额外的功能。
代理模式属于结构型模式主要用于处理类或对象的组合。

简单来讲就是你自己想做某件事,然后别人来帮你把这件事以及这件事前前后后各种杂七杂八的事情全都帮你解决了

2.静态代理和动态代理

静态代理是我们直接编写一个代理类来实现功能,我们可以通过继承或者接口来实现静态的代理

//通过继承来实现静态代理
public class Tank{
    public void move() {
        System.out.println("Tank moving cla....");
    }

    public static void main(String[] args) {
        new ProxyTank().move();
    }
}
class ProxyTank extends Tank{
    @Override
    public void move() {
        System.out.println("方法执行前...");
        super.move();
        System.out.println("方法执行后...");
    }
}
//通过接口来实现静态代理
public class Tank implements Movable{
    @Override
    public void move() {
        System.out.println("Tank moving cla....");
    }

    public static void main(String[] args) {
        Tank tank = new Tank();
        new LogProxy(tank).move();
    }
}

class LogProxy implements Movable{
    private Movable movable;
    public LogProxy(Movable movable) {
        this.movable = movable;
    }
    @Override
    public void move() {
        System.out.println("方法执行前....");
        movable.move();
        System.out.println("方法执行后....");
    }
}
interface Movable {
    void move();
}

 但是如果有一天我们的客户需求变了,我们要更改我们的被代理类,那我们就要把我们的代理类全都再改一遍,这时静态代理的缺点就显示出来了,那就是耦合性太强了,这时我们就可以用到动态代理;

1.JDK动态代理:需要被代理的类实现某些接口,生成的代理类也会实现某些接口;

2.CGLIB动态代理不需要被代理对象的类实现了某些接口生成的代理类为目标对象的类的子类


JDK动态代理

①、java.lang.reflect.Proxy:根据InvocationHandler和目标类的类加载器,实现的接口生成代理类,生成的代理类也实现了这些接口,且使用相同的类加载器加载,然后再反射生成代理类的实例,反射生成的过程中会将InvocationHandler传递给父类的构造方法,因此实例化的代理类对象会持有InvocationHandler的引用

②、InvocationHandler:包含具体的被代理对象的引用,也代码中也就是target字段,根据invoke方法执行相应的操作

③、最终生成的代理类会继承Proxy类并实现接口

public class Tank implements Movable{
    @Override
    public void move() {
        System.out.println("Tank moving cla....");
    }

    public static void main(String[] args) {
        Tank tank = new Tank();
        // reflection 反射 通过二进制字节码分析类的属性和方法

        //newProxyInstance: 创建代理对象
        // 参数一: 被代理类对象
        // 参数二:接口类对象  被代理对象所实现的接口
        // 参数三:调用处理器。 被调用对象的那个方法被调用后该如何处理
        Movable o = (Movable)Proxy.newProxyInstance(Tank.class.getClassLoader()
                ,Tank.class.getInterfaces()
                ,new LogProxy(tank));
        o.move();
    }
}
class LogProxy implements InvocationHandler {
    private Movable movable;

    public LogProxy(Movable movable) {
        this.movable = movable;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("方法:"+method.getName()+"()执行前");
        Object invoke = method.invoke(movable, args);  // 此处相当于 movable.move()
        System.out.println("方法:"+method.getName()+"()执行后");
        return invoke;
    }
}
interface Movable {
    void move();
}

再newProxyInstance中前两个参数比较固定第一个参数是被代理类的类加载,第二个是 被代理对象实现的接口,而第三个参数就比较重要了,是调用的处理器也是需要我们自定义的一个东西;

所以JDK动态代理主要执行的是我们自己定义的处理器中的invoke方法;

CGLIB动态代理:

CGLIB动态代理是针对类来实现代理的,它对指定的目标生成一个子类,并覆盖掉其中的方法实现增强,所以使用CGLIB不受代理类必须实现接口的限制;但是因为是生成子类,使用无法继承父类中的final修饰的类;

使用起来也非常简单只需要指定父类和回调方法就可以了

MethodInterceptor:方法拦截器;
在调用目标方法时,cglib会回调MethodInterceptor接口方法,来实现你自己的代理逻辑,类似于JDK中的InvocationHandler接口

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer(); // 增强者
        enhancer.setSuperclass(Tank.class); // 指定父类
        enhancer.setCallback(new TimeMethodInterceptor()); // 当被代理对象的方法调用的时候会调用 该对象的intercept
        Tank tank = (Tank)enhancer.create();  // 动态代理的生成
        tank.move();  // 生成之后会调用
    }
}

class TimeMethodInterceptor implements MethodInterceptor{
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("生成的类名"+o.getClass().getName());
        System.out.println("生成的类的父类"+o.getClass().getSuperclass().getName());
        System.out.println("方法执行前,被代理的方法"+method.getName());
        Object result = null;
        result = methodProxy.invokeSuper(o, objects);
        System.out.println("方法执行后,被代理的方法"+method.getName());
        return result;
    }
}
class Tank{
    public void move(){
        System.out.println("Tank moving clacla....");
    }
}

但是如果你把这段代码自己跑一边你会发现库库报错,
Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass ,这是为什么捏?并不是因为这段代码有问题,而是因为你上maven官网查看,cglib最后一次维修实在19年,而JDK17中引用seata作为分布式事务控制,当seata用了cglib中不安全的api就会报错,如果想要接着使用cglib可以使用spring修正版的cglib:spring-framework/spring-core/src/main/java/org/springframework/cglib at main · spring-projects/spring-framework · GitHub

还要一种东西叫SpringAOP用到了JDK和cglib,如果想了解更多可以看看这篇:Spring AOP(AOP概念、组成、Spring AOP实现及实现原理)-CSDN博客

三、反射

在说反射之前先说一个与之相对的东西,正射,一般情况下我们在使用某个类的时候一定知道这个类是用来干嘛的,然后再对这个类进行实例化,然后再对这个类对象进行操作

Apple apple = new Apple(); //直接初始化,「正射」
apple.setPrice(4);

 那么什么是反射?

Java反射机制就是在运行过程中能够知道一个类的所有属性和方法,并且能够调用其中任意一个属性和方法;但是反射在使用前有一个前提,就是必须得到该类的字节码的Class,Class类用于表示.class文件(字节码))

反射机制常用的类都在这个包下:
java.lang.reflect.*

反射机制中常见的类:

 必须先获取Class才能在获取Methond,Constructor,Filed

获取Class我们可以通过:
①Class.forName("完整的类名带包名")
②对象.getClass()
③类型.class

        Class clz;
        {
            try {
                clz = Class.forName("reflectTest.Apple");
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
        Apple apple = new Apple();
        System.out.println(clz);
        System.out.println(Apple.class);
        System.out.println(apple.getClass());
//打印出来的结果都是class reflectTest.Apple

 通过反射获取实例化对象
Class对象.newInstence(); 返回的是一个Object类型

    Object c = null;
        try {
            c = clz.newInstance();
        }  catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        System.out.println(c);//打印Apple(price=null)

 但是如果直接newInstence()只能无参构造,如果没有无参构造或者无法访问,就会报错;

Class类中常用的方法:

Field类中常用的方法:field的set方法不能访问私有属性需要打破封装才可以;

Method类中常用的方法:

        System.out.println(Modifier.toString(method.getModifiers()));//打印方法的修饰符
        System.out.println(method.getReturnType().getSimpleName());//打印方法的返回值类型

方法invoke(对象,实参) //输入的是实参
而在Class类中getDeclaredMethod("方法名",...形参); //输入的是形参

Constructor类中常用的方法:

 反编译实例化对象其实有两步:
①获取构造方法
②再用构造方法new一个

还是以刚才那个Apple为例,这时我添加一个构造函数后,就可以通过constructor进行有参构造了

        Constructor constructor = clz.getDeclaredConstructor(Integer.class);
        Apple apple1 = (Apple) constructor.newInstance( 6);
        //打印Apple(price=6)

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值