可能有的小伙伴看到这个问题会不屑一顾,这个玩意老子天天用!
可是,相信你一定有过被 NullPointerException 支配的痛苦经历,而 toString() 方法就是其中一个罪魁祸首!
我先问大家一个问题,是不是所有的Java变量都有toString呢?
如果你觉得有,请把1打在公屏上,如果没有,就打2。(话说我在干嘛,这是博客,又不是Bilibili,emmm,不过可以考虑后期写个弹幕系统)
现在我来公布答案:基本数据类型是没有的,不能使用toString方法!
不相信你可以在Eclipse或者idea里面试一试,编译会报错的。
包装类
上面已经说了,基本数据类型是没有toString的,那么包装类有吗?
包装类,用的比较多的,那就是Integer了,看下Integer有没有toString 呢?答案肯定是有的,包装类不是基本数据类型,那就是有的。
public class StringTest1 {
public static void main(String[] args) {
Integer i = 1;
System.out.println(i.toString());
}
}
注意,Integer的toString方法用了重载(jdk1.8版本源码):
public String toString() {
return toString(value);
}
重新调用了另一个toString,也是写在Integer类中:
public static String toString(int i) {
if (i == Integer.MIN_VALUE)
return "-2147483648";
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] buf = new char[size];
getChars(i, size, buf);
return new String(buf, true);
}
这些代码如果看的吃力,也没有关系,我们只需要理解大概的意思就行。在调用toString方法的时候,实际上就是把Integer类内部的value属性,转换成一个String对象返回。
Integer自动装箱原理
顺便说一下Integer的自动装箱原理,有些面试题经常喜欢考察这个。
Integer作为一个典型的包装类,当你写了如下代码:
Integer i = 1;
实际上,编译器会调用Integer的valueOf方法,将原始类型值转换成Integer对象,这个过程我们是感觉不到的。Integer还维护了一个缓存IntegerCache,它会判断i是否在-128和127之间,存在则从IntegerCache中获取包装类的实例,否则new一个新实例。IntegerCache使用了享元模式,其内部维护了一个缓存池,数值范围是-128和127之间,这个缓存是静态的,类加载的时候会自动加载。因为我们正常使用Integer的时候,赋予的值一般不会太大,所以只需牺牲一点点内存,这里用享元模式可以有效地提升效率。
附上Integer的valueOf方法:
public static Integer valueOf(int i) {
//判断i是否在-128和127之间,存在则从IntegerCache中获取包装类的实例,否则new一个新实例
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
这个功能从jdk1.5开始就有了。
Object类
在Java中,所有的类都继承自Object,所以我们有必要再来看看Object的情况。首先是Object类的toString实现:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
可以看到,Object的toString方法就是把对象的hashCode值转为16进制而已。比如:
java.lang.Object@16d3586
但是实际上,调用toString还是会根据当前对象来具体分析的,比方说下面的代码:
Object i = 1;
System.out.println(i.toString());
1明明是基本数据类型,但是赋值给Object以后,编译器就会把它看成是Integer对象。不妨做一个实验,在eclipse或者idea中打开调试模式,然后在Integer的toString方法中打上一个断点。当你调试这段代码的时候,就会发现可以进入到Integer的toString方法。
这也就印证了,这种情况编译器是会认识到,当前的1应该是一个Integer对象。
再看一个例子:
Object i = new ArrayList<>();
System.out.println(i.toString());
这时候的i其实是ArrayList的实例对象,所以实际调用toString方法的时候,也就是直接调用ArrayList的toString方法了,而ArrayList并未对toString进行重写,而是ArrayList的父类AbstractList,AbstractList的父类AbstractCollection重写了toString:
public String toString() {
Iterator<E> it = iterator();
if (! it.hasNext())
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}
这也是为什么,我们打印ArrayList的时候(这个操作实在是很普遍,懂的都懂),打印出来的是一个数组字符串。
综上,Object类调用toString,如果当前实际的对象没有重写toString,就是调用父类的toString,如果父类也没有重写toString,就层层冒泡最终到Object类。