考虑下面这个例子:
Long l1 = 1L;
Long l2 = 2L;
Long l3 = 3L;
long l4 = 3L;
Long l5 = 1 + 2L;
System.out.println(l3 == 3);
System.out.println(l4 == 3);
System.out.println(l3.equals(3));
System.out.println(l3.equals(l4));
System.out.println(l3.equals(l5));
输出的结果是
true
true
false
true
true
相信这个例子很多初学者都犯过迷糊:l3
、l4
不都是 3
吗,怎么 l3.equals(3)
是 false
呢。
这里面有很多点可以讲的,我们一个一个来看:
Long
和 long
在 Java
里只有两种类型: primitive types
原始类型 和 reference types
引用类型。
null
是一种特殊的类型
规范说明:4.1. The Kinds of Types and Values
原始类型里包括:boolean、byte、short、int、long、char、float、double
;
引用类型有四种:class、interface、type、array
(其中 type
我们平时遇到过的就是泛型 T
,详细内容可以查阅规范 4.4. Type Variables)
所以这里,long
是原始类型,Long
是引用类型,这很重要,是接下来讨论的基础。
Boxing Conversion
和 Unboxing Conversion
其实这个就是拆箱装箱,这个知识点应该不陌生吧,就是 Java
会自动帮你把原始数值类型和原始浮点类型转换为对应的引用类型,如 long
转换为 Long
。
举个栗子:
public void func(Long l) {
System.out.println(l);
}
func(1L);
这段代码是可以跑起来的,但是如果调用时是这样的 func(1)
,那么就会报错,因为 1
是整数型,它即便自动装箱也是 Integer
而不是 Long
。
Objects
,变量里存的是什么
在规范中,对于 Obejct
有这么一句话:
An object is a class instance or an array.
一个Object
可以是一个类的实例或者是一个数组 (一个数组其实是一个Object
,不过这是另一个话题了。)
The reference values (often just references) are pointers to these objects, and a special null reference, which refers to no object.
引用值(通常是引用)是指向这些对象的指针和一个特殊的null引用,它不引用任何对象。
有些学过 C++
的或是有 引用 这个概念的其他语言的同学,可能在这里要犯迷糊了:不是说 Java
里没有引用吗,怎么规范里又提到这个词了。
注意,C 里面没有引用只有指针,它跟 Java 一样是值传递的。
其实可以这么不严谨地认为:C++
里的 引用 是动词,Java
里的 引用 是名词。
C++
里的引用是对一个变量定义一个别名:
int a = 2, int &ra = a;
// a为目标原名称,ra为目标引用名。给ra赋值:ra = 1; 等价于 a = 1;
C++ 引用
Java
里的引用就是一个值,一个指向 对象 的值。
public static void func(String s) {
s = "bar";
}
String s1 = "foo";
func(s1);
System.out.println(s1); // "foo"
在这里,s1
的值并不是 foo
,而是一个指向 其字段value
值为 ['f', 'o', 'o']
的 String
实例 的引用。
比如说,再声明一个 String s2 = "foo";
,然后在 func(s1);
处下断点,可以看到:
可以看到,String{@xxx}
和 value:byte[]{@xxx}
都是一样的,因为它们就是同一个对象,s1
和 s2
这两个变量的值是 指向了同一个对象(String{@674})的引用。
如果我们在 func(String s)
里打断点,会发现在 func(s1)
的情况下,s
和 s1
的 引用值 是一样的。
因为 Java
是值传递,只不过在引用类型的情况下,传递的这个值,就是 引用值。
当 func
内部对这个 s
进行操作后,我们再来看看func
内部断点的情况:
public static void func(String s) {
s = "bar";
// 断点处,此时 s 的引用值已经变为 String{@674}
// 即此时的 s 的引用值已经不再是 s1 的引用值,自此它们已经指向的是不同的对象了。
}
由于String
的设计是不可变的,在一个String
实例上的任何增删操作都会产生一个新的String
实例,效果与重新为变量设定新的引用值是一样的。
我们再看看一个原始类型的断点情况:
int i = 0;
对于原始类型的变量而言,它们的值就是本身。
==
和 equals
==
操作符在规范里其实分了三种情况:
- 15.21.1. Numerical Equality Operators == and !=
- 15.21.2. Boolean Equality Operators == and !=
- 15.21.3. Reference Equality Operators == and !=
equals
是 Object
的方法,但是任何类都可以覆写这个方法来实现自定义的实例间判断,比如 Long.equals
就改成了这个样子:
/**
* Compares this object to the specified object. The result is
* {@code true} if and only if the argument is not
* {@code null} and is a {@code Long} object that
* contains the same {@code long} value as this object.
*
* @param obj the object to compare with.
* @return {@code true} if the objects are the same;
* {@code false} otherwise.
*/
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}
也就是说,只要待判定对象不是 Long
或其子类,那么就直接返回 false
。
结合前边讲的 int
在方法调用时会被自动装箱成 Integer
(如果参数不显式要求 int
类型),很显然,l3.equals(3)
会直接因为 Integer 3
不是 Long
而返回 false
。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。