初识Java 17-3 反射

目录

instanceof和Class的比较

运行时的类信息

动态代理

使用Optional

标签接口

模拟对象和桩


本笔记参考自: 《On Java 中文版》


instanceofClass的比较

         在进行类型比较时,instanceofClass.isInstance()并无太大区别。但若将这两者的结果与使用class直接进行对比的结果进行比较,就会发现一些区别:

【例子:使用class直接进行比较】

class Base {
}

class Derived extends Base {
}

public class FamilyVsExactType {
    static void test(Object x) {
        System.out.println("查看x的类型: " + x.getClass());

        System.out.println("x instanceof Base: " +
                (x instanceof Base));
        System.out.println("x instanceof Derived: " +
                (x instanceof Derived));

        System.out.println("Base.isInstance(x): " +
                Base.class.isInstance(x));
        System.out.println("Derived.isInstance(x): " +
                Derived.class.isInstance(x));

        System.out.println("x.getClass() == Base.class: " +
                (x.getClass() == Base.class));
        System.out.println("x.getClass() == Derived.class: " +
                (x.getClass() == Derived.class));

        System.out.println("x.getClass().equals(Base.class): " +
                (x.getClass().equals(Base.class)));
        System.out.println("x.getClass().equals(Derived.class): " +
                (x.getClass().equals(Derived.class)));
    }

    public static void main(String[] args) {
        test(new Base());
        System.out.println();
        test(new Derived());
    }
}

        程序执行的结果是:

        上述结果告诉我们:①instanceofisInstance()得出的结果完全相同;②equal()== 得出的结果也完全一致。这就体现了instanceof的概念:是否属于对应层次结构,而equals()则考虑确切的类型。

运行时的类信息

        通过instanceof获得对象的确切类型有一个前提:只有在编译时就知道的类型能够使用instanceof。换言之,编译器必须知道我们使用的所有类。

        这种限制可能会在一些大的编程环境中产生阻碍,例如:

  • 基于组件的编程。此时这些组件必须被实例化,并且公开一部分它们的信息。
  • 进行远程方法调用,即通过网络在远程平台上创建和运行对象。

假设我们面对的这些类只是文件或网络链接中的一堆字节,并且它们在编译器生成代码的很久之后才会出现,我们应该如何使用这些类呢?

        反射提供了一种检测可用方法并生成方法名称的机制Class类和java.lang.reflect库一起支持了反射,reflect库中包含的工具类的对象会由JVM在运行时创建,用来表示未知类中对应的成员。

当反射遇到未知对象时,JVM会查看这个对象,并确定它属于哪一个类。这就需要加载对应的Class对象。换言之,必须存在对应的.class文件(来自本地或是网络)。JVM将打开这个文件进行检查。

类方法提取器

        反射会被用于支持其他特性,例如对象序列化、动态提取类的信息。一般而言,我们并不会直接使用反射,但下面的例子会使用到reflect库中的工具,通过它们,我们可以调查类中那些被隐藏起来的方法:

【例子:提取类的方法】

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.regex.Pattern;

public class ShowMethods {
    private static String usage =
            "使用说明:\n" +
                    "java ShowMethods 限定的类名\n" +
                    "若需要展示类中的所有方法:\n" +
                    "java ShowMrthods 限定的类名 ‘对应的名字’\n" +
                    "(将需要搜索的方法填入‘对应的名字’中)";
    private static Pattern p =
            Pattern.compile("\\w+\\.");

    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println(usage);
            System.exit(0);
        }

        try {
            Class<?> c = Class.forName(args[0]);
            // Method、Constructor,这些都表示了类中对应的成员
            Method[] methods = c.getMethods();
            Constructor[] ctors = c.getConstructors();
            if (args.length == 1) {
                for (Method method : methods)
                    System.out.println(
                            p.matcher(method.toString())
                                    .replaceAll(""));
                for (Constructor ctor : ctors)
                    System.out.println(
                            p.matcher(ctor.toString())
                                    .replaceAll(""));
            } else {
                for (Method method : methods)
                    if (method.toString().contains(args[1])) {
                        System.out.println(
                                p.matcher(method.toString())
                                        .replaceAll(""));
                    }
                for (Constructor ctor : ctors)
                    if (ctor.toString().contains(args[1])) {
                        System.out.println(
                                p.matcher(ctor.toString())
                                        .replaceAll(""));
                    }
            }
        } catch (ClassNotFoundException e) {
            System.out.println("没有这个类:" + e);
        }
    }
}
  • 在命令行输入:java ShowMethods ShowMethods
  • 其输出如下:

  • 在命令行输入:java ShowMethods ShowMethods java.lang.String
  • 其输出如下:

        MethodConstructor类中都有对应的方法,用于进一步解析它们表示的成员,并获取其名称、参数和返回值的相关信息。但这个例子是直接使用的toString()方法(若不使用正则表达式进行处理,打印结果将会包含这些方法的完全限定名)

        Class.forName()生成的结果在编译时是未知的,其所有的方法签名信息都是在运行时提取的。反射提供了足够的支持,来创建一个在编译时完全位未知的对象。

动态代理

代理

        代理是一种基本的设计模式,这种设计模式通过一个对象代替“实际”的对象,从而提供额外或不同的服务。因为这种操作通常涉及到与“实际”对象的通信,因此代理通常会充当中间人的角色。例如:

【例子:代理结构】

interface Interface {
    void doSomething();

    void somethingElse(String arg);
}

class RealObject implements Interface {
    @Override
    public void doSomething() {
        System.out.println("做了某件事(Something)");
    }

    @Override
    public void somethingElse(String arg) {
        System.out.println("做了另一件事(Else):" + arg);
    }
}

class SimpleProxy implements Interface {
    private Interface proxied;

    SimpleProxy(Interface proxied) {
        this.proxied = proxied;
    }

    @Override
    public void doSomething() {
        System.out.println("通过代理做某件事");
        proxied.doSomething();
    }

    @Override
    public void somethingElse(String arg) {
        System.out.println(
                "通过代理做另一件事:" + arg);
        proxied.somethingElse(arg);
    }
}

public class SimpleProxyDemo {
    public static void consumer(Interface iface) {
        iface.doSomething();
        iface.somethingElse("吃饭");
    }

    public static void main(String[] args) {
        // 直接调用:
        consumer(new RealObject());

        System.out.println();
        // 使用代理:
        consumer(new SimpleProxy(new RealObject()));
    }
}

        程序执行的结果是:

        上述例子体现了代理的作用:当我们想要将某种额外的操作从“实际”的对象中分离出去,并且希望在某些时刻我们能够轻松使用这些额外的操作时,代理就发挥了作用。

    设计模式的关注点在于封装修改,因此我们需要做对应的修改来适应模式。

------

动态代理

        Java还提供了一种动态代理的方式,这种方式支持动态地创建代理,动态地处理对所代理方法的调用。这种动态代理通过一个调用处理器实现,在动态代理上进行的任何调用都会被重定位到一个调用处理器上,再由处理器决定如何处理。

        下面的例子将通过动态代理重写上面的例子:

【例子:使用动态代理】

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 若要使用动态代理,则需要实现InvocationHandler接口
class DynamicProxyHandler implements InvocationHandler {
    private Object proxied;

    DynamicProxyHandler(Object proxied) {
        this.proxied = proxied;
    }

    @Override
    public Object invoke(
            Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("=== 代理:" + proxy.getClass() +
                "\n··· 方法:" + method + "\n··· 参数:" + args);
        if (args != null)
            for (Object arg : args)
                System.out.println(" " + arg);
        return method.invoke(proxied, args);
    }
}

public class SimpleDynamicProxy {
    public static void consumer(Interface iface) {
        iface.doSomething();
        iface.somethingElse("睡觉");
    }

    public static void main(String[] args) {
        RealObject real = new RealObject();
        consumer(real);

        System.out.println();
        // 生成一个动态代理实例,其中包裹了原本的“实际”对象
        Interface proxy = (Interface) Proxy.newProxyInstance(
                Interface.class.getClassLoader(), // 用于定义代理类的类加载器
                new Class[]{Interface.class}, // 代理类要实现的接口的列表
                new DynamicProxyHandler(real)); // 方法调用应该被分配到的调用处理程序
        consumer(proxy);
    }
}

        程序执行的结果是:

        想要通过newProxyInstance()创建动态代理,需要传入三个参数:

  1. 一个类加载器(可从其他已加载的对象那里获取);
  2. 一个希望代理实现的接口列表;
  3. 一个InvocationHandler接口的实现。

动态代理会将所有的调用重定向到调用处理器,因此调用处理器的构造器通常会获得“实际”对象的引用。

        代理对象最终会被传递给对应的invoke()方法进行处理,这样我们就不需要区分请求的来源。注意:对于接口的调用是通过代理进行重定向的,在invoke()内部调用代理时需要考虑到这一点。

        再看DynamicProxyHandler中的invoke()

当我们将需要代理的操作执行完毕后,通常会使用Method.invoke()方法将请求转发给被代理的对象,并传入必要的参数。这么做的好处是,在调用那些通用的方法前,我们可以进行过滤,只挑选需要的方法调用:

【例子:过滤方法调用】

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

class MethodSelector implements InvocationHandler {
    private Object proxied;

    MethodSelector(Object proxied) {
        this.proxied = proxied;
    }

    @Override
    public Object invoke(
            Object proxy, Method method, Object[] args)
            throws Throwable {
        if (method.getName().equals("needed"))
            System.out.println("代理检测到我们需要的方法");
        return method.invoke(proxied, args);
    }
}

interface SomeMethods {
    void thing1();

    void thing2();

    void needed(String arg);

    void thing3();
}

class Implementation implements SomeMethods {
    @Override
    public void thing1() {
        System.out.println("某件事");
    }

    @Override
    public void thing2() {
        System.out.println("某件事");
    }

    @Override
    public void needed(String arg) {
        System.out.println("需要的方法:" + arg);
    }

    @Override
    public void thing3() {
        System.out.println("某件事");
    }
}

public class SelectingMethods {
    public static void main(String[] args) {
        SomeMethods proxy =
                (SomeMethods) Proxy.newProxyInstance(
                        SomeMethods.class.getClassLoader(),
                        new Class[]{SomeMethods.class},
                        new MethodSelector(new Implementation()));
        proxy.thing1();
        proxy.thing2();
        proxy.needed("我们需要的");
        proxy.thing3();
    }
}

        程序执行的结果是:

        在这里,我们通过方法签名进行了一个简单的过滤,但我们也可以通过其他方式进行过处理。

    动态代理并不常用,但在一些特定场合,它能解决我们的问题。

使用Optional

        当我们使用Java内置的null时,我们必须在对象的引用之前添加检查语句,否则就会产生异常,例如:

但这种检查往往会成为一种冗余的代码。其中的问题就在于null没有自己的行为,它只产生异常。

        因此我们可以使用java.util.Optional(对该类的介绍可见笔记12-2)来创建一个简单的代理,以此屏蔽潜在的null值。

    但在实际应用中,到处使用Optional不一定就是好的:我们有时也会需要判断一下是否为nullOptional的应用场景大多在“更接近数据”的地方,此时对象表示问题空间中的实体。

【例子:使用Optional处理null的情况】

        假设在系统中存在一个Person类,其对象不一定有对应的实体。此时我们会需要使用null引用来表示为空的状态。在之后的处理中依次来进行检查。

import java.util.Optional;

public class Person {
    public final Optional<String> family; // 姓氏
    public final Optional<String> name; // 名字
    public final Optional<String> address;

    // ...(这是这个类中的一些被省略的操作)

    public final boolean empty;

    Person(String family, String name, String address) {
        // 若为空,ofNullable()方法会返回一个空的Optional
        this.family = Optional.ofNullable(family);
        this.name = Optional.ofNullable(name);
        this.address = Optional.ofNullable(address);

        // isPresent():若值存在,返回true
        empty = !this.name.isPresent()
                && !this.family.isPresent()
                && !this.address.isPresent();
    }

    Person(String family, String name) {
        this(family, name, null);
    }

    Person(String family) {
        this(family, null, null);
    }

    Person() {
        this(null, null, null);
    }

    @Override
    public String toString() {
        if (empty)
            return "<空>";

        // 若某一项的值为空,这一项就只打印“”
        return (family.orElse("") +
                " " + name.orElse("") +
                " " + address.orElse("")).trim(); // 删除空白字符
    }

    public static void main(String[] args) {
        System.out.println(new Person());
        System.out.println(new Person("王"));
        System.out.println(new Person("王", "小二"));
        System.out.println(new Person("王", "小二",
                "人民路xxx号"));
    }
}

        程序执行的结果是:

        像Person类的这种设计有时也被称为“数据传输对象”。在这种对象中,所有字段都是publicfinal,因此Person是不可变的 —— 构造器设置后,我们就无法再进行修改了。

        由于传入的数据会在Person类内部被处理成Optional的,所以也就不会触发NullPointerException

【例子:招聘人员】

        再看一个更复杂的例子。假设我们需要招聘人员,当职位空缺时,我们就会需要使用Optional来为Person类提供占位符。

import java.util.Optional;

class EmptyTitleException extends RuntimeException {
}

class Position {
    private String title;
    private Person person;

    Position(String jobTitle, Person employee) {
        setTitle(jobTitle);
        setPerson(employee);
    }

    Position(String jobTitle) {
        this(jobTitle, null);
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String newTitle) {
        // 由于newTitle可能为空,因此需要抛出异常
        title = Optional.ofNullable(newTitle)
                .orElseThrow(EmptyTitleException::new);
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person newPerson) {
        // 若newPerson为空,则使用一个空的Person
        person = Optional.ofNullable(newPerson)
                .orElse(new Person());
    }

    @Override
    public String toString() {
        return "职位:" + title +
                ",员工:" + person;
    }

    public static void main(String[] args) {
        System.out.println(new Position("CEO"));
        System.out.println(new Position("程序员",
                new Person("王", "小二")));

        try {
            new Position(null);
        } catch (Exception e) {
            System.out.println("捕获异常:" + e);
        }
    }
}

        程序执行的结果是:

        尽管titleperson都是普通字段,没有使用Optional处理null情况。但这些字段只能通过setTitle()setPerson()进行修改,而这些方法会受到Optional的作用。

    在setTitle()setPerson()中,我们也可以自己检查传入的参数是否合法。但最终我们选择使用Optional.ofNullable()进行检测,这种精神来自于函数式编程:使用已有的经过大量验证的代码,以此来减少手动编写代码时会犯的各种错误。

        title会在setTitle()中获得一个职位,并且通过异常的约束,我们要求title不能为空。但person则被允许是为一个不设置内容的对象,尽管我们也能在对其进行一定的设置,但根据极限编程的原则,在初稿中我们只“尝试最简单且可行的事情”

【例子:简单的人事信息管理】

import java.util.ArrayList;
import java.util.stream.Collectors;

public class Staff extends ArrayList<Position> {
    public void add(String title, Person person) {
        add(new Position(title, person));
    }

    public void add(String... titles) {
        for (String title : titles)
            add(new Position(title));
    }

    public Staff(String... titles) {
        add(titles);
    }

    public boolean positionAvailable(String title) {
        for (Position position : this)
            if (position.getTitle().equals(title) &&
                    position.getPerson().empty)
                return true;

        return false;
    }

    public void fillPosition(String title, Person hire) {
        for (Position position : this)
            if (position.getTitle().equals(title) &&
                    position.getPerson().empty) {
                position.setPerson(hire);
                return;
            }
        throw new RuntimeException(
                "岗位【" + title + "】不存在");
    }

    public static void main(String[] args) {
        Staff staff = new Staff(
                "主席", "CTO",
                "市场经理", "项目经理",
                "项目负责人", "软件工程师",
                "软件工程师", "软件工程师",
                "测试工程师", "技术撰稿人");

        staff.fillPosition("主席",
                new Person("张", "三", "人民路"));
        staff.fillPosition("项目负责人",
                new Person("李", "华", "落凤坡"));
        if (staff.positionAvailable("软件工程师"))
            staff.fillPosition("软件工程师",
                    new Person("小", "明", "赤壁"));
        System.out.println(staff.stream()
                .map(position -> position + "\n")
                .collect(Collectors.joining("")));
    }
}

        程序执行的结果是:

标签接口

        也可以使用标签接口来表示可空性。标签接口没有元素,其的名称会被当作标签使用:

package onjava;

public interface Null {} // 只需要接口的名字即可

        接下来通过一个例子展示这种标签接口的使用。可以在一个接口中使用上述的标签接口,这样这个标签就能覆盖所有子类。

【例子:创建一个Robot接口】

package reflection;

import onjava.Null;

import java.util.List;


public interface Robot {
    String name(); // 名称

    String model(); // 模型

    List<Operation> operations(); // 可以执行的所有功能

    static void test(Robot r) {
        if (r instanceof Null)
            System.out.println("[Null Robot]");
        System.out.println("机器人的名字:" + r.name());
        System.out.println("机器人的模式:" + r.model());
        for (Operation operation : r.operations()) {
            System.out.println(operation.description.get());
            operation.command.run();
        }
    }
}

        其中的Operation表示的是一种命令模式,其中包含了一个描述和一条命令。两者都被定义为对函数式接口的引用,因此可以被赋予lambda表达式或方法引用:

package reflection;

import java.util.function.Supplier;

public class Operation {
    public final Supplier<String> description;
    public final Runnable command;

    public Operation(Supplier<String> descr, Runnable cmd) {
        description = descr;
        command = cmd;
    }
}

---

        接下来就是一个对Robot接口的实现:创建一个扫雪的机器人。

【例子:扫雪机器人】

package reflection;

import java.util.Arrays;
import java.util.List;

public class SnowRobot implements Robot {
    private String robotName;

    public SnowRobot(String name) {
        robotName = name;
    }

    @Override
    public String name() {
        return robotName;
    }

    @Override
    public String model() {
        return "扫地机器人 v1.1.4";
    }

    private List<Operation> ops = Arrays.asList(
            new Operation(
                    () -> robotName + "能够扫雪",
                    () -> System.out.println(
                            robotName + "扫雪中...")),
            new Operation(
                    () -> robotName + "能够除冰",
                    () -> System.out.println(
                            robotName + "除冰中...")),
            new Operation(
                    () -> robotName + "能够清理屋檐",
                    () -> System.out.println(
                            robotName + "清理屋檐...")));

    @Override
    public List<Operation> operations() {
        return ops;
    }

    public static void main(String[] args) {
        Robot.test(new SnowRobot("清理者"));
    }
}

        程序执行的结果是:

        更进一步,我们可能会需要设置许多不同类型的机器人,并且对一些类型为空的机器人做出特殊处理。一个好的办法就是使用动态代理:

【例子:通过动态代理设置不同的机器人】

package reflection;

import onjava.Null;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;

class NullRobotProxyHandler
        implements InvocationHandler {
    private String nullName;
    private Robot proxied = new NRobot();

    NullRobotProxyHandler(Class<? extends Robot> type) {
        nullName = type.getSimpleName() + " 未设置";
    }

    private class NRobot implements Null, Robot {
        @Override
        public String name() {
            return nullName;
        }

        @Override
        public String model() {
            return nullName;
        }

        @Override
        public List<Operation> operations() {
            return Collections.emptyList();
        }
    }

    @Override
    public Object invoke(Object proxy,
                         Method method, Object[] args)
            throws Throwable {
        return method.invoke(proxied, args);
    }
}

public class NullRobot {
    public static Robot
    newNullRobot(Class<? extends Robot> type) {
        return (Robot) Proxy.newProxyInstance(
                NullRobot.class.getClassLoader(),
                new Class[]{Null.class, Robot.class},
                new NullRobotProxyHandler(type));
    }

    public static void main(String[] args) {
        Stream.of(
                new SnowRobot("除雪者"),
                newNullRobot(SnowRobot.class)
        ).forEach(Robot::test);
    }
}

        程序执行的结果是:

        通过newNullRobot类,我们就可以完成对一个空的Robot对象的处理:传递给newNullRobot,由它返回一个代理。

模拟对象和桩

        简单介绍一下模拟对象和桩:

        模拟对象(Mock Object)和(Stub)都是Optional的逻辑变体,这两者都是在最终的程序中使用的“实际”对象的代理。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值