在维护一个系统的时候,发现以前的程序员在设计程序的时候,对于实体类的id都设计为对象类型的Long,而不是原始类型的long,据说这样设计是因为对象类型的Long没有值的时候时NULL,因此可以通过其值是否为null来判断所表示的实体类是否已经在在数据库中存在,也就是在添加和更新时做判断使用,因为大多数程序员都习惯用一个save方法,来处理add和update操作,而通过id是否有值来判断是add还是update.就是为了这个小小的功能,而选择Long,我个人不是很喜欢,因为用其作为id,会带来一些缺点,可以说缺点大于优点。
1. 容易引发难易发现的空指针异常
用对象型的Long,经常需要其是否为null,否则经常抛空指针异常,因为Jdk支持Long 到long的隐式转换,也就是说,方法定义的参数类型为long,传入对象型的Long也是可以的,运行时系统会自动将对象型的Long转换为long,但是如果发现对象型的Long为null时就会抛空指针异常。而这种缺陷通常不易被发现。
参考代码如下:
public class TestLong
{
public static void main(String args[])
{
TestLong testLong = new TestLong();
Long id = null;
testLong.test(id);
}
public void test(long id)
{
System.out.println("原始类型的long");
}
}
运行就会发生空指针异常,这里我们直接赋值为null,你可能说很容易看出来,但实际项目中,是很难发现的。
2. 在Long型id作为对象是否相等判断依据时,用在集合中容易出现垃圾数据。
经常我们用id作为对象唯一的表示,对应于数据库的唯一key.在设计类的时候,我们通过重写Object类的equals方法和hashCode方法从而实现从业务上正确区分不同的对象。通常的equals方法我们这么写,比如User类的部分代码如下:
private long id = 0L;
public long getId()
{
return id;
}
public void setId(long id)
{
this.id = id;
}
public boolean equals(Object obj)
{
if(this.getClass()!=obj.getClass())
{
return false;
}
return obj!=null && ((User)obj).getId() == this.id;
}
但是如果我们把这里的id设计成Long型,则这里的equals方法通常这么写。
public boolean equals(Object obj)
{
if(this.getClass()!=obj.getClass())
{
return false;
}
return obj!=null && this.id.equals(((User)obj).getId());
}
这里看似没什么问题。但是当我们需要把User对象放入一个集合中时,如HashSet中,我们希望里边的数据不重复,当然也不能有垃圾数据。
这时,如果我们的User对象的id为null时,本来设计者是不希望这类‘不合法‘的数据不能被插入,但是由于这里的equals方法执行结果为false,因为HashSet认为里边不存在,所以将其加入set,导致垃圾数据诞生。因为我们通常对于集合中的数据要进行各类维护操作,有这个垃圾数据的存在往往导致很多不爽的问题,这些问题本人可以亲历过。
另外,关于用long和Long型数据作为参数的重载方法的代码,可读性不是很强,参考如下代码:
public class TestLong
{
public static void main(String args[])
{
TestLong testLong = new TestLong();
long id = 1L;
testLong.test(id);
Long id2 = 2L;
testLong.test(id2);
}
public void test(long id)
{
System.out.println("原始类型:" + id);
}
public void test(Long id)
{
System.out.println("封装类型:" + id);
}
}
对于用long作为参数的方法调用,究竟调用的是哪个方法呢,既然是Long和long可以隐式转换,应该都可以。但是我们知道,方法的调用分派是静态分派,也就是说,这里参数是long的调用选择test(long id)方法,而参数是Long的调用选择test(Long id)方法。执行结果如下:
原始类型:1
封装类型:2
证明了我们的分析时正确的。
但是如果我们注释掉重载方法中的任何一个,如:
public class TestLong
{
public static void main(String args[])
{
TestLong testLong = new TestLong();
long id = 1L;
testLong.test(id);
Long id2 = 2L;
testLong.test(id2);
}
// public void test(long id)
// {
// System.out.println("原始类型:" + id);
// }
public void test(Long id)
{
System.out.println("封装类型:" + id);
}
}
输出结果:
封装类型:1
封装类型:2
或者注释掉第2个重载方法,保留第一个重载方法,如下:
public class TestLong
{
public static void main(String args[])
{
TestLong testLong = new TestLong();
long id = 1L;
testLong.test(id);
Long id2 = 2L;
testLong.test(id2);
}
public void test(long id)
{
System.out.println("原始类型:" + id);
}
// public void test(Long id)
// {
// System.out.println("封装类型:" + id);
// }
}
运行输出:
原始类型:1
原始类型:2
总结:我个人认为在设计id作为实体类id,并且映射到数据库的主键时,最好用long ,这样避免了一些不必要的麻烦,除非特别需要对象类型,另外,关于用对象Long来判断是add 还是update,也可以将long id初始值为-1来实现其功能。