编写高质量代码:改善Java程序的151个建议 --[106~117]

编写高质量代码:改善Java程序的151个建议 --[106~117]

动态代理可以使代理模式更加灵活
interface Subject {
    // 定义一个方法
    public void request();
}

// 具体主题角色
class RealSubject implements Subject {
    // 实现方法
    @Override
    public void request() {
        // 实现具体业务逻辑
    }

}

class SubjectHandler implements InvocationHandler {
    // 被代理的对象
    private Subject subject;

    public SubjectHandler(Subject _subject) {
        subject = _subject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // 预处理
        System.out.println("预处理...");
        //直接调用被代理的方法
        Object obj = method.invoke(subject, args);
        // 后处理
        System.out.println("后处理...");
        return obj;
    }

}

动态代理使用场景:

public static void main(String[] args) {
        //具体主题角色,也就是被代理类
        Subject subject = new RealSubject();
        //代理实例的处理Handler
        InvocationHandler handler =new SubjectHandler(subject);
        //当前加载器
        ClassLoader cl = subject.getClass().getClassLoader();
        //动态代理
        Subject proxy = (Subject) Proxy.newProxyInstance(cl,subject.getClass().getInterfaces(),handler);
        //执行具体主题角色方法
        proxy.request();
    }

不用显式创建代理类即实现代理的功能,例如可以在被代理的角色执行前进行权限判断,或者执行后进行数据校验。

使用反射增加装饰模式的普适性

反射让模板方法模式更强大

提倡异常封装

class MyException extends Exception {
    // 容纳所有的异常
    private List<Throwable> causes = new ArrayList<Throwable>();

    // 构造函数,传递一个异常列表
    public MyException(List<? extends Throwable> _causes) {
        causes.addAll(_causes);
    }

    // 读取所有的异常
    public List<Throwable> getExceptions() {
        return causes;
    }
}

具体调用如下

public void doStuff() throws MyException {
        List<Throwable> list = new ArrayList<Throwable>();
        // 第一个逻辑片段
        try {
            // Do Something
        } catch (Exception e) {
            list.add(e);
        }
        // 第二个逻辑片段
        try {
            // Do Something
        } catch (Exception e) {
            list.add(e);
        }
        // 检查是否有必要抛出异常
        if (list.size() > 0) {
            throw new MyException(list);
        }
    }

不要在finally块中处理返回值

在finally代码块中处理返回值,这是考试和面试中经常出现的题目。虽然可以以此来出考试题,但在项目中绝对不能再finally代码块中出现return语句,这是因为这种处理方式非常容易产生" 误解 ",会误导开发者。
finally块中处理返回值会产生的问题:

  1. 覆盖了try代码块中的return返回值
  2. 屏蔽异常
public static void doSomeThing(){
        try{
            //正常抛出异常
            throw new RuntimeException();
        }finally{
            //告诉JVM:该方法正常返回
            return;
        }
    }
	
public static void main(String[] args) {
        try {
            doSomeThing();
        } catch (RuntimeException e) {
            System.out.println("这里是永远不会到达的");
        }
    }

不要在构造函数中抛出异常

异常的机制有三种:

  • Error类及其子类表示的是错误,它是不需要程序员处理也不能处理的异常,比如VirtualMachineError虚拟机错误,ThreadDeath线程僵死等。
  • RunTimeException类及其子类表示的是非受检异常,是系统可能会抛出的异常,程序员可以去处理,也可以不处理,最经典的就是NullPointException空指针异常和IndexOutOfBoundsException越界异常。
  • Exception类及其子类(不包含非受检异常),表示的是受检异常,这是程序员必须处理的异常,不处理则程序不能通过编译,比如IOException表示的是I/O异常,SQLException表示的数据库访问异常。
  • 构造函数中抛出异常:
    • 构造函数中抛出错误是程序员无法处理的
    • 构造函数不应该抛出非受检异常
    • 加重了上层代码编写者的负担
    • 后续代码不会执行
  • 构造函数尽可能不要抛出受检异常
    • 导致子类膨胀
    • 违背了里氏替换原则
  • 子类构造函数扩展受限

使用Throwable获得栈信息

class Foo {
    public static boolean method() {
        // 取得当前栈信息
        StackTraceElement[] sts = new Throwable().getStackTrace();
        // 检查是否是methodA方法调用
        for (StackTraceElement st : sts) {
            if (st.getMethodName().equals("methodA")) {
                return true;
            }
        }
        throw new RuntimeException("除了methodA方法外,该方法不允许其它方法调用");
    }
}

Throwable源码

public class Throwable implements Serializable {
    private static final StackTraceElement[] UNASSIGNED_STACK = new StackTraceElement[0];
    //出现异常记录的栈帧
    private StackTraceElement[] stackTrace = UNASSIGNED_STACK;
    //默认构造函数
    public Throwable() {
        //记录栈帧
        fillInStackTrace();
    }
    //本地方法,抓取执行时的栈信息
    private native Throwable fillInStackTrace(int dummy);

    public synchronized Throwable fillInStackTrace() {
        if (stackTrace != null || backtrace != null /* Out of protocol state */) {
            fillInStackTrace(0);
            stackTrace = UNASSIGNED_STACK;
        }
        return this;
    }

}

在出现异常时(或主动声明一个Throwable对象时),JVM会通过fillInStackTrace方法记录下栈帧信息,然后生成一个Throwable对象,这样我们就可以知道类间的调用顺序,方法名称及当前行号等了。获得栈信息可以对调用者进行判断,然后决定不同的输出。

异常只为异常服务

异常只能用在非正常的情况下,不能成为正常情况下的主逻辑。
比如如下代码是不建议的:

//判断一个枚举是否包含String枚举项
    public static <T extends Enum<T>> boolean Contain(Class<T> clz,String name){
        boolean result = false;
        try{
            Enum.valueOf(clz, name);
            result = true;
        }catch(RuntimeException e){
            //只要是抛出异常,则认为不包含
        }
        return result;
    }

多使用异常,把性能问题放一边

如下业务逻辑比较清晰,正常代码和异常代码分离、能快速查找问题(栈信息快照)等,虽然性能比较差。

public void login(){
        try{
            //正常登陆
        }catch(InvalidLoginException lie){
            //    用户名无效
        }catch(InvalidPasswordException pe){
            //密码错误的异常
        }catch(TooMuchLoginException){
            //多次登陆失败的异常
        }
    }
posted @ 2018-08-13 17:31  西北野狼  阅读(271)  评论(0编辑  收藏  举报