Java编码规范

目录

1.不要在常量中出现容易混淆的字母

  • 包名全部用小写
  • 类名全部用大写
  • 变量采用驼峰式命名
  • 数字类型变量后的字母要大写,如 long num = 100LJava编码规范


2.不要让变量变成常量

  • public static final int RAND_CONST = new Random().nextInt();

这里的变量虽然加了final 但会在运行时发生改变Java编码规范


3.三元操作符后的值类型必须一致

  • int i = 80;
  • String s1 = String.valueOf( i<100?90:100 );
  • String s2 = String.valueOf( i<100?90:100.0 );
  • s1.equals(s2); ?

Q: 不相等,类型不一致

A: 当三元操作符的右边两个值类型不一致的时候,会默认向范围大的转换Java编码规范


4.避免带有长参数的方法重载

  • private void calPrice(int price, int discount);
  • private void calPrice(int price, int … discounts);


calPrice(100, 0.8);

Q: 这时候会调用哪一个方法

A: 会优先调用第一个,因为java默认会先从常量池找参数,再从对象中找,int…变长参数实际上是数组对象


5.别让空值威胁到变长方法

  • private void methodA(String str, Integer… num);
  • private void methodB(String str, Long… num);


methodA(”hi”, null);

Q: 这时候会调用哪一个方法

A: 会编译报错


6.重写变长方法也循规蹈矩

  • 重写方法不能缩小其访问权限
  • 参数列表必须与原方法相同
  • 返回类型必须是原方法类型或其子类
  • 不能抛出新的异常,或者超出父类范围的异常,但可以抛出更少、更有限的异常

//A父类

int count(int price, int… discount);


//B子类重写

@Override

int int count(int price, int[ ] discount);


//A调用,编译通过,向上转型

A a = new B();

a.count(100, 50);

//B调用,编译不通过

B b = new B();

b.count(100, 50);


7.警惕自增的陷阱

public class Client {

      public static void main(String[] args) {

              int count =0;

              for(int i=0;i<10;i++){

                      count=count++;              }

              System.out.println("count="+count);

    }

}


Q: count等于几?

A: 0


//以下是自增函数内部实现

public static int mockAdd(int count){

    //先保存初始值

    int temp =count;

    //做自增操作

    count = count+1;

    //返回原始值

    return temp;}    //先保存初始值

    int temp =count;

    //做自增操作

    count = count+1;

    //返回原始值

    return temp;

}


int count = 0;

mockAdd(count);

System.out.println("count="+count);

//由于自增返回的是原值, 所以count=count++;一直是原值0


8.不要让旧语法困扰你

少用旧语法,比如goto语句,跳出循环到某个地方

handleTag : method(int num);

break : handleTag

continue : handleTag


9.少用静态导入

静态导入会让程序的可读性变差

import static java.lang.Double.*;
import static java.lang.Math.*;
import static java.lang.Integer.*;
import static java.text.NumberFormat.*;

public class Client {
  //输入半径和精度要求,计算面积
  public static void main(String[] args) {
            double s = PI * parseDouble(args[0]);
            NumberFormat nf = getInstance(); //这个代表什么意思?
            nf.setMaximumFractionDigits(parseInt(args[1]));
            formatMessage(nf.format(s)); //这个什么意思?
  }
  //格式化消息输出
  public static void formatMessage(String s){
            System.out.println("圆面积是:"+s);
  }
}


  • 不使用 * 通配符,除非是导入静态常量类(只包含常量的类或接口)
  • 方法名是具有明确、清晰表达意义的工具类Java编码规范


10.不要在本类中覆盖静态导入的变量和方法

子类不要创建和父类同名的参数或方法,Java遵循最短路径原则,会先找本类的,但是也容易让阅读起来变困难,或者造成误解。

最好的方法是在父类重构方法,而不是在本类中覆盖。


11.养成良好的习惯,显示声明UID

序列号可以显示声明,也可以编译时,根据类路径,文件名等动态生成。

如果没有序列号,一个类在两台机器定义的属性不一致。

在这种序列化和反序列化的类不一致的情形下,反序列化时会报一个InvalidClassException异常。


12.避免序列化类在构造函数中为不变量赋值

public class Person implements Serializable{
    private static final long serialVersionUID = 91282334L;
    //不变量初始不赋值
    public final String name;
    //构造函数为不变量赋值
    public Person(){            name="名称A";
   }
}

//构造函数变量变了
    public Person(){            name="名称B";
   }

如果UID不变,构造函数的name变了,反序列化的时候取不到新值。

因为反序列化不会调用构造方法,会直接取原来序列化的值。


13.避免为final变量复杂赋值

public class Person implements Serializable{
    private static final long serialVersionUID = 91282334L;
    //通过方法返回值为final变量赋值
    public final String name=initName();
    //初始化方法名
    public String initName(){
            return "变量A";
    }
}


//反序列化时修改方法返回值
public String initName(){
            return "变量B";
}


此时反序列得到的结果是“变量B”。原因:反序列化不会调用方法。

反序列化时,final变量会被重新赋值,只有简单对象会被重新赋值:8个基本数据类型、数组

、字符串,但不能方法赋值。

序列化会记录  (1)类描述信息,包括类路径、继承关系、访问权限、变量描述、变量访问权限、方法签名、返回值、变量关联类信息。

(2)非瞬态(transient)和静态关键字的实例变量。


注意:由于序列化只记录基本类型等,如果关联了其他类,其他类也会一起关联被序列化进来,直到没有引用对象类型,所以序列化的时候,存储体积会膨胀。


14.使用序列化私有方法实现部份属性不序列化

public class Person implements Serializable{
    private static final long serialVersionUID =60407L;
    //姓名
    private String name;
    //薪水
    private transient Salary salary;

    public Person(String _name,Salary _salary){
            name=_name;
            salary=_salary;
    }
    

//序列化委托方法
private void writeObject(java.io.ObjectOutputStream out) throws IOException {            out.defaultWriteObject();            

out.writeInt(salary.getBasePay());    

}

//反序列化时委托方法
private void readObject(java.io.ObjectInputStream in) throws IOException,Class-NotFoundException {

            in.defaultReadObject();

            salary = new Salary(in.readInt(),0);  }
}


其他方法

1.属性上加transient

2.新增业务对象,只保留需要序列化的对象(不推荐,对象冗余)

3.请求端过滤(不推荐,不安全)

4.使用xml,web service传输协议(不推荐,成本高)


15.用偶判断,不用奇判断

public class Client {

    public static void main(String[] args) {

            //接收键盘输入参数

            Scanner input = new Scanner(System.in);

            System.out.print("请输入多个数字判断奇偶:");

            while(input.hasNextInt()){

                    int i = input.nextInt();

                    String str =i+ "->" + (i%2 ==1?"奇数":"偶数");

                    System.out.println(str);

            }

    }

}


请输入多个数字判断奇偶:120 -1 -2

1->奇数

2->偶数

0->偶数

-1->偶数 为什么是偶数?

-2->偶数


//模拟取余计算,dividend被除数,divisor除数

public static int remainder(int dividend,int divisor){

    return dividend - dividend / divisor * divisor;

}

浮点型小数在内存中为无限循环小数


16.不要让类型默默转换

int num = 3000 0000;

long result = num * 60 * 8;

//result = -2028888064;

java是先进行计算,再类型转换的,结果超过了int最大值21亿,所以溢出从头开始计算。

解决方法:

主动声明第一次计算的类型 num * 60L 这样就会先转换类型再进行计算


17.避免在构造函数中初始化其他类

public class Client {
    public static void main(String[] args) {
           Son s = new Son();
           s.doSomething();
    }
}
//父类
class Father{
    Father(){
           new Other();    }
}//子类
class Son extends Father{
    public void doSomething(){
           System.out.println("Hi,show me something");
    }
}
//相关类
class Other{
    public Other(){
           new Son();   }
}

//运行会无限套娃生成新的对象,导致OOM


18.避免对象的浅拷贝

条件:实现了Cloneable接口的对象就可以拷贝

好处:由于对象拷贝是在内存进行的,所以比直接new一个对象要快

可能存在的问题:浅拷贝(影子拷贝)

拷贝基本类型时,会拷贝值。

拷贝实例对象类型的时候,只会拷贝其引用地址,如果修改了原对象或者拷贝对象中的一个,那么两个对象引用的都被修改了。

而且私有的也会被拷贝出来的对象引用,这就打破了Java的封装性。


解决方法:

1.重写clone方法,new一个对象设置引用属性

2.使用序列化实现对象拷贝,序列化工具可以自己实现,也可以使用Apache

自带的SerializationUtils工具类。


public class CloneUtils {
    // 拷贝一个对象
    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T clone(T obj) {
            // 拷贝产生的对象
            T clonedObj = null;
            try {
                    // 读取对象字节数据
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    ObjectOutputStream oos = new ObjectOutputStream(baos);
                    oos.writeObject(obj);
                    oos.close();
                    // 分配内存空间,写入原始对象,生成新对象
                    ByteArrayInputStreambais = newByteArrayInputStream(baos.toByteArray());
                    ObjectInputStream ois = new ObjectInputStream(bais);
                    //返回新对象,并做类型转换
                    clonedObj = (T)ois.readObject();                    ois.close();
              } catch (Exception e) {
                    e.printStackTrace();
              }
              return clonedObj;
    }
}


19.重写equals方法时,使用getClass进行类型判断

public class Parent{

    private String id;

    @Override
    public boolean equals(Object obj) {
            if(obj instanceof Parent){
                   Parent p = (Parent) obj;
                   return super.equals(obj)&& c.getId() == id;
            }
            return false;
    }

}


public class Children{

    private String id;

    @Override
    public boolean equals(Object obj) {
            if(obj instanceof Children){
                   Children c = (Children) obj;
                   return super.equals(obj)&& c.getId() == id;
            }
            return false;
    }
}

}


Parent p = new Parent(100);

Children c = new Children(100);

p.equals(c);

c.equals(p);

// c和p1相等吗?


都相等

问题:c不应该等于p,因为C调用了父类的equals方法

解决:使用getClass比较两个对象类型

public boolean equals(Object obj) {

obj.getClass() == this.getClass()

}


20.重写equals方法必须同时重写hashCode方法

public class User{

private int number;

private String name;

public User(int number, String name){

this.number = number;

this.name = name;

}

….//省略get、set方法

public boolean equals(Object obj) {
    if(obj!=null && obj.getClass() == this.getClass()){           

    User u = (User) obj;
           if(u.getName()==null || name==null){
                   return false;
           }else{
                   return name.equalsIgnoreCase(u.getName())

&& number.equals(u.getNumber);
           }
    }
    return false;
    }

}


//Map<User, Object> map = new HashMap<>();

User user1 = new User(001, “汤姆”);

User user2 = new User(001, “汤姆”);

map.put(user1, “tom1”);

map.put(user2, “tom2”);


//map里面有几个汤姆?


//2个,我们重写了equals方法,判断名称、编码是相等的就是相同的User,按理来说应该是1个,为什么会放进去两个User呢


//因为HashMap的Put方法首先会去进行哈希运算hashCode(),相同的哈希码才会认为是同一个数组桶中的数据,进行下一步值比较。这两个User的哈希码不一致,马上会放到不同的数据桶中,自然不会相同。


修改方法,重写hashCode方法

@Override
public int hashCode() {
       return new HashCodeBuilder().append(name).toHashCode();
}


21.不要主动进行垃圾回收

调用System.gc,会使应用停止响应,来做垃圾回收检查动作,这样做是非常耗时且危险的。

如果是一个大型web项目,这样的操作可能是0.5秒,5秒,甚至几十秒。进行垃圾回收时,所有的业务访问请求都会阻塞。


22.使用String直接量赋值

String str1 = "中国";
String str2 = "中国";
String str3 = new String("中国");
String str4 = str3.intern();

//两个直接量是否相等
boolean b1 = (str1==str2);
//直接量和对象是否相等
boolean b2 = (str1 == str3);
//经过intern处理后的对象与直接量是否相等
boolean b3 = (str1 == str4);

//true false true


= 赋值的变量直接从字符串常量池返回引用,new 生成的对象是不放到常量池中的。

intern方法会先去常量池检查有没相同的字符串,如果有就直接返回字符串的引用地址,如果没有就会生成一个字符串到常量池中,再返回引用地址。


23.字符串拼接方法

String s = “汤姆”;

StringBuilder sbd = new StringBuilder(“汤姆”);

s += “a”;

s = s.concat(“a”);

sbd.append(“a”);


循环5万次

+ 耗时1400ms

concat 耗时700ms

append 耗时1ms


+拼接,Java编译器对字符串拼接做了优化,但是每次返回都需要生成StringBuilder对象,并toString。

str = new StringBuilder(str).append("c").toString();


//concat return时会创建String对象

public String concat(String str) {
    int otherLen = str.length();
    //如果追加的字符串长度为0,则返回字符串本身
    if (otherLen == 0) {
            return this;
    }
    //字符数组,容纳的是新字符串的字符
    char buf[] = new char[count + otherLen];    //取出原始字符串放到buf数组中
    getChars(0, count, buf, 0);
    //追加的字符串转化成字符数组,添加到buf中
    str.getChars(0, otherLen, buf, count);
    //复制字符数组,产生一个新的字符串
    return new String(0, count + otherLen, buf);  }


//StringBuilder.append 不会生成对象,只有在最后toString的时候才会

public AbstractStringBuilder append(String str) {
    //如果是null值,则把null作为字符串处理
    if (str == null) str = "null";
    int len = str.length();
    //字符串长度为0,则返回自身
    if (len == 0) return this;
    int newCount = count + len;
    //追加后的字符数组长度是否超过当前值
    if (newCount > value.length)
            expandCapacity(newCount); //加长,并做数组拷贝
    //字符串复制到目标数组
    str.getChars(0, len, value, count);    count = newCount;
    return this;
  }


24.谨慎创建多线程

Java线程的创建非常昂贵,需要JVM和OS(操作系统)配合完成大量的工作。

(1)线程堆栈分配和初始化大量内存块,其中至少包含1MB的堆内存。

(2)需要进行系统调用,以便在OS(操作系统)中创建和注册本地线程。


使用线程池

提升性能:使用线程池,减少线程创建、维护和分配,使用空闲的线程去执行异步任务。

线程管理:统计任务数量、空闲时间,以便对线程进行有效管理。


使用事件中心订阅(RabbitMQ实现)

削峰,减少线程的创建。虽然线程池能控制线程并发执行的数量,但是没法控制线程创建的数量。如果在没有设置超过最大线程数的线程和拒绝策略,线程>核心线程数,线程<最大线程数,线程也会创建出来,进入阻塞队列。阻塞队列的长度很大,如果大量添加到线程池,会OOM。

使用事件中心,会控制线程消费的数量,例如设置30,则最大只会有30个线程创建并在系统中运行。


25.数组和集合的选择

在实际应用场景中,如果是可以使用数组的,可以尽量使用数组。

因为基本数据类型,集合需要对基本类型进行装箱操作,遍历时需要进行拆箱操作,会损耗性能。 在进行基本类型进行遍历求和时,数据比集合快10倍。


26.为集合设置初始长度

集合在无参构造方法生成时,默认是10。集合底层是数组,在集合满了之后,会进行1.5倍的扩容。扩容是一个非常消耗性能的事情,所以在明确使用场景的情况下,预估可能会存储的数据量,在定义集合时声明,可以提高效率。


比如在一次数据库查询,ID返回实体,这里理论上是返回ID参数的个数,这时候就可以定义具体的集合容量。

public List<User> getUserFromDB(Object[ ] ids){

    List<User> users = new ArrayList<>(ids.length);

    //此处省略数据库查询处理…

}


27.基本数据类型数组转换陷阱

int[ ] arr = new int[ ]{1, 2, 3, 4, 5};

List list = Arrays.asList(arr);


System.out.println("列表中的元素数量是:" + list.size());

//集合的长度是?


// 1      集合不能存储基本数据类型的值,只能存储包装类型。

public static <T> List<T> asList(T... a) {
    return new ArrayList<T>(a);
}

asList转换时,arr会整体被当成一个数据对象类型存储到集合的第一个位置。


解决方法:使用包装类的数组 Integer[ ] arr ,其他基本数据类型也是同理。


28.asList产生的集合不可以更改

int[ ] arr = new int[ ]{1, 2, 3, 4, 5};

List list = Arrays.asList(arr);

list.add(6);//这里会报错

java.lang.UnsupportedOperationException    at java.util.AbstractList.add(AbstractList.java:131)


我们来看下源码和报错信息

public static <T> List<T> asList(T... a) {
    return new ArrayList<T>(a);}


可以看出,这里的返回的ArrayList并不是JDK的java.util.ArrayList,这个ArrayList是没有提供add方法的,而父类提供了add方法,但是直接抛了异常。

public boolean add(E e) {
    throw new UnsupportedOperationException();
}


Arrays工具类内部只提供了 size、toArray、get、set、contains。

没有提供add、remove等,add方法,需要子类自己重写实现。


29.subList子列表只是原列表的一个视图

List提供了subList方法,其作用是返回原列表的其中一段子列表

List<String> c = new ArrayList<String>();
c.add("A");
c.add("B");
//构造一个包含c列表的字符串列表
List<String> c1 = new ArrayList<String>(c);    //subList生成与c相同的列表
List<String> c2 = c.subList(0, c.size());    //c2增加一个元素
c2.add("C");
System.out.println("c == c1? " + c.equals(c1));
System.out.println("c == c2? " + c.equals(c2));


// false   true

c1是直接新建了一个集合,引用了新地址,当然不相等。

c2是返回c的一个SubList类型的子集合,其引用地址没有改变,所以相等。


public List<E> subList(int fromIndex, int toIndex) {
    return (this instanceof RandomAccess ?
            new RandomAccessSubList<E>(this, fromIndex, toIndex) :            new SubList<E>(this, fromIndex, toIndex));}


class SubList<E> extends AbstractList<E> {
    //原始列表
    private AbstractList<E> l;
    //偏移量
    private int offset;
    //构造函数,注意list参数就是我们的原始列表
    SubList(AbstractList<E> list, int fromIndex, int toIndex) {
            /*下标校验,省略*/
            //传递原始列表
            l = list;            offset = fromIndex;
            //子列表的长度
            size = toIndex - fromIndex;
    }
    //获得指定位置的元素
    public E get(int index) {
            /*校验部分,省略*/
            //从原始字符串中获得指定位置的元素
            return l.get(index+offset);
    }
    //增加或插入
    public void add(int index, E element) {
            /*校验部分,省略*/
            //直接增加到原始字符串上
            l.add(index+offset, element);
            /*处理长度和修改计数器*/
    }
    /*其他方法省略*/
}


从源码上看,SubList没有新建集合,构造方法引用的原集合,get、add方法也都是从原有集合中取值。所以我们在使用的时候,应该注意subList得到的集合不能当作一个新集合来用。


30.使用subList子列表实现删除原列表指定范围

ArrayList<Integer> list = new ArrayList<Integer>(initData);
    //删除指定范围的元素

List<Integer> childList = list.subList(20, 30);
childList.clear();

通过制定下标获取子列表,并清空子列表,可以实现快速的List指定范围删除,而不需要借助临时集合。


31.subList子列表生成后就不要再操作原列表了

public int size() {
    checkForComodification();    return size;
}


其中的checkForComodification方法就是用于检测是否并发修改的,代码如下:

private void checkForComodification() {
    //判断当前修改计数器是否与子列表生成时一致
  if (l.modCount != expectedModCount)        throw new ConcurrentModificationException();
}


这种属于防御式编程,在工具类构造方法私有化时,也可以使用了此方法,防止工具类被实例化,比如

public  class SomeUtil{

private SomeUtil(){

    throw new Exception(“不能实例化我!”);

}

}


32.Java的泛型是类型擦除的

public void methodA(List<String> list){ }

public void methodB(List<Integer> list){ }

public void methodC(List<Map<String, String>> list){ }


Q:这些方法在同一个类里,算是重载么?

A:不是。Java的泛型只在编译期有效,在运行期会被清理掉,所以上面三个方法参数类型对于JVM来说都是List。

这样做的原因

1.是避免JVM的大换血。C++的泛型生命周期是延续到运行期,如果java也把泛型延续到运行期,JVM就需要重构太多的方法。

2.版本兼容。在1.5、1.6的平台上,声明一个List的原生类型也是可以正常编译通过的,只是会产生警告信息。


33.泛型多重限制

public interface Work {

    /**

     * 打工赚钱

     * */

    int work();

}


public interface PartTimeJob {

    int doPartTimeJob();

}


public class Person implements Work,PartTimeJob {

    private int workSalary;

    private int partTimeSalary;

    Person(int workSalary, int partTimeSalary) {

        this.workSalary = workSalary;

        this.partTimeSalary = partTimeSalary;

    }


    @Override

    public int doPartTimeJob() {

        return this.partTimeSalary;

    }


    @Override

    public int work() {

        return this.workSalary;

    }

}


public static <T extends Work & PartTimeJob> void getTotalSalary(T person){

return person.work( ) + person.doPartyTimeJob( );

}


public class Rob {

    protected int robSalary;

    public int rob(){

        return this.robSalary;

    }

}


public class Robber extends Rob implements PartTimeJob{

    private int partTimeSalary;

    public Robber(int robSalary, int partTimeSalary){

        super.robSalary = robSalary;

        this.partTimeSalary = partTimeSalary;

    }


    @Override

    public int doPartTimeJob() {

        return this.partTimeSalary;

    }

}


public static <T extends Rob & PartTimeJob> int getTotalSalary(T person){

    return person.rob() + person.doPartTimeJob();

}


从上面的getTotalSalary方法可以看出

1)泛型是支持多重限定的。

2)无论是接口,还是普通类都是用extends来修饰。

3)多重限定可以限制传入参数的类型范围,还可以作为策略模式(Strategy Pattern)来使用


34.反射访问方法或属性时,设置Accessible为true

Method method = .....;
if(!method.isAccessible()){
    method.setAccessible(true);
}
//执行方法
method.invoke(obj, args);


这里的Accessible不是我们理解的public、private等访问权限,而是是否更容易获得、是否进行安全检查。动态执行或修改一个方法是非常耗时的,因为JVM会进行安全检查处理,效率很低。设置Accessible为true后,可以提升约20倍的性能。


35.动态代理可以使程序更灵活

Java的反射提供了动态代理模式(Dynamic Proxy),允许运行期对目标类生成代理,避免重复开发。下面先介绍一个简单的动态代理模式。

//抽象主题角色

public interface Operation{

//执行操作

void doOperation();

}


//具体角色A

public class Save implements Operation{

@Override

public void doOperation(){

//保存逻辑

}

}

//具体角色B

public class Delete implements Operation{

@Override

public void doOperation(){

//删除逻辑

}

}


//代理角色

public class OperationProxy implements Operation{

private Operation op;

public OperationProxy(Operation op){

this.op = op;

}

private OperationProxy( ){ }


@Override

public void doOperation( ){

beforeDoOperation( );

//被代理角色的操作

afterDoOperation( );

}


private void beforeDoOperation( ){

//操作前处理,比如校验、参数预处理等

}


private void afterDoOperation( ){

//操作后处理,比如记录日志,触发其他业务等

}

}


下面我们来看动态代理如何实现

动态代理不用创建代理角色OperationProxy,而是用一个实现InvocationHandler的类来代替


public class MyHandler implements InvocationHandler{

//被代理的对象

private Operation op;

private MyHandler( ){ }

public MyHandler(Operation op){

this.op = op;

}


//委托处理方法

public Object invoke(Object proxy, Method method, Object[ ] args) throws Throwable{

//beforeDoOperation 操作前处理

Object obj = method.invoke(op, args);

//afterDoOperation 操作后处理

return obj;

}

}


public static void main(String[] args) {

Operation op = SaveOperation();

MyHandler handler = new MyHandler(op);

//当前加载器
    ClassLoader classLoader = subject.getClass().getClassLoader();
    //动态代理
Operation proxy = (SaveOperation)Proxy.newProxyInstance

(classLoader, op.getClass().getInterfaces(), handler);    

//执行具体主题角色方法
proxy.request();

}


对比前面简单的代理模式调用,不用创建代理对象实例ProxyOperation

Operation op = SaveOperation();

ProxyOperation proxyOp = ProxyOperation(op);

proxyOp.doOperation();


可以使用动态代理实现动态切入的效果,也就是AOP编程(Aspect Oriented Programming)

好处:切入方法和实例执行的方法分离。


36.使用反射增加装饰模式的适配性

public interface Animal{

public void run( );

}


public class Cat implements Animal{

private String name;

private Cat( ){ }

public Cat(String name){

this.name = name;

}


@Override

public void run( ){ //cat run }

}


public class Rat implements Animal{

private String name;

private Rat( ){ }

public Rat(String name){

this.name = name;

}


@Override

public void run( ){ //cat run }

}


//增加能力

public interface Feature{

public void addFeature();

}


public class Fly implements Feature{

@Override

public void addFeature(){

//增加飞行能力

}

}


public class  Swimming implements Feature{

@Override

public void addFeature(){

//增加游泳能力

}

}


public class DecorateAnimal implements Animal{

private Animal animal;

private Class<? extends Feature> featureClass;

private DecorateAnimal( ){ }

private DecorateAnimal(Animal animal, Class<? extends Feature> featureClass){

this.animal = animal;

this. featureClass = featureClass;

}


@Override

public void run( ){

InvocationHandler handler = new InvocationHandler(){
            // 具体包装行为
                    public Object invoke(Object p, Method m, Object[] args) throws
                          Throwable {
                              Object obj = null;
                              //设置包装条件
    if(Modifier.isPublic(m.getModifiers())){
                                      obj = m.invoke(featureClass.newInstance(), args);
                              }
                              animal.run();
                              return obj;
                        }
           };
           // 当前加载器
           ClassLoader classLoader = getClass().getClassLoader();
           // 动态代理,由Handler决定如何包装
           Feature proxy = (Feature) Proxy.newProxyInstance(classLoader, featureClass.
                getInterfaces(), handler);
           proxy.load();

}

}


public static void main(String[] args) throws Exception {
    //定义Tom猫
    Animal Tom = new Cat();
    //为Cat增加飞行能力
    Tom = new DecorateAnimal(Tom, FlyFeature.class);    //Jerry增加挖掘能力
    Tom = new DecorateAnimal(Tom, SwimmingFeature.class);    //Jerry开始耍猫了
    Tom.run( );
}


这就是装饰模式,只需要定义装饰和被装饰类,装饰行为由动态代理实现,实现了对装饰类和被装饰类的完全解耦,提升了系统的扩展性。


37.尽量使接口单一职责

1)降低类的复杂性

2)提高可读性和可维护性


//假如一开始我们需要一个判断人员是否禁用的接口

public boolean isUserEnable(String id){

select enable from UserInfo where id =#{id};

}


//然后又一个判断人员是否管理员

public boolean isAdmin(String id){

select isAdmin from UserInfo where id =#{id};

}


//假如又要获取某个信息呢?我们又需要更改逻辑。如果抽取出一个公共代码,获取人员信息,其他新增的接口,我们都可以直接调用,而不需要改大量的代码。


public UserInfo getUser(String id){

//省略….

}

//判断人员是否管理员

public boolean isAdmin(String id){

UserInfo user = getUser(id);

return user.isAdmin();

}








=========================================================================

创作不易,请勿直接盗用,使用请标明转载出处。

喜欢的话,一键三连,您的支持是我一直坚持高质量创作的原动力。