目录
本笔记参考自: 《On Java 中文版》
instanceof和Class的比较
在进行类型比较时,instanceof和Class.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());
}
}
程序执行的结果是:
上述结果告诉我们:①instanceof和isInstance()得出的结果完全相同;②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
- 其输出如下:
Method和Constructor类中都有对应的方法,用于进一步解析它们表示的成员,并获取其名称、参数和返回值的相关信息。但这个例子是直接使用的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()创建动态代理,需要传入三个参数:
- 一个类加载器(可从其他已加载的对象那里获取);
- 一个希望代理实现的接口列表;
- 一个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不一定就是好的:我们有时也会需要判断一下是否为null。Optional的应用场景大多在“更接近数据”的地方,此时对象表示问题空间中的实体。
【例子:使用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类的这种设计有时也被称为“数据传输对象”。在这种对象中,所有字段都是public和final的,因此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);
}
}
}
程序执行的结果是:
尽管title和person都是普通字段,没有使用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的逻辑变体,这两者都是在最终的程序中使用的“实际”对象的代理。