java 常用习惯用法总结

项目github地址:bitcarmanlee easy-algorithm-interview-and-practice
欢迎大家star,留言,一起学习进步

注:本文是根据网络上的文章受启发,自己再写代码总结而成。因为无法找到原文的原始出处,所以没法给出原文链接。文章也会引用部分网络素材内容,如果原作者看到请与我联系。

0.前言

本博主的java水平与专业java开发同学相比水平不可同日而语,所以平时有空的话会多写写java代码。正好在网上看到一篇关于java习惯用法的总结,觉得还不错,于是按照文章里的大致架构,自己重新实现了一把里面的相关代码,相当于自己做个小小的总结。

1.基本方法实现

equals()
hashcode()
compareTo()
clone()

1.1 equals()实例与hashCode()实例

import java.util.Arrays;

public class Person {
	String name;
	int birthday;
	byte[] raw;
	
	public boolean equals(Object obj) {
		// 原文中的代码有误,需要将!符号后面的表达式扩起来
		if (!(obj instanceof Person)) { 
			return false;
		}
		Person other = (Person) obj;
		return name.equals(other.name)
			&& birthday == other.birthday
			&& Arrays.equals(raw, other.raw);
	}
	
	public int hashCode() {
		return name.hashCode() + birthday + Arrays.hashCode(raw);
	}
	
	public static void main(String[] args) {
		Person leilei = new Person();
		leilei.name = "leilei";
		leilei.birthday = 19991010;
		
		Person lulu = new Person();
		lulu.name = "leilei";
		lulu.birthday = 19991010;
		
		System.out.println(leilei.equals(lulu));
	}

}

equals()方法需要注意的几个点
1.参数必须是Object类型,不能是外围类。jdk源码中的equals()方法,传入的参数就是Object类型。
2.foo.equals(null) 必须返回false,不能抛NullPointerException。(注意,null instanceof 任意类 总是返回false,因此上面的代码可以运行)
3.基本类型比较使用 ==,基本类型数组域的比较使用Arrays.equals()。
4.覆盖equals()时,记得要相应地覆盖 hashCode(),与 equals() 保持一致

hashCode()方法注意的几个点
1.当x和y两个对象具有x.equals(y) == true ,你必须要确保x.hashCode() == y.hashCode()。
2.根据逆反命题,如果x.hashCode() != y.hashCode(),那么x.equals(y) == false 必定成立。
3.你不需要保证,当x.equals(y) == false时,x.hashCode() != y.hashCode()。但是,如果你可以尽可能地使它成立的话,这会提高哈希表的性能。
4.hashCode()最简单的合法实现就是简单地return 0;虽然这个实现是正确的,但是这会导致HashMap这些数据结构运行得很慢。

1.2 compareTo()方法实现

package leilei.bit.edu.common;

import java.util.TreeSet;

public class PersonA implements Comparable<PersonA>{
	String firstName;
	String lastName;
	int birthday;
	
	public int compareTo(PersonA 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(birthday < other.birthday) {
			return -1;
		} else if(birthday > other.birthday) {
			return 1;
		} else {
			return 0;
		}
	}
	
	@Override
	public String toString() {
		return "firstName: " + firstName + ",lastName: " + lastName;
	}
	
	public static void main(String[] args) {
		PersonA p1 = new PersonA();
		p1.firstName = "li";
		p1.lastName = "lei";
		
		PersonA p2 = new PersonA();
		p2.firstName = "han";
		p2.lastName = "meimei";
		
		PersonA p3 = new PersonA();
		p3.firstName = "xiao";
		p3.lastName = "ming";
		
		TreeSet tree = new TreeSet();
		tree.add(p1);
		tree.add(p2);
		tree.add(p3);
		
		for(Object obj:tree) {
			System.out.println(obj);
		}
	}
}

输出:

firstName: han,lastName: meimei
firstName: li,lastName: lei
firstName: xiao,lastName: ming

TreeSet在java内部是一种排序的数据结构。根据程序的输出可以看出,person对象在tree中是根据firstName排序的。

1.总是实现泛型版本 Comparable 而不是实现原始类型 Comparable 。因为这样可以节省代码量和减少不必要的麻烦。
2.只关心返回结果的正负号(负/零/正),它们的大小不重要。
3.Comparator.compare()的实现与这个类似。

1.3 clone()方法

实际中我很少使用clone方法,怕自己的理解有偏差,直接将原文的clone部分摘过来

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);
    }
  }
}

1.使用 super.clone() 让Object类负责创建新的对象。
2.基本类型域都已经被正确地复制了。同样,我们不需要去克隆String和BigInteger等不可变类型。
3.手动对所有的非基本类型域(对象和数组)进行深度复制(deep copy)。
4.实现了Cloneable的类,clone()方法永远不要抛CloneNotSupportedException。因此,需要捕获这个异常并忽略它,或者使用不受检异常(unchecked exception)包装它。
5.不使用Object.clone()方法而是手动地实现clone()方法是可以的也是合法的。

2.应用类

2.1 StringBuilder 与 StringBuffer

这两个类在实际开发中用得很多,举一个很简单的小例子。

	@Test
	public void Str_Build() {
		StringBuilder sb = new StringBuilder();
		sb.append("I ");
		sb.append("love");
		sb.append(" coding");
		System.out.println(sb.toString());
	}

让代码run起来

I love coding

注意点:
1.不要像这样使用重复的字符串连接:s += item ,因为它的时间效率是O(n^2)。
2.使用StringBuilder或者StringBuffer时,可以使用append()方法添加文本和使用toString()方法去获取连接起来的整个文本。示例代码中就是这么做的。
3.优先使用StringBuilder,因为它更快。StringBuffer的所有方法都是同步的,而你通常不需要同步的方法。
4.StringBuilder的缺陷之一,就是开发过程中,经常用sb作为new出来的对象名称。。。

2.2 生成一个随机整数

	@Test
	public void int_random() {
		Random rand = new Random();
		int a = rand.nextInt(10) + 1;
		System.out.println("random num is: " + a);
	}

让代码run起来

random num is: 10

2.3 使用Iterator.remove()

	@Test
	public void collections_remove() {
		List<Integer> list = new ArrayList<Integer>();
		for(int i=1; i<=10; i++) {
			list.add(i);
		}
		
		Iterator<Integer> it = list.iterator();
		while(it.hasNext()) {
			int num = it.next();
			
			if(num%2 == 0) {
				System.out.println(num + "===" + num);
				it.remove();
			}
		}
		
		for(int each:list) {
			System.out.print(each + " ");
		}
	}

让代码run起来

2===2
4===4
6===6
8===8
10===10
1 3 5 7 9 

remove()方法作用在next()方法最近返回的条目上。每个条目只能使用一次remove()方法。

2.4 反转字符串

c++的程序猿里的一道经典面试题就是反转字符串。对于java或者python等更高级的语言来说,反转字符串都是一句话搞定的事情。

	@Test
	public void reverse_str() {
		String raw_str = "some";
		String rev_str = new StringBuilder(raw_str).reverse().toString();
		System.out.println("rev_str is: " + rev_str);
	}

让代码run起来

rev_str is: emos

2.5 Thread/Runnable

//实现Runnable的方式
void startAThread0() {
  new Thread(new MyRunnable()).start();
}
 
class MyRunnable implements Runnable {
  public void run() {
    ...
  }
}

//继承Thread的方式
void startAThread1() {
  new MyThread().start();
}
 
class MyThread extends Thread {
  public void run() {
    ...
  }
}

匿名继承Thread的方式
void startAThread2() {
  new Thread() {
    public void run() {
      ...
    }
  }.start();
}

不要直接调用run()方法。总是调用Thread.start()方法,这个方法会创建一条新的线程并使新建的线程调用run()。

2.6 try-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.如果try之前的语句运行失败并且抛出异常,那么finally语句块就不会执行。但无论怎样,在这个例子里不用担心资源的释放。
2.如果try语句块里面的语句抛出异常,那么程序的运行就会跳到finally语句块里执行尽可能多的语句,然后跳出这个方法(除非这个方法还有另一个外围的finally语句块)。

3.输入输出

3.1 从输入流中读取字节数据

	@Test
	public void test1() throws Exception {
		InputStream in = new FileInputStream(new File("xxx"));
		try {
			while(true) {
				int n = in.read();
				if(n == -1) {
					break;
				} else {
					System.out.println(n);
				}
			}
			
		} finally {
			in.close();
		}
	}

输出:

112
97
99
107
97
103
101
...

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

3.2 从输入流中读取块数据

	@Test
	public void test2() throws Exception {
		InputStream in = new FileInputStream(new File("xxx"));
		try {
			byte[] buf = new byte[100];
			while(true) {
				int n = in.read(buf);
				if(n == -1) {
					break;
				} else {
					System.out.println(n);
				}
			}
			
		} finally {
			in.close();
		}
	}

输出:

100
100
100
100
100
100
81

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

3.3 读取文本文件

	@Test
	public void test3() throws Exception {
		String filename = "xxx";
		BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(new File(filename)),"utf-8"));
		try {
			while (true) {
				String line = br.readLine();
				if (line == null) {
					break;
				} else {
					System.out.println(line);
				}
			}
		} finally {
			br.close();
		}
	}

1.BufferedReader对象的创建显得很冗长。这是因为Java把字节和字符当成两个不同的概念来看待
2.你可以使用任何类型的InputStream来代替FileInputStream,比如socket
3.当达到流的末端时,BufferedReader.readLine()会返回null。
4.要一次读取一个字符,使用Reader.read()方法。
5.你可以使用其他的字符编码而不使用UTF-8,但最好不要这样做。

3.4.向文本写文件

	@Test
	public void test4() throws Exception {
		String filename = "xxx";
		PrintWriter out = new PrintWriter(
				new OutputStreamWriter(new FileOutputStream(new File(filename)),"utf-8"));
		try {
			out.println("hello");
			out.println(42);
			out.println("world");
		} finally {
			out.close();
		}
	}

1.Printwriter对象的创建显得很冗长。这是因为Java把字节和字符当成两个不同的概念来看待
2.就像System.out,你可以使用print()和println()打印多种类型的值。
3.你可以使用其他的字符编码而不使用UTF-8,但最好不要这样做。

4 预防性(Defensive Checking)检测

4.1 预防性检测数值

package leilei.bit.edu.common;

public class Checking {
	
	public static int factorial(int n) {
		if (n < 0) {
			throw new IllegalArgumentException("Undefined");
		} else if(n > 13) {
			throw new ArithmeticException("Reuslt overflow");
		} else if(n == 0) {
			return 1;
		} else {
			return factorial(n-1) * n;
		}
	}
	
	public static void main(String[] args) {
		int n = 5;
		int ret = factorial(n);
		System.out.println("ret is: " + ret);
	}
}

1.不要认为输入的数值都是正数、足够小的数等等。要显式地检测这些条件。
2.一个设计良好的函数应该对所有可能性的输入值都能够正确地执行。要确保所有的情况都考虑到了并且不会产生错误的输出(比如溢出)。

4.2 预防性检测对象

public int findIndex(List<String> list, String target) {
  if (list == null || target == null)
    throw new NullPointerException();
  ...
}

1.不要认为对象参数不会为空(null)。要显式地检测这个条件。

4.3预防性检测数组索引

	public static void outOfBound(int[] b, int index) {
		if(b==null) {
			throw new NullPointerException();
		}
		if(index < 0 || index >= b.length) {
			throw new IndexOutOfBoundsException();
		}
		...
	}

不要认为所以给的数组索引不会越界。要显式地检测它。

4.4 预防性检测数组区间

	public static void errorRange(int[] b, int off, int len) {
		if(b == null) {
			throw new NullPointerException();
		}
		if (off < 0 || off > b.length || len < 0 || b.length - off < len) {
			throw new IndexOutOfBoundsException();
		}
		...
	}

不要认为所给的数组区间(比如,从off开始,读取len个元素)是不会越界。要显式地检测它。

5.数组

5.1 填充数组元素

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

5.3 调整数组大小

具体代码如下:

package leilei.bit.edu.bigNum;

import java.util.Arrays;

public class Array_Test {
	
	public static void printArray(int[] a) {
		for(int i=0; i<a.length; i++) {
			System.out.print(a[i] + " ");
		}
	}
	
	public static void fill_test() {
		int[] a = new int[5];
		Arrays.fill(a,0,5,1);
		printArray(a);
	}
	
	public static void copy_test() {
		int[] a = {1,2,3,4,5};
		int[] b = new int[5];
		System.arraycopy(a, 0, b, 0, a.length);
		printArray(b);
	}
	
	public static void copyOf_test() {
		int[] a = {1,2,3,4,5};
		a = Arrays.copyOf(a,10);
		printArray(a);
	}
	
	public static void main(String[] args) {
		fill_test();
		System.out.println();
		copy_test();
		System.out.println();
		copyOf_test();
	}
}

代码运行结果如下:

1 1 1 1 1 
1 2 3 4 5 
1 2 3 4 5 0 0 0 0 0 

6.包装

以下代码演示了将四个字节包装成一个int,以及将一个int分解成四个字节。

package leilei.bit.edu.bigNum;

public class Pack {
	
	public static void pack() {
		byte[] a = {1,2,3,4};
		int result = (a[0] & 0xFF) << 24
					|(a[1] & 0xFF) << 16
					|(a[2] & 0xFF) << 8
					|(a[3] & 0xFF) << 0;
		System.out.println("result is: " + result);
 	}
	
	public static void unpack() {
		int x = 16909060;
		byte[] result = {
				(byte)(x >>> 24),
				(byte)(x >>> 16),
				(byte)(x >>> 8),
				(byte)(x >>> 0)
		};
		System.out.print("the unpack result is: ");
		for(int i=0; i<result.length; i++) {
			System.out.print(result[i] + " ");
		}
	}
	
	public static void main(String[] args) {
		pack();
		unpack();
	}

}

让代码run起来:

result is: 16909060
the unpack result is: 1 2 3 4 
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值