As a part of the “fail fast” approach,公共方法在执行任何逻辑之前应该始终验证输入参数. 这样可以避免以后出现错误,因为错误的原因很难找到.
Since version 1.4, Java has supported the assert keyword for this purpose. For example:
public void setAge(int age) {
assert age >= 0 : "Age must be positive";
…
}
使用断言的一个优点是,可以在运行时禁用它们,以避免在生产系统中产生任何性能影响.
Another approach is to use Google Guava, where you can use Preconditions 类,它提供静态方法. 这样,您可以获得更准确的异常,因为断言只抛出泛型 AssertionError. The following example will throw an IllegalArgumentException:
public void setAge(int age) {
Preconditions.checkArgument(assert age >= 0, "Age must be positive");
…
}
In a similar way, you could use Preconditions.checkState(…) 来验证post条件,这会抛出 IllegalStateException. 有关先决条件方法的完整列表,请查看 Guava documentation.
检查参数是否为空对于前提条件验证特别有用. 检查所有不能使用的参数是一个很好的做法 null. 另一个好的做法是用标记可空参数 javax.annotation.Nullable annotation. This way, your code is more readable. Adding Nullable 注释到可以返回的方法 null values is also a good practice.
避免方法中不必要的注释, 特别是因为随着时间的推移,注释和代码很容易彼此不同步. If you write too many comments, 也许最好将方法拆分为自文档私有方法. 例如,不添加注释 double finalPrice = price + (price * taxRate); // computes taxes,一个更好的方法是创建一个私有方法,它更容易阅读: double finalPrice = computeTax(price, taxRate);
How to Properly Use Utility Classes
实用程序类是不打算实例化的类. Instead, 它们通常为泛型功能提供静态方法, 它们就像OOP中的全局函数. 因此,如果您编写了太多实用程序类,您应该检查您的编码设计. 然而,有时您需要编写一些实用程序类,一个很好的例子是泛型函数. 另一个例子是内部领域特定语言(DSL). When implementing a DSL in Java, 您通常不遵循OOP原则并使用静态方法编写类, 因为我们的目标是使语法更短, rather than reusable components.
However, 如果您想要编写一个实用程序类, 考虑以下建议:
Make the constructor private. 这样,类就不能被实例化.
Make the class final. 即使私有构造函数将限制子类中的实例化, 有人可以创建一个子类并添加更多的静态方法.
根据其方法提供的功能创建内聚的实用程序类. Generic names like MvcUtils or CommonUtils usually lead to a bad design.
不要过度使用反思
反射是一个很酷的Java特性,因为它允许查询类结构和动态调用方法, object instantiation, or setting field values. 该特性在框架和库中广泛使用,用于构建在不同类中工作的泛型代码.
When you add two string in a loop (for, while, do-while), concatenating them using the + 操作符导致内存浪费并增加性能时间. 这是因为每次添加一个新的String对象时都会创建一个新的String对象. 相反,最佳实践是使用 StringBuilder class.
Let’s show it with the examples:
//Bad Practice
String output = "";
for (String s : largeArray) {
output = output + s;
}
如果我们将这个Java片段添加到字节码中,然后生成的字节码反编译回来, we will get the following code:
String output = "";
for( String s : largeArray ) {
output = new StringBuilder().append(output).append(s).toString();
}
return output;
As we can see, Java used an internal StringBuilder, and in the loop, 每次执行两个操作.
因此,一个更好的解决方案是首先使用StringBuilder:
//Best Practice
StringBuilder sb = new StringBuilder();
for (String s : largeArray) {
sb.append(s);
}
String output = sb.toString();
为了证实这一点,我测试了两种场景(I.e.,使用' + '操作符加上StringBuilder). Here are the results:
Java作为一种强类型语言,提供了一个泛型类型系统. 设计您的类如何使用泛型类型可能既困难又乏味, 但它提供了显著的优势. 结果代码更加清晰,因为您不再需要大多数强制转换操作. Also, the code will be safer, 因为更多的类型验证将在编译时完成, thus avoiding ClassCastException errors at runtime.
但是,如前所述,泛型类型可能很复杂. 由于泛型是一个很大的主题,请额外阅读 Java documentation is a good idea.
处理泛型的不变性
让我们开始解释方差的含义. In object-oriented (OO) programming, 将超类视为比子类更泛型的类型是很自然的. 我们可以将超类视为“更大”的类型,因为它们可以包含比特定子类更多的类. For example, java.lang.Number,可以被认为是比 java.lang.Integer, since it can also contain java.lang.Double, java.lang.Float, and so on.
According to Wikipedia, four categories can be used:
* Type A is covariant to B if A >= B. 这意味着,a类型的变量可以保存B的实例.
* Type A is contravariant to B if A <= B. 这几乎与前一点相反.
* A type is bivariant if it is both covariant and contravariant at the same time. This is not supported by Java.
* A type is invariant 如果它不符合前面的任何定义.
To explain this a bit: String[] is a subtype of the Object[]. 这种方法的问题是它可能导致运行时异常. 例如,执行下面这行代码将生成一个 java.lang.ArrayStoreException exception:
objectArray[0] = 123;
To avoid this, 默认情况下,泛型类型不是协变的, this way, 编译器可以防止这类错误. 下面的代码将在第二行产生编译时错误:
List stringList = new ArrayList<>();
List
但是,如果我们需要使用更泛型或更具体的类型参数,会发生什么呢? Here is where extends and super keywords come into play.
With extends 可以指定类型参数可以是扩展指定类型的任何类. For example:
List integerList = new ArrayList<>();
List extends Number> numberList = integerList;
This code block is valid. 这会有和数组一样的问题吗? 不会,因为Java编译器可以在编译时检查类型参数. So, while integerList.add(Integer.valueOf(123)); is valid, numberList.add(Integer.valueOf(123)); will produce a compilation error. 但是,读取一个元素并从元素中请求一个方法 Number class is perfectly valid:
numberList.get(0).intValue();
This is valid because, 即使编译器不确切知道哪个类是参数化的, it knows that it must be a Number subclass. 因此,可以调用这个类中找到的任何方法.
Type erasure enables the use of raw types. 在声明变量时使用原始类型. 使用这种声明,您可以省略泛型类型声明. 例如,编译以下代码(带有警告):
List integerList = new ArrayList<>();
List rawList = integerList;
Note that rawList 缺少泛型类型定义,因为它是原始类型. 不鼓励使用原始类型,因为它可能导致类似于处理数组时可能发生的问题. 下面的代码行是有效的,但是会产生一个 java.lang.ClassCastException exception.
rawList.add("Hi");
大多数情况下,您不会注意到类型擦除,但有时您需要注意它. For example, some frameworks, like Guice, 强制您在运行时使用子类来保持泛型类型信息, to do generic-type bindings. 在Guice中,如果需要使用泛型类型指定绑定,则需要扩展 TypeLiteral class:
这样做的原因是类型擦除. As explained before, Java从方法参数类型中删除泛型信息, but it is kept when subclassing, 因此通过反思是可以得到的.
Note that .NET设计人员在这样的平台上实现泛型时采用了不同的方法. There is no type erasure there. For example, Ninject (a .. NET依赖注入器)允许这样设置绑定:
this.Bind().To();
这是因为关于类型参数的信息 IWeapon 方法调用时在运行时维护的 Bind method.
Avoid Object Mutability
在Java中,对象可以是可变的,也可以是不可变的. 可变意味着对象状态和变量值可以在其生命周期中更改, while immutability means the opposite; the state can’t be changed once the object is created. It is important to remember object mutability could be a problem; it might become hard to track where and when the object state changed since many variables can point to the same instance. 因此,您应该尽可能避免可变性.
一个好的做法(即使不能保证不变性)是生成所有字段 final:
public class Person {
private final int id;
private final String name;
public Person(int id, String name) {
this.id = id;
this.name = Preconditions.checkNotNull(name);
}
…
}
这样,Java编译器强制您在构造函数中为每个函数设置一个值 final field. 即使您有多个构造函数,编译器也会跟踪所有路径以确保这一点 final fields will be initialized.
Dependency injectors, like Spring or Guice,允许实现不同的注入模型,使用 setter, field, constructor. 为了保持对象的不变性,最好使用基于构造函数的依赖注入器.
学习使用实用程序类来正确实现“equals()”和“hashCode()”方法
如果要使用Java提供的不同集合类, 您应该熟悉实现的需求 equals() and hashCode() methods. 或者,您可以在 Object class javadoc.
实现这些方法可能会很繁琐. 大多数ide都提供代码生成功能,但生成的方法可能过于冗长. Typically, when you change the class structure, 您需要重新生成这些方法, 并且您将丢失手动进行的任何更改. 此外,它可能容易出错,因为您需要再次选择所有涉及的字段.
Here’s an example of Eclipse-generated methods:
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
Result = prime * Result + ((key == null) ? 0 : key.hashCode());
结果=质数*结果+ ((value == null) ? 0 : value.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MyClass other = (MyClass) obj;
if (key == null) {
if (other.key != null)
return false;
} else if (!key.equals(other.key))
return false;
if (value == null) {
if (other.value != null)
return false;
} else if (!value.equals(other.value))
return false;
return true;
}
Luckily, Java 7 and Google Guava 提供实用程序类以简化此类任务. Java 7 provides two static methods: Objects.hash() for building hash codes and Objects.equals() for comparing objects for equality.
例如,你可以这样做:
@Override
public int hashCode() {
return Objects.hash(key, value);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MyClass that = (MyClass) o;
return Objects.equals(key, that.key)
&& Objects.equals(value, that.value);
}