作者 | Manoj Debnath
译者 | IT外文选刊(公众号回复“IT666”领取技术学习全网最全资料,更多资源持续更新中!)
1. 变量作用域、可读性和Lambda表达式
「在Java中,每个声明的变量都有一个作用域。」这意味着,变量只能在作用域内可见和使用。如果是局部变量,那么它从声明的时候开始,到它声明的方法结束的地方或代码块结束的地方都是可见的。「一个好的做法是尽可能接近变量使用的地方声明变量。」这不仅可以提高代码的可读性,而且方便调试。
try(
Connection con = DriverManager.getConnection(DATABASE_URL,
USERNAME,PASSWORD);
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(SQL_QUERY)
) {
//...
} catch(SQLException e) {
e.printStackTrace();
}
上面这段代码,可以发现局部变量的范围是被限制在被声明的块中。只要块结束,变量就会被忽略。同时,代码变得更加直观、可读和干净。
然而,「从Java 8开始,我们可以利用lambda表达式来使代码更加简洁。」为了体现这个,请注意我们是如何在单行的lambda表达式中实现多个操作的。
Integer[] nums={90,71,26,34,42,35,66,57,88,89};
输出原始数字:
final List l1 = Arrays.asList(nums);
System.out.printf("Original :%s%n",l1);
现在,我们使用lambda表达式来打印出按升序排序后的数字。
final List l2 = Arrays.stream(nums)
.sorted().collect(Collectors.toList());
System.out.printf("After sorting: %s%n",l2);
我们可以应用更多的方法,比如说按升序排序后,只打印那些大于50的数字。
final List l3 = Arrays.stream(nums)
.filter(num -> num > 50).collect(Collectors.toList());
System.out.printf("Sorting only of numbers > 50
: %s%n", l3);
2. 类的成员变量
在Java中,方法要么属于一个类,要么属于一个接口。因此,局部变量有可能会被程序员无意中赋予与类成员变量相同的名称。不过,Java编译器能够从作用域中挑选出正确的。而且,现代的IDE已经足够成熟,能够识别冲突。「无论如何,程序员本身应该有足够的责任心,避免这种冲突,因为结果可能是相当灾难性的。」下面的代码展现了如果我们不处理冲突的变量名,会得到一个非常不同的结果。
public class TestClass {
private double commission = 5.5d;
public double increaseCommission(final double newComm){
double commission = newComm;
commission += commission;
return commission;
}
public double getCommission(){
return commission;
}
public static void {
TestClass m = new TestClass();
System.out.println(m.increaseCommission(3.3));
System.out.println(m.getCommission());
}
}
另一种避免类的字段名与本地变量冲突的方法是使用this关键字。例如this.commission将永远只引用类的字段名。但是,「经验表明,在构造函数以外的方法中,最好完全使用不同的名称来避免字段名冲突。」
3. 将函数的参数转为局部变量
在Java中,一个变量一旦声明了就可以重复使用。因此,方法的入参中声明的非final的局部变量也可以赋予不同的值重用。但是,最好别这样做,因为变量作为入参传入的应该是它原来的值,也就是方法应该使用的原始值。如果我们改变了这个值,我们就会完全失去传入方法的原始值。相反,我们必须要做的是将值复制到另一个变量中,然后进行必要的处理。我们也可以通过使用下面的final关键字来完全限制和使参数变量成为一个常量。
public double calculate(final double newVal){
double tmp = newVal;
// ...
return tmp;
}
4. 装箱(boxing)和拆箱(unboxing)
在Java中,装箱和拆箱是同一种技术的相反面,被称作基类型和对应的封装类之间的转换。封装类是原始类型的 "类 "版本,比如int到Integger、float到Float、double到Double、char到Character、byte到Byte、boolean到Boolean等等。装箱是将int转换为Integger,拆箱是将Integger转换回int。「编译器经常在幕后执行转换,这就是所谓的自动装箱(autoboxing)。」但是,有时这种自动装箱(autoboxing)可能会产生一个意想不到的结果,我们必须注意这一点。
public static void update(final double newVal){
// ...
}
final Double val = null;
update (val);
Java在编译过程中有问题,但是当我们执行这段代码时,会抛出NullPointerException。这是个例子,说明「自动装箱并不总是能产生我们想要的结果。我们应该意识到并思考一下,在当前的上下文中,自动装箱是否足够。」
5. 接口
因为接口没有绑定到任何方法(默认方法除外),「所以在不需要具体类的地方,我们必须尽可能使用接口。」接口就像协议一样,有充分的自由度和灵活性来布置协议。具体类可以利用这种灵活性来丰富和补充其功能。特别是当实现涉及到外部系统或服务的时候,这一点就显得尤为明显,而且经常可以看到。
package com.mano.examples;
public interface Payable {
public double calcAmount();
}
package com.mano.examples;
public class Invoice implements Payable {
private String itemNumber;
private String itemName;
private int quantity;
private double unitPrice;
public Invoice(String itemName, int quantity,
double unitPrice) {
// ...
}
public int getQuantity() {
// ...
return quantity;
}
public double getUntPrice() {
// ...
return unitPrice;
}
@Override
public double calcAmount() {
return getQuantity()*getUntPrice();
}
}
6. 字符串
在Java中,没有任何其他类型像String一样得到了扩展。Java的字符串是用UTF-16格式表示的,是不可更改的对象。「这意味着,每当我们执行一个像串联这样的操作,需要修改原字符串的时候,就会创建一个新的字符串对象。」这种中间字符串对象,只是为了执行需要的操作而创建的中间字符串对象,是一种浪费和低效的做法。这是因为中间对象的创建是外在的;它涉及到垃圾收集,虽然我们可以避免。有两个配套的字符串类,叫做StringBuffer和StringBuilder,可以恰如其分地方便我们可能需要的那种字符串操作。这两个类就是为此而构建的。「StringBuffer和StringBuilder之间唯一的区别是前者是线程安全的。我们可以在需要字符串操作时广泛的使用这两个类,而不是使用不可变的String实例。」
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append( "Hello" );
stringBuilder.append( 108 );
stringBuilder.deleteCharAt( 0 );
stringBuilder.insert( 0, "h" );
stringBuilder.replace( stringBuilder.length() - 3,
stringBuilder.length(), "." );
System.out.println(stringBuilder);
7. 命名规则
Java命名规则,旨在使Java代码在整个Java项目和库中看起来统一。它们并不是严格的规则,而是作为一种良好的编程实践要遵守的准则。因此,无论是出于急于求成还是叛逆,违反代码统一性都不是一个好的做法。
命名的规则相当简单:
- 包名都用小写字母:javax.sql、org.junit、java.lang.
- 类、enum、接口和注解名用大写字母开头:Thread、Runnable、@Override。
- 方法和字段名用驼峰式命名:deleteCharAt、add、isNull。
- 常量用大写字母且用下划线隔开:SIZE, MIN_VALUE。
- 局部变量用驼峰式:employeeName、birthDate、email。
这些约定已经成为Java的一部分,以至于几乎每个程序员都虔诚地遵循这些约定。因此,任何改变这一点看起来都是不合常理和错误的。遵循这些约定的一个明显的好处是,代码的风格与库或框架代码一致。它还可以帮助其他程序员利用代码的整体可读性快速看懂代码。
8. 标准库
Java以其丰富的库而闻名。然而,并不是说每一个库都设计得很完美,但是大部分的库都是最优的。Java每隔一段时间就会发布新的库,并积极改进现有的库。「因此,人们应该尽可能多地使用库中的类、方法、接口、枚举和注释。这样可以大大减少生产时间。」而且,所包含的功能都是经过了很好的测试的。「最好是在需要的时候就使用库中的功能,而不是重新发明轮子。」
由于Java平台的更新是非常频繁的,所以我们必须时刻关注第三方库或框架中已经存在于Java标准库中的功能。「经验是先尽可能使用标准库中已有功能,其次再从外部源获取支持。」
9. 不变性
不变性的概念是非常重要的。「我们必须确定我们打算设计的类是否可以被设计成不可变的,因为不可变性保证了它几乎可以在任何地方都可以使用,而不会因为并发修改而带来任何麻烦。」不幸的是,不是所有的类都可以被设计成不可变的。但是,确保我们必须尽可能地做到这一点。这样一来,程序员生活会变的简单很多,以后你会回来感谢我的。
10. 测试
测试驱动开发(TDD)是Java社区中高质量代码的象征。测试是现代Java开发的一部分,Java标准库有Junit框架来协助测试。所以,一个初出茅庐的Java程序员不应该回避用代码编写测试用例。「尽量保持测试简单、简短,一次只专注于一件事。」生产环境中的测试用例可能有数百个。编写测试用例的一个明显的好处是,它们可以立即测试正在开发的功能。
结论
除了这十条之外,还有很多技巧。在这里,我们试图挑出一些不怎么被谈及的。请记住,指南都是最佳的实践。在很少的情况下,他们可以被忽略。但是,要想成为社区的一部分,遵循统一的指南是很有用的。你们可以按照这个思路来思考,欢迎补充。
END
外文链接:
https://www.developer.com/java/data/top-10-java-coding-guidelines.html
版权声明:
本译文仅用于学习、研究和交流目的,欢迎非商业转载。转载请注明出处、译者和IT外文选刊的完整链接。