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

我命由我不禁天 --哪吒java

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

建议102:适时选择getDeclaredXXX和getXXXsql

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

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

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

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

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

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

建议109:不须要太多关注反射效率性能

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

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

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

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

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

三、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个建议@目录

相关文章
相关标签/搜索