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();
}
=========================================================================
创作不易,请勿直接盗用,使用请标明转载出处。
喜欢的话,一键三连,您的支持是我一直坚持高质量创作的原动力。