Java/数据类型

维基教科书,自由的教学读本

Java > 数据类型


上下文链接[编辑]

数据类型[编辑]

数据类型是程序设计语言描述事物、对象的方法。Java数据类型分为内置类型扩展类型两大类。内置类型就是Java语言本身提供的基本数据类型,比如,整型数,浮点数,字符,布尔值等等。而扩展类型则是Java语言根据基本类型扩展出的其他类型,Java要求所有的扩展类型都必须包括在类定义里面,这就是Java被叫做面向对象编程语言的原因。不同于像SmallTalk一样的纯粹的面向对象语言,Java保留了基本数据类型,但这主要是为满足Java高效率执行的需求。JRE(Java Runtime Environment)提供了丰富实用的扩展类型供程序员使用,程序员也可以根据需要通过继承机制自己扩展。

内置类型[编辑]

Java的内置类型包括如下几种:

类型名称 类型定义 类型取值
boolean 布尔值,作二元判断 true, false
byte 8位有符号整数 最小值-128,最大值127
short 16位有符号整数 最小值-32768,最大值32767
int 32位有符号整数 最小值-2147483648(-231),最大值2147483647(231-1)
long 64位有符号整数 -263~(263-1)
float 32位浮点数 1.4E-45~3.4028235E38
double 64位浮点数 4.9E-324~1.7976931348623157E308
char 32位Unicode字符(UTF-16) 不适用(可以与int互相转换)

内置类型也称作基本类型(Primitive Types),是其他类型的基础。所有的其他类型(包括Java核心库和用户自定义类型)都是通过基本类型扩展而来的。这些类型是我们描述对象最基本的方式。比如:判断是否使用boolean;衡量大小、长度使用各种整型(包括short, int, long)和浮点数(floatdouble);表达字符使用char;而处理二进制数据块使用byte。Java也提供了基本类型的类包裹(Wrapper),这些类包裹都包括在了java.lang包里面作为Java的语言基础,比如对int的类包裹是Integer类。提供这些类包裹的原因主要是为了在某些接口上提供和类(对象)一致的接口,比如在泛型设计上的数据模板等等。而且相应的包裹类中还提供了对基本数据类型的操作,比如int Integer.parseInt(String s, int radix)将字符串转化为相应进制的整型数。

浮点数[编辑]

浮点值的精度[编辑]

浮点值与我们通常所使用的小数是不同的。在使用中,它往往是难以确定的。常见的问题是定义了一个浮点数,经过一系列的计算,在数学上,它本来应该等于某个确定值 。

但是使用相等运算符(==)进行判断时,往往得出的结论是不等;或者本来应该大于/小于某个特定值,结果却可能刚好相反。因此,对于浮点值比较时,不能简单地使用==, >, <等运算符。

我们做计算时,往往都有一个精度,或由于客观事实,或由于我们的需要。这样我们比较两个浮点数时,就不是简单的使用比较运算符了,而是在允许的精度之内作比较。比如:比较两个浮点数(f1, f2)是否相等,精度为p

| f1 - f2 | < p   ----------  ''true''
| f1 - f2 | > p   ----------  ''false''

上面的||表示绝对值运算,java.lang.Math类对绝对值运算提供了支持。 同样,比较f1是否大于f2,如下:

( f1 - p ) > f2   ----------  ''true''

这种方法适用于大多数数据处理情况,但也有时候,必须是完全精确计算,这样就不能使用这种方法了,而应该采用java.math.BigDecimal和java.math.BigInteger来计算。

关于浮点数精度问题还可以参看您的小数点到哪里去了

值类型与引用类型[编辑]

不同于C/C++语言,Java中是看不到指针概念的,这是因为Java认为对指针的误操作会引起内存的外溢,而且对指针的管理应该是虚拟机本身的事情,而和程序无关。Java虚拟机会对引用对象的次数做出管理,并决定对那些引用次数变为零的对象在适当的时候做出回收释放内存的操作,这里称之为“垃圾回收”。比如:

String text = "World";
text = "Hello " + text;

在上段程序中,先行赋值了的text在下一段代码中会被赋予新值,做为旧值的对象"World"因为没有别的引用而不可能再被使用到,所以它将进入垃圾回收阶段。所以,大家可以看出,在Java中,对象的引用其实就类似于指针,只是少了C/C++中的“*”号,而且因为有垃圾回收机制,所以不用额外delete掉旧数据。对于熟知C/C++的朋友可以知道,在调用函数的时候,传递基本类型,传递指针和传递引用是有区别的,这里将示例如下:

int x = 10;

void func1(int a){
    a++; return;
}

void func2(int* p_a){
    (*p_a)++; 
    p_a++; 
    return;
}

void func3(int& a){
    a++; return;
}
int main(){
    func1(x);//x=10
    func2(x);//x=11
    func3(x);//x=12
    return 0;
}

如果将x传递分别传递到三个函数的结果是不一样的。对于func1来说,在传递的时候x将被赋值一遍给a,所以执行完a++以后,x的值是不变的还是10。而func2的参数是一个指针,传递的时候先取x的地址赋给p_a,执行完(*p_a)++之后因为p_a是指向x的,所以x本身会被加一,但是之后的p_a++只是将指针挪到下一个int数据上,并不会改变x的地址。最后,func3是传递引用,也就是说函数内的a和函数外的x本质上是同一个数,所以a++使得x本身加一。

看完C/C++的例子,我们来看Java的情况。因为Java分基本类型和对象类型两种,对于基本类型在调用函数的时候,Java都是传递值的,类似上例中的func1。而对于对象类型,Java是传递引用的,也就是说函数内和函数外是同一个对象。举个例子:

public class Test {
    public static void main(String[] args){
        StringBuffer buffer = new StringBuffer();
        for(String s : args){
            strcat(buffer, s);
        }
        System.out.println(buffer.toString());

        int x = 10;
        increaseOne(x);
        System.out.println(x);
    }

    public void strcat(StringBuffer sb, String s){
        sb.append(s);
    }

    // 错误示例
    public void increaseOne(int x){
        x++;
    }
}

上面的程序将命令行参数连接在一起并输出,其中strcat函数起到了连接作用,而我们用StringBuffer来构造新的字符串。在这里,buffer是在main函数里新建的StringBuffer,并传递给了strcat。注意,因为是引用传递,所以在strcat函数里的append操作其实就是main里的buffer,最后的输出的是连接起来的字符串。这里还给出了一个错误示范increaseOne,因为是传值调用,所以increaseOne执行完返回后,main函数里的x仍然是10。