Linux 一般作为服务器使用,而服务器一般放在机房,你不可能在机房操作你的 Linux 服务器。
这时我们就需要远程登录到Linux服务器来管理维护系统。
多多剪贴板
screenpress
标识符的命名应该使用具有实际含义的英文单词作为标识符的名称。
具体的标识符包括:包名、类名、方法名、属性名、方法参数、局部变量名等;
要求使用具有实际含义的英文单词作为标识符的名称,不应该使用汉语拼音、数字序列等作为标识符的名称,如:Class Yonghu(用户)、int C_1001都不是符合规范的标识符。
标识符应该尽量使用完整的英文单词的组合作为标识符的名称,当需要使用缩写时,只能使用计算机领域或业务领域内公认的缩写,如:url、html等就是符合规范的缩写;缩写的大小写要求同普通英文单词,具体视标识符的类型而定。
包的路径应该使用域名的反转,包名称应该全部使用小写字母。
如:com.hisense.hitv就是一个符合规范的包路径。
包的名称要全部使用小写字母,即使某个子包名由多个英文单词组成,如:“serviceloader”,也应该全部使用小写字母,这是Java约定俗成的规范。
类的名称通常使用名词,并且首字母大写,如果类名由多个英文单词组成时,每个英文单词的首字母也要大写。
不符合规范的类名如:
public class metadata
public class Metadatafactory
符合规范的如:
public class Metadata
public class MetadataFactory
类名的首字母大写,类名由多个英文单词组成时,每个英文单词的首字母大写,这是Java约定俗成的规范。
类的命名规范同样适用于接口和枚举。
常见的类和接口命名。
抽象类一般以“Abstract”开始,如:AbstractSimpleQueryParams。
异常类的类名的最后要加上“Exception”,如:ServiceActionException。
当接口需要和类名称区分时,可增加前缀“I”,如:ICommonDao。
当实现类需要和接口区分时,可增加后缀“Impl”,如:CasInfoDaoImpl。
追加功能型的接口名要在名称后面加“able”后缀,如:Cloneable。
单元测试类的类名一般以要测试类的名称+“Test”作为测试类名,如要编写类SampleClass的单元测试类,则该单元测试命名为:SampleClassTest。
方法名称的第一个单词通常使用动词,描述方法要进行的操作;方法名称首字母小写,如果方法名由多个英文单词组成时,第二个和以后的英文单词的首字母大写。
不符合规范的方法名如:
public void LoadMetadata
public ClassMetadata getclassmetadatabyname
符合规范的方法名如:
public void loadMetadata
public ClassMetadata getClassMetadataByName
方法名称首字母小写,如果方法名由多个英文单词组成时,第二个和以后的英文单词的首字母大写,这是Java约定俗成的规范。
6. 属性的get方法和set方法通常是在属性的名称前增加get或set前缀,并将属性的首字母转换成大写。
例如,当有属性:private int age;
符合规范的get方法和set方法为:
public int getAge();
public void setAge(int age);
在属性的名称前增加get或set前缀,并将属性的首字母转换成大写,这是JavaBean要求的规范。
7. 布尔类型的get方法,应使用能反映出属性状态的疑问词作为前缀。
符合规范的get方法示例如下:
public boolean isExisted(){}
public boolean canSpeak(){}
public boolean hasChild(){}
8. 用正确的反义词组命名具有相反动作的方法。
对于具有相反动作的方法,应该使用反义词的英文单词来命名方法。常见的反义词组如下:
get / set add / remove begin / end insert / delete
first / last next / previous open / close min / max
start / stop send / receive show / hide up / down
符合规范的示例如下:
public int getAge();
public void setAge(int age);
或 public void addUser(User user);
public void removeUser(User user);
9. 静态常量要使用static final作为修饰符,并且全部使用大写字母;常量由多个单词组成时,单词之间使用_隔开。
符合规范的示例如下:
public static final int SESSION_MAX_NUMBER = 20;
public static final String DEFAULT_CLASS_NAME = “BaseBean”;
常量全部使用大写字母,如常量由多个单词组成时,单词之间使用_隔开,这是Java约定俗成的规范。
常量的命名规范同样适用于枚举的枚举项。
10. 变量名首字母小写,如果变量名由多个英文单词组成时,第二个和以后的英文单词的首字母大写。
这里所说的变量包括:属性名、方法参数和局部变量。
符合规范的示例如下:
private int age;
public void setUserName(String userName);
int userId;
变量名首字母小写,如果变量名由多个英文单词组成时,第二个和以后的英文单词的首字母大写, 这是Java约定俗成的规范。
除了for循环的计数变量和catch中的异常变量外,禁止使用单个字符作为变量名;当多个循环嵌套时,推荐使用i, j, k 作为循环的计数变量。
使用单个字符作为变量名时,无法表示出变量的意图,从而影响程序的可读性。好的变量命名能准确的表示出变量的意图,如:int index;
在for循环中使用单个字符作为循环的计数变量,是多种开发语言约定俗成的规范。如:
for (int i = 0; i < 100; i++)
当多个循环嵌套时,推荐使用i, j, k 作为循环的计数变量,这样从循环变量就能看出循环所在的嵌套层;
在异常中的catch中可以使用e作为异常的变量名。
11. 方法参数或局部变量禁止与类的属性名称相同,除非是属性的set方法的参数。
在Java中允许方法参数和局部变量与类的属性名称同名,但调用类的属性时必须使用“this.属性名”的方式调用,这种情况导致了代码的可读性降低。
而在属性的set方法中使用和属性同名的方法参数,是Java约定俗成的规范。如有一个属性定义为:private int age,下面的set方法是符合规范的:
public void setAge (int age){
this.age = age;
}
12. 避免在编码中直接使用字符串或数字;正确的方式是定义常量,并在程序中使用常量的标识符。
String start = request.getParameter(“start”);
String limit = request.getParameter(“limit”);
上述代码是不符合规范的,在编码中直接使用字符串或数字,会导致使用硬编码的字符串或数字分布在程序的多个地方,使程序难以维护。
通过定义常量,并在程序中使用常量的标识符,能提高程序的可维护性。符合规范的示例如下:
private static final String BY_PAGE_START_NAME = “start”;
private static final String BY_PAGE_LIMIT_NAME = “limit”;
String start = request.getParameter(BY_PAGE_START_NAME);
String limit = request.getParameter(BY_PAGE_LIMIT_NAME);
13. 在定义数组时,应使用Type[] arrayName,而不是使用Type arrayName[]。
括号是数组类型的一部分,数组定义如:String[] args,而不是使用 String args[]的方式来定义。
14. 【推荐】在表示长整数常量的时候,用L来代替l。
Long id = 10l;
上述代码是不符合规范的,由于小写字符”l”在很多编辑器中和数字”1”很难区分,在定义长整数常量的时候,建议采用L来代替l,如:
Long id = 10L;
15. 【推荐】在import引入类时,应具体到要引入的类,不要使用号来引入整个包。
import java.util.;
上述代码是不符合规范的,Java程序在执行时,JVM会根据import语句加载需要的类,当import语句引入了整个包时,JVM会加载整个包中的类,这样会给Java程序的执行带来性能上的损失。正确的方式是应该只引入需要的类。符合规范的示例如下:
import java.util.Date;
16. 【推荐】如果模块、接口、类、方法使用了设计模式,在命名时体现出具体模式。
将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。
推荐规范的示例如下:
public class OrderFactory;
public class LoginProxy;
public class ResourceObserver;
17. 【推荐】各层命名规范。
A) Service/DAO 层方法命名规约
1)获取单个对象的方法用 get 做前缀。
2)获取多个对象的方法用 list 做前缀。
3)获取统计值的方法用 count 做前缀。
4)插入的方法用 save/insert 做前缀。
5)删除的方法用 remove/delete 做前缀。
6)修改的方法用 update 做前缀。
B) 领域模型命名规约
1)数据对象:xxxDO,xxx 即为数据表名。
2)数据传输对象:xxxDTO,xxx 为业务领域相关的名称。
3)展示对象:xxxVO,xxx 一般为网页名称。
4)POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO。
源代码格式
一个类应该只做一件事。
当我们设计或创建一个类时,应该明确的知道这个类的职责,这个类的所有代码应该都和这个职责相关,不要仅仅把类当成实现功能的容器,而把多个不相关的功能放到一个类中,这样做可以提高类的内聚性,也符合“单一职责”的设计原则。
当一个属性或方法不能确定它的可见性时,优先采用较低的可见性。
Java提供了public、protected、默认的和private四种由高到低的可见性,当我们不能确定一个属性和方法应该使用什么可见性时,应该优先选择较低的可见性。如:一个方法,我们希望这个方法能在子类中也能被调用,但不能确定是使用public还是protected,这时应该优先选择protected。
在面向对象的系统中,一个低可见性的方法修改为高可见性时,不会产生任何的问题,如:一个private的方法修改为protected时不会有任何的问题;但相反则不行。
一个public方法应该只提供一个完整的、唯一的功能实现。
一个public的方法应该提供一个完整的功能实现,如:一个类有两个属性,分别为性别标识和性别名称,假设当性别标识为“1”,性别名称为 “男”,当性别标识为“2”,性别名称为“女”,反之也存在如此的逻辑。当一个public的方法为设置性别时,应该同步设置性别标识和性别名称;假设一个public方法只能设置性别标识或性别名称,则是一个不完整的功能实现,不完整的public方法会将这种本来应该属于类内部的逻辑扩散到了类外部,从而影响到了类的内聚性。另外,不完整的public方法也可能导致类内部数据的不一致,如出现性别标识为“1”,但性别名称为 “女”的情况。
一个public的方法应该提供一个唯一的功能实现,即一个方法只做一件事,如上述的例子,如果在设置性别的方法内同时也设置了年龄,这样就会违反“单一职责”的设计原则,影响方法的可扩展性和可重用性。
一个方法的参数之间不应该存在逻辑关系。
引用上述的例子,假设有一个方法需要根据性别查询用户,方法的伪码如下:
List<用户> getUserList(int 性别标识, String 性别名称);
上述示例是不符合规范的,由于性别标识和性别名称之间存在一定的逻辑关系,如果同一个方法的参数之间使用存在逻辑关系,会使这种逻辑关系扩散,从而影响类的内聚性;同样,也可能会出现参数不一致的情况。
接口的方法应尽量保持稳定,不要因为细节的变化而频繁改变接口方法。
如接口方法:
public List getUserList(int userId, String userName,int sexFlag);
该接口方法主要用来根据查询条件查询符合条件的用户列表,但由于查询条件的变化,可能导致接口方法的参数频繁变化,导致接口方法不稳定。
正确的方法应该是将查询条件进行封装,将查询条件的变化隔离到查询条件类中,从而保证接口方法的稳定。如接口方法:
public List getUserList(UserQueryParams queryParams);
可定义查询条件类UserQueryParams,如下:
public class UserQueryParams {
private int userId;
private String userName;
private int sexFlag;
//get和set方法略
}
谨慎的运用final来限制继承和重载。
当final关键字应用到类时,可以限制不能从该类继承;当final关键字应用到方法,可以限制该方法不会在子类中被覆盖。在正常的情况下,Java中的类和方法应该是能够被继承和被覆盖的,只有在明确希望类不能被继承和方法不能被覆盖时才使用final关键字。
不能因为性能的问题而将方法设置为final,Java中的方法在默认情况下都是动态绑定的,也就是说具体执行的方法需要在运行期才能决定,这种动态绑定机制提供了Java语言的灵活性。当一个方法被设置为final时,该方法也就失去这种灵活性,虽然方法被设置为final后会带来些许的性能提升,但这种置换往往是得不偿失的。因此,只有在明确希望方法不能被覆盖时才将方法设置为final。
在进行方法的重载时,重载方法的参数之间不应该存在继承关系。
public void draw(Object object)
public void draw(String string)
上述示例是不符合规范的,当重载方法的参数之间存在继承关系时,仅通过阅读代码很难确定在运行期执行的方法,导致代码的可读性降低。符合规范的示例如下:
public void draw(int number)
public void draw(String string)
【推荐】尽量采取“面向抽象编程”的思路。
在类的继承体系中,上层的接口和类相对于下层的接口和类来说是抽象的,“面向抽象编程”就是说要在开发时尽量使用上层的接口和类。
在Java中可以使用父类或接口声明变量,使用子类实例化;而父类能适用的场合子类都能适用,因此可采用不同的子类来实例化声明的父类或接口,而无需修改任何其他的代码。如:
List list = new ArrayList();
采用List接口定义变量,具体应用时只调用List接口的方法,这样如果将ArrayList更换为LinkedList时,无需修改其他任何的代码,这样做也是符合“依赖倒转原则”的。
另外,在我们使用的Spring中,可将要实例化的类通过配置注入,这样进一步提高了“面向抽象编程”的应用场合。
子类的属性名称不要和父类的属性名称重名。
子类的属性名称和父类的属性名称重名时,容易照成混乱,导致代码的可读性降低,当具体需要访问子类或父类的属性时往往需要强制转型,破坏了“面向抽象编程”的原则。
禁止子类的属性名称和父类的属性名称重名,可以避免上述的问题。
【推荐】优先使用组合或聚合,而不是继承。
继承是一种“白盒”复用,继承的子类和父类之间是一种强耦合的关系,当父类的细节发生变化时,子类相应的也会发生变化。而组合或聚合是一种“黑盒”复用,组合或聚合的类之间是一种弱耦合的关系,被引用的类的细节对于引用者来说是不可见,被引用的类细节的变化也不会影响到引用者,而且被引用的类可以依赖于抽象而进一步提升扩展性和重用性。
因此,在能使用组合或聚合时,应优先使用组合或聚合。
【推荐】对于公司外的 http / api 开放接口必须使用“错误码” ; 而应用内部推荐异常抛出 ;跨应用间 RPC 调用优先考虑使用 Result 方式,封装 isSuccess()方法 、“错误码”、“错误简短信息”。
【推荐】对于浮点型变量,当对两个变量进行是否相等判断时应避免使用”==”,防止因为精度问题引起执行结果的错误。
6.5 集合处理
hashCode和equals的重写需要遵循规则。
遵循如下规则:
1)只要重写equals,就必须重写hashCode。
说明:由于equals()方法的结果,取决于hashCode()方法的返回值,因此实现了类的equals()方法时,应同时实现hashCode ()方法。
2)因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须重写这两个方法。
3)如果自定义对象做为Map的键,那么必须重写hashCode和equals。
说明:String重写了hashCode和equals方法,所以我们可以非常愉快地使用String对象作为key来使用。
ArrayList的subList结果不可强转成ArrayList类型。
如果强转,会抛出ClassCastException异常,即:
java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。
说明:subList 返回的是 ArrayList 的内部类 SubList,并不是ArrayList ,而是 ArrayList 的一个视图,对SubList子列表的所有操作最终会反映到原列表上。
在subList场景中,高度注意对原集合元素个数的修改,会导致子列表的遍历、增加、删除均会产生ConcurrentModificationException 异常。
不符合规范的示例如下:
List listNum = new ArrayList();
listNum.add(1);
listNum.add(2);
// 通过subList生成一个与listNum一样的列表 listSubNum
List listSubNum = listNum.subList(0, listNum.size());
// 修改listNum
listNum.add(3);
// 正常
System.out.println("listNum'size:" + listNum.size());
// 出现ConcurrentModificationException异常
System.out.println("listSubNum'size:" + listSubNum.size());
使用集合转数组的方法,必须使用集合的toArray(T[] array)。
toArray传入的是类型完全一样的数组,大小就是list.size()。
使用toArray带参方法,入参分配的数组空间不够大时,toArray方法内部将重新分配内存空间,并返回新数组地址;如果数组元素大于实际所需,下标为[ list.size() ]的数组元素将被置为null,其它数组元素保持原值,因此最好将方法入参数组大小定义与集合元素个数一致。
符合规范的示例如下:
List list = new ArrayList(2);
list.add(“guan”);
list.add(“bao”);
String[] array = new String[list.size()];
array = list.toArray(array);
说明:直接使用toArray无参方法存在问题,此方法返回值只能是Object[]类,若强转其它类型数组将出现ClassCastException错误。
使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出UnsupportedOperationException异常。
asList的返回对象是一个Arrays内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。
String[] str = new String[] { “you”, “wu” };
List list = Arrays.asList(str);
第一种情况:list.add(“yangguanbao”); 运行时异常。
第二种情况:str[0] = “gujin”; 那么list.get(0)也会随之修改。
泛型通配符 extends T>来接收返回的数据,此写法的泛型集合不能使用add方法,而 super T>不能使用get方法,做为接口调用赋值时易出错。
扩展到PECS(Producer Extends Consumer Super)原则:
1)频繁往外读取内容的,适合用 extends T>。
2)经常往里插入的,适合用 super T>。
不要在foreach循环里进行元素的remove/add操作。
remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。
符合规范的示例如下:
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (删除元素的条件) {
iterator.remove();
}
}
不符合规范的示例如下:
List list = new ArrayList();
list.add(“1”);
list.add(“2”);
for (String item : list) {
if (“1”.equals(item)) {
list.remove(item);
}
}
8. 【推荐】使用entrySet遍历Map类集合KV,而不是keySet方式进行遍历。
keySet其实是遍历了2次,一次是转为Iterator对象,另一次是从hashMap中取出key所对应的value。而entrySet只是遍历了一次就把key和value都放到了entry中,效率更高。
如果是JDK8,使用Map.foreach方法。 正例:values()返回的是V值集合,是一个list集合对象;keySet()返回的是K值集合,是一个Set集合对象;entrySet()返回的是K-V值组合集合。
9. 【推荐】高度注意Map类集合K/V能不能存储null值的情况。
Map类集合K/V的存储状况如下表:
集合类 Key Value Super 说明
Hashtable 不允许为null 不允许为null Dictionary 线程安全
ConcurrentHashMap 不允许为null 不允许为null AbstractMap 锁分段技术(JDK8:CAS)
TreeMap 不允许为null 允许为null AbstractMap 线程不安全
HashMap 允许为null 允许为null AbstractMap 线程不安全
注意: 由于HashMap的干扰,很多人认为ConcurrentHashMap是可以置入null值,而事实上,存储null值时会抛出NPE异常。
10. 【推荐】合理利用好集合的有序性(sort)和稳定性(order),避免集合的无序性(unsort)和不稳定性(unorder)带来的负面影响。
有序性是指遍历的结果是按某种比较规则依次排列的。稳定性指集合每次遍历的元素次序是一定的。如:ArrayList是order/unsort;HashMap是unorder/unsort;TreeSet是order/sort。
11. 【推荐】利用Set元素唯一的特性,可以快速对一个集合进行去重操作,避免使用List的contains方法进行遍历、对比、去重操作。
6.6 并发处理
获取单例对象需要保证线程安全。
单例的目的是为了保证运行时只有唯一的一个实例,多个线程同时获取该实例,容易出现多例的状况。获取单例时的工具类、单例工厂类、资源驱动类(如:获取DB数据源类)都应注意保证线程安全。
不符合规范的示例如下:
public class MyFactory {
private static MyFactory instance = null;
public static MyFactory getInstance(){
if(instance == null){
instance = new MyFactory();
}
return instance;
}
}
符合规范的示例如下:
public class MyFactory {
private static class instanceHolder {
public static MyFactory instance = new MyFactory();
}
public static MyFactory getInstance() {
return MyFactory.instanceHolder.instance;
}
}
2. 【推荐】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
符合规范的示例如下:
public class TimerTaskThread extends Thread {
public TimerTaskThread() {
super.setName(“TimerTaskThread”);
…
}
}
3. 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
4. 【推荐】线程池不使用Executors去创建,而是通过ThreadPoolExecutor的方式。
这样的处理方式让编写的同学更加明确线程池的运行规则,规避资源耗尽的风险。Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
5. SimpleDateFormat 是线程不安全的类,一般不要定义为static变量。
如果确实需要定义为static,必须加锁,或者使用其它线程安全的类库。
符合规范的做法如下:
注意线程安全,使用DateUtils工具类或joda-time库。
或者:
private static final ThreadLocal df = new ThreadLocal() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat(“yyyy-MM-dd”);
}
};
说明:如果是JDK8的应用,可以使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat,官方给出的解释:simple beautiful strong immutable thread-safe。
if (isFree()) {
System.out.println(“go to travel.”);
return;
}
System.out.println(“stay at home to learn Java.”);
return;
}
4. 【推荐】除常用方法(如getXxx/isXxx)等外,不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。
符合规范的示例如下:
final boolean existed = (file.open(fileName, “w”) != null)
&& (…) || (…);
if (existed) {
…
}
不符合规范的示例如下:
if ((file.open(fileName, “w”) != null) && (…) || (…)) {
…
}
5. 【推荐】应尽量保证循环内的工作最小化;除非循环的规模能得到控制,循环内禁止调用如数据库操作、文件读写、网络访问等高负荷的操作。
1)当某些代码既可以放在循环内,又可以放在循环外时,应尽量保证循环内的工作最小化,这样做可以提高程序的性能。
2)数据库操作、文件读写、网络访问,实现这些功能的代码在执行时都需要付出高昂的代价,在循环的规模不可控的情况下,在循环中执行这些代码,可能导致软件的性能达不到要求或出现执行超时的情况。
6. 【推荐】除非必须,不要手工改变for循环计数器的值。
改变for循环中计数器的值,会让循环变得不易控制,让程序难以理解而容易产生错误。
7. 【推荐】进行参数校验的场景
1) 调用频次低的方法。
2) 执行时间开销很大的方法。
3) 需要极高稳定性和可用性的方法。
4) 对外提供的开放接口,不管是RPC/API/HTTP接口。
5) 敏感权限入口。
8. 【推荐】不需要进行参数校验的场景
1) 极有可能被循环调用的方法。但在方法说明里注明外部参数检查要求。
2) 底层调用频度比较高的方法。如:DAO层与Service层都在同一个应用中,部署在同一台服务器中,所以DAO的参数校验,可以省略。
3) 被声明成private只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参数已经做过检查或者肯定不会有问题,可以不校验参数。
6.8 异常处理