在前边的博文中,我已经介绍了Java核心的容器IO等,如今我来讲一下java中的数据类型。在java中,一切东西皆为对象(这句话意思是java中绝大数状况都用对象),极少数不是对象的,也存在与之对应的对象(好比基本数据类型存在与之对应的包装类,数组有List对象能够代替)html
Java中数据类型 主要有“基本数据类型”、“String”、“引用类型” (基本的引用类型很少作介绍,在下一篇博文中着重介绍“枚举”,也算是引用类型的一种)java
byte、char、int、 float 、double、long...这些属于java的基本数据类型。具体用法能够参照 (Java基本数据类型总结 ) .在java看来,使用基本类型并非面向对象的设计,因而提供一些专门的包装类。实际开发中,不须要咱们考虑究竟是用基本类型仍是包装类(Java提供了自动装箱机制)。固然基本类型仍是有必要学习一下的。程序员
基本类型能够分为三类,字符类型char,布尔类型boolean以及数值类型byte、short、int、long、float、double。JAVA中的数值类型不存在无符号的,它们的取值范围是固定的,不会随着机器硬件环境或者操做系统的改变而改变 api
Java决定了每种简单类型的大小,并不随着机器结构的变化而变化。这正是Java程序具备很强移植能力的缘由之一。下表列出了Java中定义的简单类型、占用二进制位数及对应的封装器类。 数组
简单类型缓存 |
boolean安全 |
byteoracle |
charapp |
shortjvm |
Int |
long |
float |
double |
void |
二进制位数 |
1 |
8 |
16 |
16 |
32 |
64 |
32 |
64 |
-- |
封装器类 |
Boolean |
Byte |
Character |
Short |
Integer |
Long |
Float |
Double |
Void |
这张表能够简单的看一下,但不推荐花费太多时间(实际开发不须要,若是是应付考试仍是须要记一下的)。由于 Java语言之因此流行就是但愿程序员能够消耗更少的心力在语法上,从而省出更多的时间去整理具体的业务逻辑。在基本数据类型这一块,Java提供自动装箱机制,下面简单介绍一下自动装箱。
自动装箱就能够简单的理解为将基本数据类型封装为对象类型,来符合java的面向对象。好比你能够直接把一个int值复制给一个Integer对象
//声明一个Integer对象 Integer num = 10;
自动装箱的时候,存在一个细节点就是“对于值从–128到127之间的值,它们被装箱为Integer对象后,会存在内存中被重用,始终只存在一个对象”,测试以下
//在-128~127 以外的数
Integer num1 = 297; Integer num2 = 297;
System.out.println("num1==num2: "+(num1==num2)); // 在-128~127 以内的数 Integer num3 = 97; Integer num4 = 97; System.out.println("num3==num4: "+(num3==num4)); //测试结果:num1==num2: false \n num3==num4: true
有时候,只能用包装类,不能用基本数据类型,好比集合内
具体的细节能够参考这几位仁兄的博客 (Java 自动装箱与拆箱(Autoboxing and unboxing),java 自动装箱与拆箱)
基本类型存储在栈中,处于效率考虑,基本类型保存在栈中。延伸一下,包装类保存在堆中(注意我以前说过的-127到128之间的包装类Integer)
Java的堆是一个运行时数据区,不须要程序代码来显式的释放,由垃圾回收来负责便可。堆的优点是能够动态地分配内存 大小,生存期也没必要事先告诉编译器(由于是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些再也不使用的数据)。但缺点是,因为要在运行时动态分配内存,存取速度较慢。
栈的优点是,存取速度比堆要快,仅次于寄存器,栈数据能够共享。但缺点是,存在栈中的数据大小与生存期必须是肯定的,缺少灵活性。栈中主要存放一些基本类型的变量数据(int, short, long, byte, float, double, boolean, char)和引用数据类型(这个和基本数据类型无关,很少作介绍)
//基本数据类型的共享和对象不同,对象共享的本质上是引用,对象修改会影响另一个变量,基本类型只是共享的值,当值修改时,其实是让变量又从新指向了另外一个地方
public static void main(String[] args) { int a = 5; int b = 5;//必定是先找栈里有没有5,有就让b也指向5 a = 6; //先找栈里有没有6,若是没有则新建6并让a指向
System.out.println(b); }
对于成员变量和局部变量:成员变量就是方法外部,类的内部定义的变量;局部变量就是方法或语句块内部定义的变量。局部变量必须初始化。
1.class BirthDate {
2. private int day; //day是成员变量
3. private int month;
4. private int year;
5. public BirthDate(int d, int m, int y) {
6. day = d; //d是局部变量
7. month = m;
8. year = y;
9. }
10. 省略get,set方法………
11.}
简单类型数据间的转换,有两种方式:自动转换和强制转换,一般发生在表达式中或方法的参数传递时。
当一个较"小"数据与一个较"大"的数据一块儿运算时,系统将自动将"小"数据转换成"大"数据,再进行运算 。
这些类型由"小"到"大"分别为 (byte,short,char)--int--long--float—double,这里咱们所说的"大"与"小",并非指占用字节的多少,而是指表示值的范围的大小 。因此byte --char--short之间不能够自动转换
将"大"数据转换为"小"数据时,你必须使用强制类型转换。即你必须采用下面这种语句格式: int n=(int)3.14159/2;能够想象,这种转换确定可能会致使溢出或精度的降低
只有boolean不参与数据类型的转换
在实际开发中 String使用很是普遍。因而Java设计者针对String作了很是多的优化来提升效率,这虽然提升了程序的效率,可是在必定程度上也会给咱们开发提升了难度,因而在Thinking in Java中单独把String看成一个章节。下面我会从总体上总结一下String,一些具体的方法能够去查询API(Java API)
new是按照面向对象的标准语法,在内存使用上存在比较大的浪费。因此String对象的建立是不须要new的(这样能够提升效率,可是若是用new建立字符串也不会报错)
分别是 new 关键字、Class类的 newInstance 方法、Constructor类的 newInstance 方法、String对象的 clone方法、反序列化机制。可是String对象还有一种特殊的建立方式,就是经过使用 “ 或 ’ 包裹字符序列
public static void main(String[] args) { String s = "Hello World!";//实际上当""的时候java就建立了该对象 System.out.println(s);
}
下面的代码详细的对比了java的正常建立形式(“”)和 new的区别 (参照自 深刻理解Java:String ),在这里,我推荐一下 String的原理与用法总结 。该博主图画的仍是挺清晰的,一目了然
public static void main(String[] args) {
String s1 = "abc"; // ↑ 在字符串池建立了一个对象 String s2 = "abc"; // ↑ 字符串pool已经存在对象“abc”(共享),因此建立0个对象,累计建立一个对象 System.out.println("s1 == s2 : " + (s1 == s2)); // ↑ true 指向同一个对象, System.out.println("s1.equals(s2) : " + (s1.equals(s2))); String s3 = new String("abc"); // ↑ 建立了两个对象,一个存放在字符串池中,一个存在与堆区中; // ↑ 还有一个对象引用s3存放在栈中 String s4 = new String("abc"); // ↑ 字符串池中已经存在“abc”对象,因此只在堆中建立了一个对象 System.out.println("s3 == s4 : " + (s3 == s4)); // ↑false s3和s4栈区的地址不一样,指向堆区的不一样地址 System.out.println("s3.equals(s4) : " + (s3.equals(s4))); // ↑true s3和s4的值相同 System.out.println("s1 == s3 : "+(s1==s3)); //↑false 存放的地区多不一样,一个栈区,一个堆区 System.out.println("s1.equals(s3) : "+(s1.equals(s3))); //↑true 值相同 /** * 情景三: * 因为常量的值在编译的时候就被肯定(优化)了。 * 在这里,"ab"和"cd"都是常量,所以变量str3的值在编译时就能够肯定。 * 这行代码编译后的效果等同于: String str3 = "abcd"; */ String str1 = "ab" + "cd"; //1个对象 String str11 = "abcd"; System.out.println("str1 = str11 : "+ (str1 == str11)); /** * 情景四: * 局部变量str2,str3存储的是存储两个拘留字符串对象(intern字符串对象)的地址
* 第三行代码原理(str2+str3): * 运行期JVM首先会在堆中建立一个StringBuilder类, * 同时用str2指向的拘留字符串对象完成初始化, * 而后调用append方法完成对str3所指向的拘留字符串的合并, * 接着调用StringBuilder的toString()方法在堆中建立一个String对象, * 最后将刚生成的String对象的堆地址存放在局部变量str4中
* 而str5存储的是字符串池中"abcd"所对应的拘留字符串对象的地址。 * str4与str5地址固然不同了 * 内存中实际上有五个字符串对象: * 三个拘留字符串对象、一个String对象和一个StringBuilder对象。 */ String str2 = "ab"; //1个对象 String str3 = "cd"; //1个对象 String str4 = str2+str3; String str5 = "abcd"; System.out.println("str4 = str5 : " + (str4==str5)); // false //↑------------------------------------------------------over /** * 情景五: * JAVA编译器对string + 基本类型/常量 是当成常量表达式直接求值来优化的。 * 运行期的两个string相加,会产生新的对象的,存储在堆(heap)中 */ String str6 = "b"; String str7 = "a" + str6; String str67 = "ab"; System.out.println("str7 = str67 : "+ (str7 == str67)); //↑str6为变量,在运行期才会被解析。 final String str8 = "b"; String str9 = "a" + str8; String str89 = "ab"; System.out.println("str9 = str89 : "+ (str9 == str89)); //↑str8为常量变量,编译期会被优化 }
用“”建立对象的时候,String对象是放到常量池中,只会建立一个,每次都是先去找一下常量池有没有该字符串
用 new建立对象,会在队中建立一个对象,而后在栈内建立该对象应用,每次都是新建立
String类初始化以后不可变,由于java设计者不但愿咱们方法传参是字符串的时候,方法内修改会影响外边的串,因此采起了一种传递拷贝的方式(也就是传值)
String ss = "this is the origen String";
TestString.showString(ss);
public static void showString(String s){ System.out.println(s); }
java中,一旦产生String对象,该对象就不会在发生变化。可是String另外一方面的确提供了修改String的方法。这看起来很矛盾,其实是咱们没有仔细的了解那些修改的方法
好比replace(),若是能够看到源码,能够清楚的看到该方法实际上新产生一个字符串,替换操做是针对新的字符串。(下图参考自参考Java进阶01 String类,简单的表示replace()方法调用时
s的变化)
下边的代码 :我在原字符串的基础上添加了一句话,而后判断他们是否相同(若是是同一个对象修改,==输出结果应该是true)
String s1 = "我";
s1+="我想在加点东西";
system.out.println(s1 == s2)//输出结果是false
思考一下 "s1指向的对象中的字符串是什么"(咱们潜意识的认为s1也会被修改,可是当s2 = "s2"时,实际上s2的引用已经被修改,它和s1不要紧了)
String s1 = "s1";
String s2 = s1; s2 = "s2";//s1指向的对象中的字符串是什么?
System.out.println(s1);//输出结果是s1
再重复一遍,不管是修改字符串的方法仍是对字符串赋值,都和普通的对象不一样。赋值是让字符串指向一个新的字符串,方法传参是copy一份值,传入进去。
再好比说:String str=”kv”+”ill”+” “+”ans”; 就是有4个字符串常量,首先”kv”和”ill”生成了”kvill”存在内存中,而后”kvill”又和” ” 生成 “kvill “存在内存中,最后又和生成了”kvill ans”;并把这个字符串的地址赋给了str
因此 + 会产生不少临时变量。下文中会说到StringBuilder 来避免这种状况。不过有一种特殊状况。 "ab"+"cd" 在JVM编译后和"abcd"同样
String str1 = "ab" + "cd"; //1个对象
String str11 = "abcd";
System.out.println("str1 = str11 : "+ (str1 == str11));
在String源码中 ,使用private final char value[]来实现字符串的存储,就是由于final,才说String类型是不可变
/** The value is used for character storage. */ private final char value[];
深刻一点,咱们了解一下String保存的位置(能够参考java+内存分配及变量存储位置的区别[转],java中的String类常量池详解)
String常量是保存在常量池中。JVM中的常量池在内存当中是以表的形式存在的, 对于String类型,有一张固定长度的CONSTANT_String_info表用来存储文字字符串值,注意:该表只存储文字字符串值,不存储符号引用。说到这里,对常量池中的字符串值的存储位置应该有一个比较明了的理解了。在程序执行的时候,常量池会储存在Method Area,而不是堆中。常量池中保存着不少String对象; 而且能够被共享使用,所以它提升了效率
常量池指的是在编译期被肯定,并被保存在已编译的.class文件中的一些数据。
除了包含代码中所定义的各类基本类型(如int、long等等)和对象型(如String及数组)的常量值(final)还包含一些以文本形式出现的符号引用。
整体来讲,若是你仍是对String感到困惑,不如把握住一点就是“ 建立时间” 若是编译期就知道是啥,会丢到常量池.
单独使用""引号建立的字符串都是常量,编译期就已经肯定存储到String Pool中;
使用new String("")建立的对象会存储到heap中,是运行期新建立的;
使用只包含常量的字符串链接符如"aa" + "aa"建立的也是常量,编译期就能肯定,已经肯定存储到String Pool中;
使用包含变量的字符串链接符如"aa" + s1建立的对象是运行期才建立的,存储在heap中;
intern(): String实例调用该方法可让JVM检查常量池,若是没有实例的value属性对应的字符串序列,就将本实例放入常量池,若是有则返回常量池中相对应的实例的引用而不是当前实例的引用
首先StringBuilder 和StringBuffer区别是 StringBuffer线程安全(存在一堆synchronized)
其次,咱们推荐用StringBuilder 而不是+ ,虽然+号在jvm中本质也是建StringBuilder,可是每s = s+"1";都会引入一个StringBuilder对象
String [] aaa = {"1","2","3"};
for (String s : aaa) { s+="1";//每循环一次,都会产生一个StringBuilder对象 }
另外:StringBuilder容许咱们在声明的时候指定大小,避免咱们屡次分配缓存
Java有 5种引用类型(对象类型):类 接口 数组 枚举 标注。
new 的对象会放到java堆中,而后把引用放到栈内,这里很少加叙述