Java学习之一切都是对象(二)

注:本博客内容是本人在看《Jave编程思想》这本书时从该书上抄录下来的一些片段。这里也强烈建议各位读者去购买这本书进行阅读学习。

一、用引用操纵对象

在Java的世界中,一切都被视为对象,因此可以采用单一固定的语法。尽管一切都看作对象,但操纵的标识符实际上是对象的一个“引用”(reference)。可以将这一情形想象成用遥控器(引用)来操纵电视(对象)。只需要握住这个遥控器,就能保持与电视机的连接。当想改变电视器的音量时,实际操控的是遥控器(引用),再由遥控器来调控电视机(对象)。如果想在房间四处走走,同时仍能控制电视机,那么只需要携带遥控器(引用)而不是电视机(对象)。

此外,即使没有电视机,遥控其亦可单独存在。也就是说,你拥有一个引用,并不一定需要有一个对象与之关联。例如:

String str;

这里所创建的只是一个引用,并不是一个对象。如果此时向str发送一个消息,就会返回一个运行时错误(异常)。这是因为此时str实际上没有和任何事物(对象)相关联。因此,一种安全的做法是:创建一个引用的同时便进行初始化。

String str = “Hello Java”;

二、必须由你创建所有对象

一旦创建了一个引用,就希望它能与一个对象相关联。通常使用new关键字来实现这一目的。new关键字的意思是“创建一个新的对象”。所以前面的例子可以写成:

String str = new String("Hello Java");

Java除了String类型外,还提供了大量的现成类型。这里更重要的是,你可以使用new关键字自行创建类型。

2.1 存储到什么地方

Java程序运行时,对象是如何进行放置安排的呢?特别是如何分配内存?对这些方面的了解会对你的学习有很大帮助。有五个地方可以存储数据:

  • 寄存器。这是最快的存储区,因为它位于不同于其它存储区的地方——处理器内部。但是寄存器的数量极其有限,所以寄存器根据需求分配。你不能直接控制,也不能在程序中感觉到寄存器存在的任何痕迹(另一方面,C和C++允许你向编辑器建议寄存器的分配方式)。
  • 堆栈。位于通用RAM(随机访问存储器)中,但是通过堆栈指针可以从处理器那里获取直接支持。堆栈指针若向下移动,则分配新的内存;若向上移动,则释放内存。这是一种快速有效的分配存储方法,仅次于寄存器。创建程序时,Java系统必须知道存储在堆栈内所有项的确切生命周期,以便上下移动指针。这一约束限制了程序的灵活性,所以虽然某些Java数据存储于堆栈中——特别是对象的引用但是Java对象并不存储在其中
  • 。一种通用的内存池(也位于RAM区),用于存放所有的Java对象。对不同于堆栈的好处是:编辑器不需要知道存储的数据在堆中存活多久。因此,在堆里分配存储有很大的灵活性。当需要一个对象时,只需要用new写一行简单的代码,当执行代码是,会自动在堆中进行存储分配。当然,为这种灵活性必须付出相应的代价:用堆进行存储分配和清理可能比用堆栈进行存储分配需要更多的时间。
  • 常量存储。常量值通常直接存放在程序代码内部,这样是安全的,因为它们永远不会被改变。
  • 非RAM存储。如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。其中两个基本的例子是流对象持久化对象。在流对象中,对象转化成字节流,通常被发送给另一台机器。在“持久化对象”中,对象被存放于磁盘上,因此,即使程序终止,它们仍可以保持自己的状态。这种存储方式的技巧在于:把对象转化成可以存放在其它媒介上的事物,在需要的时候,可以恢复成常规的、基于RAM的对象。Java提供了对轻量级持久化的支持,而诸如JDBC和Hibernate这样的机制提供了更加复杂的对在数据库中存储和读取对象信息的支持。

2.2 基本数据类型

在程序中经常使用一系列类型,它们需要特殊对待。可以把它们想象成“基本”类型。之所以特殊对待,是因为new将对象放在中,故用new创建一个对象——特别是小的、简单的变量,往往不是很有效。因此,对于这些类型,Java采取与C和C++相同的方法,也就是说,不用new创建变量,而是创建一个并非引用的“自动”变量。这个变量直接存储“值”,并置于堆栈中,因此更加高效。

Java要确定每种基本类型所占用存储空间的大小。它们的大小不像其它大多数语言那样随机器架构的变化而变化。这种所占存储空间大小的不变性是Java程序比用其他大多数语言编写的程序更具有可移植性的原因之一。

基本类型大小最小值最大值包装器类型
boolean______Boolean
char16-bitUnicode 0Unicode 2^{16}-1Character
byte8 bits-128(-2^{7}+127(2^{7}-1Byte
short16 bits-2^{15}+2^{15}-1Short
int32 bits-2^{31}+2^{31}-1Integer
long64 bits-2^{63}2^{63}-1Long
float32 biteIEEE754IEEE754Float
double64 bitsIEEE754IEEE754Double
void______Void

所有数值类型都有正负号,所以不要去寻找无符号的数值类型。boolean类型所占存储空间大小没有明确指定,仅定义了能够取字面值truefalse

基本数据类型具有包装类,使得可以在堆中创建一个非基本对象,用来表示对应基本类型。例如:

char c = 'x';
Character ch = new Character(c);

也可以这样使用:

Character ch = new Character('x');

Java SE5的自动包装功能将自动地将基本类型转换成包装类型:

Character ch = 'x';

并且可以反向转换(自动拆箱):

char c = ch;

高精度数字

Java提供了两个用于该精度计算的类:BigIntegreBigDecimal。虽然它们大体上属于“包装器类型”的范畴,但二者没有对应的基本类型。

不多,这两个类包含的方法,提供的操作与对基本类型所能执行的操作相似。也就是说,能用于intfloat的操作。也同样能够作用于BigIntegerBigBecimal。只不过必须以方法调用方式取代运算符方式来实现。由于这么做复制了些,所以运算速度会比较慢。在这里,我们以速度换取了精度。

BigInteger支持任意精度的整数。也就是说,在运算中,可以准确地表示任何大小的整数值,而不会丢失任何信息。

Bigdecimal支持任何精度的定点数,例如,可以用于精确的货币计算。

2.3 Java中的数组

几乎所有的程序设计语言都支持数组。在C和C++中使用数组是很危险的,因为C和C++中的数组就是内存块。如果一个程序要访问其自身内存内存块之外的数组,或在数组初始化前使用内存(程序中常见的错误),都会产生难以预料的后果。

Java的主要目标之一是安全,所以许多C和C++里困扰程序员的问题在Java里不会再出现。Java确保数组会被初始化,而且不能在它的范围之外被访问。这种范围检查,是以每个数组上少量的内存开销及运行时标检查为代价的。但由此换来的安全性和效率的提高,因此付出的代价是值得的。

当创建一个数组对象时,实际上就是常见了一个引用数组,并且每个引用都会自动初始化为一个特定的值,该值拥有自己的关键字null。一旦Java看到null,就知道这个引用还没有指向某个对象。在使用引用前,必须为其指定对象;如果试图使用一个null的引用,在运行时会报错。因此,常犯的数组错误在Java中就可以避免。

还可以创建用来存放基本数据类型的数组。同样,编译器也能确保这种数组的初始化,因为它会将这种数组所占的内存全部清零。

3、永远不需要销毁对象

3.1 作用域

大多数过程语言都有作用域(scope)的概念。作用域决定了其定义的变量名的可见性和生命周期。在C、C++和Java中,作用域由花括号“{}”的位置决定。例如:

{
    int x = 10;
    // Only x available
    {
        int q = 20;
        // Both x & q available
    }
    // Only x available
    // q is out of scope
}

在作用域里定义的变量只可用于作用域结束之前。

尽管以下代码在C和C++中是合法的,但是在Java中却不可以这样书写:

{
    int x = 0;
    {
        // in C or C++ is available
        int x = 1;
        // in Java "Variable 'x' is already defined in the scop"
    }
}

编译器将会报告变量x已经定义过。所以,在C和C++里将一个较大作用域的变量“隐藏”起来的做法,在Java中时不应许的。因为Java设计者认为这样做会导致程序混乱。

3.2 对象的作用域

Java对象不具备和基本类型一样的生命周期。当使用new创建一个Java对象时,它可以存活于作用域之外。所以假如你采用代码:

{
    
    {
        String str = new String("Hello Scope")
    }
    System.out.println(str); // Cannot resolve symbol 'str'
}

引用str在作用域终点就消失了。然而,str指向的String对象仍然继续占据内存空间。在这一段代码中,我们无法在这个作用域之外访问这个对象,因为对他唯一的引用已经超出了作用域的范围。

事实上,由new创建的对象,只要你需要,就会一直保留下去。这样,许多C++编程问题在Java中就完成消失了。在C++中,你不仅必须确保对象的保留时间与你需要这些对象的时间一样长,而且还必须在你使用完之后,将其销毁。

如果让Java对象持续存在,那么靠什么才能防止这些对象填满内存空间,进而阻塞你的程序呢?Java有一个垃圾回收器,用来监视new创建的所有对象,并辨识那些不会再被引用的对象。随后,释放这些对象的内存空间,以便其他新的对象使用。也就是说,你根本不需要担心内存回收的问题。你只需要创建对象,一旦不再需要,它们就会自行消失。

4、创建新的数据类型:类

如果一切都是对象,那么是什么决定了某一类对象的外观和行为呢?花句话说,是什么确定了对象的类型?在Java中使用class关键字之后跟随着的是新类型的名称。例如:

class TypeName {
    /* Class body goes here */
}

这就引入了一个新的类型,尽管类的主体仅包含一条注释语句。因此,你还不能用它做太多的事情。然而你可以使用new关键字来创建这种类型的对象:

TypeName typeName = new TypeName()

但是,在定义它的所有方法之前,还没办法让它做更多的事情(也就是说,不能向它发送任何有意义的消息)。

4.1 字段和方法

一旦定义了一个类(在Java中你所做的全部工作就是定义类,产生那些定义类的对象,以及发送消息给这些对象),就可以在类中设置两种类型的元素:字段(有时被称为数据成员)和方法(有时被称为成员函数)。字段可以是任意类型的对象,可以通过其引用与其进行通信;也可以是基本类型中的一种。如果字段是某个对象的引用,那么必须初始化该用,以便使其与一个实际的对象所关联。

每个对象都有用来存储其字段的空间,普通字段不能在对象间共享。例如

class DateOnly {
      int i;
      double d;
      double b;
}

尽管这个类除了存储数据之外什么也不做,但仍然可以使用new创建一个新的对象。

DateOnly dateOnly = new DateOnly();

可以该字段赋值,但首先必须知道如何引用一个对象的成员。具体的实现为:在对象引用的名称之后紧接一个句点,然后在接着是对象内部的成员名称:

dateOnly.i = 1;
dateOnly.b = 2.0;
dateOnly.d = 5.0;

想要修改的数据也可可能位于对象包含的其他对象中。在这种情况下,自需要再使用连接句点即可。例如:

类型定义如下:

class MyPlane {
      int serverPort;
}
class DateOnly {
      int i;
      double d;
      double b;
      MyPlane myPlane;
}

对象创建以及赋值语句如下:

DateOnly dateOnly = new DateOnly();
dateOnly.i = 1;
dateOnly.b = 2.0;
dateOnly.d = 5.0;
dateOnly.myPlane.serverPort = 8080;

DateOnly类除了保存数据之外没有别的用处,因为他没有任何成员方法。

若类的某个成员是基本数据类型,即使没有进行初始化,Java也会确保它获得一个默认值。如下表所示:

                 基本类型                  默认值
booleanfalse
char'\uoooo'(null)
byte(byte)0
short(short)0
int0
long0L
float0.0f
double0.0d

当变量作为类的成员使用时,Java才确保给其默认值,以确保那些是基本类型的成员变量得到初始化,防止产生程序错误。但是,这些初始值对你来说,可能不是正确的,甚至是不合法的。所以最好明确对变量进行初始化。

然而上述确保初始化的方法并不适用于“局部”变量(即非某个类的字段)。因此,如果在某个方法定义中有:

int x;

那么这个x得到的可能是任意值(与C和C++)一样,而不会被自动初始化为零。所以在使用变量x之前,应先对其赋一个适当的值。如果忘了这么着,Java会在编译时返回一个错误,告述你次变量没有初始化,这正是Java由于C++的地方。(许多C++)编译器会对为初始化变量给予警告,而Java视为错误。例如:

int j;
System.out.println(j); // variable ‘j’ might not bean initialized

五、方法、参数和返回值

在许多程序设计语言(像C和C++)用函数这个术语来描述命令子程序;而在Java里却常用方法这个术语来表示“做某些事情的方式”。实际上,继续把它看成函数也无妨。

Java方法决定了一个对象能够接收什么样的消息。方法的基本组成部分包括:名称、参数、返回值和方法体。下面是它最基本的形式:

ReturnType methodName (/* Argumanet list */) {
    /* Method Body */
}

返回类型描述的是在调用方法之后返回的值。参数列表给出了要传递给方法信息的类型和名称。方法名和参数列表(它们合起来被称为“方法签名”)唯一地标识某个方法。

Java中的方法只能作为类的一部分来构建。方法只有通过对象才能被调用,而且这个对象必须能执行这个方法的调用。如果试图在某个对象上调用它并不具备的方法,那么编译时就会得到一个错误信息。通过对象调用方法时,需要先列出对象名,紧接着是句点,然后是方法名和参数列表。如:

objectName.methodName(arg1, arg2, arg3, ....);

5.1 参数列表

方法的参数列表指定要传递给方法什么样的信息。这些信息像Java中其他信息一样,采用的都是对象形式。因此,在参数列表中必须指定每个所传递对象的类型及名字。像Java中传递对象的场合一样,这里传递的实际上也是引用(对于基本数据类型boolean、char、byte、short、int、long、float和double来说是一个例外。通常,尽管传递的是对象,而实际上传递的是对象的引用),并且引用类型必须正确。如果参数被设为String类型,则必须传递一个String对象,否则,编译器将抛出错误。例如:

int storage (String str) {
    return srt.length * 2;
}

此方法告述你,需要对少个字节才能容纳一个特定的String对象中的信息(字符串中的每个字符的尺寸都是16位或2字节,以此来提供对Unicode字符集的支持)。通过这个例子,还可以了解到关键字return的用法,它包含两方面:首先,它代表“已经做完,离开此方法”。其次,如果此方法产生了一个值,这个值要放到retrun语句的后面。

你可以定义方法返回你想要的任意类型,如果不想返回任何值,可以指示次方法返回void(空)。当方法返回类型是void时,return关键字的作用只是用来退出此方法。因此,没有必要一定要到方法结束时才离开,可以在任何地方返回。但是如果返回类型不是void,那么无论何处返回,编译器都会强制返回一个正确类型的返回值。

6、static关键字

通常来说,当创建类时,就是在描述那个类的对象的外观与行为。除非用new关键字创建那个类的对象,否则,实际上并未获取任何对象。执行new创建对象时,数据存储空间才被分配,其方法才能供外部调用

有两种情形用上述方法是无法解决的。一种情形是,只想为某特定域分配单一存储空间,而不去考虑究竟要创建多少对象,甚至根本就不创建任何对象。另一种情形是,希望某个方法不与包含它的类的任何对象关联在一起。也就是说,即使没有创建对象,也能够调用这个方法。

通过static关键字可以满足这两方面的需要。当声明一个事物是static时,就意味着这个域或方法不会与包含它的那个类的任何对象实例关联在一起。所以,即使从未创建某个类的任何对象,也可以调用其static方法或访问static域。通常,你必须创建一个对象,并用它来访问数据或方法。

有些面向对象语言采用类数据类方法两个术语,代表那些数据和方法只是作为整个类,而不是类的某个特定对象而存在的。有时,在Java的一些文献中也用到这两个术语。

只须将static关键字放在定义之前,就可以将字段或方法设定为static。如下代码就生成了一个static字段,并对其进行了初始化:

classs StaticTest {
    static int INT_I = 10;
}

现在即使你创建了两个或多个StaticTest对象,StaticTest.INT_I也只有一份存储空间,这些对象共享同一个INT_I。通常使用类名是引用static变量和方法的首选方式,这不仅是因为他强调了变量的static结构,而且在某些情况下他还有为编辑器进行优化提供了更好的机会。

尽管当static作用于某个字段时,肯定会改变数据创建的方式(因为一个static字段对每个类来说都只有一份存储空间,而非static字段则是对每个对象有一个存储空间),但是如果static作用于某个方法,差别没有那么大。static方法的一个重要用法就是在不创建任何对象的前提下就可以调用它。这一点对于定义main()方法很重要,这个方法即使运行一个应用时的入口。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值