十大Java编程指南

十大Java编程指南_第1张图片

作者 | 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外文选刊的完整链接。

你可能感兴趣的:(java,编程,编程语言,编程习惯,编程风格)