第三章、基本数据类型和变量


Java 语言把数据类型分为基本类型和引用类型。

3.1 基本数据类型

数据类型关键字在内存中占用字节数取值范围默认值
布尔型boolean1字节(8bit)true, falsefalse
字节型byte1字节(8bit) − 128 -128 128 ~ 127 127 1270
字符型char2字节(16bit)0 ~ 2 16 − 1 2^{16} - 1 2161‘\u0000’
短整型short2字节(16bit) − 2 15 -2^{15} 215 ~ 2 15 − 1 2^{15} - 1 21510
整型int4字节(32bit) − 2 31 -2^{31} 231 ~ 2 31 − 1 2^{31} - 1 23110
长整型long8字节(64bit) − 2 63 -2^{63} 263 ~ 2 63 − 1 2^{63} - 1 26310
单精度浮点型float4字节(32bit) 1.4013 E − 45 1.4013E-45 1.4013E45 ~ 3.4028 E + 38 3.4028E+38 3.4028E+380.0F
双精度浮点型double8字节(64bit) 4.9 E − 324 4.9E-324 4.9E324 ~ 1.7977 E + 308 1.7977E+308 1.7977E+3080.0D

boolean 类型的变量的赋值只能是 true 或 false。但 Java 虚拟机对 boolean 类型的处理时会用 int 或 byte 来表示 boolean。Java 虚拟机这种底层处理方式对 Java 源程序是透明的。

byteshortintlong 都是整数类型,并且都是有符号整数。如果一个整数值在某种整数类型的取值范围内,就可以把它直接赋值给这种类型的变量,否则必须进行强制类型的转换。

byte b = (byte)129;  // b = -127 直接截取后 8 位,把它赋值给变量b

char 是字符类型, Java 语言对字符采用 Unicode 字符编码 。Java 语言采用转义字符来表示单引号和其他特殊字符:

转义字符描述
\n换行符,将光标定位在下一行的开头
\t垂直制表符,将光标移到下一个制表符的位置
\r回车,将光标定位在当前行的开头;不会跳到下一行
\\代表反斜杠字符
\'代表单引号字符
\"代表双引号字符

floatdouble 类型都遵循 IEEE 754 标准,该标准分别为 32 位和 64 位浮点数规定了二进制表示形式。 对于 float 浮点数,用 1 位表示数字的 符号,用 8 位来表示 指数(底数为 2) ,用 23 位来表示尾数。对于 double 浮点数,用 1 位表示数字的符号,用 11 位表示指数(底数为2), 52 位表示尾数。

|  S   |   EEEE EEEE   | MMMM MMMM MMMM MMMM MMMM MMM |
| sign | exponent bias |          fraction            |
Value = sign x exponent x fraction

float类型的二进制存储规则

S(1bit)E(8bit)M(23bit)N(32bit)
符号00表示0.0
符号0非0 ( − 1 ) S × 2 126 × ( 0. M ) (-1)^{S} \times 2^{126} \times (0.M) (1)S×2126×(0.M)
符号1 ~ 254非0 ( − 1 ) S × 2 E − 127 × ( 1. M ) (-1)^{S} \times 2^{E - 127} \times (1.M) (1)S×2E127×(1.M)
符号255非0表示特殊数字

3.2 引用类型

引用类型可分为类引用类型、接口引用类型和数组引用类型。类引用类型的变量引用这个类或者共子类的实例,接口引用类型的变量引用实现了这个接口的类的实例,数组引用类型的变量引用这个数组类型的实例 。

Employee employee;              //类引用类型
java.lang.Runnable myThread;    //接口引用类型
int[] myArray;                  //数组引用类型

基本类型与引用类型有以下区别:

  • 括本类型代表简单的数据类型,引用类型所引用的实例能表示任意一种复杂的数据结构。
  • 基本类型仅表示数据类型,而引用类型所引用的实例除了表示复杂数据类型,还能包括操纵这种数据类型的行为。以表示字符串的 String 类为例 ,它包含了各种操纵字符串的方法,如 substring() 方法能够截取子字符串。
  • Java 虚拟机处理引用类型变量和基本类型变量的方式是不一样的:对于基本类型的变量,Java 虚拟机会为其分配数据类型实际占用的内存大小,而对于引用类型变量,它仅仅是一个指向堆(栈)区中某个实例的指针。

当一个引用类型的变量被声明后,如果没有初始化,那么它不指向任何对象。 Java语言用 new 关键字创建对象, 它有以下作用:

  • 为对象分配内存空间,将对象的实例变量自动初始化为其变最类型的默认值。
  • 如果实例变址在声明时被显式初始化 ,那就把初始化值赋给实例变量。
  • 调用构造方法。
  • 返回对象的引用。

对于下述 Sample 类源程序, Java虚拟机执行创建实例的语句步骤如下:

public class Sample {
    int member0;
    int member1 = 1;
    int member2;

    public Sample() {
        member2 = 2;
    }

    public static void main(String[] args) {
        Sample obj = new Sample();
    }
}
//               __Sample___
//              |member0 = 0|
//  obj  --->   |member1 = 1|
//              |member2 = 2|
  1. 为一个新的 Sample 对象分配内存空间,它所有的成员变量都被分配了内存,并自动初始化为其变量类型的默认值。此时有member0 = 0 member1 = 0 member2 = 0
  2. 显式初始化 member1 变量,设置其值为1。此时有member1 = 1
  3. 调用构造器方法,显式初始化成员变量member。此时有member2 = 2
  4. 将对象的引用赋值给变量 obj

一个对象可以被多个引用变量引用,例如:

Employee employee1 = new Employee("Alice");
Employee employee2 = new Employee("Bob");
Employee employee3 = employee1;
Employee employee4 = null;
//               ______堆区_______
//  employee1-->| Employee(Alice) |<--employee3
//  employee2-->| Employee(Bob)   |
//              |_________________|   employee4 = null 

3.3 变量的作用域

变量的作用域是指它的存在范围,只有在这个范围内,程序代码才能访问它。其次,作用域决定了变量的生命周期,变量的生命周期是指从一个变量被创建并分配内存开始,到这个变量被销毁并清除内存的过程。当一个变量被定义时,它的作用域就被确定了。按照作用域的不同,变量可分为以下类型:

  • 成员变量:在类中声明,它的作用域是整个类。
  • 局部变量:在一个方法的内部或方法的一个代码块的内部声明。如果在一个方法内部声明,它的作用域是整个方法;如果在一个方法的某个代码块的内部声明,那么它的作用域是这个代码块。
  • 方法参数:方法或者构造方法的参数 ,它的作用域是整个方法或者构造方法。
  • 异常处理参数:异常处理参数传递参数给异常处理代码块,异常处理参数是指 catch(Exception e) 语句中的异常参数 e ,它的作用域是紧跟着 catch(Exception e) 语句后的代码块。

静态变量和实例变量的生命周期

类的成员变量有两种: 一种是被 static 关键字修饰的变量,叫类变量,或静态变量;一种是没有被 static 关键字修饰的变量, 叫实例变量。静态变量和实例变量的区别在于:

  • 类的静态变量在内存中只有一个,Java 虚拟机在加载类的过程中为静态变量分配内存,静态变量位于方法区,被类的所有实例共享。静态变量可以直接通过类名被访问。静态变量的生命周期取决于类的生命周期,当加载类的时候,静态变量被创建并分配内存,当卸载类的时候,静态变量被销毁并撤销内存。
  • 类的每个实例都有相应的实例变量。每创建一个类的实例, Java 虚拟机就会为实例变量分配一次内存,实例变量位于堆区中。实例变量的生命周期取决于实例的生命周期 ,当创建实例的时候,实例变量被创建并分配内存,当销毁实例的时候 ,实例变量被销毁并撤销内存。
public class Counter {
    public int count1 = 0;                  //实例变量
    public static int count2 = 0;           //静态变量

    public static void main(String[] args) {
        Counter A = new Counter();
        Counter B = new Counter();
        A.counter1++;
        A.counter2++;                       //也可以通过类名来访问count2
        B.counter1++;       
        B.counter2++;                       //等同于Counter.count2++;
    }
}
//     ___________堆区_______     ________方法区_____
// A->|  Counter:count1 = 1 |--> |                  |
// B->|  Counter:count1 = 1 |--> |Counter:count2 = 2|
// A,B变量分别引用不同的Counter实例,每个实例都有单独的count1实例变量。
// 位于方法区的count2静态变量被两个实例共享。

局部变量的生命周期

局部变量的生命周期取决于所属的方法何时被调用及结束调用:

  • 当 Java 虚拟机中的某个线程调用一个方法时,会为这个方法中的局部变量分配内存。
  • 当 Java 虚拟机中的某个线程结束调用一个方法时,会结束这个方法中局部变量的生命周期。

由于局部变量和成员变量有着完全不同的生命周期,在使用局部变量时,受到以下限制:

  • 局部变量不能被 staticprivateprotectedpublic 等修饰符修饰 。
  • 不能通过类名或引用变量名来访问局部变量。

3.4 对象的默认引用:this

当一个对象创建好后, Java 虚拟机就会给它分配一个引用自身的指针:this。所有对象默认的引用名均为 this。Java 虚拟机如何区分这些同名的 this 呢?当 Java 虚拟机在执行 Dog 对象的 setOwner() 方法时,它肯定明确地知道到底执行哪个 Dog 对象的 setOwner() 方法,因此在 setOwner()方法中的 this 就引用当前 Dog 对象。同样,当 Java 虚拟机在执行 Owner 对象的 setDog()方法时,它肯定明确地知道到底执行哪个 Owner 对象的 setDog()方法,因此,在 setDog() 方法中的 this 就引用当前 Owner 对象 。

class Owner {
    private Dog dog;
    public Dog getDog() {
        return dog;
    }
    public void setDog(Dog dog) {
        this.dog = dog;                 //this 指当前 Owner 对象
    }
}

public class Dog{
    private Owner owner;
    public Owner getOwner() {
        return owner;
    }
    public void setOwner(Owner owner) {     
        if (this.owner != null)
            this.owner.setDog(null);        
        this.owner = owner;             //this 指当前 Dog 对象
        owner.setDog(this);
    }

    public static void main(String[] args) {
        Owner o1 = new Owner();
        Owner o2 = new Owner();
        Dog d1 = new Dog();

        d1.setOwner(o1);
        d1.setOwner(o2);
    }
}

在程序中,在以下情况会使用 this 关键字:

  1. 在类的构造方法中,通过 this 语句调用这个类的另 一个构造方法,参见重载构造方法。
  2. 在一个实例方法内,局部变量或参数和实例变量同名,实例变量被屏蔽,因此采用 this.owner 的方式来指代实例变量。

3.5 参数传递

如果方法 A 调用方法 B, 那么称方法 A 是方法 B 的调用者 。 如果方法 B 的参数是基本数据类型,那么方法 A 向方法 B 传递参数的值 。 如果方法 B 的参数是对象或数组,那么方法 A 向方法 B 传递对象或数组的引用。

public class Param {
    public int member = 0;                  //成员变量

    public static void main(String[] args) {
        int p1 = 0;                         //基本数据类型
        Param p2 = new Param();
        Param p3 = new Param();             //对象引用类型
        int[] p4 = {0};                     //数组引用类型

        changeParameter(p1, p2, p3, p4);

        System.out.printf("%d %d %d %d", p1, p2.member, p3.member, p4[0]);
        //打印结果:0 1 0 1 (p1 = 0, p2.member = 1, p3.member = 0, p4[0] = 1)
    }

    public static void changeParameter(int p1, Param p2, Param p3, int[] p4) {
        p1 = 1;                     //改变基本数据类型的参数值
        p2.member = 1;              //改变对象类型的实例变量
        p3 = new Param();           //改变对象类型参数引用,使他引用一个新的对象
        p3.member = 1;              //改变新的对象的实例变量
        p4[0] = 1;                  //改变数组类型参数的元素
    }
}

Java 虚拟机会创建一个主线程,该线程从程序入口 main()方法开始执行。主线程在 Java 栈区内有一个方法调用栈,每执行一个方法,就会向方法调用栈中压入一个包含该方法的局部变量及参数的栈桢。

主线程首先把 main() 方法的栈桢压入方法调用栈,在这个栈桢中包含 4 个 param 局部变量。当主线程开始执行 changeParameter()方法时,会把该方法的栈桢也压入方法调用栈。在这个栈桢中包含 4 个 param 参数,它们的初始值由 main() 方法的 4 个 param 局部变量传递。即此时Java栈区有两个方法栈帧:changeParament() 方法帧和main() 方法帧(自顶向下)。

main() 方法把 p1 局部变量的值 传给 changeParameter() 方法的第一个参数,此外,p2、p3 和 p4 局部变量的引用 分别传给changeParameter() 方法的剩余参数。

接下来主线程执行 changeParameter(),即将退出该方法时,changeParameter()的方法调用栈的状态与main() 方法栈帧中的 p3 参数不再引用同一个 Param 对象。在退回到 main() 方法之前 , 会把方法调用栈中 changeParameter() 方法的栈桢弹出,该栈桢中包含的 4 个 param 参数都结束生命周期。

主线程继续执行 main() 方法,此时打印的 p3 是之前 main() 帧中存的旧的 p3 对象,而之前创建的新的赋值为1的 p3 对象随着 changeParameter() 方法结束而消亡了。

3.6 变量的初始化

Java 语言要求变量遵循先定义、再初始化、 再使用的规则 。变量的初始化是指自从定义变量以后,首次给它赋初始值的过程。对于类的成员变量,不管程序有没有显式地进行初始化, Java 虚拟机会先自动给它初始化为默认值。对于 局部变量 声明后,Java虚拟机不会自动为它初始化默认值,故局部变量必须显式初始化才能使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值