数据类型有什么好处](https://hollischuang.github.io/toBeTopJavaer/#/basics/java"/>
### [基本数据类型有什么好处](https://hollischuang.github.io/toBeTopJavaer/#/basics/java
基本数据类型有什么好处
我们都知道在 Java 语言中,new
一个对象是存储在堆里的,我们通过栈中的引用来使用这些对象;所以,对象本身来说是比较消耗资源的。
对于经常用到的类型,如 int 等,如果我们每次使用这种变量的时候都需要 new 一个 Java 对象的话,就会比较笨重。所以,和 C++ 一样,Java 提供了基本数据类型,这种数据的变量不需要使用 new 创建,他们不会在堆上创建,而是直接在栈内存中存储,因此会更加高效。
为什么需要包装类
很多人会有疑问,既然 Java 中为了提高效率,提供了八种基本数据类型,为什么还要提供包装类呢?
这个问题,其实前面已经有了答案,因为 Java 是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。比如,在集合类中,我们是无法将 int 、double 等类型放进去的。因为集合的容器要求元素是 Object 类型。
为了让基本类型也具有对象的特征,就出现了包装类型,它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。
自动装箱与自动拆箱的实现原理
既然 Java 提供了自动拆装箱的能力,那么,我们就来看一下,到底是什么原理,Java 是如何实现的自动拆装箱功能。
我们有以下自动拆装箱的代码:
public static void main(String[]args){Integer integer=1; //装箱int i=integer; //拆箱}复制ErrorOK!
对以上代码进行反编译后可以得到以下代码:
public static void main(String[]args){Integer integer=Integer.valueOf(1);int i=integer.intValue();}复制ErrorOK!
从上面反编译后的代码可以看出,int 的自动装箱都是通过 Integer.valueOf()
方法来实现的,Integer 的自动拆箱都是通过 integer.intValue
来实现的。如果读者感兴趣,可以试着将八种类型都反编译一遍 ,你会发现以下规律:
自动装箱都是通过包装类的
valueOf()
方法来实现的.自动拆箱都是通过包装类对象的xxxValue()
来实现的。
自动拆装箱与缓存
Integer integer1 = 3; Integer integer2 = 3; integer1 == integer2
Integer integer3 = 300; Integer integer4 = 300; integer3 != integer4
原因就和 Integer 中的缓存机制有关。在 Java 5 中,在 Integer 的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。
适用于整数值区间 -128 至 +127。
只适用于自动装箱。使用构造函数创建对象不适用。
success 还是 isSuccess
到底应该是用success还是isSuccess来给变量命名呢?从语义上面来讲,两种命名方式都可以讲的通,并且也都没有歧义。那么还有什么原则可以参考来让我们做选择呢。
在阿里巴巴Java开发手册中关于这一点,有过一个『强制性』规定:

那么,为什么会有这样的规定呢?我们看一下POJO中布尔类型变量不同的命名有什么区别吧。
class Model1 {private Boolean isSuccess;public void setSuccess(Boolean success) {isSuccess = success;}public Boolean getSuccess() {return isSuccess;}}class Model2 {private Boolean success;public Boolean getSuccess() {return success;}public void setSuccess(Boolean success) {this.success = success;}
}class Model3 {private boolean isSuccess;public boolean isSuccess() {return isSuccess;}public void setSuccess(boolean success) {isSuccess = success;}
}class Model4 {private boolean success;public boolean isSuccess() {return success;}public void setSuccess(boolean success) {this.success = success;}
}复制ErrorOK!
以上代码的setter/getter是使用Intellij IDEA自动生成的,仔细观察以上代码,你会发现以下规律:
- 基本类型自动生成的getter和setter方法,名称都是
isXXX()
和setXXX()
形式的。 - 包装类型自动生成的getter和setter方法,名称都是
getXXX()
和setXXX()
形式的。
既然,我们已经达成一致共识使用基本类型boolean来定义成员变量了,那么我们再来具体看下Model3和Model4中的setter/getter有何区别。
我们可以发现,虽然Model3和Model4中的成员变量的名称不同,一个是success,另外一个是isSuccess,但是他们自动生成的getter和setter方法名称都是isSuccess
和setSuccess
。
Java Bean中关于setter/getter的规范
关于Java Bean中的getter/setter方法的定义其实是有明确的规定的,根据JavaBeans™ Specification规定,如果是普通的参数propertyName,要以以下方式定义其setter/getter:
public <PropertyType> get<PropertyName>();
public void set<PropertyName>(<PropertyType> a);复制ErrorOK!
但是,布尔类型的变量propertyName则是单独定义的:
public boolean is<PropertyName>();
public void set<PropertyName>(boolean m);复制ErrorOK!
序列化带来的影响
关于序列化和反序列化请参考Java对象的序列化与反序列化。我们这里拿比较常用的JSON序列化来举例,看看看常用的fastJson、jackson和Gson之间有何区别:
public class BooleanMainTest {public static void main(String[] args) throws IOException {//定一个Model3类型Model3 model3 = new Model3();model3.setSuccess(true);//使用fastjson(1.2.16)序列化model3成字符串并输出System.out.println("Serializable Result With fastjson :" + JSON.toJSONString(model3));//使用Gson(2.8.5)序列化model3成字符串并输出Gson gson =new Gson();System.out.println("Serializable Result With Gson :" +gson.toJson(model3));//使用jackson(2.9.7)序列化model3成字符串并输出ObjectMapper om = new ObjectMapper();System.out.println("Serializable Result With jackson :" +om.writeValueAsString(model3));}}class Model3 implements Serializable {private static final long serialVersionUID = 1836697963736227954L;private boolean isSuccess;public boolean isSuccess() {return isSuccess;}public void setSuccess(boolean success) {isSuccess = success;}public String getHollis(){return "hollischuang";}
}复制ErrorOK!
以上代码的Model3中,只有一个成员变量即isSuccess,三个方法,分别是IDE帮我们自动生成的isSuccess和setSuccess,另外一个是作者自己增加的一个符合getter命名规范的方法。
以上代码输出结果:
Serializable Result With fastjson :{"hollis":"hollischuang","success":true}
Serializable Result With Gson :{"isSuccess":true}
Serializable Result With jackson :{"success":true,"hollis":"hollischuang"}复制ErrorOK!
在fastjson和jackson的结果中,原来类中的isSuccess字段被序列化成success,并且其中还包含hollis值。而Gson中只有isSuccess字段。
我们可以得出结论:fastjson和jackson在把对象序列化成json字符串的时候,是通过反射遍历出该类中的所有getter方法,得到getHollis和isSuccess,然后根据JavaBeans规则,他会认为这是两个属性hollis和success的值。直接序列化成json:{“hollis”:“hollischuang”,“success”:true}
但是Gson并不是这么做的,他是通过反射遍历该类中的所有属性,并把其值序列化成json:{“isSuccess”:true}
可以看到,由于不同的序列化工具,在进行序列化的时候使用到的策略是不一样的,所以,对于同一个类的同一个对象的序列化结果可能是不同的。
前面提到的关于对getHollis的序列化只是为了说明fastjson、jackson和Gson之间的序列化策略的不同,我们暂且把他放到一边,我们把他从Model3中删除后,重新执行下以上代码,得到结果:
Serializable Result With fastjson :{"success":true}
Serializable Result With Gson :{"isSuccess":true}
Serializable Result With jackson :{"success":true}复制ErrorOK!
现在,不同的序列化框架得到的json内容并不相同,如果对于同一个对象,我使用fastjson进行序列化,再使用Gson反序列化会发生什么?
public class BooleanMainTest {public static void main(String[] args) throws IOException {Model3 model3 = new Model3();model3.setSuccess(true);Gson gson =new Gson();System.out.println(gson.fromJson(JSON.toJSONString(model3),Model3.class));}
}class Model3 implements Serializable {private static final long serialVersionUID = 1836697963736227954L;private boolean isSuccess;public boolean isSuccess() {return isSuccess;}public void setSuccess(boolean success) {isSuccess = success;}@Overridepublic String toString() {return new StringJoiner(", ", Model3.class.getSimpleName() + "[", "]").add("isSuccess=" + isSuccess).toString();}
}复制ErrorOK!
以上代码,输出结果:
Model3[isSuccess=false]复制ErrorOK!
这和我们预期的结果完全相反,原因是因为JSON框架通过扫描所有的getter后发现有一个isSuccess方法,然后根据JavaBeans的规范,解析出变量名为success,把model对象序列化城字符串后内容为{"success":true}
。
根据{"success":true}
这个json串,Gson框架在通过解析后,通过反射寻找Model类中的success属性,但是Model类中只有isSuccess属性,所以,最终反序列化后的Model类的对象中,isSuccess则会使用默认值false。
但是,一旦以上代码发生在生产环境,这绝对是一个致命的问题。
所以,作为开发者,我们应该想办法尽量避免这种问题的发生,对于POJO的设计者来说,只需要做简单的一件事就可以解决这个问题了,那就是把isSuccess改为success。这样,该类里面的成员变量时success,getter方法是isSuccess,这是完全符合JavaBeans规范的。无论哪种序列化框架,执行结果都一样。就从源头避免了这个问题。
String对“+”的重载
Java中,想要拼接字符串,最简单的方式就是通过"+"连接两个字符串。
有人把Java中使用+拼接字符串的功能理解为运算符重载。其实并不是,Java是不支持运算符重载的。这其实只是Java提供的一个语法糖。
运算符重载:在计算机程序设计中,运算符重载(英语:operator overloading)是多态的一种。运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
语法糖:语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。
前面提到过,使用+拼接字符串,其实只是Java提供的一个语法糖, 那么,我们就来解一解这个语法糖,看看他的内部原理到底是如何实现的。
还是这样一段代码。我们把他生成的字节码进行反编译,看看结果。
String wechat = "Hollis";
String introduce = "每日更新Java相关技术文章";
String hollis = wechat + "," + introduce;复制ErrorOK!
反编译后的内容如下,反编译工具为jad。
String wechat = "Hollis";
String introduce = "\u6BCF\u65E5\u66F4\u65B0Java\u76F8\u5173\u6280\u672F\u6587\u7AE0";//每日更新Java相关技术文章
String hollis = (new StringBuilder()).append(wechat).append(",").append(introduce).toString();复制ErrorOK!
通过查看反编译以后的代码,我们可以发现,原来字符串常量在拼接过程中,是将String转成了StringBuilder后,使用其append方法进行处理的。
那么也就是说,Java中的+对字符串的拼接,其实现原理是使用StringBuilder.append。
但是,String的使用+字符串拼接也不全都是基于StringBuilder.append,还有种特殊情况,那就是如果是两个固定的字面量拼接,如:
String s = "a" + "b"复制ErrorOK!
编译器会进行常量折叠(因为两个都是编译期常量,编译期可知),直接变成 String s = “ab”。
更多推荐
### [基本数据类型有什么好处](https://hollischuang.github.io/toBeTopJavaer/#/basics/java
发布评论