Java数据类型在实际开发中的应用一

时间:2021-06-29 15:18:42

  在前边的博文中,我已经介绍了Java核心的容器IO等,现在我来说一下java中的数据类型。在java中,一切东西皆为对象(这句话意思是java中绝大数情况都用对象),极少数不是对象的,也存在与之对应的对象(比如基本数据类型存在与之对应的包装类,数组有List对象与之对应)

  Java中数据类型 主要有基本数据类型,String ,(我考虑了好久,还是决定把枚举也当作是java数据类型,因篇幅问题,放到下一遍博文中说明)

一:基本数据类型 

基本数据类型的定义

  int float double char ...这些属于java的基本数据类型。在java看来,这并不是面向对象的设计,所以java会提供一些专门的包装类。

至于具体用法,可以参考Java基本数据类型总结

1 具体的基本类型

   基本类型可以分为三类,字符类型char,布尔类型boolean以及数值类型byte、short、int、long、float、double。JAVA中的数值类型不存在无符号的,它们的取值范围是固定的,不会随着机器硬件环境或者操作系统的改变而改变   

Java决定了每种简单类型的大小,并不随着机器结构的变化而变化。这正是Java程序具有很强移植能力的原因之一。下表列出了Java中定义的简单类型、占用二进制位数及对应的封装器类。

简单类型

boolean

byte

char

short

Int

long

float

double

void

二进制位数

1

8

16

16

32

64

32

64

--

封装器类

Boolean

Byte

Character

Short

Integer

Long

Float

Double

Void

这些不需要我们记忆,进一步说,这些只是为了让你了解Java.JVM会帮我完成基本类型和它们对应包装类之间的自动转换。这些数据类型的长度在某种程度上是对我们不透明的 。我们只需要知道赋值,参数传递,运算的时候也可以用包装类。

另外,所有基本类型(包括void)的包装类都使用了final修饰。所以我们也不需要考虑扩展。 

基本数据类型的保存位置

  java基本类型存储在栈中,它们的存取速度要快于存储在堆中的对应包装类的实例对象。

堆栈的简单介绍

  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.}

java中常量

  • 十六进制整型常量:以十六进制表示时,需以0x或0X开头,如0xaa,0X9f。
  • 八进制整型常量:八进制必须以0开头,如0123,034。
  • 长整型:             长整型必须以L作结尾,如9L,342L。
  • 浮点数常量:      由于小数常量的默认类型是double型,所以float类型的后面一定要加f(F)。同样带小数的变量默认为double类型。 如:float f;f=1.3f;//必须声明f。
  • 字符常量:         字符型常量需用两个单引号括起来(注意字符串常量是用两个双引号括起来)。Java中的字符占两个字节。

Java中类型转换

简单类型数据间的转换,有两种方式:自动转换和强制转换,通常发生在表达式中或方法的参数传递时。

自动转换:是JVM根据条件自动帮助我们转换,可以简单了解一下转换规则。

  当一个较"小"数据与一个较"大"的数据一起运算时,系统将自动将"小"数据转换成"大"数据,再进行运算  。

这些类型由"小"到"大"分别为 (byte,short,char)--int--long--float—double,这里我们所说的"大"与"小",并不是指占用字节的多少,而是指表示值的范围的大小 。所以byte --char--short之间不可以自动转换

强制转换:

 将"大"数据转换为"小"数据时,你必须使用强制类型转换。即你必须采用下面这种语句格式: int n=(int)3.14159/2;可以想象,这种转换肯定可能会导致溢出或精度的下降

  1.  表达式的数据类型自动提升  eg 所有的byte,short,char型的值将被提升为int型;
  2.  包装类和基本类型转换 :实际上Java存在一个自动装箱的机制,他可以自动转换。但是,我们可以用构造器转为包装类;用包装类对象的xxxValue()把包装类转为基本类型
  3.  其它类型间转为字符串:①调用类的串转换方法:X.toString();  ②自动转换:X+"";  ③使用String的方法:String.volueOf(X);
  4.  字符串转为其它类型  ①先转换成相应的封装器实例,再调用对应的方法转换成其它类型 new Double("3.1").doubleValue().或者Double.valueOf("32.1").doubleValue()②静态parseXXX方法String s = "1";byte b = Byte.parseByte( s );
  5. Date类与其它数据类型的相互转换 :整型和Date类之间并不存在直接的对应关系,只是你可以使用int型为分别表示年、月、日、时、分、秒,这样就在两者之间建立了一个对应关系 。具体转换,可能用到format类

备注              

  只有boolean不参与数据类型的转换  

二:String类型(Java API

  Java中String使用之广,我不在多做介绍,由于String的频繁使用使设计人员在string的设计上花大量的功夫。可以说String源码包含很多精妙的设计思想

2.1 String创建方式

  new 是按照面向对象的标准语法,在内存使用上存在比较大的浪费。所以String对象的创建是不需要new的,这就是String的妙处之一。当然你非要用new也是可以的

    public static void main(String[] args)
    {
        String s = "Hello World!";//实际上当""的时候java就创建了该对象
        System.out.println(s); 
}

下面的代码详细的对比了java的正常创建形式(“”)和 new的区别  (参照自深入理解Java:String) 在这里,延伸一下,java中创建对象的方式一共存在五种:

  它们分别是 new 关键字、Class类的 newInstance 方法、Constructor类的 newInstance 方法、String对象的 clone方法、反序列化机制。但是String对象还有一种特殊的创建方式,就是通过使用  或  包裹字符序列。现在,我们重点关注一下 new关键字 与 字符序列 这两种创建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对象的堆地址存放在局部变量str3中
* 而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 abc  ="abc";  String s = abc+"s";这种情况和 String s1 = "abcs"是一样的,具体原因如下:

  事实上JVM编译代码的时候会把一些常量做修改优化,你可以看一下啊下边的源码,在编译期就解析为String s = "abcs"

/*    */public static void main(String[] args)
    /*    */{
        /*  5 */String s1 = "abc";
        /*    */
        /*  7 */String s2 = "abc";
        /*    */
        /*  9 */System.out.println("s1 == s2 : " + (s1 == s2));
        /*    */
        /* 11 */System.out.println("s1.equals(s2) : " + s1.equals(s2));
        /* 12 */String s3 = new String("abc");
        /*    */
        /* 15 */String s4 = new String("abc");
        /*    */
        /* 17 */System.out.println("s3 == s4 : " + (s3 == s4));
        /*    */
        /* 19 */System.out.println("s3.equals(s4) : " + s3.equals(s4));
        /*    */
        /* 21 */System.out.println("s1 == s3 : " + (s1 == s3));
        /*    */
        /* 23 */System.out.println("s1.equals(s3) : " + s1.equals(s3));
        /*    */
        /* 31 */String str1 = "abcd";
        /* 32 */String str11 = "abcd";
        /* 33 */System.out.println("str1 = str11 : " + (str1 == str11));
        /*    */
        /* 51 */String str2 = "ab";
        /* 52 */String str3 = "cd";
        /* 53 */String str4 = str2 + str3;
        /* 54 */String str5 = "abcd";
        /* 55 */System.out.println("str4 = str5 : " + (str4 == str5));
        /*    */
        /* 62 */String str6 = "b";
        /* 63 */String str7 = "a" + str6;
        /* 64 */String str67 = "ab";
        /* 65 */System.out.println("str7 = str67 : " + (str7 == str67));
        /*    */
        /* 67 */String str8 = "b";
        /* 68 */String str9 = "ab";
        /* 69 */String str89 = "ab";
        /* 70 */System.out.println("str9 = str89 : " + (str9 == str89));
        /*    */}

2.2 String类初始化后是不可变的(immutable)

String类初始化之后不可变,因为java设计者不希望我们方法传参是字符串的时候,方法内修改会影响外边的串,所以采取了一种传递拷贝的方式(也就是传值)

 String ss = "this is the origen String";
 TestString.showString(ss);
public static void showString(String s){
    System.out.println(s);
}

Java中String不可变是怎么一回事

  java中,一旦产生String对象,该对象就不会在发生变化。但是,String另一方面的确提供了修改String的方法。这看起来很矛盾,但实际上,是我们没有仔细的了解那些修改的方法说明。

比如replace(),实际上是通过创建一个新的对象来实现的,而不是对原有对象进行修改。(下图参考自参考Java进阶01 String类,简单的表示replace()方法调用时,s的变化)

                                  Java数据类型在实际开发中的应用一

下边的代码 :我在原字符串的基础上添加了一句话,然后判断他们是否相同(如果是同一个对象修改,==输出结果应该是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 来避免这种情况。

Java是怎样做到String的不可变的

在String源码中 ,使用private final char value[]来实现字符串的存储,就是因为final,才说String类型是不 可变的(immutable)

 /** The value is used for character storage. */
    private final char value[];

2.3 String的保存位置

  深入一点,我们了解一下String保存的位置(可以参考java+内存分配及变量存储位置的区别[转]java中的String类常量池详解

  String常量是保存在常量池中。JVM中的常量池在内存当中是以表的形式存在的, 对于String类型,有一张固定长度的CONSTANT_String_info表用来存储文字字符串值,注意:该表只存储文字字符串值,不存储符号引用。说到这里,对常量池中的字符串值的存储位置应该有一个比较明了的理解了。在程序执行的时候,常量池会储存在Method Area,而不是堆中。常量池中保存着很多String对象; 并且可以被共享使用,因此它提高了效率

  什么是常量池

  常量池指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。

除了包含代码中所定义的各种基本类型(如int、long等等)和对象型(如String及数组)的常量值(final)还包含一些以文本形式出现的符号引用。

2.4主要的方法   

  • s.length()                 返回s字符串长度
  • s.charAt(2)                 返回s字符串中下标为2的字符
  • s.substring(0, 4)             返回s字符串中下标0到4的子字符串
  • s.indexOf("Hello")            返回子字符串"Hello"的下标
  • s.startsWith(" ")             判断s是否以空格开始
  • s.endsWith("oo")              判断s是否以"oo"结束
  • s.equals("Good World!")         判断s是否等于"Good World!" ==只能判断字符串是否保存在同一位置。需要使用equals()判断字符串的内容是否相同。
  • s.compareTo("Hello Nerd!")       比较s字符串与"Hello Nerd!"在词典中的顺序, 返回一个整数,如果<0,说明s在"Hello Nerd!"之前  如果>0,说明s在"Hello Nerd!"之后 如果==0,说明s与"Hello Nerd!"相等。
  • s.trim()                   去掉s前后的空格字符串,并返回新的字符串
  • s.toUpperCase()              将s转换为大写字母,并返回新的字符串
  • s.toLowerCase()              将s转换为小写,并返回新的字符串
  • s.replace("World", "Universe")    将"World"替换为"Universe",并返回新的字符串

2.5:简单概括总结String中的细节点

  总体来说,如果你还是对String感到困惑,不如把握住一点就是“ 创建时间” 如果编译期就知道是啥,会丢到常量池.

  1.  单独使用""引号创建的字符串都是常量,编译期就已经确定存储到String Pool中;

  2.  使用new String("")创建的对象会存储到heap中,是运行期新创建的;  

  3.  使用只包含常量的字符串连接符如"aa" + "aa"创建的也是常量,编译期就能确定,已经确定存储到String Pool中;

  4. 使用包含变量的字符串连接符如"aa" + s1创建的对象是运行期才创建的,存储在heap中;

  5. intern(): String实例调用该方法可以让JVM检查常量池,如果没有实例的value属性对应的字符串序列,就将本实例放入常量池,如果有则返回常量池中相对应的实例的引用而不是当前实例的引用

2.6 StringBuilder 和StringBuffer 和 +

  首先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引用类型

Java有 5种引用类型(对象类型):类 接口 数组 枚举 标注。

new 的对象会放到java堆中,然后把引用放到栈内,这里不多加叙述

  

相关文章