本节课的目标:
1、掌握Java中的值类型和引用类型
2、掌握Java中的字面量。了解其他现代语言的字面量。
3、掌握值Java中的自动拆装箱
对象,还是非对象?
Java中一切都是对象。所有的函数,变量,常量都必须在一个类里定义。这本来很好,很完美的规定。但是Java在一开始设计的时候,出于性能上的考虑,却留下了一道口子:primitive类型,我们在开发中也常称为值类型,或者干脆按字面意思翻译为基础类型或者原始类型。
primitive类型包括byte, char, short, int, long, float, double, boolean。我们经常会用到这些类型,并不陌生。Java提供了这些基础类型以后,为了仍然保持强对象的要求(即Java中的所有东西都是对象),又引入了它们的包装类(wrapper class)。分别是
byte -> Byte
char -> Character
short -> Short
int -> Integer
long -> Long
float -> Float
double -> Double
boolean -> Boolean
这就是说,同样一个整数,在Java有两种表示方法,一个是值,一个是对象。Java还怕你用得不顺手,又提供了一个自动拆装箱的功能。所谓自动装箱,就是说,在需要使用对象的地方,传一个值,JVM会自动帮你转成对象,例如:
public class Test {
public static void main(String args[]) {
Integer a = 1;
Integer b = new Integer(1);
Integer c = inc(0);
}
public static int inc(Integer x) {
return x + 1;
}
}
所谓自动拆箱,就是反过来,在需要使用值的地方,允许你传一个对象,比如:
public class Test {
public static void main(String args[]) {
int a = new Integer(1);
int b = Integer.valueOf(1);
int c = inc(0);
}
public static Integer inc(Integer x) {
return x + 1;
}
}
同一个数,有两种表示方法,这是要逼死强迫症的节奏啊。”幸好我不是处女座“,我心里一定会暗自窃喜,但别高兴得太早,运行一下这个程序。看看有多少地方是出乎你预料的。
public class Test {
public static void main(String args[]) {
Integer a = new Integer(1);
Integer b = Integer.valueOf(1);
Integer c = inc(0);
Integer d = 1;
System.out.println(a == b); // false
System.out.println(a.equals(b)); // true
System.out.println(a == c); // false
System.out.println(a.equals(c)); // true
System.out.println(a == d); // false
System.out.println(a.equals(d)); // true
System.out.println(b == c); // true
System.out.println(b.equals(c)); //true
System.out.println(b == d); // true
System.out.println(b.equals(d)); // true
System.out.println(((Long)1L) == 1); // true
System.out.println(new Long(1).equals(1)); // false
Long e = 100;
Long f = 100;
System.out.println(e == f); // true
e = 1000;
f = 1000;
System.out.println(e == f); // false
}
public static Integer inc(Integer x) {
return x + 1;
}
}
wrapper类留下了多少坑,由此可见一斑,这也许还不是全部的,因为我的例子构造的未必那么全。这已经不是一个设计的品味或者取舍权衡的问题了。这完全就是错误的。为了使这种错误少出现,我们能不使用外包类的时候还是尽量不要使用外包类,除非在使用容器,泛型等等的时候,不得不使用。跟这么多出乎意料的结果比起来,审美和品位已经不是最重要的了。
我们还是尝试着解答一下上面的各个结果。首先明确一点,"=="判断的是两边的对象是不是同一个对象,即两个引用指向同一个对象。"equals"是函数,如何实现取决于类里如何定义。我们以JDK中Long的源代码举例:
class Long {
public Long(long value) {
this.value = value;
}
private static class LongCache {
private LongCache(){}
static final Long cache[] = new Long[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Long(i - 128);
}
}
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
// 神坑!!!!
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}
}
可以看到,直接使用new构造一个长整型的时候,会生成一个新的对象。而使用 valueOf 的时候,如果参数位于[-128, 127]这个区间内,则直接从cache数组中取一个对象,而不是新建,这就回答了为什么e 和 f 为100时,"=="为true,而 e 和 f 为1000时, "=="为false。
自动装箱时,Java调用了valueOf函数,拆箱时,调用 xxxValue 函数。这就回答了,为什么b, c, d 是同一个对象,而与a却是不同的对象。
比较难理解的是
System.out.println(((Long)1L) == 1); // true
System.out.println(new Long(1).equals(1)); // false
“==”操作符的左边是一个Long型的对象,为了比较就使用了自动拆箱,左边拆箱成了整数1,所以与右边的值是相等的。而第二行,equals参数接受一个Object对象,所以1就被自动装箱成了Integer.valueOf(1),我们看一下Long的equals的实现。如果传进来的是Long型的,那么就比较值,如果传进来的是其他类型,直接返回false。。。。我隔着屏幕似乎都听到了你说“我擦嘞"的声音。。。
好了。练习一下,想想看,下面的语句输出是什么?
System.out.println(((Long)1000L) == 1000);
System.out.println(((Long)1000L) == Long.valueOf(1000));
System.out.println(new Long(1).equals(1L));
如果有哪位读者,你们将来开始负责招聘了,请千万不要出这种题目,很多人可能没在意到这些细节。不要拿这些语言中的糟粕来刁难应试者。这些东西不看原代码真的很难搞明白。
字面量都不是对象
字面量(literal)是用于表达源代码中一个固定值的记号。
定义看上去不那么直观。其实很简单,整数1,2, 3是整型字面量;用引号引起来的"hello world",是字符串(String)字面量;1.0, 2.1是浮点型字面量;'a', 'c'是字符型字面量。可以使用字面量初始化primitive类型,也可以用于初始化某些特定的对象。例如:
public class Test {
public static void main(String args[]) {
int a = 1;
char c = 'z';
String s = "hello java!";
double[] t = {1.0, 2.1, 3.4};
}
}
Java允许通过字面量给String类型的对象赋值,却不允许字面量本身就是一个对象。对于一个强对象的语言,这其实是很落后的做法。我们来看几个其他语言的例子:
# Python,字符串连接的写法
"-".join["hello", "world"] # hello-world
// groovy, 打印三次hello world
3.times {
println("hello world");
}
// scala
"hello".length //res0: Int = 5
3.compareTo(2)
还有,深刻改变网络消息格式的JSON:
//javascript
var d = {"hello" : 1, "world" : 2};
其实JSON的结构不过就是javascript的字典类型的字面量而已。这种数据即代码的做法才是现代语言应该具备的特征。
我们看到,在很多种语言中,都可以把字面量当做一个对象进行调用。例如groovy和ruby里,都可以直接在一个整数上调用times,代表后面的代码块要执行多少次。在这些语言里,才真正的一切都是对象,就连函数都是对象(这个扯远了,我们会尝试在课程里引入一些scala的知识,会介绍函数做为对象带来了哪些变化)。
对比起来,Java不仅字面量不是对象,而且字面量向对象的转变还留下了一个个不大不小的坑。这已经不是bad taste的问题,而是强对象这件事情,Java 根本就没做对。
今天的作业:
1. 我们只介绍了Long的源代码。所以,请大家下载OpenJDK的源代码,找到Integer.java,把文章里关于Integer的例子解释清楚。注意我们昨天讲的static, final的用法,看看JDK源码里怎么使用的。
2. 自己找一下json格式的定义。Java 程序员往往都是后端开发人员,但后端也要对前端的工具有所了解。json又是前后端通信的重要纽带。搞清楚它。
3. 修改Long.java,看看能不能改变equals的行为,使得下面的语句输出为true
System.out.println(new Long(1).equals(1));
小密圈的同学,请直接提交到你自己的git上,并私信我。(一定记得要私信我一下,不然我可能会漏看了。有些加了圈,但还没给我git地址的同学,加快一些哦。后加圈的同学,也不用着急,做一题,就私信我告诉一题, 我会review的)
上一节:Java语言的品味(一)
下一节:Java语言的品味(三)
回目录:课程目录