java开发易错点

Top1. 数组转换为数组列表

将数组转换为数组列表,开发者经常会这样做:

[java]  view plain copy
  1. List<String> list = Arrays.asList(arr);  

Arrays.asList()将返回一个数组内部是私有静态类的ArrayList,这不是java.util.ArrayList类,java.util.Arrays.ArrayList类有set()、 get()、 contains()方法,但是没有任何加元素的方法,,因此它的大小是固定的。java.util.ArrayList类中有加元素的方法等等,你应该这么做来创建一个真正的数组:

[java]  view plain copy
  1. ArrayList<String> arrayList = new ArrayList<String>(Arrays.asList(arr));  

ArrayList的构造函数能够接受一个集合类型,这也是java.util.Arrays.ArrayList的超级类型。

		String[] arr=new String[]{"aa","bb"};
		List<String> lists=Arrays.asList(arr);
		for(String str:lists){
			System.err.println(str);
		}
		
		ArrayList<String> arrayList = new ArrayList<String>(Arrays.asList(arr));
		arrayList.add("abc");

Top2. 检查一个数组包含一个值

开发者经常这么做:

[java]  view plain copy
  1. Set<String> set = new HashSet<String>(Arrays.asList(arr));  
  2. return set.contains(targetValue);  

代码可以工作,但是没有必要首先转换列表到Set,转换一个列表到一个Set需要额外的时间。因此你可以把它简化为:

[java]  view plain copy
  1. Arrays.asList(arr).contains(targetValue);  

[java]  view plain copy
  1. for(String s: arr){  
  2.     if(s.equals(targetValue))  
  3.         return true;  
  4. }  
  5. return false;  

第一个比第二个更具可读性

Top3. 在一个循环中从一个列表里删除一个元素

考虑下面删除元素的代码在迭代中的结果:

[java]  view plain copy
  1. ArrayList<String> list = new ArrayList<String>(Arrays.asList("a""b""c""d"));  
  2. for (int i = 0; i < list.size(); i++) {  
  3.     list.remove(i);  
  4. }  
  5. System.out.println(list);  

输出是:

[java]  view plain copy
  1. [b, d]  

该方法有一个严重的问题,当一个元素被删除时,列表收缩的大小以及指针改变了。所以想要在循环内利用指针删除多个元素是无法正常进行的。

这种情况下使用迭代器才是正确的方法,foreach循环在Java中的工作像是一个迭代器,但实际上并不是,考虑下面的代码:

[java]  view plain copy
  1. ArrayList<String> list = new ArrayList<String>(Arrays.asList("a""b""c""d"));  
  2.    
  3. for (String s : list) {  
  4.     if (s.equals("a"))  
  5.         list.remove(s);  
  6. }  

它会报出ConcurrentModificationException异常。

相反下面这个就可以正常工作。

[java]  view plain copy
  1. ArrayList<String> list = new ArrayList<String>(Arrays.asList("a""b""c""d"));  
  2. Iterator<String> iter = list.iterator();  
  3. while (iter.hasNext()) {  
  4.     String s = iter.next();  
  5.    
  6.     if (s.equals("a")) {  
  7.         iter.remove();  
  8.     }  
  9. }  

.next()必须在.remove()之前被调用。在foreach循环中,编译器将在删除元素操作之后调用.next(),这也是导致ConcurrentModificationException异常的原因,你可以点击此处查看ArrayList.iterator()的源代码。

HashMap vs. TreeMap vs. Hashtable vs. LinkedHashMap

		//Map是包含一系列key-value对的数据结构,每一个key都是唯 一的.你可以总是假设成key,value是comparable的.
		Map<String, String> map=new HashMap<String, String>();
		map.put("a", "b");
		map.put("b", "c");
		map.put("c", "d");
		
		//convert a map to list
		List keyList=new ArrayList(map.keySet());
		List valueList=new ArrayList(map.values());
		List entryList=new ArrayList<>(map.keySet());
		System.out.println(keyList.toString());//b,c,a
		System.out.println(valueList.toString());//b,c,d
		System.out.println(entryList.toString());//b,c,a
		
		//iterator over each entry in a map
		for(Entry<String,String> entry:map.entrySet()){
			String key=entry.getKey();
			String value=entry.getValue();
		}
		Iterator<Entry<String, String>> it=map.entrySet().iterator();
		while (it.hasNext()) {
			Entry<String,String> entry = it.next();
			String key=entry.getKey();
			String value=entry.getValue();
		}
		
		//sort a map on the keys
		Collections.sort(entryList, new Comparator<Entry<String,String>>() {

			@Override
			public int compare(Entry<String, String> o1, Entry<String,String> o2) {
				// TODO Auto-generated method stub
				return o1.getKey().compareTo(o2.getKey());
			}
		});
		
		//使用sortedmap,键必须实现comparable接口,或comparator
		SortedMap<String, String> sortedMap=new TreeMap<>(new Comparator<String>() {

			@Override
			public int compare(String o1, String o2) {
				// TODO Auto-generated method stub
				return o1.compareTo(o2);
			}
		});
		sortedMap.putAll(map);

4. Initialize a static/immutable Map

When you expect a map to remain constant, it's a good practice to copy it into an immutable map. Such defensive programming techniques will help you create not only safe for use but also safe for thread maps.

To initialize a static/immutable map, we can use a static initializer (like below). The problem of this code is that, although map is declared as static final, we can still operate it after initialization, likeTest.map.put(3,"three");. Therefore it is not really immutable. To create an immutable map using a static initializer, we need an extra anonymous class and copy it into a unmodifiable map at the last step of initialization. Please see the second piece of code. Then, anUnsupportedOperationException will be thrown if you run Test.map.put(3,"three");.

public class Test {
 
  private static final Map map;
  static {
    map = new HashMap();
    map.put(1, "one");
    map.put(2, "two");
  }
}
public class Test {
 
  private static final Map map;
  static {
    Map aMap = new HashMap();
    aMap.put(1, "one");
    aMap.put(2, "two");
    map = Collections.unmodifiableMap(aMap);
  }
}
Guava  libraries also support different ways of intilizaing a static and immutable collection.

5. Difference between HashMap, TreeMap, and Hashtable

There are three main implementations of Map interface in Java: HashMapTreeMap, and Hashtable. The most important differences include:

  1. The order of iterationHashMap and Hashtable make no guarantees as to the order of the map; in particular, they do not guarantee that the order will remain constant over time. But TreeMap will iterate the whole entries according the "natural ordering" of the keys or by a comparator.
  2. key-value permission. HashMap allows null key and null values (Only one null key is allowed because no two keys are allowed the same). Hashtable does not allow null key or null values. IfTreeMap uses natural ordering or its comparator does not allow null keys, an exception will be thrown.
  3. Synchronized. Only Hashtable is synchronized, others are not. Therefore, "if a thread-safe implementation is not needed, it is recommended to use HashMap in place of Hashtable."

A more complete comparison is

                 | HashMap | Hashtable | TreeMap
-------------------------------------------------------
iteration order  | no      | no        | yes
null key-value   | yes-yes | no-no   | no-yes
synchronized     | no      | yes       | no
time performance | O(1)    | O(1)      | O(log n)
implementation   | buckets | buckets   | red-black tree

6. A Map with reverse view/lookup

Sometimes, we need a set of key-key pairs, which means the map's values are unique as well as keys (one-to-one map). This constraint enables to create an "inverse lookup/view" of a map. So we can lookup a key by its value. Such data structure is called bidirectional map, which unfortunetely is not supported by JDK.

Both Apache Common Collections and Guava provide implementation of bidirectional map, calledBidiMap and BiMap, respectively. Both enforce the restriction that there is a 1:1 relation between keys and values.

7. Shallow copy of a Map

Most implementation of a map in java, if not all, provides a constructor of copy of another map. But the copy procedure is not synchronized. That means when one thread copies a map, another one may modify it structurally. To [prevent accidental unsynchronized copy, one should useCollections.synchronizedMap() in advance.

Map copiedMap = Collections.synchronizedMap(map);

8. Create an empty Map

If the map is immutable, use

map = Collections.emptyMap();

Otherwise, use whichever implementation. For example

map = new HashMap();

THE END

Map is one of the most important data structures. In this tutorial, I will show you how to use different maps such as HashMap, TreeMap, HashTable and LinkedHashMap.

hashtable,linkedhashMap,hashMap实现map接口.

SortedMap接口继承自Map接口.TreeMap实现了sortedMap接口.

  • HashMap is implemented as a hash table, and there is no ordering on keys or values.
  • TreeMap is implemented based on red-black tree structure, and it is ordered by the key.
  • LinkedHashMap preserves the insertion order
  • Hashtable is synchronized, in contrast to HashMap.

    This gives us the reason that HashMap should be used if it is thread-safe, since Hashtable has overhead for synchronization.

  红黑树是一种“平衡的”二叉查找树,它是一种经典高效的算法,能够保证在最坏的情况下动态集合操作的时间为O(lgn)。

2. HashMap

If key of the HashMap is self-defined objects, then equals() and hashCode() contract need to be followed.

class Dog {
	String color;
 
	Dog(String c) {
		color = c;
	}
	public String toString(){	
		return color + " dog";
	}
}
 
public class TestHashMap {
	public static void main(String[] args) {
		HashMap<Dog, Integer> hashMap = new HashMap<Dog, Integer>();
		Dog d1 = new Dog("red");
		Dog d2 = new Dog("black");
		Dog d3 = new Dog("white");
		Dog d4 = new Dog("white");
 
		hashMap.put(d1, 10);
		hashMap.put(d2, 15);
		hashMap.put(d3, 5);
		hashMap.put(d4, 20);
 
		//print size
		System.out.println(hashMap.size());
 
		//loop HashMap
		for (Entry<Dog, Integer> entry : hashMap.entrySet()) {
			System.out.println(entry.getKey().toString() + " - " + entry.getValue());
		}
	}
}

Output:

4
white dog - 5
black dog - 15
red dog - 10
white dog - 20

Note here, we add "white dogs" twice by mistake, but the HashMap takes it. This does not make sense, because now we are confused how many white dogs are really there.

The Dog class should be defined as follows:

class Dog {
	String color;
 
	Dog(String c) {
		color = c;
	}
 
	public boolean equals(Object o) {
		return ((Dog) o).color.equals(this.color);
	}
 
	public int hashCode() {
		return color.length();
	}
 
	public String toString(){	
		return color + " dog";
	}
}

Now the output is:

3
red dog - 10
white dog - 20
black dog - 15

The reason is that HashMap doesn't allow two identical elements. By default, the hashCode() and equals() methods implemented in Object class are used. The default hashCode() method gives distinct integers for distinct objects, and the equals() method only returns true when two references refer to the same object. Check out the hashCode() and equals() contract if this is not obvious to you.

Check out the most frequently used methods for HashMap, such as iteration, print, etc.

3. TreeMap

A TreeMap is sorted by keys. Let's first take a look at the following example to understand the "sorted by keys" idea.

class Dog {
	String color;
 
	Dog(String c) {
		color = c;
	}
	public boolean equals(Object o) {
		return ((Dog) o).color.equals(this.color);
	}
 
	public int hashCode() {
		return color.length();
	}
	public String toString(){	
		return color + " dog";
	}
}
 
public class TestTreeMap {
	public static void main(String[] args) {
		Dog d1 = new Dog("red");
		Dog d2 = new Dog("black");
		Dog d3 = new Dog("white");
		Dog d4 = new Dog("white");
 
		TreeMap<Dog, Integer> treeMap = new TreeMap<Dog, Integer>();
		treeMap.put(d1, 10);
		treeMap.put(d2, 15);
		treeMap.put(d3, 5);
		treeMap.put(d4, 20);
 
		for (Entry<Dog, Integer> entry : treeMap.entrySet()) {
			System.out.println(entry.getKey() + " - " + entry.getValue());
		}
	}
}

Output:

Exception in thread "main" java.lang.ClassCastException: collection.Dog cannot be cast to java.lang.Comparable
	at java.util.TreeMap.put(Unknown Source)
	at collection.TestHashMap.main(TestHashMap.java:35)

Since TreeMaps are sorted by keys, the object for key has to be able to compare with each other, that's why it has to implement Comparable interface. For example, you use String as key, because String implements Comparable interface.

Let's change the Dog, and make it comparable.

class Dog implements Comparable<Dog>{
	String color;
	int size;
 
	Dog(String c, int s) {
		color = c;
		size = s;
	}
 
	public String toString(){	
		return color + " dog";
	}
 
	@Override
	public int compareTo(Dog o) {
		return  o.size - this.size;
	}
}
 
public class TestTreeMap {
	public static void main(String[] args) {
		Dog d1 = new Dog("red", 30);
		Dog d2 = new Dog("black", 20);
		Dog d3 = new Dog("white", 10);
		Dog d4 = new Dog("white", 10);
 
		TreeMap<Dog, Integer> treeMap = new TreeMap<Dog, Integer>();
		treeMap.put(d1, 10);
		treeMap.put(d2, 15);
		treeMap.put(d3, 5);
		treeMap.put(d4, 20);
 
		for (Entry<Dog, Integer> entry : treeMap.entrySet()) {
			System.out.println(entry.getKey() + " - " + entry.getValue());
		}
	}
}

Output:

red dog - 10
black dog - 15
white dog - 20

It is sorted by key, i.e., dog size in this case.

If "Dog d4 = new Dog("white", 10);" is replaced with "Dog d4 = new Dog("white", 40);", the output would be:

white dog - 20
red dog - 10
black dog - 15
white dog - 5

The reason is that TreeMap now uses compareTo() method to compare keys. Different sizes make different dogs!

4. Hashtable

From Java Doc:
The HashMap class is roughly equivalent to Hashtable, except that it is unsynchronized and permits nulls.

5. LinkedHashMap

LinkedHashMap is a subclass of HashMap. That means it inherits the features of HashMap. In addition, the linked list preserves the insertion-order.

Let's replace the HashMap with LinkedHashMap using the same code used for HashMap.

class Dog {
	String color;
 
	Dog(String c) {
		color = c;
	}
 
	public boolean equals(Object o) {
		return ((Dog) o).color.equals(this.color);
	}
 
	public int hashCode() {
		return color.length();
	}
 
	public String toString(){	
		return color + " dog";
	}
}
 
public class TestHashMap {
	public static void main(String[] args) {
 
		Dog d1 = new Dog("red");
		Dog d2 = new Dog("black");
		Dog d3 = new Dog("white");
		Dog d4 = new Dog("white");
 
		LinkedHashMap<Dog, Integer> linkedHashMap = new LinkedHashMap<Dog, Integer>();
		linkedHashMap.put(d1, 10);
		linkedHashMap.put(d2, 15);
		linkedHashMap.put(d3, 5);
		linkedHashMap.put(d4, 20);
 
		for (Entry<Dog, Integer> entry : linkedHashMap.entrySet()) {
			System.out.println(entry.getKey() + " - " + entry.getValue());
		}		
	}
}

Output is:

red dog - 10
black dog - 15
white dog - 20

The difference is that if we use HashMap the output could be the following - the insertion order is not preserved.

red dog - 10
white dog - 20
black dog - 15

在Java中,原始类型和无限制的通配符类型很容易被混淆。以Set为例,Set是原始类型,而Set(?)则是无限制的通配符类型。

考虑下面的代码,以一个原始类型List作为参数:

[java]  view plain copy
  1. public static void add(List list, Object o){  
  2.     list.add(o);  
  3. }  
  4. public static void main(String[] args){  
  5.     List<String> list = new ArrayList<String>();  
  6.     add(list, 10);  
  7.     String s = list.get(0);  
  8. }  

该代码会抛出一个异常:

[java]  view plain copy
  1. Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String  
  2.     at ...  

使用原始类型集合是危险的,因为原始类型集合跳过了泛型类型检查,也不安全。Set、Set<?>和Set<Object>之间有很大的不同。详细可查看

Top7.ArrayList VS LinkedList

如果你不知道ArrayList和LinkedList之间的区别时,你可能会经常的选用ArrayList,因为它看起来看熟悉。然而它们之间有巨大的性能不同。简单的来说,如果有大量的添加/删除操作,并且没有很多的随机存取操作时,LinkedList应该是你的首选。如果您对此不是很了解的话,点此此处查看更多关于它们性能的信息。

Top8. Mutable VS Immutable

Immutable对象有很多优势,比如简单、安全等等。但它要求每一个不同的值都需要有一个不同的对象,而太多的对象可能会导致垃圾收集的高成本。所以对Mutable和Immutable的选择应该有一个平衡点。

一般来说,Mutable对象用于避免产生过多的中间对象,经典的例子是连接大量的字符串数。如果你使用Immutable字符串,那么会产生很多符合垃圾收集条件的对象。这对CPU是浪费时间和精力的,当其可以使用Mutable对象作为正确的解决方案。(如StringBuilder)

[java]  view plain copy
  1. String result="";  
  2. for(String s: arr){  
  3.     result = result + s;  
  4. }  

这里还有一些其他Mutable对象可取的情况。例如mutable对象传递到方法中允许你在不跳过太多语法的情况下收集多个结果。另一个例子是排序和过滤,你可以构建一个带有原有集合的方法,并返回一个已排序的,不过这对大的集合来说会造成更大的浪费。

Top9. Super和Sub构造函数


这个编译错误是因为默认的Super构造函数是未定义的。在Java中,如果一个类没有定义一个构造函数,编译器会默认的为类插入一个无参数构造函数。如果一个构造函数是在Super类中定义的,这种情况下Super(String s),编译器不会插入默认的无参数构造函数。

另一方面,Sub类的构造函数,无论带不带有参数,都会调用无参数的Super构造函数。

编译器在Sub类中试图将Super()插入到两个构造函数中,但是Super默认的构造函数是没有定义的,编译器才会报错。如何解决这一问题?你只需在Super类中添加一个Super()构造函数,如下所示:

[java]  view plain copy
  1. public Super(){  
  2.     System.out.println("Super");  
  3. }  

或移除自定义的Super构造函数,又或者在Sub函数中添加super(value)。

op10. ""或构造函数?

字符串可以通过两种方式创建:

[java]  view plain copy
  1. //1. use double quotes  
  2. String x = "abc";  
  3. //2. use constructor  
  4. String y = new String("abc");  

它们之间有何不同?下面的例子可以给出答案:

[java]  view plain copy
  1. String a = "abcd";  
  2. String b = "abcd";  
  3. System.out.println(a == b);  // True  
  4. System.out.println(a.equals(b)); // True  
  5.    
  6. String c = new String("abcd");  
  7. String d = new String("abcd");  
  8. System.out.println(c == d);  // False  
  9. System.out.println(c.equals(d)); // True  


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值