编写高质量代码:改善Java程序的151个建议(第7章:泛型和反射___建议101~109)

我命由我不由天 --哪吒

建议101:注意Class类的特殊性

建议102:适时选择getDeclaredXXX和getXXX

建议103:反射访问属性或方法时Accessible设置为true

建议104:使用forName动态加载类文件

建议105:动态加载不适合数组

建议106:动态代理可以使代理模式更加灵活

建议107:使用反射增加修饰模式的普适性

建议108:反射让模板方法模式更强大

建议109:不需要太多关注反射效率

建议101:注意Class类的特殊性

Java语言是先把Java源文件编译成后缀为class的字节码文件,然后通过classLoader机制把这些类加载到内存中。最后生成实例执行。

class类是Java的反射入口,只有在获得一个类的描述对象后才能动态的加载、调用,一般获得class对象的三种方法:

1、类属性加载:如String.class

2、对象的getClass方法:如new String.getClass()

3、forName方法加载:如Class.forName("java.lang.String")

获得class对象之后,就可以通过getAnnotation()获得注解,通过getMethods()获得方法,通过getConstructors()获得构造函数等。

建议102:适时选择getDeclaredXXX和getXXX

getMethod方法获得的是所有public访问级别的方法,包括从父类继承的方法。

getDeclaredMethod获得的是自身类的方法,包括公用的(public)方法、私有(private)方法,而且不受限于访问权限。

建议103:反射访问属性或方法时Accessible设置为true

Java中通过反射执行一个方法:获取一个方法对象,然后根据isAccessible返回值确定是否能够执行,如果返回false,则调用setAccessible(true),然后再调用invoke执行方法:

 

Method method= ...;
//检查是否可以访问
if(!method.isAccessible()){
     method.setAccessible(true);
}
//执行方法
method.invoke(obj, args);

通过反射方法执行方法时,必须在invoke之前检查Accessible属性。

建议104:使用forName动态加载类文件

动态加载是指程序运行时加载需要的类库文件,对Java程序来说,雷哥类文件在启动时或首次初始化时会被加载到内存中,而反射则可以在运行时再决定是否需要加载。

动态加载的意义在于:加载一个类表示要初始化该类的static变量,特别是static代码块,在这里可以做大量的工作,比如注册,初始化环境等等。

对于动态加载最经典的应用就是数据库驱动程序的加载片段,代码如下:

//加载驱动
Class.forName("com.mysql..jdbc.Driver");
String url="jdbc:mysql://localhost:3306/db?user=&password=";
Connection conn =DriverManager.getConnection(url);
Statement stmt =conn.createStatement();

当程序动态加载该驱动时,也就是执行到Class.forName("com.mysql..jdbc.Driver")时,Driver类会被加载到内存中,于是static代码块开始执行,也就是把自己注册到DriverManager中。

forName只是把一个类加载到内存中,并不保证由此产生一个实例对象,也不会执行任何方法,之所以会初始化static代码,那是由类加载机制所决定的,而不是forName方法决定的。也就是说,如果没有static属性或static代码块,forName就是加载类,没有任何的执行行为。

总而言之,forName只是把一个类加载到内存中,然后初始化static代码。

建议105:动态加载不适合数组

建议106:动态代理可以使代理模式更加灵活

Java的反射框架提供了动态代理(Dynamic Proxy)机制,允许在运行期对目标类生成代理,避免重复开发。我们知道一个静态代理是通过主题角色(Proxy)和具体主题角色(Real Subject)共同实现主题角色(Subkect)的逻辑的,只是代理角色把相关的执行逻辑委托给了具体角色而已,一个简单的静态代理如下所示:

interface Subject {
    // 定义一个方法
    public void request();
}

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

}

class Proxy implements Subject {
    // 要代理那个实现类
    private Subject subject = null;

    // 默认被代理者
    public Proxy() {
        subject = new RealSubject();
    }

    // 通过构造函数传递被代理者
    public Proxy(Subject _subject) {
        subject = _subject;
    }

    @Override
    public void request() {
        before();
        subject.request();
        after();
    }

    // 预处理
    private void after() {
        // doSomething
    }

    // 善后处理
    private void before() {
        // doSomething
    }
}

这是一个简单的静态代理。Java还提供了java.lang.reflect.Proxy用于实现动态代理:只要提供一个抽象主题角色和具体主题角色,就可以动态实现其逻辑的,其实例代码如下:

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;
    }
}

注意这里没有代理主题角色,取而代之的是SubjectHandler作为主要的逻辑委托处理,其中invoke方法是接口InvocationHandler定义必须实现的,它完成了对真实方法的调用。

通过InvocationHandler接口的实现类来实现,所有的方法都是有该Handler进行处理的,即所有被代理的方法都是由InvocationHandler接管实际的处理任务。

代码如下:

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();
}

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

动态代理很容易实现通用的代理类,只要在InvocationHandler的invoke方法中读取持久化的数据即可实现,而且还能实现动态切入的效果。

建议107:使用反射增加修饰模式的普适性

装饰模式(Decorator Pattern)的定义是“动态的给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比于生成子类更加灵活“,不过,使用Java的动态代理也可以实现修饰模式的效果,而且其灵活性、适应性会更强。

我们以卡通片《猫和老鼠》(Tom and Jerry)为例,看看如何包装小Jerry让它更强大。首先定义Jerry的类:老鼠(Rat类),代码如下: 

interface Animal{
    public void doStuff();
}

class Rat implements Animal{
    @Override
    public void doStuff() {
        System.out.println("Jerry will play with Tom ......");
    }
}

接下来,我们要给Jerry增加一些能力,比如飞行,钻地等能力,当然使用继承也很容易实现,但我们这里只是临时的为Rat类增加这些能力,使用装饰模式更符合此处的场景,首先定义装饰类,代码如下:

//定义某种能力
interface Feature{
    //加载特性
    public void load();
}
//飞行能力
class FlyFeature implements Feature{

    @Override
    public void load() {
        System.out.println("增加一对翅膀...");
    }
}
//钻地能力
class DigFeature implements Feature{
    @Override
    public void load() {
        System.out.println("增加钻地能力...");
    }   
}

此处定义了两种能力:一种是飞行,另一种是钻地,我们如果把这两种属性赋予到Jerry身上,那就需要一个包装动作类了,代码如下: 

class DecorateAnimal implements Animal {
    // 被包装的动物
    private Animal animal;
    // 使用哪一个包装器
    private Class<? extends Feature> clz;

    public DecorateAnimal(Animal _animal, Class<? extends Feature> _clz) {
        animal = _animal;
        clz = _clz;
    }

    @Override
    public void doStuff() {
        InvocationHandler handler = new InvocationHandler() {
            // 具体包装行为
            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                Object obj = null;
                if (Modifier.isPublic(method.getModifiers())) {
                    obj = method.invoke(clz.newInstance(), args);
                }
                animal.doStuff();
                return obj;
            }
        };
        //当前加载器
        ClassLoader cl = getClass().getClassLoader();
        //动态代理,又handler决定如何包装
        Feature proxy = (Feature) Proxy.newProxyInstance(cl, clz.getInterfaces(), handler);
        proxy.load();
    }
}

注意看doStuff方法,一个修饰类型必然是抽象构建(Component)的子类型,它必须实现doStuff方法,此处的doStuff方法委托了动态代理执行,并且在动态代理的控制器Handler中还设置了决定修饰方式和行为的条件(即代码中InvocationHandler匿名类中的if判断语句),当然,此处也可以通过读取持久化数据的方式进行判断,这样就更加灵活了。

编写客户端进行调用了,代码如下: 

public static void main(String[] args) {
    //定义Jerry这只老鼠
    Animal jerry = new Rat();
    //为Jerry增加飞行能力
    jerry = new DecorateAnimal(jerry, FlyFeature.class);
    //jerry增加挖掘能力
    jerry = new DecorateAnimal(jerry, DigFeature.class);
    //Jerry开始戏弄毛了
    jerry.doStuff();
}

此类代码只是一个比较通用的装饰模式,只需要定义被装饰的类及装饰类即可,装饰行为由动态代理实现,实现了对装饰类和被装饰类的完全解耦,提供了系统的扩展性。

建议109:不需要太多关注反射效率

反射的效率相对于正常的代码执行确实低很多,但它是一个非常有效的运行期工具类,只要代码结构清晰、可读性好那就先开发出来,等到进行性能测试时有问题再优化。

最基本的编码规则:"Don't  Repeat Yourself"

 

编写高质量代码:改善Java程序的151个建议@目录

转载于:https://my.oschina.net/u/4006148/blog/3081495

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值