Java中常见的坑

Published: 07 Mar 2014 Category: Java
概述

Java是门极简风格的语言,比其它语言相比,它故意保持较少的特性,不仅在有些不常见的情况下会出些奇奇怪怪的错误,即使很一般的情况下也有可能让人栽根头。如果你习惯了别的语言,你读Java 的代码很容易搞错一些概念。

变量要么是引用 ,要么是基础类型

这是对的,变量不是对象。也就是说在下面这个例子里,s不是个对象,也不是字符串,它只是一个字符串的引用。

String s = "Hello";

这个能解释很多的问题,比如: 问题:如果说字符 串是不可变的,为什么我能修改它? s+=“!” 回答:在Java里是不可变的,你改变的只是引用而已。

==比较的是引用,不是内容

让人更混乱的是,有时候用==是能比较内容的。如果你有两个一样的不可变值,JVM会尝试引用 同一个对象 。

String s1 = "Hi", s2 = "Hi";
Integer a = 12, b = 12;

这两个例子中用到了对象池,所以最后引用 的是同样的对象。s1==s2和a==b都是返回true,JVM已经把两个引用 都指向了同一个对象。然而,如果稍微改下代码,JVM没有把对象放到池里的话,==就会返回false,可能会让你意想不到。这个时候你得用equals了。

String s3 = new String(s1);
   Integer c = -222, d = -222;
s1 == s2      // is true
  s1 == s3      // is false
  s1.equals(s3) // is true
  a == b        // is true
  c == d        // is false (different objects were created)
  c.equals(d)   // is true

对于整型来说,对象池缓存的范围是-128到127(还有可能更高)。

Java通过传值进行引用传递

所有的变量都是传值,包括引用。这就是说如果你有个变量,它是一个对象的引用,这个引用会被拷贝后再传参,而不是传递的对应的那个对象。

public static void addAWord(StringBuilder sb) {
     sb.append(" word");
     sb = null;
}  


StringBuilder sb = new StringBuilder("first ");
addWord(sb);
addWord(sb);
System.out.println(sb); // prints "first word word"

引用的对象可以改变,不过如果修改拷贝的这个引用,对调用方是没有影响的。

在大多数JVM实现里,Object.hashCode和内存地址无关

hashCode必须是保持不变的。不然的话HashSet或者ConcurrentHashMap就没法玩了。然而对象可以在内存的任何地方,并且它的位置还可能不断变化,而这个对你的程序来说是透明的。使用内存地址来当做hashCode是不可行的(除非你自己有一个JVM,对象是固定不动的)。 对于 OpenJDK和Hotspot JVM来说,hashCode是按需生成的,并存储在对象的头部。使用Unsafe API你看到hashCode是否已经生成了,甚至还可以修改它。

Object.toString那些不为人知的事情

toString的默认行为是打印类的的内部名称还有对象的hashCode。 上面已经提到,hashCode并不是内存地址,尽管它是用16进制打印的。同样的,类名,尤其是数组的类名,更容易让人头晕。比如说String[]的名称是[Ljava.lang.String; 这个[表明它是个数组,L说明它是Java语言(Language)创建的类,并不是基础类型比如byte这些,顺便提一下byte内部名称是B。;号标识类的结束。比如你有个这样的数组:

String[] words = { Hello, World };
System.out.println(words);

输出会是这样:

[Ljava.lang.String;@45ee12a7

很不幸你只知道这是个对象数组。如果你只有一个Object实例words,这样是不够的,你得调用下Arrays.toString(words)。这极其恶劣地破坏了封装的原则,在StackOverflow上面这也是最常见的一类问题。

我问过Oracle的好些个工程师,从他们的反馈感觉,这个问题目前很难解决。

原创文章转载请注明出处:Java中常见的坑

英文原文链接