第一个问题-关于引用类型的null探讨
最近在补习数据结构-树的时候,发现了一个问题,发现一个null引用的有趣问题,先上代码:
1.Node.java
public class Node {
public int data;
public Node leftChild;
public Node rightChild;
public Node(int data) {
this.data = data;
leftChild = null;
rightChild = null;
}
}
2.MyTree
public class MyTree {
private Node root;
public void insert(int value) {
Node node = new Node(value);
if (root == null) {
root = node;//根节点
} else {
Node current = root;
while (true) {
//左小右大
if (current.data > node.data) {
//先找左边
current = current.leftChild;
if (current == null) {
current = node;
System.out.println("左边插入之后current==node" + (current == node));
System.out.println("左边插入之后current==node" + (root.leftChild == node));
return;
}
} else {
//再找右
current = current.rightChild;
if (current == null) {
current = node;
System.out.println("右边边插入之后current==node" + (current == node));
System.out.println("右边插入之后current==node" + (root.leftChild == node));
return;
}
}
}
}
}
public Node getRoot() {
return root;
}
}
3.测试代码:
MyTree mt = new MyTree();
mt.insert(3);
mt.insert(2);
mt.insert(1);
mt.insert(4);
Node root = mt.getRoot();
System.out.println(root.data);//3
System.out.println(root.leftChild);//null
System.out.println(root.rightChild);//null
这样就尴尬了,为什么会这样的呢?明明就是current有引用的,但root的左右子树都是null,因此我做了下面这个实验:
Node node = new Node(1);
Node left = node.leftChild;
Node newNode = new Node(111);
left = newNode;
System.out.println(node.leftChild);//null
总结:
如下面的代码所示,其实上面的代码就是把一个null给了另外一个引用对象,null在java中代码没有引用任何对象(即没任何地址),所以就造成了上面的尴尬局面:
Apple a=null;
APPle b=null;
b=new APPle();//此时b有引用对象,a还是null
第二个问题-关于string类型null的探讨
String b=null+"111";
System.out.println(b);//null111
String bNull=null;
String b1=bNull+"111111";
System.out.println(b1);//null111
String b2=b+b1;
System.out.println(b2);//null111null111111
在很多时候,我们都学到一个空值+任何非null都会出现NullPointerException,当你运行上面的代码时候,会发现string类型的这种null居然不报,为什么呢?
个人理解:在c/c++中,+/-运算符都是可以被重载的,在Java核心1书本中提及了Java中的String类型的+是唯一被重载的,我认为是这里做了一定的处理(建议往下看)
进阶(先看下面代码):
1.Grandpa类
class Grandpa {
public double salary;
public int age;
public String name;
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public String getName() {
return name;
}
}
2.Father类
class Father extends Grandpa {
@Override
public String toString() {
return "Father{" +
"salary=" + salary +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
3.测试代码:
Father father = new Father();
Grandpa grandpa = new Grandpa();
Father fatherNull=null;
//com.test.Grandpa@74a14482ccccccccccc
System.out.println(grandpa + "ccccccccccc");
//Father{salary=0.0, age=0, name='null'}ccccccccccc
System.out.println(father + "ccccccccccc");
//nullccccccccccc
System.out.println(fatherNull+ "ccccccccccc");
try {
//java.lang.NullPointerException
System.out.println(fatherNull.toString() + "ccccccccccc");
} catch (Exception e) {
System.out.println(e);
}
在这里亲友们可能发现了问题了吧???特别是最后两行的代码有木有感觉很意外呢?个人想解释的就是,String重载的+号作用是先把式子A+B+C中的A,B,C都变为字符串再进行相加的(非null的引用类型自动调用toString()方法,null引用类型的就直接给null表示),但最后一行很遗憾就是null.toString()方法是null指针调用方法了导致出现异常的
以上解释还是不合理的,继续探讨
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(fatherNull);
stringBuffer.append(father);
//String.valueOf(father): Father{salary=0.0, age=0, name='null'}
System.out.println("String.valueOf(father): "+String.valueOf(father));
//stringBuffer: nullFather{salary=0.0, age=0, name='null'}
System.out.println("stringBuffer: "+stringBuffer.toString());
JDK中文文档叙述的:
Java 语言提供对字符串串联符号(”+”)以及将其他对象转换为字符串的特殊支持。字符串串联是通过 StringBuilder(或 StringBuffer)类及其 append 方法实现的。字符串转换是通过 toString 方法实现的,该方法由 Object 类定义,并可被 Java 中的所有类继承。有关字符串串联和转换的更多信息,请参阅 Gosling、Joy 和 Steele 合著的 The Java Language Specification。
就是说String类型在运行+的时候其实就是调用 StringBuilder(或 StringBuffer)类及其 append 方法实现的,然后我们就需要看看源码的实现了;
1.首先来到了StringBuffer的Append方法
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;//这里不需要理会
super.append(String.valueOf(obj));
return this;
}
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();//返回null
int len = str.length();
ensureCapacityInternal(count + len);//扩容
str.getChars(0, len, value, count);//使用下面代码复制
// System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
count += len;
return this;
}
//其实就是返回null
private AbstractStringBuilder appendNull() {
int c = count;
ensureCapacityInternal(c + 4);//扩容,扩展成为c+4长度的数组
final char[] value = this.value;
value[c++] = 'n';
value[c++] = 'u';
value[c++] = 'l';
value[c++] = 'l';
count = c;
return this;
}
2.String.valueOf(obj)源码
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
看完之后,你就会发现为什么Null会返回null了,看str参数的太多了,使用Object作为形参的来解释:是因为String.valueOf(obj)做了处理,obj为null就返回null要不然就调用obj.toString()
核心:是null的话就返回null,不为null就另外处理
PS:无论是在常量池字符串相加还是堆内存字符串相加,都是调用StringBuilder(或 StringBuffer)类及其 append 方法实现的