原生与引用:熟知你的变量

变量有两种:原生变量primitive)和 引用变量reference)。到现在为止,我们在两个地方用到了变量:作为对象状态(实例变量),以及作为本地变量(在方法中声明的变量)。后面会把变量用作 参数arguments, 由调用代码发送给方法的值),同时作为返回类型(return types,返回到方法调用者的值)。在前面已经见到了声明为简单的 原生 整数值(比如 int 类型)。也见到过声明为更为复杂的诸如字符串(String)或数组(array)变量。但在现实生活中,有着远比整数、字符串以及数组复杂的东西,比如一个有着 Dog 实例变量的 PetOwner 对象,或者有着 EngineCar 对象,本章将揭开 Java 类型谜题,看看可以 声明declare)出什么样的变量,在变量中可以 放入put in)些什么东西,以及对变量可以进行什么操作。还会揭示 垃圾回收堆garbage-collectible heap)上,所发生的事情。

声明一个变量

Java 是类型敏感的Java cares about type)。他不会让你去干那些诡异又危险的事情,比如把一头长颈鹿当成兔子来加以引用,就如同某人要让长颈鹿去像兔子那样蹦跳时会怎样呢?Java也不会让你把一个浮点数放到整数变量里,除非你 告知了编译器,你知道那样做可能会失去精度(也就是小数点后面的所有数字)。

编译器(javac)会发现大部分问题:

Rabbit hopper = new Giraffe();

别指望这代码会被编译出来。拜托!

为了能让 类型安全type-safty)发挥作用,就要声明变量的类型。变量是个整数?还是 Dog? 还是是单个的字符。变量类型有两种:原生primitive)与 对象引用object reference)。原生变量保存的是一些基础类型的值(可以理解为简单的位模式,simple bit patterns),包括整数、逻辑值以及浮点数。对象引用变量,则是保存了对对象的引用(references to objects)。

所有变量,都必须有一个类型

除了类型,变量还必须有个名称,如此就可以在代码中,使用他的名称了。

变量必须有个名称

int count;

int是类型(type),count是名称(name)。

注意:如果你遇到这样一个语句时:an object of type X,就把 typeclass当成同义词吧。

变量是一个容器,用于 保存 某个事物。(A variable is just a cup. A container. It holds something)

原生类型

类型位深度(Bit Depth)取值范围
逻辑值与字符------
逻辑值(boolean特定于 JVMtruefalse
字符(char16位065535
数值(都是有符号的)------
字节(byte8位-128127
短整数(short16位-32,76832,767
整数(int32位-2,147,483,6482,147,483,647
长整数 (long64位-9,223,372,036,854,775,808 (-2^63) 到 9,223,372,036,854,775,807 (2^63 – 1)
浮点数------
单精度浮点数(float32位根据JVM的不同而不同
双精度浮点数(double64位根据JVM的不同而不同

带赋值的原生变量声明

int x;
x = 234;
byte b = 89;
boolean isFun = true;
double d = 3456.98;
char c = 'f';
int z = x;
boolean isPunkRock;
isPunkRock = false;
boolean powerOn;
powerOn = isFun;
long big = 3456789;
float f = 32.5f;

注意最后一个中的 f,因为Java会将所有带着小数点的没有 f 的数,看着是双精度浮点数,因此要在后面加上 f 来表示单精度浮点数。

将值赋给变量的方式有以下三种

  • 在等号之后键入一个 字面值 (比如: x = 12, isGood = true
  • 将一个变量的值赋给另一个变量 (比如 x = y
  • 使用结合了变量与字面值的表达式 (比如 x = y + 43

Java中声明了原生变量后需要初始化,若未初始化,将报出以下错误:

int y;
System.out.println(y);
java.lang.Error: Unresolved compilation problem:
        The local variable y may not have been initialized

而对象中的变量,则不存在这样的问题,其在对象初始化时,会被自动赋值。

变量命名规则

  • 必须以字母、下划线(_)或美元符号($)开头。那么就不能以数字开头。
  • 后面就可以使用数字了。只是不能以数字开头。
  • 可以是你所喜爱的任何字符串,除开上面两条规则外,还要不能是 Java 的保留字。

Java的保留字

booleanbytechardoublefloatintlongshortpublicprivate
protectedabstractfinalnativestaticstrictfpsyschronizedtransientvolatileif
elsedowhileswitchcasedefaultforbreakcontinueassert
classextendsimplementsimportinstanceofinterfacenewpackagesuperthis
catchfinallytrythrowthrowsreturnvoidconstgotoenum

关于引用变量

  • 实际上是不存在 对象 变量的(There is actually no such thing as an object variable)
  • 有的只是对象 引用 变量(There's only an object reference variable)
  • 对象引用变量所存储的,是表示一种访问对象的方式的数据位(An object reference variable holds bits that represent a way to access an object)
  • 对象引用变量,并非保存的是对象本身,而是类似于指针的东西,或者说是一个内存地址。就算在不知道某个引用变量里头具体是什么,我们也明白他到底是个什么,引用变量代表着唯一的对象。同时JVM也知道如何使用这个引用,来获取到某个对象。

对象引用 只是另一个变量值(An object reference is just another variable value)。

对象声明、创建与赋值三步骤

Dog myDog=new Dog();
132
  1. 声明一个引用变量(Declare a reference variable)

告诉JVM为某个引用变量分配空间,同时给变量命名。引用变量的类型,就固定下来了(Dog)。

  1. 创建一个对象

告诉虚拟机给新的对象,在内存堆上分配空间。

  1. 将对象和对象引用连接起来(Link the object and the reference)

将新的对象赋值给引用变量(实际上是把新对象在内存堆上的地址,赋值给对象的引用)。

引用变量的大小

跟JVM相关,是内存堆的编址/寻址方式决定的。对象引用本质上是一些指针,但不能访问到他们。也不需要访问他们。可以把这些对象引用看着是一个64位的地址。

不管对象有多大,各种对象的引用,都是一样大的(64位)。对于给定的JVM,所有对象引用变量的大小都是一样的。各个JVM在表示对象引用变量上可能有所不同。

不能对引用变量做C语言中,指针那样的前后移动的算术运算,Java 不是 C

  • 对象引用是一个到对象的指针(存储对象在内存堆中的地址),可以是同一类型下不同对象的引用
  • final 关键字修饰的对象引用,就只能是一个对象的引用了,不能再被赋值其他对象
  • null 是一个值,表示空指针,在某个对象被赋值了空指针后,就失去了对原来对象的引用,原来那个对象,就再也访问不到了,将被垃圾回收
  • 两个对象引用变量,可以指向同一个对象
  • 失去了对象引用变量的对象,将被垃圾回收
Last change: 2023-07-28, commit: dfd1a5e