本文整理一些Java开发中常用的惯用法。

Object方法:equals和hashCode

equals和hashCode都是Object对象中的非final方法,它们设计的目的就是被用来覆盖(override)的。

equals的作用是用来判断两个对象是否相等,在Object里面的定义是:

public boolean equals(Object obj) {
   return (this == obj);
}

hashCode用于返回对象的hash值,主要用于查找的快捷性,因为hashCode也是在Object对象中就有的,所以所有Java对象都有hashCode,在HashTable和HashMap这一类的散列结构中,都是通过hashCode来查找在散列表中的位置的。

class Person {
  String name;
  int birthYear;
  byte[] raw;
  
  public boolean equals(Object obj) {
    if (!obj instanceof Person)
      return false;
    
    Person other = (Person)obj;
    return name.equals(other.name)
        && birthYear == other.birthYear
        && Arrays.equals(raw, other.raw);
  }
  
  public int hashCode() {
      return a.hashCode() + b.hashCode() + c + Arrays.hashCode(d);
  }
}

当两个对象 x 和 y ,满足 x.equals(y) == true, 则必须要满足 x.hashCode() == y.hashCode()。

关于Comparable和Comparator

Comparable可以认为是一个内比较器,实现了Comparable接口的类有一个特点,就是这些 类是可以和自己比较的,至于具体和另一个实现了Comparable接口的类如何比较,则依赖compareTo方法的实现,compareTo方法也被称为自然比较方法。

class Person implements Comparable<Person> {
  String firstName;
  String lastName;
  int birthdate;
  
  // Compare by firstName, break ties by lastName,
  // finally break ties by birthdate
  public int compareTo(Person other) {
    if (firstName.compareTo(other.firstName) != 0)
      return firstName.compareTo(other.firstName);
    else if (lastName.compareTo(other.lastName) != 0)
      return lastName.compareTo(other.lastName);
    else if (birthdate < other.birthdate)
      return -1;
    else if (birthdate > other.birthdate)
      return 1;
    else
      return 0;
  }
}

Comparator可以认为是是一个外比较器,有两种情况可以使用实现Comparator接口的方式:

  1. 一个对象不支持自己和自己比较(没有实现Comparable接口),但是又想对两个对象进行比较。
  2. 一个对象实现了Comparable接口,但是开发者认为compareTo方法中的比较方式并不是自己想要的那种比较方式。

Comparator接口里面有一个compare方法,方法有两个参数T o1和T o2,是泛型的表示方式,分别表示待比较的两个对象,方法返回值和Comparable接口一样是int,有三种情况:

  1. o1大于o2,返回正整数
  2. o1等于o2,返回0
  3. o1小于o3,返回负整数
public class PersonComparator implements Comparator<Person> {
    @Override
    public int compare(Person g1, Person g2) {
        return g1.getBirthdate() - g2.getBirthdate();
    }    
}

实现克隆方法clone

Java默认对象赋值都是直接拷贝引用地址,如果需要创建一个对象和原始对象无关,互不影响,这时就需要深拷贝。

重写clone()方法,一般会先调用super.clone()进行浅复制,然后再复制那些易变对象,从而达到深复制的效果。除了基本类型(int,double等)和不可变类型(String等),其他类型的字段都需要做深拷贝。

class Values implements Cloneable {
  String abc;
  double foo;
  int[] bars;
  Date hired;
  
  public Values clone() {
    try {
      Values result = (Values)super.clone();
      result.bars = result.bars.clone();
      result.hired = result.hired.clone();
      return result;
    } catch (CloneNotSupportedException e) {  // Impossible
      throw new AssertionError(e);
    }
  }
}

使用StringBuilder或StringBuffer

// join(["a", "b", "c"]) -> "a and b and c"
String join(List<String> strs) {
  StringBuilder sb = new StringBuilder();
  boolean first = true;
  for (String s : strs) {
    if (first) first = false;
    else sb.append(" and ");
    sb.append(s);
  }
  return sb.toString();
}

反转字符串

String reverse(String s) {
  return new StringBuilder(s).reverse().toString();
}

使用try-finally

如果try语句块里面的语句抛出异常,那么程序的运行就会跳到finally语句块里执行尽可能多的语句,然后跳出这个方法(除非这个方法还有另一个外围的finally语句块)。

// IO
void writeStuff() throws IOException {
  OutputStream out = new FileOutputStream(...);
  try {
    out.write(...);
  } finally {
    out.close();
  }
}

// 锁
void doWithLock(Lock lock) {
  lock.acquire();
  try {
    ...
  } finally {
    lock.release();
  }
}

流数据读写

1、从输入流里读取字节数据

read()方法要么返回下一次从流里读取的字节数(0到255,包括0和255),要么在达到流的末端时返回-1。

InputStream in = (...);
try {
  while (true) {
    int b = in.read();
    if (b == -1)
      break;
    (... process b ...)
  }
} finally {
  in.close();
}

2、从输入流里读取块数据

read()方法不一定会填满整个buf,所以你必须在处理逻辑中考虑返回的长度。

InputStream in = (...);
try {
  byte[] buf = new byte[100];
  while (true) {
    int n = in.read(buf);
    if (n == -1)
      break;
    (... process buf with offset=0 and length=n ...)
  }
} finally {
  in.close();
}

3、从文件里读取文本

BufferedReader in = new BufferedReader(
    new InputStreamReader(new FileInputStream(...), "UTF-8"));
try {
  while (true) {
    String line = in.readLine();
    if (line == null)
      break;
    (... process line ...)
  }
} finally {
  in.close();
}

4、向文件里写文本

PrintWriter out = new PrintWriter(
    new OutputStreamWriter(new FileOutputStream(...), "UTF-8"));
try {
  out.print("Hello ");
  out.print(42);
  out.println(" world!");
} finally {
  out.close();
}

数组操作

1、填充数组元素

byte[] a = new byte[3];
Arrays.fill(a, (byte)123);

2、复制一个范围内的数组元素

// Copy 8 elements from array 'a' starting at offset 3
// to array 'b' starting at offset 6,
// assuming 'a' and 'b' are distinct arrays
byte[] a = (...);
byte[] b = (...);

System.arraycopy(a, 3, b, 6, 8);

3、调整数组大小

a = Arrays.copyOf(a, newLen);

可变参数列表转范型数组

public class Test {
   public static void main(String[] args) {
       Integer[] a = of(1, 2, 3);
       String[] b = of("hello", "world");
   }

   private static <T> T[] of(T... values) {
       return values;
   }
}

集合操作使用stream和函数式编程

Java8加了stream和lambda机制,表达能力得到大大提升

public static List<String> getModelsAfter2000UsingPipeline(
    List<Car> cars) {
    return 
      cars.stream()
          .filter(car > car.getYear() > 2000)
          .sorted(Comparator.comparing(Car::getYear))
          .map(Car::getModel)
          .collect(toList());
  }

参考