Java开发人员犯的10大错误

一、将数组转换为ArrayList
要将数组转换为ArrayList,开发人员通常会这样做:

List<String> list = Arrays.asList(arr);
Arrays.asList()将返回 ArrayList私有静态类的Arrays,而不是java.util.ArrayList类。该java.util.Arrays.ArrayList有set(),get(),contains()方法,但没有添加元素的任何方法,所以它的大小是固定的。要创建一个real ArrayList,您应该执行以下操作:

ArrayList<String> arrayList = new ArrayList<String>(Arrays.asList(arr));
构造函数 ArrayList可以接收 Collection类型,它也是超类型 java.util.Arrays.ArrayList。

二、检查数组是否包含值
开发人员经常这样做:

Set<String> set = new HashSet<String>(Arrays.asList(arr));
return set.contains(targetValue);
该代码有效,但是无需先转换列表即可设置。将列表转换为集合需要额外的时间。它可以很简单:

Arrays.asList(arr).contains(targetValue);
or

for(String s: arr){
if(s.equals(targetValue))
return true;
}
return false;
第一个比第二个更具可读性。

三、从循环内的列表中删除元素
考虑以下代码,该代码在迭代期间删除元素:

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));
for (int i = 0; i < list.size(); i++) {
list.remove(i);
}
System.out.println(list)
输出为:

[b,d]
该方法存在严重的问题。删除元素后,列表的大小会缩小,索引也会更改。因此,如果您想通过使用索引删除循环中的多个元素,那将无法正常工作。您可能知道使用迭代器是删除循环内元素的正确方法,并且您知道 Java中的 foreach循环就像迭代器一样工作,但实际上并非如此。考虑以下代码:

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));

for (String s : list) {
if (s.equals("a"))
list.remove(s);
}
它将抛出ConcurrentModificationException,因为它去检查list前后的大小的时候,发现不相等就会跑错,具体源码中有体现。相反,可以执行以下操作:

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
String s = iter.next();

if (s.equals("a")) {
iter.remove();
}
}
.next()必须在.remove()方法之前调用。在 foreach循环中,编译器将 .next()方法在元素删除操作之后进行调用,从而导致ConcurrentModificationException。

ArrayList.iterator()的源代码:

...

public Iterator<E> iterator() {
return new Itr();
}

/**
* AbstractList.Itr的优化版本
*/
private class Itr implements Iterator<E> {
int cursor; // 下一元素的索引返回
int lastRet = -1; // 返回的最后一个元素的索引,如果没有这为 -1
int expectedModCount = modCount;

public boolean hasNext() {
return cursor != size;
}

@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}

public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();

try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}

final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}

...
四、HashTable 、HashMap、LinkedHashMap、TreeMap
HashMap 和 HashTable之间的主要区别是 HashTable同步(所有的读写等操作都进行了锁(synchronized)保护,在多线程环境下没有安全问题。但是锁保护也是有代价的,会对读写的效率产生较大影响)。因此,通常建议不使用 HashTable,而使用 HashMap。


Java SE中有4种常用的Map实现-HashMap,TreeMap,Hashtable和LinkedHashMap。如果我们仅使用一个句子来描述每个实现,则将是以下内容:这就是如果程序是线程安全的,则应使用 HashMap的原因。
【1】HashMap被实现为哈希表,并且键或值没有排序。
【2】TreeMap是基于红黑树结构实现的,并通过 key进行排序。
【3】LinkedHashMap保留插入顺序
【4】与 HashMap相比,Hashtable是同步的。同步有开销。
如果 HashMap的键是自定义对象,则需要遵循equals()和hashCode()协定。

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());
}
}
}
【输出结果】 :

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

注意这里,我们错误地两次添加了“white dog”,但是 HashMap接受了它。这没有道理,因为现在我们对真正有多少只 white dog感到困惑。Dog类应定义如下:

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";
}
}
【输出结果】 :

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

原因是 HashMap不允许两个相同的元素。默认情况下,使用在 Object类中实现的hashCode()和equals()方法。默认的hashCode()方法为不同的对象提供不同的整数,而equals()方法仅在两个引用引用同一对象时才返回true。所以hashCode()和equals()方法校验结果不相同。如果重写了此方法,就会返回true,过滤掉多余的 white dog 。

五、使用原始集合类型
在Java中,原始类型和无界通配符类型很容易混合在一起。以 Set为例,Set是原始类型,Set<?>无界通配符类型。考虑以下使用原始类型 List作为参数的代码:

public static void add(List list, Object o){
list.add(o);
}
public static void main(String[] args){
List<String> list = new ArrayList<String>();
add(list, 10);
String s = list.get(0);
}
此代码将引发异常:

线程“主”中的异常java.lang.ClassCastException:无法将java.lang.Integer强制转换为java.lang.String
...
使用原始类型集合很危险,因为原始类型集合会跳过泛型类型检查并且不安全。之间存在巨大差异Set,Set<?>和Set<Object>。如果要使用泛型类型,但不知道或不在乎该参数的实际类型,则可以使用<?>但不能插入nul。如果知道类型则需要传入类型,因为原始类型没有限制。

六、访问权限
开发人员经常将 public用于类字段。通过直接引用很容易获得字段值,但这是一个非常糟糕的设计。经验法则是为成员提供尽可能低的访问级别。下面总结了成员的不同修饰符的访问级别。访问级别确定字段和方法的可访问性。它具有4个级别:公共,受保护,包私有(无显式修饰符)或私有。


七、ArrayList与LinkedList
当开发人员不知道 ArrayList和LinkedList 之间的区别时,他们经常使用ArrayList,因为它看起来很熟悉。但是,它们之间存在巨大的性能差异。简而言之,LinkedList如果有大量的添加/删除操作并且没有很多随机访问操作,则应首选此方法。如果您是新手,请查看 ArrayListvs.LinkedList以获得有关其性能的更多信息。

八、可变与不可变
不可变的对象具有许多优点,例如简单性,安全性等。但是对于每个不同的值,它都需要一个单独的对象,并且太多的对象可能会导致垃圾回收的高成本。在可变和不可变之间进行选择时应保持平衡。通常,使用可变对象以避免产生太多中间对象。一个经典的例子是 String 连接大量的字符串时,如果使用不可变的字符串,则会立即产生许多符合垃圾回收条件的对象,会浪费CPU的时间和精力。所以需要使用可变对象(例如 StringBuilder)

String result="";
for(String s: arr){
result = result + s;
}
还有其他一些情况需要可变对象。例如,将可变对象传递给方法收集多个结果。另一个示例是排序和过滤:当然,您可以创建一个原始集合,利用原始集合的排序方法返回排序结果,但是这对于较大的集合将变得非常浪费。

九、Super 和 Sub的构造函数


因为未定义默认的超级构造函数,所以会发生此编译错误。在Java中,如果类未定义构造函数,则编译器将默认为该类插入默认的无参数构造函数。如果在Super类中定义了构造函数,在这种情况下为Super(String s),则编译器将不会插入默认的无参数构造函数。上面的超级类就是这种情况。

Sub类的构造函数(带参数或无参数)将调用无参数Super构造函数。由于编译器试图将super() 插入Sub类中的2个构造函数,但是未定义 Super的默认构造函数,因此编译器将报告错误消息。

要解决此问题,只需【1】将Super() 构造函数添加到Super类,例如:

public Super(){
System.out.println("Super");
}
【2】或者删除自定义的Super构造函数;
【3】或者添加super(value)到子构造函数;

十、还是构造函数
可以通过两种方式创建字符串:

//1. use double quotes
String x = "abc";
//2. use constructor
String y = new String("abc")
有什么区别?以下示例可以提供快速解答:

String a = "abcd";
String b = "abcd";
System.out.println(a == b); // True
System.out.println(a.equals(b)); // True

String c = new String("abcd");
String d = new String("abcd");
System.out.println(c == d); // False
System.out.println(c.equals(d)); // True


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355