《编写高质量代码-改善java程序的151个建议》php
--秦小波html
第一章、开发中通用的方法和准则java
一、不要在常量和变量中出现易混淆的字母python
long a=0l; --> long a=0L;
二、莫让常量蜕变成变量c++
static final int t=new Random().nextInt();
三、三元操做符的类型无比一致web
int i=80; String s=String.valueOf(i<100?90:100); String s1=String.valueOf(i<100?90:100.0); System.out.print(s.equals(s1)); //false
编译器会进行类型转换,将90转为90.0。有必定的原则,细节不表正则表达式
四、避免带有变长参数的方法重载算法
public class MainTest { public static void main(String[] args) throws ExecutionException, InterruptedException { System.out.println(PriceTool.calPrice(12, 1)); // 1 } } class PriceTool { public static int calPrice(int price, int discount) { return 1; } public static int calPrice(int price, int... discount) { return 2; } }
编译器会从最简单的开始猜测,只要符合编译条件的即采用spring
五、别让null值和空值威胁到变长方法 数据库
其中client.methodA("china")和client.methodA("china",null) 是编译不经过的,由于编译器不知道选择哪一个方法
六、覆写变长方法也循规蹈矩
重写是正确的,由于父类的calprice编译成字节码后的形参是一个int类型的形参加上一个int数组类型的形参,子类的参数列表也是如此。
sub.fun(100,50) 编译失败,方法参数是数组,java要求严格类型匹配
七、警戒自增的陷阱
输出:0
步骤1:jvm把count(此时是0)值拷贝到临时变量区
步骤2:count值加1,这时候count的值是1
步骤3:返回临时变量区的值,0
步骤4:返回值赋值给count,此时count值被重置为0
八、少用静态导入
java5开始引入 import static ,其目的是为了减小字符输入量,提升代码的可阅读性。
举个例子:
滥用静态导入会使程序更难阅读,更难维护。静态导入后,代码中就不用再写类名了,可是咱们知道类是“一类事物的描述”,缺乏了类名的修饰,静态属性和静态方法的表象意义能够被无限放大,这会让阅读者很难弄清楚所谓何意。
举个糟糕的例子:
对于静态导入,必定要遵循两个规则:
1)、不使用*通配符,除非是导入静态常量类(只包含常量的类或接口)
2)、方法名是具备明确、清晰表象意义的工具类
九、不要在本类中覆盖静态导入的变量和方法
本地的方法和属性会被使用。由于编译器有最短路径原则,以确保本类中的属性、方法优先
十、养成良好习惯,显示声明UID
十一、避免用序列化类在构造函数中为不变量赋值
序列化1.0
序列化2.0
此时反序列化,name:混世魔王
由于饭序列化时构造函数不会执行。jvm从数据流中获取一个object对象,而后根据数据流中的类文件描述信息查看,发现时final变量,须要从新计算,因而引用person类中的name值,而辞职jvm又发现name居然没有赋值,不能引用,因而再也不初始化,保持原值状态。
反序列化时final变量在如下状况不会被从新赋值
1)经过构造函数为final变量赋值
2)经过方法返回值为final变量赋值
3)final修饰的属性不是基本类型
原理:
保存在磁盘(网络传输)的对象文件包括两部分
1)类描述信息
包括路径、继承关系、访问权限、变量描述、变量访问权限、方法签名、返回值,以及变量的关联类信息。与class文件不一样的是,它不记录方法、构造函数、statis变量等的具体实现。
2)非瞬态(transient关键字)和非静态实例变量值
这里的值若是是一个基本类型,就保存下来;若是是复杂对象,就连该对象和关联类信息一块儿保存,而且持续递归下去,其实仍是基本数据类型的保存。
也正是由于这两点,一个持久化后的对象文件会比一个class类文件大不少
举个例子
一个服务像另外一个服务屏蔽类A的一个属性x
class A{ int a; int b; int x; }
可能有几种解决方案
1)在属性x前加上transient关键字(失去了分布式部署的能力?todo)
2)新增业务对象类A1,去掉x属性(符合开闭原则,并且对原系统没有侵入型,可是增长代码冗余,且增长了工做量)
class A{ int a; int b; int x; }
3)请求端过滤。得到A对象之后,过滤掉x属性。(方案可行但不合规矩,本身服务的安全性须要外部服务承担,不符合设计规范)
理想的解决方案:
用Serializable接口的两个私有方法 writeObject和readObject,控制序列化和反序列化的过程
序列化回调:
java调用objectOutputStream类把一个对象转换成流数据时,会经过反射检查被序列化的类是否有writeObject方法,而且检查其是否符合私有、无返回值的特性。如有,则会委托该方法进行对象序列化,若没有,则由ObjectOutputStream按照默认规则继续序列化。一样,从流数据恢复成实例对象时,也会检查是否有一个私有的readObject方法。
java世界一直在遭受异种语言的入侵,好比php,ruby,groovy,js等。这种入侵者都有一个共同特征:脚本语言,他们都在运行期解释执行。为何java这种强编译型语言会须要这些脚本语言呢?那是由于脚本语言的三大特性:
1)灵活。脚本语言通常都是动态类型,能够不用声明变量类型而直接使用,也在能够在运行期改变类型
2)便捷。脚本语言是一种解释性语言,不须要编译成二进制代码,也不须要像java同样生成字节码。它的执行是依靠解释器解释的,所以在运行期变动带啊吗很是容易,并且不用中止应用
3)简单。
脚本语言的这些特性是java所缺乏的,引入脚本语言可使java更强大,因而java6开始正式支持脚本语言。可是由于脚本语言比较多,java的开发者也很难肯定该支持哪一种语言,因而jcp提出了jsr223规范,只要符合该规范的语言均可以在java平台上运行(默认支持js)
动态编译一直是java的梦想,从java6版本开始支持动态编译,能够在运行期直接编译.java文件,执行.class等,只要符合java规范均可以在运行期动态家在。
在使用动态编译时,须要注意如下几点:
1)在框架中谨慎使用
好比在Spring中,写一个动态类,要让它动态注入到spring容器中,这是须要花费老大功夫的
2)不要在要求高性能的项目使用
动态编译毕竟须要一个编译过程,与静态编译相比多了一个执行环节,所以在高性能项目中不要使用动态编译。不过,若是在工具类项目中它则能够很好的发挥其优越性,好比在idea中写一个插件,就能够很好地使用动态编译,不用重启便可实现运行、调试,很是方便。
3)考虑安全问题
若是你在web界面上提供了一个功能,容许上传一个java文件而后运行,那就等于说“个人机器没有密码,你们都来看个人隐私吧”,这是很是典型的注入漏洞,只要上传一个恶意java程序就可让你全部的安全工做毁于一旦。
4)记录动态编译过程
建议记录源文件、目标文件、编译过程、执行过程等日志,不只仅是为了诊断,仍是为了安全和审计,对java项目来讲,空中编译和运行是很不让人放心的,留下这些依据能够更好地优化程序
instanceof是一个简单的二元操做符,它是用来判断一个对象是不是一个类实例的,两侧操做符须要有继承或实现关系。
1)‘A’ instanceof Character :编译不经过 ‘A’ 是一个char类型,也就是一个基本类型,不是一个对象,instanceof只能用于对象的判断。
2)null instanceof String:编译经过,返回false。这是instanceof特有的规则:若左操做符是null,结果直接返回false
3)(String)null instanceof String :编译经过,返回false。null是一个万用类型,也能够说是没类型,即便作类型转换仍是个null
4)new Date()instanceof String:编译不经过,date类和string没有继承或实现关系
5)new GenericClass<String>().isDateInstance("") :编译经过,返回false。T是string,与date之间没有继承或实现关系,是由于java的泛型是为编码服务的,在编译成字节码时,T已是object类型了。传递的实参是string类型,也就是说T的表面类型是object,实际类型是string,这句话等价于object instance of date ,因此返回false。
在防护式编程中常常会用断言对参数和环境作出判断,避免程序因不当的输入或错误的环境而产生逻辑异常,断言在不少语言中都存在,c、c++、python都有不一样的断言表达形式。在java中断言的使用是assert关键字,以下
assert <布尔表达式> :<错误信息>
在布尔表达式为假时,抛出AssertionError错误,并附带错误信息
1)assert默认是不开启的
2)AssertionError是继承自Error的。这是错误,不可恢复
1)在对外公开的方法中
2)在执行逻辑代码的状况下。由于生产环境是不开启断言的。避免由于环境的不一样产生不一样的业务逻辑
1)在私有方法中,私有方法的使用者是本身,能够更好的预防本身犯错
2)流程控制中不可能到达的区域。若是到达则抛异常
3)创建程序探针。咱们可能会在一段程序中定义两个变量,分别代码两个不一样的业务含义,可是二者有固定的关系。例如 var1=var2*2,那咱们就能够在程序中处处设‘桩’,断言这二者的关系,若是不知足即代表程序已经出现了异常,业务也就没有必要运行下去了
举个例子:
若是在一个运行中项目,直接替换constans.class ,其中 maxage改成180。client中的输入依然是150
对于final修饰的基本类型和string类型,编译器会认为它是稳定态,因此在编译时就直接把值编译到字节码中了,避免了在运行期引用,以提升代码的执行效率。
对于final修饰的类,编译器认为它是不稳定态,在编译时创建的则是引用关系(soft final),若是client类引入的常量是一个类或实例,即便不从新编译也会输出最新值
基本数据类型相关
i%2==1?奇数:偶数
这个逻辑是不对的,当i为负数时计算错误。由于取余的计算逻辑为
int remainder(int a,int b){ return a-a/b*b; }
在计算机中浮点数有多是不许确的,它只能无限接近准确值,而不能彻底精确。这是因为浮点数的存储规则决定的(略过)。
举个例子:system.out.print(10.00-9.06) :0.4000000000000036
有两种解决方案:
1)BigDecimal
BigDecimal是专门为弥补浮点数没法精确计算的缺憾而设计的类,而且它自己也提供了加减乘除的经常使用数学算法。特别是与数据库Decimal类型的字段映射时,BigDeciaml是最优的解决方案。
2)使用整型
把参与运算的值扩大100倍,并转变为整型,而后在展示时再缩小100倍。
举个例子:
太阳逛照射到地球上须要8分钟,计算太阳到地球的距离。
long result=light_speed * 60 * 8;
输出的结果是 -202888064
缘由:java是先运算而后再进行类型转换的,三者相乘,超过了int的最大值,因此其值是负值(溢出是负值的缘由看一下)
正确的处理是 long result=light_speed * 60L * 8;
举个例子:
if(order+base<limit){...}
当order+base足够大时,超过了int的最大值,其值是负值,因此业务逻辑会有问题
math.round(-10.5) 输出 -10 这是math。round采用的舍入规则所决定的(采用的是正无穷方向舍入规则)
以上算法对于一个5000w存款的银行来讲,一年将损失10w。一个美国银行家发现了此问题并提出了一个修正算法,叫作银行家舍入的近似算法(规则不记录了)。java5能够直接用RoundingMode类提供的Round模式。与BigDecimal绝配。RoundingMode支持7种舍入模式:
远离零方向舍入、趋向零方向舍入、向正无穷方向舍入、向负无穷方向舍入、最近数字舍入、银行家算法
举个例子。当list中有null元素,自动拆箱时调用intValue()会报空指针异常。
举个例子。i==j false。Integer是引用类型
Integer缓存了-128-127的Integer对象。因此经过装箱(Integer.valueOf())得到的对象能够复用。
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
自动装箱有一个重要的原则:基本类型能够先加宽,再转变成宽类型的包装类型,但不能直接转变成宽类型的包装类型。
举个例子
两次调用都是基本类型的方法
程序启动后,生成的随机数会不一样。可是每次启动程序,生成的都会是三个随机数。产生随机数和seed之间的关系以下:
1)种子不一样,产生不一样的随机数
2)种子相同,即便实例不一样也产生相同的随机数
Random的默认种子(无参构造)是System.nanoTime()的返回值(jdk1.5之前是System.currentTimeMillis()),这个值是距离某一个固定时间点的纳秒数,不一样的操做系统和硬件有不一样的固定时间点,随机数天然也就不一样了
其实是有这种可能的,可是千万不要这样写
举个例子
举个例子:输出1
静态变量的初始化:先分配空间,再赋值
类初始化时会先先分配空间,再按照加载顺序去赋值 :静态的(变量、静态块)的加载顺序是 从上到下
在子类中构建与父类相同的方法名、输入参数、输出参数、访问权限,而且父类、子类都是静态方法,此种行为叫作隐藏,它与重写有两点不一样:
1)表现形式不一样。@override能够用于重写,不能用于隐藏
2)指责不一样。隐藏的目的是为了抛弃父类静态方法。重写则是将父类的行为加强或者减弱,延续父类的指责
1)更符合面向对象编程
2)类与类关系复杂,容易形成栈溢出
什么是构造代码块
构造代码块的特性:在每一个构造函数中都运行,且会首先运行
1)提供封装性
2)提升代码可读性
举个例子
List l1=new ArrayList(); List l2=new ArrayList(){}; List l3=new ArrayList(){{}}; System.out.println(l1.getClass()==l2.getClass());//false System.out.println(l1.getClass()==l3.getClass());//false System.out.println(l3.getClass()==l2.getClass());//false
l1:arraylist实例
l2:{}表示一个匿名内部类,可是没有重写任何方法,至关于匿名内部类的实例
l3:外层{}表示一个匿名内部类,可是没有重写任何方法,内层{}表示匿名内部类的初始化块,能够有多个。
通常类默认都是调用父类的无参构造函数的,而匿名类由于没有名字,只能由构造代码块代替,也就无所谓的有参和无参构造函数类,它在初始化时直接调用类父类的同参构造函数,而后再调用本身的构造代码块
使用内部类实现多继承
java项目中使用的工具类很是多,好比jdk本身的工具类java.lang.math java.util.collections等都是咱们常常用到的。工具类的方法和属性都是静态的,不须要生成实例便可访问,并且jdk也作了很好的处理,因为不但愿被初始化,因而就设置构造函数为private。也能够在构造函数中抛一个error。
一个类实现类cloneable接口就表示它具有类被拷贝的能力,若是再重写clone方法就会彻底具有拷贝能力。拷贝是在内存中进行的,因此在性能方面比直接经过new生成对象要快不少,特别是在大对象的生成上,这会使性能的提高很是显著。可是object提供的默认对象拷贝是浅拷贝。
浅拷贝的规则:
1)基本类型
若是变量是基本类型,则拷贝其值
2)对象
拷贝地址引用
3)string字符串
这个比较特殊,拷贝的也是一个地址,是个引用。可是在修改时,它会从字符串池中从新生成新的字符串,原有的字符串对象保持不变,在此处咱们能够认为string是一个基本类型
实现serializable接口,使用序列化实现对象的深拷贝。或者其余序列化方式json等
一句话总结,equals知足自反性,传递性,对称性,一致性规则 ,参考:http://www.javashuo.com/article/p-guhzjvpv-mk.html
一句话总结,equals知足自反性,传递性,对称性,一致性规则 ,参考:http://www.javashuo.com/article/p-guhzjvpv-mk.html
两个不一样的类,可能具有相同的属性,致使equals相等
参考:http://www.javashuo.com/article/p-guhzjvpv-mk.html
java中有一个特殊的类:package-info类,它是专门为本包服务的。package-info特性
1)它不能随便被建立
不能经过new的形式建立。能够在text建立,拷贝过来
2)它服务的对象很特殊
一个类是一类或一组事物的描述,但package-info是描述和记录本包信息的
3)package-info类不能有代码实现
package-info也会被编译成package-info.class ,可是在package-info.java文件里不能声明package-info类。不能够继承,没有接口...
package-info做用
1)声明友好类和包内访问常量
虽然它没有编写package-info的实现,可是package-info.class类文件仍是会生成。
2)为在包上标注注解提供便利
好比咱们要写一个注解,查看一个包下的全部对象,只要把注解标注到package-info文件中便可,并且不少开源项目也采用类此方法,好比struts2的@namespace、hibernate的@filterdef等
3)提供包的总体注释说明
经过javadoc生成文档时,会把这些说明做为包文档的首页,让读者更容易对该包有一个总体的认识。固然在这点上它与package.htm的做用是相同的,不够package-info能够在代码中维护文档的完整性,而且能够实现代卖与文档的同步更新
常量池
举个例子:
string.replaceAll("","") 要求第一个参数传的是正则表达式。若是传了一些$($在正则中表示字符串的结束位置)等,会有异常
java对加号的处理机制:在使用加号进行计算的表达式中,只要遇到string字符串,则全部的数据都会转换为string类型进行拼接,若是是对象,调用tostring方法的返回值拼接
string s=1+1+"a"; //2a
1)+ :编译器对字符串的加号作了优化,它会使用tringbuilder的append方法进行追加,而后经过tostring方法转换成字符串
2)concat():数组拷贝,可是会会建立string对象
public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); return new String(buf, true); }
3)stringbuffer、stringbuilder:数组拷贝
public AbstractStringBuilder append(String str) { if (str == null) return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; }
比较器通常是经过compareTo比较。该方法是先取得字符串的字符数组,而后一个个比较大小(减号操做符),也就是unicode码值的比较。因此非英文排序会出现不许确的状况。java推荐使用collator类进行排序
public int compareTo(String anotherString) { int len1 = value.length; int len2 = anotherString.value.length; int lim = Math.min(len1, len2); char v1[] = value; char v2[] = anotherString.value; int k = 0; while (k < lim) { char c1 = v1[k]; char c2 = v2[k]; if (c1 != c2) { return c1 - c2; } k++; } return len1 - len2; }
参考list扩容
arrays.copyof, clone都是浅拷贝
一句话总结:没必要追求最快算法,仍是要结合业务,找准侧重点
基本数据类型不能做为aslist的输入参数
输出1
int类型不能泛型化。替换成Intger
public static <T> List<T> asList(T... a) { return new ArrayList<>(a); }
private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable {}
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> { public void add(int index, E element) { throw new UnsupportedOperationException(); } }
arraylist是arrays的静态内部类,在父类声明类add方法,抛出异常 。为啥要设计成这样?若是是不可变类推荐guava。immlist
两种方式
1) foreach :shi iterator的变形用法。也就是须要先建立一个迭代器容器,而后屏蔽内部遍历细节,对外提供hasnext等方法。
2) for(int i=0 ) 采用下标方式遍历列表
arraylist 实现类RandomAccess接口(随机存取接口),这也就标志着arraylist是一个能够随机存取的列表 。适合采用下标方式来访问
linkedlist,双向链表,两个元素原本就是有关联的,用foreach会高效
s1.equals(s2) 。二者都是list,equals方法是在abstractlist中定义的
list接口提供来sublist方法,返回的子列表只是一个视图,对子列表的操做至关于操做原列表
代码比较简洁
checkForconmodification方法是用于检测并发修改的。modcount是从子列表的构造函数中赋值的,其值等于生成子列表时的修改次数。由于在生成子列表后再修改原始列表modcount的值就不相等了。
public void add(int index, E e) { rangeCheckForAdd(index); checkForComodification(); parent.add(parentOffset + index, e); this.modCount = parent.modCount; this.size++; }
private void checkForComodification() { if (ArrayList.this.modCount != this.modCount) throw new ConcurrentModificationException(); }
binarySearch基于二分算法。要求列表自己升序。推荐indexof
好比说 indexOf()依赖equals方法查找,binarySearch则依赖compareTo方法查找
1)并集:list1.addAll(list2)
2) 交集:list.retainAll(list2)
3) 差级:list1.removeAll(list2)
4)无重复的并集:list1.removeAll(list2); list1.addAll(list2)
哈?行吧。entry对象和2倍扩容 注意下内存使用就行
map key 冲突。下降效率
算了吧
原文是与treeset作对比的
8三、推荐使用枚举定义常量
8四、使用构造函数协助描述枚举项add code
8五、当心switch带来的空值异常
8六、在switch的default代码快中增长assertionerror错误
switch代码与枚举之间没有强制的约束关系,只是在语义上创建了联系。在default后直接抛出AssertionError错误,其含义就是“不要跑到这里来”
8七、使用valueof前必须校验
valueof先经过反射从枚举类的常量声明中查找,若找到就直接返回,若找不到则抛出无效参数异常。valueof本意是保护编码中的枚举安全性,使其不产生空枚举对象。
8八、用枚举实现工厂方法模式更简洁
1)避免错误调用
2)性能更好
3)下降类间耦合
8九、枚举项的数量限制在64个之内
为了更好的使用枚举,java提供了两个枚举集合EnumSet和EnumMap。EnumSet很好用,可是它有一个隐藏的特色。
一句话总结:当枚举项《64时,建立RegularenumSet实例,大于64时,建立JumboEnumSet实例对象。而JumboEnumSet内部分段处理。多了一次映射。因此小于64时效率比较高
90、当心使用注解
@inherited注解有利有弊,利的地方是一个注解只要标注到父类,全部的子类都会自动具备父类相同的注解,整齐、统一并且便于管理,弊的地方是单单
9一、枚举和注解结合使用威力更大
9二、注意@override不一样版本的区别
jdk1.5严格遵照重写的定义。1.6之后开放了不少。好比说继承接口的,在1.5不能用@override
9三、java的泛型是类型擦除的
之因此这样处理:
1)避免jvm的大换血。c++的泛型生命期延续到了运行期,而java是在编译器擦除掉的。避免jvm大量的重构工做
2)版本兼容。在编译器擦除能够更好的支持原生类型。在java1.5以上,即便声明一个list这样的原生类型也是能够正常编译经过的,只是会产生警告
9四、不能初始化泛型参数和数组
T[] tArray=new T[3]; 编译失败
List<T> list=new ArrayList<T>();编译成功
为何数据不能够。可是集合能够?由于arraylist表面是泛型,其实已经在编译器转型为object了。在某些状况下,咱们确实须要泛型数组,能够以下实现:
9五、强制声明泛型的实际类型
9六、不一样的场景使用不一样的泛型通配符
?:任意类型
extends:某一个类的子类型
super:某一个类的父类型
1)泛型结构只参与‘读’操做则限定上界
2)泛型结构只参与‘写’操做则限定下界
9七、警戒泛型是不能协变和逆变的
协变:用一个窄类型替换宽类型
举个例子:
逆变:一个宽类型替换窄类型
举个例子:
逆变不属于重写,只是重载而已。因为此时的dostuff方法已经与父类没有任何关系类,只是子类独立扩展出的一个行为,因此是否声明为dostuff方法名意义不大,逆变已经不具备特别的意义类。因此重点关注下协变。(其实也就是多态)
泛型不支持协变、逆变
9八、建议采用的顺序是List<T> List<?> List<Object>
1)List<T>表示的是list集合中的元素都为t类型,具体类型在运行期决定; List<?> 也是;List<Object>则表示集合中的全部元素
2)List<T>能够进行读写操做,它的类型是固定的T类型,在编码期不须要进行任何的转型操做;List<?> 是只读类型的,由于编译器不知道list中容纳的是什么类型的元素,并且读出来的元素都是object类型的,须要主动转型,因此它常常用于泛型方法的返回值。注意list<?>能够remove,clear等,由于删除动做与泛型类型无关 ; List<Object>也能够读写操做,可是它执行写入操做时须要转型,而此时已经失去了泛型存在的意义了
9九、严格限定泛型类型采用多重界限
举个例子
java语言是先把java源文件编译成后缀为class的字节码文件,而后再经过classloader机制把这些类文件加载到内存中,最后生成实例执行的。java使用一个元类MetaClass来描述加载到内存中的类数据,这就是Class类,它是一个描述类的类对象。特殊性:
1)无构造函数。Class对象是在加载类时由java虚拟机经过调用类加载器中的defineClas方法自动构造的
2)能够描述基本类型。虽然8个基本类型在jvm中并非一个对象,它们通常存在于栈内,可是class类仍然能够描述它们,int.class
3)其对象都是单例模式。一个Class的实例对象描述一个类,而且只描述一个类。
Class类是java的反射入口,只有在得到类一个类的描述对象后才能动态地加载,调用。通常得到一个class对象有三种途径
1) 类属性方式 String.class
2) 对象的getClass方法 new String().getClass()
3) forName方法加载 Class.forName("java.lang.String")
getMethod:得到全部public访问级别的方法,包括从父类继承的方法
getDeclaredMethod:得到自身类的全部方法,包括public、private等
accessible属性表示是否容易得到,是否须要进行安全检查。咱们知道,动态修改一个类或方法都会受java安全体系的制约,而安全的处理是很是消耗资源的(性能很是低),所以对于运行期要执行的方法或属性就提供类accessible可选项:由开发者决定是否要逃避安全体系的检查
invoke的执行:
@CallerSensitive public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) {//保存了accessible的值 if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, obj, modifiers); } } MethodAccessor ma = methodAccessor; // read volatile if (ma == null) { ma = acquireMethodAccessor(); } return ma.invoke(obj, args); }
accessible属性只是用来判断是否须要进行安全检查的,若是不须要则直接执行,这就能够大幅度地提高系统性能(因为取消了安全检查,也能够运行private方法,访问private属性)。通过大量测试,在大量的反射状况下,设置accessible为true能够提高性能20倍以上
数组是一个很是特殊的类,虽然它是一个类,但没有定义类路径。编译后会为不一样的数组类型生成不一样的类
因此其实是能够动态加载一个对象数组的
可是这没有任何意思,由于它不能生成一个数组对象,也就是说以上代码只是把一个string类型的数组类和long类型的数组类加载到类内存中,并不能经过newinstance方法生成一个实例对象,由于它没有定义数组的长度,没有长度的数组是不容许存在的。
可是!能够用使用array数组反射类来动态加载:
由于数组比较特殊,要想动态建立和反问数组,基本的反射是没法实现的。因而java就专门定义来一个array数组反射工具类来实现动态探知数组的功能
通常状况下反射并非性能的终极杀手,而代码结构混乱、可读性差则极可能会埋下隐患
1)提升系统的友好性
2)提升系统的可维护性
3)解决java异常机制自身的缺陷,抛多个异常
public class IOException extends Exception { public IOException() { super(); } //记录上一级异常 public IOException(String message, Throwable cause) { super(message, cause); } }
1)受检异常使接口声明脆弱
oop要求咱们尽可能多的面向接口编程,能够提升代码的扩展性、稳定性等。可是一旦设计异常问题就不同了。好比一个接口抛出了异常a,随着业务的发展,该接口可能还会抛出异常b、异常c等。这会产生两个问题:
a)异常是主逻辑的补充逻辑,修改一个补充逻辑,就会致使主逻辑也被修改,也就是出现了实现类“逆影响”接口的情景,咱们知道实现类是不稳定的,而接口是稳定的,一旦定义了异常,则增长了接口的不稳定性
b)实现的类变动最终会影响到调用者,破坏了封装性,这也是迪米特法则所不能容忍的(设计模式6原则:一个对象应该对其余对象保持最少的了解)
2)受检异常使代码的可读性下降
3)受检异常增长了开发工做量
咱们知道,异常须要封装和传递,只有封装才能让异常更容易理解,上层模块才能更好的处理,可这也会致使低层级的异常没完没了的封装,无故加剧了开发的工做量。可是咱们也不能把全部的受检异常转化为非受检异常,缘由是在编码期上层模块不知道下层模块会抛出何种非受检异常,只有经过规则或文档来约束,能够这样说:
受检异常:法律下的自由
非受检异常:协约性质的自由
受检异常威胁到系统的安全性、稳定性、可靠性、正确性时,不能转换为非受检异常
1)覆盖了try代码块中的return返回值
在代码中加上try代码块就标志着运行时会有一个throwable线程监视着该方法的运行,若出现异常,则交由异常逻辑处理。
a)finally中修改基本数据类型返回值。返回值不会变化
方法在栈内存中运行,而且会按照‘先进后出’的原则执行,当dostuff方法执行完return a时,此方法的返回值已经肯定是int类型1,此后finally代码块再修改a的值已经于dostuff返回值没有任何关系了
b)finally中修改基本引用类型返回值。返回值会变化
返回李四。person是一个引用对象,在try代码块中的返回值的person对象的地址。
2)屏蔽异常
异常线程在监视到有异常发生时,就会登记当前的异常类型为dataformatexception,可是当执行finally代码块时,则会重新为dostuff方法赋值,也就是告诉调用者‘该方法执行正确,没有产生异常,返回值是1’
1)加剧了上层代码编写者的负担
只能经过文档约束来告知上层代码有异常
2)致使子类代码膨胀
子类的无参构造函数默认调用的是父类的构造函数,因此子类的无参构造也必须抛出该异常或父类
3)违背来里氏替换原则(父类能出现的地方子类就能够出现,并且将父类替换为子类也不会产生任何异常)
若是子类抛出的异常比父类抛出的异常范围大,则没法直接直接替换
4)子类构造函数扩展受限
子类存在的缘由就是指望实现并扩展父类的逻辑,可是父类构造函数抛出异常却会让子类构造函数的灵活性大大下降
aop编程能够很轻松的控制一个方法调用哪些类,也能控制哪些方法容许被调用,通常来讲切面编程只能控制到方法级别,不能实现代码级别的植入,好比一个方法被类A调用时放回1,在类B调用时放回0,这就要求被调用者具备识别调用者的能力。在这种状况下,可使用throwable得到栈信息,而后鉴别调用者并分别输出
不要包含业务流转
java的异常处理机制确实比较慢,单单从对象的建立来讲,new一个ioexception会比string慢5倍,由于它要执行fillinstatcktrace方法,要记录当前栈的快照,而string类则要直接申请一个内存建立对象。并且,异常类是不能缓存的,指望预先创建大量的异常对象是不可能的。(在jdk1.6,一个异常对象建立的时间1.4毫秒)
从多线程的设计思想来讲。run方法是业务的处理逻辑,start是启动一个线程,并执行run方法
stop():对于未启动的线程(线程状态为new),会设置其标志位为不可启动,而其余的状态则是直接中止
start():会先启动线程,再判断标志位,若是标志位是不可启动,则中止线程
会有一个时间差
1)stop方法是过期的
2)stop方法会致使代码逻辑不完整(好比说stop时还没释放io资源等等)
3)stop方法会破坏原子逻辑(会直接释放全部锁,致使原子逻辑受损)
线程的优先级(priority)决定了线程得到cpu运行的机会,优先级越高得到的运行机会越大,优先级越低得到的机会越小。但不保证顺序执行。thread类中设置了三个优先级,建议使用优先常量,而不是1到10随机的数字。
class thread{ /** * The minimum priority that a thread can have. */ public final static int MIN_PRIORITY = 1; /** * The default priority that is assigned to a thread. */ public final static int NORM_PRIORITY = 5; /** * The maximum priority that a thread can have. */ public final static int MAX_PRIORITY = 10; }
jdk1.5之后,在thread类中增长了setUncaughtExceptionHandler方法,实现了线程异常的捕捉和处理
其实比较鸡肋
volatile关键字比较少用,缘由
1)java1.5以前该关键字在不一样的操做系统上有不一样的表现,所带来的问题是移植性比较差;
2)只保证了可见性,不保证原子性
1)尽量多地占用系统资源,提供快速运算??
2)能够监控线程执行的状况,好比是否执行完毕,是否有返回值,是否有异常等
3)能够为用户提供更好的支持,好比计算进度
死锁须要4个条件
1)互斥条件:一个资源每次只能被一个线程使用
2)资源独占条件:一个线程因请求资源而阻塞时,对已得到的资源保持不放
3)不剥夺条件:线程已得到的资源在未使用完以前,不能强行剥夺
4)循环等待条件:若干线程之间造成一种头尾相接的循环等待资源关系
解决:
1)减小资源共享
2) 使用自旋锁
lock.trylock(1,TimeUnit.SECONDS)必定时间内获取不到锁则放弃
功能与countdownlotch相似,增长了子线程结束后的处理线程
1)不要在循环条件中计算
2)尽量把变量、方法声明为fianl static类型
3)缩小变量的做用范围(加快gc)
4)使用stringbuilder stringbuffer
5)使用非线性检索
6)覆写exception的fillinstacktrace方法
7)不创建冗余对象
这个方法是用来记录异常时的栈信息的,很是耗时,若是不关注能够覆盖之,会使性能提高10倍以上
经过clone方法生成一个对象时,就会再也不执行构造函数了,只是再内存中进行数据块的拷贝,此方法看上去彷佛应该比new的性能好不少,可是java的缔造者们也认识到二八原则,80%的对象是经过new关键字建立出来的,因此对new再生成对象时作了充分的性能优化,事实上,通常状况下new生成的对象clone生成的性能方面要好不少
??????
??????
?????
13九、大胆采用开源工具
140、推荐使用guava扩展工具包
14二、apache扩展包
14三、推荐使用joda日期时间扩展包
14四、能够选择多种collections扩展
。。。。后面的就不说了 淡疼