2.1用引用操纵对象
一切都看作对象,但操作的标识符实际是对象的一个引用(reference);
例如遥控器(引用)来操纵电视机(对象),实际操控的遥控器(引用),再由遥控器来调控电视机(对象)。如果在房间四处走动,同时又想调控电视机,那么只需携带遥控器(引用),而不是电视机(对象)。
没有电视机,遥控器可独立存在,也就是说,你拥有一个引用,并不一定要有一个对象与它关联,例如创建一个String的引用:
String s;
这里创建的只是引用,并不是对象,如果此时向s发送一个消息,就会返回一个运行时错误,此时s没有与任何对象关联,因此,比较安全的做法是:创建一个引用的同时便进行初始化。
String s="hello123";
此处java语言的一个特性,字符串可以用带引号的文本初始化,但是还是建议对对象采用更通用的初始化方法。
2.2 必须由你创建所有对象
当你创建一个引用时,就希望它能与一个新的对象想关联,通常我们是通过new操作符来实现的,2.1里的例子可以写成:
String s=new String("hello123");
2.2.1存储到什么地方
程序运行时,对象怎么进行放置安排的?内存怎么分配的?有5个不同的地方可以存储数据:
1) 寄存器,最快的存储区,因为它位于处理器内部,不同于其他存储区的地方。但是寄存器的数量有限,因此寄存器需要根据需求进行分配,而不能人为控制;
2) 堆栈,位于通用RAM(随机访问存储器),也即内存中,但通过堆栈指针可以从处理器那里获得直接支持。堆栈指针若向下移动,则分配新的内存;若向上移动,则释放那些内存。这是一种快速有效的分配存储方法,仅次于寄存器。创建程序时,java系统必须知道存储在堆栈内所有项的确切生命周期,以便上下移动堆栈指针,这一约束限制了程序的灵活性,所以虽然某些java数据存储在堆栈中—-特别是对象引用,但是java对象并不存储与其中;
3) 堆 , 一种通用的内存池(位于RAM区),用于存放所有的java 对象,堆不同于堆栈的好处是:编译器不需要知道存储的数据在堆里存活多长时间。因此,在堆里分配存储有很大的灵活性。当需要一个对象时,只需用new写一行简单的代码,当执行这行代码时,会自动在堆里进行内存分配,当然,为这种灵活性必须付出相应的代价,用堆进行存储分配和清理可能比用堆栈进行存储需要更多的时间;
4)常量存储 , 常量值通常直接存放在程序代码内部,这样做是安全的,因为他们永远不会被改变。有时,在嵌入式系统中,常量本身会和其他部分分隔离开,所以在这种情况下,可以选择将其存放在ROM(只读存储器)中;
5)非RAM存储 , 如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。器中两个基本的例子是“流对象”和“持久化对象”。在“流对象”中,对象转化成字节流,通常被发送给另一台机器。在“持久化对象”中,对象被存放于磁盘上,因此,即使程序终止,它们仍可以保持自己的状态。这种存储方式的技巧在于:把对象转化成可以存放在其他媒介上的事物,在需要时,可恢复成常规的,基于RAM的对象。
2.2 特例—-基本类型
在程序设计中经常用到一系列类型,它们需要特殊对待。你可以把它们想象成“基本”类型。之所以特殊对待,是因为new将对象存在“堆”里,故用new创建一个对象——特别是小的、简单的变量,往往不是很有效。因此,对于这些类型,Java采取与C和C++相同的方法。也就是说,不用new来创建变量,而是创建一个并非是“引用”的“自动”变量。这个变量拥有它的“值”,并置于堆栈中,因此更加高效。
Java确定每种基本类型所占存储空间的大小,不会随着硬件架构而改变,存储空间大小的不变形是java程序更具可移植性
所有数值类型都有正负号,所以不要去寻找无符号的数值类型。
Boolean类型所占的空间大小没有明确指出,仅定义为能够取字面值的true和false;
基本类型具有包装器类,继承Object类,可以在堆中创建一个非基本对象,用来表示对应的基本类型,例如:
int a=10;
Integer object=new Integer(10);
注:这里涉及到自动拆箱和自动装箱,之后会提到
—–高精度数字
java 提供了两个用于高精度计算的类:BigInteger和BigDecimal(java.math.*)。虽然它们大体上属于”包装器类”的范畴,但二者都没有对应的基本类型。这两个类包含的方法,提供的操作与对基本类型所能执行的操作相似,只不过必须以方法调用方式取代运算符方式来实现,关于调用这两个类的构造器和方法的详细信息,请查阅JDK文档
或者参考一下这篇文章处理大数字BigInteger与BigDecimal
——java中的数组
C和C++的数组就是内存块,如果一个程序访问其自身内存块之外的数组,或在数组初始化前使用内存,会产生难以预料的后果。
java的主要目标之一是安全性,java确保数组会被初始化,而且不能在它的范围之外被访问,这种范围检查,是以每个数组上少量的内存开销及运行时的下标检查为代价的,但由此换来的是安全性和效率的提高,因此付出的代价是值得的。
当创建一个数组对象时,实际上就是创建了一个引用数组,并且每个引用都会自动被初始化为一个特定值,该值拥有自己的关键字null,一旦java 看到null,就知道这个引用还没有指向某个对象,在使用任何引用前
必须为其指定一个对象,如果试图使用一个还是null的引用,在运行时将会报错,因此,常犯的数组错误在java中就可以避免,还可以创建用来存放基本数据类型的数组,同样,编译器也能确保这种数组的初始化,因为它会将这种数组所占的内存全部置零。
2.3 永远不需要销毁对象
2.3.1 作用域
作用域决定了在其内定义的变量名的可见性和生命周期,在C,C++和Java中,作用域由花括号的位置决定,
例如:
{
int a=1;
//此处只有a可以用
{
int b=2;
//此处a和b都可以用
}
//此处只有a可以用,b已经超过作用域了
}
在作用域里定义的变量只可用于作用域结束之前
在C和C++中以下代码是合法的,但在java中是错的,
{
int a=2;
{
int a=3; //不合法Illegal
}
}
编译器会报告变量a已经定义过,所以,在C和C++中将一个较大作用域的变量“隐藏”起来的做法,在java中是不允许的,因为java设计者认为这样会导致程序混乱。
2.3.2 对象的作用域
java 对象不具备和基本类型一样的生命周期,当用new创建一个java对象时,它可以存活于作用域之外,
所以假如这样写:
{
String s=new String("hello123");
}
引用s在作用域终点就消失了,然而,s指向的String对象仍继续占据内存空间,在这一段代码中,我们无法在这个作用域之后访问这个对象,因为对它唯一的引用已超出了作用域的范围;
事实证明:
由new创建的对象,只要你需要,就会一直保留下去,这样,许多C++的编程问题在java中就完全消失了,在C++中,你不禁必须要确保对象的保留时间与你需要这些对象的时间一样长,而且还必须在你使用完它们之后,将其销毁。
大家想一下,如果java让对象继续存在,那么靠什么才能防止这些对象填满内存空间,进而阻塞你的程序呢?
这正是C++里面可能发生的问题,这也是java神奇之所在,java 有一个垃圾回收器,用来监视用new创建的所有对象,并辨别哪些不会再被引用的对象,随后释放这些对象的内存空间,以便供其他新的对象使用,也就是说,你根本不必担心内存回收的问题,你只需要创建对象,一旦不再需要,它们就会自行消失,这样做就消除了这类编程问题(即“内存泄漏”),这是由于程序员忘记释放内存而产生的问题。
2.4 创建新的数据类型:类
class 关键字 之后紧跟着的是新类型的名称,例如
class ClassA{
/*
Class Body
*/
}
这样就创建了一种新的类型,你可以用new 创建这个ClassA这个类型的对象了;
ClassA a=new ClassA();
但是,在定义它的所有方法之前,还没有办法能让它去做更多的事情。
2.4.1 字段和方法
一旦定义了一个类(在java中你所做的全部工作就是定义类,产生哪些类的对象,以及发送消息给这些对象),就可以在
类中设置两种类型的元素:字段(或称数据成员)和方法(或称成员函数),字段可以是任何类型的对象,可以通过其
引用与其进行通信,也可以是基本类型中的一种,如果字段是对某个对象的引用,那么必须初始化该引用,以便使其
与一个实际的对象相关联。
每个对象都有用来存储其字段的空间,普通字段不能在对象间共享,下面是一个具有某些字段的类:
class DataOnly{
int i;
double d;
boolean b;
}
尽管这个类除了存储数据之外什么也不能做,但是仍旧可以像下面这样创建它的一个对象:
DataOnly data=new DataOnly();
可以给字段赋值,但首先必须要知道如何引用一个对象的成员,具体的实现为:在对象引用的名称之后紧接着一个句点,然后再接着是对象内部的成员名称:
objectReference.member(对象引用的成员)
例如:
data.i=2;
data.d=1.1;
data.b=true;
想修改的数据也有可能位于对象所包含的其他对象中,在这种情况下,只需要再使用连接句点即可,例如:
myPlane.leftTank.capacity=100;
DataOnly类除了保存数据外没别的用处,因为它没有任何成员方法,如果想了解成员方法的运行机制,就得先了解参数和返回值的概念,稍后将对此作简略描述。
——基本成员默认值
若类的某个成员是基本数据类型,即使没有初始化,java也会确保它获得一个默认值,以确保哪些是基本类型的成员变量得到初始化(C++没有此功能),防止产生程序错误,但是,这些初始化对你的程序来说,可能是不正确的,甚至是不合法的,所以最好明确地对变量进行初始化。
然而上述确保初始化的方法并不适用于”局部“变量(即并非某个类的字段)。因此,如果在某个方法定义中有 int x;那么变量x得到的可能是任意值(与C和C++中一样),而不会被自动初始化为零,所以在使用x前,应当对其赋一个适当的值,如果忘记了这么做,java会在编译时返回一个错误,告诉你此变量没有初始化,这正是java优于C++的地方,(许多C++编译器会对未初始化变量给予警告,而java则视为错误)
2.5 方法、参数和返回值
许多程序设计语言(像C和C++)用函数这个术语来描述命名子程序,而在java里却常用方法这个术语来表示”做某些事情 的方式”。
java的方法决定了一个对象能够接收什么样的消息。方法的基本组成部分包括:名称、参数、返回值和方法体。
下面是它最基本的形式:
ReturnType methodName(/* argument list*/){
/* Method Body*/
}
返回类型描述的是在调用方法之后从方法返回的值,参数列表给出了要传给方法的信息的类型和名称,方法名 和参数列表(合起来称为“方法签名”)唯一的标识出某个方法。
java中的方法只能作为类的一部分来创建,方法只能通过对象调用,且这个对象必须能执行这个方法调用。
如果试图在某个对象上调用它并不具备的方法,那么在编译时就会得到一条错误信息,通过对象调用方法时,需要先列出对象名,紧接着是句点,然后是方法名和参数列表。如:
objectName.methodName(arg1,arg2,arg3);
例如 对象a,可以调用方法f(),返回值为int
int x=a.f();
这种调用方法的行为通常称为发送消息给对象,消息为f(),对象为a。面向对象的
程序设计通常称为“向对象发送消息”。
2.5.1 参数列表
方法的参数列表也是采用对象形式,在参数列表必须指定每个所传递对象的类型和名字,java中任何传递对象的场合,实际上传递的也是引用,并且引用的类型必须正确,如果参数被设为String类型,则必须传递一个String对象,否则比那一起将抛出错误。
例如:
int storage(){
return s.length() +1;
}
这个方法必须置于某个类的定义内才能被正确编译。
将s传递给此方法,就可以把他当作其他对象一样进行处理(可以给它传递消息),在这里,s的length()方法被调用,它是String类提供的方法之一,会返回字符串包含的字符数。
return关键字包括两方面:
1、代表“已经做完,离开此方法”;
2,如果方法返回值,这个值要放在return语句后面,这个例子中返回值是计算s.length()+1这个表达式得到的。
返回值可以返回任意类型,如果不想返回值,可以指示此方法返回void(空),例子如下:
boolean flag() {return true;}
double aaa(){return 2.73;}
void nothing{return;}
void nothing2{}
若返回类型是void,return关键字的作用只是退出方法,因此,没有必要到方法结束时才离开,可在任何地方返回,但如果返回类型不是void,那么无论在何处返回,编译器都会强制返回一个正确类型的返回值。
2.6 构建一个java程序
2.6.1 名字可见性
如果在程序的某个模块使用了一个名字,在程序的另一模块也使用了相同的名字,为了避免名字重复引发的问题,java采用了全新的方法避免问题,为了给一个类库生成不会与其他名字混淆的名字,java设计者希望程序员反过来使用自己的域名,因为这样可以保证他们肯定是独一无二的,我的域名是www.lyt.com,我的各种类库都被命名为
com.lyt.XXX(XXX可以是支付,也可以是购买),反转后,句点代表子目录的划分。这种机制意味着所有的文件都能够自动存活于自己的名字空间内,而且同一个文件内的每个类都有唯一的标识符。
2.6.2 运用其他构件
如果想在自己的程序中使用预先定义好的类,那么编译器就必须知道怎么定位它们,当然,这个类可能就在发出调用的那个源文件中,在这种情况下, 就可以直接使用这个类-即使这个类在文件的后面才会被定义(java 消除了所谓的“向前引用”问题)
如果那个类位于其他文件中,可以使用关键字import来准确的告诉编译器想要的类是什么。import指示编辑器导入一个包,也就是一个类库。
大多时候,我们使用与编译器附在一起的java标准库里的构件。例如:
import java.util.ArrayList;
这行代码告诉编译器,你想使用java的ArrayList类,但是util包含了数量众多的类,有时你想使用其中的几个,同时又不想明确地逐一声明:那么你很容易使用通配符“*”来达到这个目的:
import java.util.*;
2.6.3 static 关键字
当创建类时,实际就是描述类的对象的外观和行为。执行new来创建对象时,数据储存空间才被分配,其方法才能被外界调用。
有两种用上述方法无法解决:
1、只想为某特定域分配单一存储空间,而不考虑创建对象的个数;
2、希望某个方法不与包含它的类的任何对象关联在一起,也就是说,即使没有创建对象,也能够调用这个方法。
通过static关键字可以满足这两方面的需要,当声明一个事物是static时,就意味着这个域或方法不会与包含它的类的任何对象实例关联在一起,所以,即使从未创建过某个类的对象,也可以调用其static方法或访问其static域,通常,你必须创建一个对象,并用它来访问数据或方法。因为非static域和方法必须知道他们一起运作的特定对象。
例如:
class User(){
static int age=17;
}
生成了一个static字段,并进行了初始化;现在创建两个StaticTest对象,User.age也只有一份存储空间, 这两个对象共享一个age,
User user=new User();
User user1=new User();
user.age和user1.age指向同一存储空间,因此他们具有相同的值17。
引用static变量有两种方法:
1、可以通过对象定位它,user.age;
2、也可以通过其类名直接引用,User.age,而对于非静态成员则不行。
使用类名引用static变量是首选,静态方法和静态对象的操作类似,引用可以用ClassName.method()形式,定义静态方法:
class User(){
static void sing(){
//唱歌
}
}
当static作用于某个字段时,会改变数据创建的方式(因为一个static字段对每个类来说都只有一份存储空间,而非static字段则是对每个对象有一个存储空间),但是如果static作用于某个方法,差别却没那么大, static方法的一个重要用法就是在不创建任何对象的前提下就可以调用它,这一点对于定义main()方法很重要,这个方法是运行一个应用的入口点。
import java.util.*;
public class Util {
public static void main(String[] args) {
System.out.println(new Date()); // java标准库里的Date类
}
}
程序开头,必须声明import语句,引入在文件代码中需要用到的额外类,之所以说它们“额外”,是因为有一个特定类会自动被导入到每一个java文件中:java.lang。java.lang是默认导入到每个java文件中的,所以它的所有类都可以被直接使用。system是它里面的类。
类的名字必须和文件名相同,如果创建一个独立运行的程序,文件中必须存在某个类和该文件同名,且那个类必须包含一个名为main()的方法,
public static void main(String[] args){
}
其中 public 关键字是指这是一个可由外部调用的方法,main()方法的参数是一个String对象的数组,程序中为用到args,但是java编译器要求必须这么做,因为args要用来存储命令行参数。
打印完后,Date对象就不再被使用,而垃圾回收器会发现这一情况,并在任意时刻回收。
2.7.1 编译和运行
要编译、运行java程序,首先要有一个java开发环境,安装好jdk,配置好路径,确保计算机能找到javac和java这两个文件,打开到java文件所在目录,输入
javac HelloWorld.java //编译
//正常不会有任何反应,如果有错会返回错误,
接着运行java HelloWord //运行
2.8 注释和嵌入式文档
java中有两种注释风格
/** *第一种 * */
/** 第一种 */
和
// 第二种 单行注释
2.8.1 注释文档
javadoc用于提取注释的工具,是jdk安装的一部分,提取注释标签,输出一个HTML文件,用于Web浏览器查看。
2.8.2 语法
三种类型的注释文档,对应注释后面的三种元素:类、域、方法。
/** A Class comment */
public class Documentationl{
/** A field comment */
public int i;
/** A method comment */
public void f(){}
}