扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
对于大多数刚刚接触jdk8的同学来说,应该都会认为lambda表达式其实就是匿名内部类的语法糖(包括我自己,在刚刚接触的时候,也是这样认为的),但实际上二者还是存在不少差异,其中最主要的两点就是标识性和作用域。
创新互联从2013年成立,是专业互联网技术服务公司,拥有项目网站建设、网站制作网站策划,项目实施与项目整合能力。我们以让每一个梦想脱颖而出为使命,1280元灌云做网站,已为上家服务,为灌云各地企业和个人服务,联系电话:18982081108
首先,内部类在创建表达式时,会确保创建一个拥有唯一标识的新对象,而对于lambda,其计算结果(其实就是一个映射的过程)可能有也可能没有唯一标识,这取决于具体实现。
其次,内部类的声明会创建出一个新的命名作用域,在这个作用域中,this与super指向内部类本身的当前实例;但是lambda恰恰相反,它不会引入任何新的命名环境,这样就避免了内部类名称查找的复杂性,名称查找会导致很多问题,比如想要调用外围实例的方法时却错误的调用了内部类实例的Object方法。
可供程序利用的资源(内存、CPU时间、网络带宽等)是有限的,优化的目的就是让程序用尽可能少的资源完成预定的任务。优化通常包含两方面的内容:减小代码的体积,提高代码的运行效率。本文讨论的主要是如何提高代码的效率。
在Java程序中,性能问题的大部分原因并不在于Java语言,而是在于程序本身。养成好的代码编写习惯非常重要,比如正确地、巧妙地运用java.lang.String类和java.util.Vector类,它能够显著地提高程序的性能。下面我们就来具体地分析一下这方面的问题。
1、 尽量指定类的final修饰符带有final修饰符的类是不可派生的。在Java核心API中,有许多应用final的例子,例如java.lang.String。为String类指定final防止了人们覆盖length()方法。另外,如果指定一个类为final,则该类所有的方法都是final。Java编译器会寻找机会内联(inline)所有的final方法(这和具体的编译器实现有关)。此举能够使性能平均提高50%
。
2、 尽量重用对象。特别是String 对象的使用中,出现字符串连接情况时应用StringBuffer 代替。由于系统不仅要花时间生成对象,以后可能还需花时间对这些对象进行垃圾回收和处理。因此,生成过多的对象将会给程序的性能带来很大的影响。
3、 尽量使用局部变量,调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(Stack)中,速度较快。其他变量,如静态变量、实例变量等,都在堆(Heap)中创建,速度较慢。另外,依赖于具体的编译器/JVM,局部变量还可能得到进一步优化。请参见《尽可能使用堆栈变量》。
4、 不要重复初始化变量 默认情况下,调用类的构造函数时,
Java会把变量初始化成确定的值:所有的对象被设置成null,整数变量(byte、short、int、long)设置成0,float和double变量设置成0.0,逻辑值设置成false。当一个类从另一个类派生时,这一点尤其应该注意,因为用new关键词创建一个对象时,构造函数链中的所有构造函数都会被自动调用。
5、 在JAVA + ORACLE 的应用系统开发中,java中内嵌的SQL语句尽量使用大写的形式,以减轻ORACLE解析器的解析负担。
6、 Java 编程过程中,进行数据库连接、I/O流操作时务必小心,在使用完毕后,即使关闭以释放资源。因为对这些大对象的操作会造成系统大的开销,稍有不慎,会导致严重的后果。
7、 由于JVM的有其自身的GC机制,不需要程序开发者的过多考虑,从一定程度上减轻了开发者负担,但同时也遗漏了隐患,过分的创建对象会消耗系统的大量内存,严重时会导致内存泄露,因此,保证过期对象的及时回收具有重要意义。JVM回收垃圾的条件是:对象不在被引用;然而,JVM的GC并非十分的机智,即使对象满足了垃圾回收的条件也不一定会被立即回收。所以,建议我们在对象使用完毕,应手动置成null。
8、 在使用同步机制时,应尽量使用方法同步代替代码块同步。
9、 尽量减少对变量的重复计算
例如:for(int i = 0;i list.size; i ++) {
…
}
应替换为:
for(int i = 0,int len = list.size();i len; i ++) {
…
}
10、尽量采用lazy loading 的策略,即在需要的时候才开始创建。
例如: String str = “aaa”;
if(i == 1) {
list.add(str);
}
应替换为:
if(i == 1) {
String str = “aaa”;
list.add(str);
}
11、慎用异常
异常对性能不利。抛出异常首先要创建一个新的对象。Throwable接口的构造函数调用名为fillInStackTrace()的本地(Native)方法,fillInStackTrace()方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,VM就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。异常只能用于错误处理,不应该用来控制程序流程。
12、不要在循环中使用:
Try {
} catch() {
}
应把其放置在最外层。
13、StringBuffer 的使用:
StringBuffer表示了可变的、可写的字符串。
有三个构造方法 :
StringBuffer (); //默认分配16个字符的空间
StringBuffer (int size); //分配size个字符的空间
StringBuffer (String str); //分配16个字符+str.length()个字符空间
你可以通过StringBuffer的构造函数来设定它的初始化容量,这样可以明显地提升性能。这里提到的构造函数是StringBuffer(int
length),length参数表示当前的StringBuffer能保持的字符数量。你也可以使用ensureCapacity(int
minimumcapacity)方法在StringBuffer对象创建之后设置它的容量。首先我们看看StringBuffer的缺省行为,然后再找出一条更好的提升性能的途径。
StringBuffer在内部维护一个字符数组,当你使用缺省的构造函数来创建StringBuffer对象的时候,因为没有设置初始化字符长度,StringBuffer的容量被初始化为16个字符,也就是说缺省容量就是16个字符。当StringBuffer达到最大容量的时候,它会将自身容量增加到当前的2倍再加2,也就是(2*旧值+2)。如果你使用缺省值,初始化之后接着往里面追加字符,在你追加到第16个字符的时候它会将容量增加到34(2*16+2),当追加到34个字符的时候就会将容量增加到70(2*34+2)。无论何事只要StringBuffer到达它的最大容量它就不得不创建一个新的字符数组然后重新将旧字符和新字符都拷贝一遍――这也太昂贵了点。所以总是给StringBuffer设置一个合理的初始化容量值是错不了的,这样会带来立竿见影的性能增益。
StringBuffer初始化过程的调整的作用由此可见一斑。所以,使用一个合适的容量值来初始化StringBuffer永远都是一个最佳的建议。
14、合理的使用Java类 java.util.Vector。
简单地说,一个Vector就是一个java.lang.Object实例的数组。Vector与数组相似,它的元素可以通过整数形式的索引访问。但是,Vector类型的对象在创建之后,对象的大小能够根据元素的增加或者删除而扩展、缩小。请考虑下面这个向Vector加入元素的例子:
Object obj = new Object();
Vector v = new Vector(100000);
for(int I=0;
I100000; I++) { v.add(0,obj); }
除非有绝对充足的理由要求每次都把新元素插入到Vector的前面,否则上面的代码对性能不利。在默认构造函数中,Vector的初始存储能力是10个元素,如果新元素加入时存储能力不足,则以后存储能力每次加倍。Vector类就象StringBuffer类一样,每次扩展存储能力时,所有现有的元素都要复制到新的存储空间之中。下面的代码片段要比前面的例子快几个数量级:
Object obj = new Object();
Vector v = new Vector(100000);
for(int I=0; I100000; I++) { v.add(obj); }
同样的规则也适用于Vector类的remove()方法。由于Vector中各个元素之间不能含有“空隙”,删除除最后一个元素之外的任意其他元素都导致被删除元素之后的元素向前移动。也就是说,从Vector删除最后一个元素要比删除第一个元素“开销”低好几倍。
假设要从前面的Vector删除所有元素,我们可以使用这种代码:
for(int I=0; I100000; I++)
{
v.remove(0);
}
但是,与下面的代码相比,前面的代码要慢几个数量级:
for(int I=0; I100000; I++)
{
v.remove(v.size()-1);
}
从Vector类型的对象v删除所有元素的最好方法是:
v.removeAllElements();
假设Vector类型的对象v包含字符串“Hello”。考虑下面的代码,它要从这个Vector中删除“Hello”字符串:
String s = "Hello";
int i = v.indexOf(s);
if(I != -1) v.remove(s);
这些代码看起来没什么错误,但它同样对性能不利。在这段代码中,indexOf()方法对v进行顺序搜索寻找字符串“Hello”,remove(s)方法也要进行同样的顺序搜索。改进之后的版本是:
String s = "Hello";
int i = v.indexOf(s);
if(I != -1) v.remove(i);
这个版本中我们直接在remove()方法中给出待删除元素的精确索引位置,从而避免了第二次搜索。一个更好的版本是:
String s = "Hello"; v.remove(s);
最后,我们再来看一个有关Vector类的代码片段:
for(int I=0; I++;I v.length)
如果v包含100,000个元素,这个代码片段将调用v.size()方法100,000次。虽然size方法是一个简单的方法,但它仍旧需要一次方法调用的开销,至少JVM需要为它配置以及清除堆栈环境。在这里,for循环内部的代码不会以任何方式修改Vector类型对象v的大小,因此上面的代码最好改写成下面这种形式:
int size = v.size(); for(int I=0; I++;Isize)
虽然这是一个简单的改动,但它仍旧赢得了性能。毕竟,每一个CPU周期都是宝贵的。
15、当复制大量数据时,使用System.arraycopy()命令。
16、代码重构:增强代码的可读性。
例如:
public class ShopCart {
private List carts ;
…
public void add (Object item) {
if(carts == null) {
carts = new ArrayList();
}
crts.add(item);
}
public void remove(Object item) {
if(carts. contains(item)) {
carts.remove(item);
}
}
public List getCarts() {
//返回只读列表
return Collections.unmodifiableList(carts);
}
//不推荐这种方式
//this.getCarts().add(item);
}
17、不用new关键词创建类的实例
用new关键词创建类的实例时,构造函数链中的所有构造函数都会被自动调用。但如果一个对象实现了Cloneable接口,我们可以调用它的clone()方法。clone()方法不会调用任何类构造函数。
在使用设计模式(Design Pattern)的场合,如果用Factory模式创建对象,则改用clone()方法创建新的对象实例非常简单。例如,下面是Factory模式的一个典型实现:
public static Credit getNewCredit() {
return new Credit();
}
改进后的代码使用clone()方法,如下所示:
private static Credit BaseCredit = new Credit();
public static Credit getNewCredit() {
return (Credit) BaseCredit.clone();
}
上面的思路对于数组处理同样很有用。
18、乘法和除法
考虑下面的代码:
for (val = 0; val 100000; val +=5) {
alterX = val * 8; myResult = val * 2;
}
用移位操作替代乘法操作可以极大地提高性能。下面是修改后的代码:
for (val = 0; val 100000; val += 5) {
alterX = val 3; myResult = val 1;
}
修改后的代码不再做乘以8的操作,而是改用等价的左移3位操作,每左移1位相当于乘以2。相应地,右移1位操作相当于除以2。值得一提的是,虽然移位操作速度快,但可能使代码比较难于理解,所以最好加上一些注释。
19、在JSP页面中关闭无用的会话。
一个常见的误解是以为session在有客户端访问时就被创建,然而事实是直到某server端程序调用HttpServletRequest.getSession(true)这样的语句时才被创建,注意如果JSP没有显示的使用 %@pagesession="false"% 关闭session,则JSP文件在编译成Servlet时将会自动加上这样一条语句HttpSession
session = HttpServletRequest.getSession(true);这也是JSP中隐含的session对象的来历。由于session会消耗内存资源,因此,如果不打算使用session,应该在所有的JSP中关闭它。
对于那些无需跟踪会话状态的页面,关闭自动创建的会话可以节省一些资源。使用如下page指令:%@ page session="false"%
20、JDBC与I/O
如果应用程序需要访问一个规模很大的数据集,则应当考虑使用块提取方式。默认情况下,JDBC每次提取32行数据。举例来说,假设我们要遍历一个5000行的记录集,JDBC必须调用数据库157次才能提取到全部数据。如果把块大小改成512,则调用数据库的次数将减少到10次。
[p][/p]21、Servlet与内存使用
许多开发者随意地把大量信息保存到用户会话之中。一些时候,保存在会话中的对象没有及时地被垃圾回收机制回收。从性能上看,典型的症状是用户感到系统周期性地变慢,却又不能把原因归于任何一个具体的组件。如果监视JVM的堆空间,它的表现是内存占用不正常地大起大落。
解决这类内存问题主要有二种办法。第一种办法是,在所有作用范围为会话的Bean中实现HttpSessionBindingListener接口。这样,只要实现valueUnbound()方法,就可以显式地释放Bean使用的资源。另外一种办法就是尽快地把会话作废。大多数应用服务器都有设置会话作废间隔时间的选项。另外,也可以用编程的方式调用会话的setMaxInactiveInterval()方法,该方法用来设定在作废会话之前,Servlet容器允许的客户请求的最大间隔时间,以秒计。
22、使用缓冲标记
一些应用服务器加入了面向JSP的缓冲标记功能。例如,BEA的WebLogic Server从6.0版本开始支持这个功能,Open
Symphony工程也同样支持这个功能。JSP缓冲标记既能够缓冲页面片断,也能够缓冲整个页面。当JSP页面执行时,如果目标片断已经在缓冲之中,则生成该片断的代码就不用再执行。页面级缓冲捕获对指定URL的请求,并缓冲整个结果页面。对于购物篮、目录以及门户网站的主页来说,这个功能极其有用。对于这类应用,页面级缓冲能够保存页面执行的结果,供后继请求使用。
23、选择合适的引用机制
在典型的JSP应用系统中,页头、页脚部分往往被抽取出来,然后根据需要引入页头、页脚。当前,在JSP页面中引入外部资源的方法主要有两种:include指令,以及include动作。
include指令:例如%@ include file="copyright.html"
%。该指令在编译时引入指定的资源。在编译之前,带有include指令的页面和指定的资源被合并成一个文件。被引用的外部资源在编译时就确定,比运行时才确定资源更高效。
include动作:例如jsp:include page="copyright.jsp"
/。该动作引入指定页面执行后生成的结果。由于它在运行时完成,因此对输出结果的控制更加灵活。但时,只有当被引用的内容频繁地改变时,或者在对主页面的请求没有出现之前,被引用的页面无法确定时,使用include动作才合算。
24、及时清除不再需要的会话
为了清除不再活动的会话,许多应用服务器都有默认的会话超时时间,一般为30分钟。当应用服务器需要保存更多会话时,如果内存容量不足,操作系统会把部分内存数据转移到磁盘,应用服务器也可能根据“最近最频繁使用”(Most
Recently
Used)算法把部分不活跃的会话转储到磁盘,甚至可能抛出“内存不足”异常。在大规模系统中,串行化会话的代价是很昂贵的。当会话不再需要时,应当及时调用HttpSession.invalidate()方法清除会话。HttpSession.invalidate()方法通常可以在应用的退出页面调用。
25、不要将数组声明为:public static final 。
26、HashMap的遍历效率讨论
经常遇到对HashMap中的key和value值对的遍历操作,有如下两种方法:MapString, String[] paraMap = new HashMapString, String[]();
................//第一个循环
SetString appFieldDefIds = paraMap.keySet();
for (String appFieldDefId : appFieldDefIds) {
String[] values = paraMap.get(appFieldDefId);
......
}
//第二个循环
for(EntryString, String[] entry : paraMap.entrySet()){
String appFieldDefId = entry.getKey();
String[] values = entry.getValue();
.......
}
第一种实现明显的效率不如第二种实现。
分析如下 SetString appFieldDefIds = paraMap.keySet(); 是先从HashMap中取得keySet
代码如下:
public SetK keySet() {
SetK ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
}
private class KeySet extends AbstractSetK {
public IteratorK iterator() {
return newKeyIterator();
}
public int size() {
return size;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return HashMap.this.removeEntryForKey(o) != null;
}
public void clear() {
HashMap.this.clear();
}
}
其实就是返回一个私有类KeySet, 它是从AbstractSet继承而来,实现了Set接口。
再来看看for/in循环的语法
for(declaration : expression_r)
statement
在执行阶段被翻译成如下各式
for(IteratorE #i = (expression_r).iterator(); #i.hashNext();){
declaration = #i.next();
statement
}
因此在第一个for语句for (String appFieldDefId : appFieldDefIds) 中调用了HashMap.keySet().iterator() 而这个方法调用了newKeyIterator()
IteratorK newKeyIterator() {
return new KeyIterator();
}
private class KeyIterator extends HashIteratorK {
public K next() {
return nextEntry().getKey();
}
}
所以在for中还是调用了
在第二个循环for(EntryString, String[] entry : paraMap.entrySet())中使用的Iterator是如下的一个内部类
private class EntryIterator extends HashIteratorMap.EntryK,V {
public Map.EntryK,V next() {
return nextEntry();
}
}
此时第一个循环得到key,第二个循环得到HashMap的Entry
效率就是从循环里面体现出来的第二个循环此致可以直接取key和value值
而第一个循环还是得再利用HashMap的get(Object key)来取value值
现在看看HashMap的get(Object key)方法
public V get(Object key) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length); //Entry[] table
EntryK,V e = table;
while (true) {
if (e == null)
return null;
if (e.hash == hash eq(k, e.key))
return e.value;
e = e.next;
}
}
其实就是再次利用Hash值取出相应的Entry做比较得到结果,所以使用第一中循环相当于两次进入HashMap的Entry中
而第二个循环取得Entry的值之后直接取key和value,效率比第一个循环高。其实按照Map的概念来看也应该是用第二个循环好一点,它本来就是key和value的值对,将key和value分开操作在这里不是个好选择。
1、组织与风格
(1).关键词和操作符之间加适当的空格。
(2).相对独立的程序块与块之间加空行
(3).较长的语句、表达式等要分成多行书写。
(4).划分出的新行要进行适应的缩进,使排版整齐,语句可读。
(5).长表达式要在低优先级操作符处划分新行,操作符放在新行之首。
(6).循环、判断等语句中若有较长的表达式或语句,则要进行适应的划分。
(7).若函数或过程中的参数较长,则要进行适当的划分。
(8).不允许把多个短语句写在一行中,即一行只写一条语句。
(9).函数或过程的开始、结构的定义及循环、判断等语句中的代码都要采用缩进风格。
注:如果大家有兴趣可以到安安DIY创作室博客,有相关说明性的文章和解释。
2、注解
Java 的语法与 C++ 及为相似,那么,你知道 Java 的注释有几种吗?是两种?
// 注释一行
/* ...... */ 注释若干行
不完全对,除了以上两种之外,还有第三种,文档注释:
/** ...... */ 注释若干行,并写入 javadoc 文档
注释要简单明了。
String userName = null; //用户名
边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。
在必要的地方注释,注释量要适中。注释的内容要清楚、明了,含义准确,防止注释二义性。
保持注释与其描述的代码相邻,即注释的就近原则。
对代码的注释应放在其上方相邻位置,不可放在下面。对数据结构的注释应放在其上方相邻位置,不可放在下面;对结构中的每个域的注释应放在此域的右方;
同一结构中不同域的注释要对齐。
变量、常量的注释应放在其上方相邻位置或右方。
全局变量要有较详细的注释,包括对其功能、取值范围、哪些函数或过程存取它以及存取时注意事项等的说明。
在每个源文件的头部要有必要的注释信息,包括:文件名;版本号;作者;生成日期;模块功能描述(如功能、主要算法、内部各部分之间的关系、该文件与其它文件关系等);主要函数或过程清单及本文件历史修改记录等。
/**
* Copy Right Information : Neusoft IIT
* Project : eTrain
* JDK version used : jdk1.3.1
* Comments : config path
* Version : 1.01
* Modification history :2003.5.1
* Sr Date Modified By Why What is modified
* 1. 2003.5.2 Kevin Gao new
**/
在每个函数或过程的前面要有必要的注释信息,包括:函数或过程名称;功能描述;输入、输出及返回值说明;调用关系及被调用关系说明等
/**
* Description :checkout 提款
* @param Hashtable cart info
* @param OrderBean order info
* @return String
*/
public String checkout(Hashtable htCart,
OrderBean orderBean)
throws Exception{
}
javadoc注释标签语法
@author 对类的说明 标明开发该类模块的作者
@version 对类的说明 标明该类模块的版本
@see 对类、属性、方法的说明 参考转向,也就是相关主题
@param 对方法的说明 对方法中某参数的说明
@return 对方法的说明 对方法返回值的说明
@exception 对方法的说明 对方法可能抛出的异常进行说明
3、命名规范
定义这个规范的目的是让项目中所有的文档都看起来像一个人写的,增加可读性,减少项目组中因为换人而带来的损失。(这些规范并不是一定要绝对遵守,但是一定要让程序有良好的可读性)较短的单词可通过去掉元音形成缩写;要不然最后自己写的代码自己都看不懂了,那可不行。
较长的单词可取单词的头几发符的优先级,并用括号明确表达式的操作顺序,避免使用默认优先级。
使用匈牙利表示法
Package 的命名
Package 的名字应该都是由一个小写单词组成。
package com.neu.util
Class 的命名
Class 的名字必须由大写字母开头而其他字母都小写的单词组成,对于所有标识符,其中包含的所有单词都应紧靠在一起,而且大写中间单词的首字母。
public class ThisAClassName{}
Class 变量的命名
变量的名字必须用一个小写字母开头。后面的单词用大写字母开头
userName , thisAClassMethod
Static Final 变量的命名
static Final 变量的名字应该都大写,并且指出完整含义。
/**
*DBConfig PATH
**/
public static final String
DB_CONFIG_FILE_PATH =com.neu.etrain.dbconfig;
参数的命名
参数的名字必须和变量的命名规范一致。
数组的命名
数组应该总是用下面的方式来命名:
byte[] buffer;
而不是:
byte buffer[];
方法的参数
使用有意义的参数命名,如果可能的话,使用和要赋值的字段一样的名字:
SetCounter(int size){
this.size = size;
}
4、文件样式
所有的 Java(*.java) 文件都必须遵守如下的样式规则:
版权信息
版权信息必须在 java 文件的开头,比如:
/*
* Copyright ? 2000 Shanghai XXX Co. Ltd.
* All right reserved.
*/
其他不需要出现在 javadoc 的信息也可以包含在这里。
Package/Imports
package 行要在 import 行之前,import 中标准的包名要在本地的包名之前,而且按照字母
顺序排列。如果 import 行中包含了同一个包中的不同子目录,则应该用 * 来处理。
package hotlava.net.stats;
import java io.*;
import java.util.Observable;
import hotlava.util.Application;
这里 java。io.* 使用来代替InputStream and OutputStream 的。
Class
接下来的是类的注释,一般是用来解释类的。
/**
* A class representing a set of packet and byte counters
* It is observable to allow it to be watched, but only
* reports changes when the current set is complete
*/
接下来是类定义,包含了在不同的行的 extends 和 implements
public class CounterSet
extends Observable
implements Cloneable
Class Fields
接下来是类的成员变量:
/**
* Packet counters
*/
protected int[] packets;
public 的成员变量必须生成文档(JavaDoc)。proceted、private和 package 定义的成
员变量如果名字含义明确的话,可以没有注释。
存取方法
接下来是类变量的存取的方法。它只是简单的用来将类的变量赋值获取值的话,可以简单的
写在一行上。
/**
* Get the counters
* @return an array containing the statistical data. This array has been
* freshly allocated and can be modified by the caller.
*/
public int[] getPackets() { return copyArray(packets, offset); }
public int[] getBytes() { return copyArray(bytes, offset); }
public int[] getPackets() { return packets; }
public void setPackets(int[] packets) { this.packets = packets; }
其它的方法不要写在一行上
构造函数
接下来是构造函数,它应该用递增的方式写(比如:参数多的写在后面)。
访问类型 (public, private 等.) 和 任何 static, final 或 synchronized 应该在一行
中,并且方法和参数另写一行,这样可以使方法和参数更易读。
public
CounterSet(int size){
this.size = size;
}
克隆方法
如果这个类是可以被克隆的,那么下一步就是 clone 方法:
public
Object clone() {
try {
CounterSet obj = (CounterSet)super.clone();
obj.packets = (int[])packets.clone();
obj.size = size;
return obj;
}catch(CloneNotSupportedException e) {
throw new InternalError(Unexpected CloneNotSUpportedException: +
e.getMessage());
}
}
类方法
下面开始写类的方法:
/**
* Set the packet counters
* (such as when restoring from a database)
*/
protected final
void setArray(int[] r1, int[] r2, int[] r3, int[] r4)
throws IllegalArgumentException
{
//
// Ensure the arrays are of equal size
//
if (r1.length != r2.length || r1.length != r3.length || r1.length != r4.length)
throw new IllegalArgumentException(Arrays must be of the same size);
System.arraycopy(r1, 0, r3, 0, r1.length);
System.arraycopy(r2, 0, r4, 0, r1.length);
}
toString 方法
无论如何,每一个类都应该定义 toString 方法:
public
String toString() {
String retval = CounterSet: ;
for (int i = 0; i data.length(); i++) {
retval += data.bytes.toString();
retval += data.packets.toString();
}
return retval;
}
}
main 方法
如果main(String[]) 方法已经定义了, 那么它应该写在类的底部.
5、代码可读性
避免使用不易理解的数字,用有意义的标识来替代。
不要使用难懂的技巧性很高的语句。
源程序中关系较为紧密的代码应尽可能相邻。
6、代码性能
在写代码的时候,从头至尾都应该考虑性能问题。这不是说时间都应该浪费在优化代码上,而是我们时刻应该提醒自己要注意代码的效率。比如:如果没有时间来实现一个高效的算法,那么我们应该在文档中记录下来,以便在以后有空的时候再来实现她。
不是所有的人都同意在写代码的时候应该优化性能这个观点的,他们认为性能优化的问题应该在项目的后期再去考虑,也就是在程序的轮廓已经实现了以后。
不必要的对象构造
不要在循环中构造和释放对象
使用 StringBuffer 对象
在处理 String 的时候要尽量使用 StringBuffer 类,StringBuffer 类是构成 String 类的基础。
String 类将 StringBuffer 类封装了起来,(以花费更多时间为代价)为开发人员提供了一个安全的接口。当我们在构造字符串的时候,我们应该用 StringBuffer 来实现大部分的工作,当工作完成后将 StringBuffer 对象再转换为需要的 String 对象。比如:如果有一个字符串必须不断地在其后添加许多字符来完成构造,那么我们应该使用StringBuffer 对象和她的 append() 方法。如果我们用 String 对象代替StringBuffer 对象的话,会花费许多不必要的创建和释放对象的 CPU 时间。大家可以来安安DIY创作室一起讨论。
避免太多的使用 synchronized 关键字避免不必要的使用关键字 synchronized,应该在必要的时候再使用她,这是一个避免死锁的好方法。
7、编程技巧
byte 数组转换到 characters
为了将 byte 数组转换到 characters,你可以这么做:
Hello world!.getBytes();
Utility 类
Utility 类(仅仅提供方法的类)应该被申明为抽象的来防止被继承或被初始化。
初始化
下面的代码是一种很好的初始化数组的方法:
objectArguments = new Object[] { arguments };
枚举类型
JAVA 对枚举的支持不好,但是下面的代码是一种很有用的模板:
class Colour {
public static final Colour BLACK = new Colour(0, 0, 0);
public static final Colour RED = new Colour(0xFF, 0, 0);
public static final Colour GREEN = new Colour(0, 0xFF, 0);
public static final Colour BLUE = new Colour(0, 0, 0xFF);
public static final Colour WHITE = new Colour(0xFF, 0xFF, 0xFF);
}
这种技术实现了RED, GREEN, BLUE 等可以象其他语言的枚举类型一样使用的常量。
他们可以用 '==' 操作符来比较。
但是这样使用有一个缺陷:如果一个用户用这样的方法来创建颜色 BLACK new Colour(0,0,0)
那么这就是另外一个对象,'=='操作符就会产生错误。她的 equal() 方法仍然有效。由于这个原因,这个技术的缺陷最好注明在文档中,或者只在自己的包中使用。
8、编写格式
代码样式
代码应该用 unix 的格式,而不是 windows 的(比如:回车变成回车+换行)
文档化
必须用 javadoc 来为类生成文档。不仅因为它是标准,这也是被各种 java 编译器都认可的方法。使用 @author 标记是不被推荐的,因为代码不应该是被个人拥有的。
缩进
缩进应该是每行2个空格. 不要在源文件中保存Tab字符. 在使用不同的源代码管理工具时Tab字符将因为用户设置的不同而扩展为不同的宽度.如果你使用 UltrEdit 作为你的 Java 源代码编辑器的话,你可以通过如下操作来禁止保存Tab字符, 方法是通过 UltrEdit中先设定 Tab 使用的长度室2个空格,然后用 Format|Tabs to Spaces 菜单将 Tab 转换为空格。
页宽
页宽应该设置为80字符. 源代码一般不会超过这个宽度, 并导致无法完整显示, 但这一设置也可以灵活调整. 在任何情况下, 超长的语句应该在一个逗号或者一个操作符后折行. 一条语句折行后, 应该比原来的语句再缩进2个字符.
{} 对
{} 中的语句应该单独作为一行. 例如, 下面的第1行是错误的, 第2行是正确的:
if (i0) { i ++ }; // 错误, { 和 } 在同一行
if (i0) {
i ++
}; // 正确, { 单独作为一行
} 语句永远单独作为一行.如果 } 语句应该缩进到与其相对应的 { 那一行相对齐的位置。
括号
左括号和后一个字符之间不应该出现空格, 同样, 右括号和前一个字符之间也不应该出现空格. 下面的例子说明括号和空格的错误及正确使用:
CallProc( AParameter ); // 错误
CallProc(AParameter); // 正确
不要在语句中使用无意义的括号. 括号只应该为达到某种目的而出现在源代码中。下面的例子说明错误和正确的用法:
if ((I) = 42) { // 错误 - 括号毫无意义
if (I == 42) or (J == 42) then // 正确 - 的确需要括号
9、代码编译
1.编写代码时要注意随时保存,并定期备份,防止由于断电、硬盘损坏等原因造成代码丢失。
2.同一项目组内,最好使用相同的编辑器,并使用相同的设置选项。
3.合理地设计软件系统目录,方便开发人员使用。
4.打开编译器的所有告警开关对程序进行编译。
5.在同一项目组或产品组中,要统一编译开关选项。
6.使用工具软件(如Visual SourceSafe)对代码版本进行维护。如果大家有不明白的可以到安安DIY创作室留言。
10、可移植性
Borland Jbulider 不喜欢 synchronized 这个关键字,如果你的断点设在这些关键字的作用域内的话,调试的时候你会发现的断点会到处乱跳,让你不知所措。除非必须,尽量不要使用。
换行
如果需要换行的话,尽量用 println 来代替在字符串中使用\n。
你不要这样:
System.out.print(Hello,world!\n);
要这样:
System.out.println(Hello,world!);
或者你构造一个带换行符的字符串,至少要象这样:
String newline = System.getProperty(line.separator);
System.out.println(Hello world + newline);
PrintStream
PrintStream 已经被不赞成(deprecated)使用,用 PrintWrite 来代替它。
Java是一种跨平台,适合于分布式计算环境的面向对象编程语言。
具体来说,它具有如下特性:简单性、面向对象、分布式、解释型、可靠、安全、平台无关、可移植、高性能、多线程、动态性等。
有充足的开源库来保证低水平开发者能轻松完成高水平的任务,代码的可移植性。每台设备都可以用相同的调用运行Java。如果你用C++或其他面向对象语言构建东西,那么你必须得针对所要运行的每个平台进行调整。
Java内存分析,我们用它来查看哪里正在发生内存泄漏。较新的异步框架,使用Java代码工作,从线程跳转到线程。从一个线程被传递到另一个线程的 请求。我们跨越线程边界将信息联合在一起。APM工具遵循跨线程的请求,以知道组件是否运行缓慢。我们提供终端到终端的事务细节。当查看损坏的数据时,很 难说明这是一个还是多个请求。确定性能下降的真正原因是有必要的。
Java的可靠性和安全性
Java最初设计目的是应用于电子类消费产品,因此要求较高的可靠性。Java虽然源于C++,但它消除了许多C++不可靠因素,可以防止许多编程错 误。首先,Java是强类型的语言,要求显式的方法声明,这保证了编译器可以发现方法调用错误,保证程序更加可靠;其次,Java不支持指针,这杜绝了内 存的非法访问;第三,Java的自动单元收集防止了内存丢失等动态内存分配导致的问题;第四,Java解释器运行时实施检查,可以发现数组和字符串访问的 越界,最后,Java提供了异常处理机制,程序员可以把一组错误代码放在一个地方,这样可以简化错误处理任务便于恢复。
由于Java主要用于网络应用程序开发,因此对安全性有较高的要求。如果没有安全保证,用户从网络下载程序执行就非常危险。Java通过自己的安全机 制防止了病毒程序的产生和下载程序对本地系统的威胁破坏。当Java字节码进入解释器时,首先必须经过字节码校验器的检查,然后,Java解释器将决定程 序中类的内存布局,随后,类装载器负责把来自网络的类装载到单独的内存区域,避免应用程序之间相互干扰破坏。最后,客户端用户还可以限制从网络上装载的类 只能访问某些文件系统。
上述几种机制结合起来,使得Java成为安全的编程语言。
.Java面向对象
面向对象其实是现实世界模型的自然延伸。现实世界中任何实体都可以看作是对象。对象之间通过消息相互作用。另外,现实世界中任何实体都可归属于某类事 物,任何对象都是某一类事物的实例。如果说传统的过程式编程语言是以过程为中心以算法为驱动的话,面向对象的编程语言则是以对象为中心以消息为驱动。用公 式表示,过程式编程语言为:程序=算法+数据;面向对象编程语言为:程序=对象+消息。
所有面向对象编程语言都支持三个概念:封装、多态性和继承,Java也不例外。现实世界中的对象均有属性和行为,映射到计算机程序上,属性则表示对象 的数据,行为表示对象的方法(其作用是处理数据或同外界交互)。所谓封装,就是用一个自主式框架把对象的数据和方法联在一起形成一个整体。可以说,对象是 支持封装的手段,是封装的基本单位。Java语言的封装性较强,因为Java无全程变量,无主函数,在Java中绝大部分成员是对象,只有简单的数字类 型、字符类型和布尔类型除外。而对于这些类型,Java也提供了相应的对象类型以便与其他对象交互操作。
多态性就是多种表现形式,具体来说,可以用“一个对外接口,多个内在实现方法”表示。举一个例子,计算机中的堆栈可以存储各种格式的数据,包括整型, 浮点或字符。不管存储的是何种数据,堆栈的算法实现是一样的。针对不同的数据类型,编程人员不必手工选择,只需使用统一接口名,系统可自动选择。运算符重 载(operatoroverload)一直被认为是一种优秀的多态机制体现,但由于考虑到它会使程序变得难以理解,所以Java最后还是把它取消了。
继承是指一个对象直接使用另一对象的属性和方法。事实上,我们遇到的很多实体都有继承的含义。例如,若把汽车看成一个实体,它可以分成多个子实体, 如:卡车、公共汽车等。这些子实体都具有汽车的特性,因此,汽车是它们的“父亲”,而这些子实体则是汽车的“孩子”。Java提供给用户一系列类 (class),Java的类有层次结构,子类可以继承父类的属性和方法。与另外一些面向对象编程语言不同,Java只支持单一继承。
代码可读性好,维护性好 ,数据库访问显示了很多,并且通过事务请求作出了很多数据库调用。你必须能够看到自由数据采集的空白以便于整体地看数据。这使得我们能够看到问题是否单独发生。
必须是静态强类型语言,从编译期间就保证能排查出大量错误,并且能通过各种代码手段限制开发人员的代码风格和写法。,在DevOps中,你需要能够在 你检查代码的那一刻看到整个应用程序生命周期,以及开始构建的时候。通过嵌入前期,你可以捕捉可能会导致QA问题的代码模式,并且这些QA问题会损害收入 和品牌。应用程序生命周期管理的关键是看到你发布到管道的东西。
任何需要稳健性的地方——它的可扩展性,可工作于许多机器和许多不同的操作系统。它是开发者的通用语,并且有针对所有内容的库。这使得生活美好多了。 它并不神奇,不能与其它语言相比。对于健壮性,可扩展性,和工作于许多操作系统的能力而言,选择要么是.NET要么是Java。
代码是否具有可读性应该主要取决于写的人规划的代码有没有可读性,和编程语言本身没有关系
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流