Java 类和对象 一、 理解面向对象 1、 为什么要面向对象 传统的开发方法是面向过程的,面向过程是一种以事件为中心的编程思想。就是分析出解决 问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以 了。 面向过程其实是最为实际的一种思考方式,就是算面向对象的方法也是含有面向过程的思想 可以说面向过程是一种基础的方法,它考虑的是实际的实现。一般的面向过程是从上往下步步求 精,当程序规模不是很大时,面向过程的方法还会体现出一种优势,因为程序的流程会很清楚。 比如拿学生早上做的事情来说说这种面向过程,粗略的可以将过程拟为:   (1)起床    (2)穿衣    (3)洗脸刷牙    (4)去学校 这 4 步就是一步一步的完成,它的顺序很重要,你只须一个一个的实现就行了。 面向过程开发具有如下缺点: ➢ 软件重用性差 重用性是指同一事物不经修改或稍加修改就可多次重复使用的性质。软件重用性是软件工 程追求的目标之一。 ➢ 软件可维护性差 软件工程强调软件的可维护性,强调文档资料的重要性,规定最终的软件产品应该由完 整、一致的配置成分组成。在软件开发过程中,始终强调软件的可读性、可修改性和可测试性是软 件的重要的质量指标。实践证明,用传统方法开发出来的软件,维护时其费用和成本仍然很高, 其原因是可修改性差,维护困难,导致可维护性差。 ➢ 开发出的软件不能满足用户需要 用传统的结构化方法开发大型软件系统涉及各种不同领域的知识,在开发需求模糊或需 求动态变化的系统时,所开发出的软件系统往往不能真正满足用户的需要,用户需求的变化往往 造成系统结构的较大变化,从而需要花费很大代价才能实现这种变化 面向对象(Object Oriented,简称 OO)是一种以事物为中心的编程思想。 对象是真实世界中的事物在人脑中的映象。在实际生活中,我们每时每刻都与“对象”在打 1 交道,我们用的钢笔,骑的自行车,乘坐的公共汽车等都是对象。这些对象是能看得见、摸得着, 实际存在的东西,我们称之为实体对象;有的对象是针对非具体物体的,但是在逻辑关系上的反 映,比如:钢笔与墨水的关系,人与自行车的关系,我们称之为逻辑对象。 如果是用面向对象的方法来模拟学生早上做的事情。可以抽象出一个学生的类,它包括四个 方法,但是具体的顺序就不能体现出来。根据类创建一个对象,再调用对象的方法,在调用方法 时体现出顺序。 张三起床 张三穿衣服 张三洗脸刷牙 张三去学校   基于对象的编程更符合人的思维模式,编写的程序更加健壮和强大,更重要的是,面向对象 编程鼓励创造性的程序设计。 为了便于初学者理解,这里先讨论实体对象。 2、 对象的基本构成 现实中的人是一个实体对象,分析实体对象的构成,发现有这样一些共同点,这些实体对象 都有自己的状态描述,比如:人有姓名、身高、体重、发型、着装等,有了这些描述,我们可以想象 出一个人的样子。我们把这些描述称之为属性,属性是静态的,这些属性用来决定了对象的具体 表现形式。 除了这些静态的,实体对象还有自己的动作,通过这些动作能够完成一定的功能,我们称之 为方法,比如:人能写字,能刷牙,能跑步,打篮球,踢足球等。我们知道了对象的方法,也就 知道了这个对象可以做什么,有什么用。 依照这个理论我们再分析一下汽车,首先想到的是静态的属性,有颜色、车牌号、标志、发动 机的功率、乘载人数、自重、轮子数目等等。然后是动态的功能:加速、减速、刹车、转弯等等。 总之一句话,对象同时具备静态属性和动态的功能。 3、 如何进行对象抽象 抽象是在思想上把各种对象或现象之间的共同的本质属性抽取出来而舍去个别的非本质的属 性的思维方法。也就是说把一系列相同或类似的实体对象的特点抽取出来,采用一个统一的表达 方式,这就是抽象。 比如:张三这个人身高 180cm,体重 75kg,会打篮球,会跑步 李四这个人身高 170cm,体重 70kg,会踢足球 现在想要采用一个统一的对象来描述张三和李四,那么我们就可以采用如下的表述方法来表 述: 人{ 2 静态属性: 姓名; 身高; 体重; 动态动作: 打篮球(); 跑步(); 踢足球(); } “人”这个对象就是对张三和李四的抽象,那么如何表述张三、李四具体的个体呢: 人{ 静态属性: 姓名 = 张三; 身高 = 180cm; 体重 = 75kg; 动态动作: 打篮球();//打篮球的功能实现 跑步(); //跑步的功能实现 } 人{ 静态属性: 姓名 = 李四; 身高 = 170cm; 体重 = 70kg; 动态动作: 踢足球();//踢足球的功能实现 } 对实体对象的抽象一定要很好的练习,可以把你所看到的任何物体都拿来抽象,“一切都是 对象”。要练习到,你看到的没有物体,全是对象。 4、 抽象对象和实体对象的关系 仔细观察上面的抽象对象——“人”,和具体的实体对象:“张三”、“李四”。你会发现, 抽象对象只有一个,实体对象却是无数个,通过对抽象对象设置不同的属性,赋予不同的功能, 那么就能够表示不同的实体对象。 这样就大大简化了对象的描述工作,使用一个对象就可以统一地描述某一类实体了,在需要 具体的实体的时候,分别设置不同的值就可以表示具体对象了。 5、 Java 中的类和对象 ➢ Java 中的类 把抽象出来的对象使用 Java 表达出来,那就是类 class。 类是对具有相似性质的一类事物的抽象,类封装了一类对象的属性和方法。实例化一个类, 可以获得属于该类的一个实例(即对象)。 类在 Java 编程语言中作为定义新类型的一种途径,类是组成 Java 程序的基本要素。类声明 可定义新类型并描述这些类型是如何实现的。 比如前面讨论过的“人”、“汽车”使用 Java 表达出来就是一个类。 3 ➢ Java 中的对象 对象是类的一个实例,类的具体化,也称实例对象。实例就是实际例子,就好像真实存在一 样。 类可被认为是一个模板---你正在描述的一个对象模型。一个对象就是你每次使用的时候创 建的一个类的实例的结果。 比如前面讨论的张三和李四,他们就是通过“人”这个类的创建出来的实例。 二、 Java 类的基本构成 一个完整的 Java 类通常由下面六个部分组成: 包定义语句 import 语句 类定义{ 成员变量 构造方法 成员方法 } 其中:只有类定义和“{}”是不可或缺的,其余部分都可以根据需要来定义。 下面分别来学 习各个部分的基本规则,看看如何写 Java 的类,建议初学者,先看类、成员变量、方法部分,再 看包、import 部分。 1、 包 什么是包 计算机操作系统使用文件夹或者目录来存放相关或者同类的文档,在 Java 编程语言中,提 供了一个包的概念来组织相关的类。包在物理上就是一个文件夹,逻辑上代表一个分类概念。 包是类、接口或其它包的集合,包对类进行有效管理的机制。 包对于下列工作非常有用: (1)包将包含类代码的文件组织起来,易于查找和使用适当的类。 (2)包不止是包含类和接口,还能够包含其它包。形成层次的包空间。 (3)有助于避免命名冲突。当使用很多类时,确保类和方法名称的唯一性是非常困难的。包能够 形成层次命名空间,缩小了名称冲突的范围,易于管理名称。 (4)控制代码访问权限。 为便于管理数目众多的类,Java 语言中引入了“包”的概念,可以说是对定义的 Java 类 进行“分组”,将多个功能相关的类定义到一个“包”中,以解决命名冲突、引用不方便、安全性 等问题。 比如户籍制度,每个公民除有自己的名字“张三”、“李四”外还被规定了他的户籍地。假定 有两个人都叫张三,只称呼名字就无法区分他们,但如果事先登记他们的户籍分别在北京和上海, 4 就可以很容易的用“北京的张三”、“上海的张三”将他们区分开来。如果北京市仍有多个张三, 还可以细分为“北京市.海淀区的张三”、“北京市.西城区.平安大街 的张三”等等,直到能惟 一标识每个“张三”为止。 JDK 中常用的包 JDK 中定义的类就采用了“包”机制进行层次式管理,简而言之:从逻辑上讲,包是一组相 关类的集合;从物理上讲,同包即同目录。 java.lang----包含一些 Java 语言的核心类,包含构成 Java 语言设计基础的类。在 此包中 定义的最重要的一个类是“Object”,代表类层次的根,Java 是一个单根系统,最终 的根就 是 Object”。 java.awt----包含了构成抽象窗口工具集(abstract window toolkits)的多个类, 这些类被用来构建和管理应用程序的图形用户界面(GUI)。 javax.swing----完全 Java 版的图形用户界面(GUI)解决方案,提供了很多完备的组 件,可以应对复杂的桌面系统构建。 java.net----包含执行与网络相关的操作的类,如 URL, Socket, ServerSocket 等。 java.io----包含能提供多种输入/输出功能的类。 java.util----包含一些实用工具类,如定义系统特性、使用与日期日历相关的方法。还有 重要的集合框架。 代码中如何表达包 Java 语言使用 package 语句来实现包的定义。package 语句必须作为 Java 源文件的非注 释语句第一条语句,指明该文件中定义的类所在的包。若缺省该语句,则指定为无名包,其语法 格式为: package pkg1[.pkg2[.pkg3…]]; //“[]”表示可选 Java 编译器把包对应于文件系统的目录管理,因此包也可以嵌套使用,即一个包中可以含 有类的定义也可以含有子包,其嵌套层数没有限制。package 语句中,用“.”来指明包的层次。 程序 package 的使用:Test.java package p1; public class Test { public void display() { System.out.println("in method display()"); } } Java 语言要求包声明的层次和实际保存类的字节码文件的目录结构存在对应关系,以便将 来使用该类时能通过包名(也就是目录名)查找到所需要的类文件。简单地说就是包的层次结构 需要和文件夹的层次对应。 注意:每个源文件只有一个包的声明,而且包名应该全部小写。 5 编译和生成包 如 果 在 程 序 Test.java 中 已 定 义 了 包 p1 , 就 必 须 将 编 译 生 成 的 字 节 码 文 件 Test.class 保存在与包名同名的子目录中,可以选用下述两种方式之一: ➢ 直接编译 javac Test.java 则编译器会在当前目录下生成 Test.class 文件,再在手动创建一个名为 p1 的文件夹, 将 Test.class 复制到该 p1 目录下。 ➢ 带包编译 带包编译是简化的编译命令。 ● javac -d .\ Test.java 编译器会自动在当前目录下建立一个子目录 p1,并将生成的.class 文件自动保存到 p1 文 件夹下。 ● javac -d .\test\ Test.java 编译器会自动在当前目录下的 test 文件夹中建立一个子目录 p1,并将生成的.class 文 件自动保存到 p1 文件夹下。前提是当前目录下必须有 test 文件夹,否则报错: “未找到目录:.\test\” ● javac -d D:\test\ Test.java 编译器会自动在 D 盘根目录下的 test 文件夹中建立一个子目录 p1,并将生成的.class 文件自动保存到 p1 文件夹下。前提是 D 盘根目录下必须有 test 文件夹,否则报错: “未找到目录:D:\test\” 带包运行 运行带包的程序,需要使用类的全路径,也就是带包的路径,比如上面的那个程序,就使用 如下的代码进行运行: java p1.Test 2、 import 语句 为了能够使用某一个包的成员,我们需要在 Java 程序中明确导入该包。使用“import” 语句可完成此功能。在 java 源文件中 import 语句应位于 package 语句之后,所有类的定 义之前,可以没有,也可以有多条,其语法格式为: import package1[.package2…].(classname|*); java 运行时环境将到 CLASSPATH + package1.[package2…]路径下寻找并载入相 应的字节码文件 classname.class。“*”号为通配符,代表所有的类。也就是说 import 语 句为编译器指明了寻找类的途径。 例,使用 import 语句引入类程序:TestPackage.java 6 import p1.Test; //或者 import p1.*; public class TestPackage { public static void main(String args[]) { Test t = new Test(); // Test 类在 p1 包中定义 t.display(); } } java 编译器默认为所有的 java 程序引入了 JDK 的 java.lang 包中所有的类(import java.lang.*;),其中定义了一些常用类:System、String、Object、Math 等。因此我们可 以直 接使用这些类而不必显式引入。但使用其它非无名包中的类则必须先引入、后使用。 Java 类搜寻方式 程序中的 import 语句标明要引入 p1 包中的 Test 类,假定环境变量 CLASSPATH 的 值为“.;C:\jdk6\lib;D:\ex”,java 运行环境将依次到下述可能的位置寻找并载入该字 节码文件 Test.class: .\p1\Test.class C:\jdk6\lib\p1\Test.class D:\ex\p1\Test.class “.”代表当前路径,如果在第一个路径下就找到了所需的类文件,则停止搜索。否则依次搜 索后续路径,如果在所有的路径中都未找到所需的类文件,则编译或运行出错。 3、 访问修饰符 Java 语言允许对类中定义的各种属性和方法进行访问控制,即规定不同的保护等级来限制 对它们的使用。为什么要这样做?Java 语言引入类似访问控制机制的目的在于实现信息的封装和 隐 藏。Java 语 言为 对 类 中 的属性 和 方 法进行 有效 地 访问控 制, 将 它 们 分 为四个 等级: private、无修饰符、protected、public,具体规则如下: 表 Java 类成员的访问控制 变量和方法可以使用四个访问级别中的任意一个修饰,类可以使用公共或无修饰级别修饰。 变量、方法或类有缺省(无修饰符)访问性,如果它没有显式受保护修饰符作为它的声明的 7 一部分的话。这种访问性意味着,访问可以来自任何方法,当然这些方法只能在作为对象的同一 个包中的成员类当中。 以修饰符 protected 标记的变量或方法实际上比以缺省访问控制标记的更易访问。一个 protected 方法或变量可以从同一个包中的类当中的任何方法进行访问,也可以是从任何子类 中的任何方法进行访问。当它适合于一个类的子类但不是不相关的类时,就可以使用这种受保护 访问来访问成员。 4、 类定义 Java 程序的基本单位是类,你建立类之后,就可用它来建立许多你需要的对象。Java 把每 一个可执行的成分都变成类。 类的定义形式如下: <权限修饰符> [一般修饰符] class <类名> [extends 父类][implements 接口]{ [<属性定义>] [<构造方法定义>] [<方法定义>] } 这里,类名要是合法的标识符。在类定义的开始与结束处必须使用花括号。你也许想建立一个 矩形类,那么可以用如下代码: public class Rectangle{ ......//矩形具体的属性和方法 } 5、 构造方法 什么是构造方法 类有一个特殊的成员方法叫作构造方法,它的作用是创建对象并初始化成员变量。在创建对 象时,会自动调用类的构造方法。 构造方法定义规则 Java 中的构造方法必须与该类具有相同的名字,并且没有方法的返回类型(包括没有 void)。另外,构造方法一般都应用 public 类型来说明,这样才能在程序任意的位置创建类 的实例--对象。 示例 下面是一个 Rectangle 类的构造方法,它带有两个参数,分别表示矩形的长和宽: public class Rectangle { public Rectangle(int w, int h) { 8 width = w; height = h; } public Rectangle() { } } 每个类至少有一个构造方法。如果不写一个构造方法,Java 编程语言将提供一个默认的, 该构造方法没有参数,而且方法体为空。 注意:如果一个类中已经定义了构造方法则系统不再提供默认的构造方法。 6、 析构方法 当垃圾回收器将要释放无用对象的内存时,先调用该对象的 finalize()方法。如果在程 序终止前垃圾回收器始终没有执行垃圾回收操作,那么垃圾回收器将始终不会调用无用对象的 finalize()方法。在 Java 的 Object 基类中提供了 protected 类型的 finalize()方法, 因此任何 Java 类都可以覆盖 finalize()方法,通常,在析构方法中进行释放对象占用的相 关资源的操作。 Java 虚拟机的垃圾回收操作对程序完全是透明的,因此程序无法预料某个无用对象的 finalize()方法何时被调用。如果一个程序只占用少量内存,没有造成严重的内存需求,垃 圾回收器可能没有释放那些无用对象占用的内存,因此这些对象的 finalize()方法还没有被 调用,程序就终止了。 程序即使显式调用 System.gc()或 Runtime.gc()方法,也不能保证垃圾回收操作一定执 行,也就不能保证对象的 finalize()方法一定被调用。 当垃圾回收器在执行 finalize()方法的时候,如果出现了异常,垃圾回收器不会报告异 常,程序继续正常运行。 @Override protected void finalize(){ System.out.println("in finalize"); } 在 Java 编程里面,一般不需要我们去写析构方法,这里只是了解一下就可以了。 7、 成员变量 成员变量是指类的一些属性定义,标志类的静态特征,它的基本格式如下: 访问修饰符 修饰符 类型 属性名称=初始值; 访 问 修 饰 符 : 可 以 使 用 四 种 不 同 的 访 问 修 饰 符 中 的 一 种 , 包 括 public( 公 共 的 ) 、 protected (受保护的),无修饰符和 private(私有的)。public 访问修饰符表示属性可 以从任何其它代码调用。private 表示属性只可以由该类中的其它方法来调用。protected 将 9 在以后的课程中讨论。 修饰符:是对属性特性的描述,例如后面会学习到的:static、final 等等。 类型:属性的数据类型,可以是任意的类型。 属性名称:任何合法标识符 初始值:赋值给属性的初始值。如果不设置,那么会自动进行初始化,基本类型使用缺省值, 对象类型自动初始化为 null。 成员变量有时候也被称为属性、实例变量、域,它们经常被互换使用。 8、 方法 定义 方法就是对象所具有的动态功能。Java 类中方法的声明采用以下格式: 访问修饰符 修饰符 返回值类型 方法名称 (参数列表) throws 异常列表 {方法体} 访问修饰符 : 可以使用四种不同的访问修饰符中的一种,包括 public、 protected、无 修饰符和 private。public 访问修饰符表示方法可以从任何其它代码调用。private 表示方 法只可以由该类中的其它方法来调用。protected 将在以后的课程中讨论。 修饰符:是对方法特性的描述,例如后面会学习到的:static、final、abstract、 synchronized 等等。 返回值类型:表示方法返回值的类型。如果方法不返回任何值,它必须声明为 void(空)。 Java 技术对返回值是很严格的,例如,如果声明某方法返回一个 int 值,那么方法必须从所有 可能的返回路径中返回一个 int 值(只能在等待返回该 int 值的上下文中被调用。) 方法名称:可以是任何合法标识符,并带有用已经使用的名称为基础的某些限制条件。 参数列表:允许将参数值传递到方法中。列举的元素由逗号分开,而每一个元素包含一个类 型和一个标识符。在下面的方法中只有一个形式参数,用 int 类型和标识符 days 来声明: public void test(int days){} throws 异常列表:子句导致一个运行时错误(异常)被报告到调用的方法中,以便以合 适的方式处理它。异常在后面的课程中介绍。 花括号内是方法体,即方法的具体语句序列。 示例 比如现在有一个“车”的类——Car,“车”具有一些基本的属性,比如四个轮子,一个方 向盘,车的品牌等等。当然,车也具有自己的功能,也就是方法,比如车能够“开动”——run。 要想车子能够开动,需要给车子添加汽油,也就是说,需要为 run 方法传递一些参数“油”进去。 车子就可以跑起来,这些油可以供行驶多少公里?就需要 run 方法具有返回值“行驶里程数”。 package cn.Javadriver.javatest; public class Car {// 车这个类 private String make;// 一个车的品牌 10 private int tyre;// 一个车具有轮胎的个数 private int wheel;// 一个车具有方向盘的个数 public Car() { // 初始化属性 make = "BMW";// 车的品牌是宝马 tyre = 4;// 一个车具有4个轮胎 wheel = 1;// 一个车具有一个方向盘 } /** * 车这个对象所具有的功能,能够开动 * * @param oil 为车辆加汽油的数量 * @return 车辆行驶的公里数 */ public double run(int oil) { // 进行具体的功能处理 return 100*oil/8; } public static void main(String[] args){ Car c=new Car(); double mileage=c.run(100); System.out.println("行驶了 "+mileage+" 公里"); } } main 方法是一个特殊的方法,如果按照 public static void main(String[] args) 的格式写,它就是一个类的入口方法,也叫主函数。当这个类被 java 指令执行的时候,首先执行的是 main 方法,如果一个类没有入口方法,就不能使用 java 指令执行它,但可以通过其他的方法调用 它。 形参和实参 形参:就是形式参数的意思。是在定义方法名的时候使用的参数,用来标识方法接收的参数 类型,在调用该方法时传入。 实参:就是实际参数的意思。是在调用方法时传递给该方法的实际参数。 比如:上面的例子中“int oil”就是个形式参数,这里只是表示需要加入汽油,这个方法 才能正常运行,但具体加入多少,要到真正使用的时候,也就是调用这个方法的时候才具体确定, 加入调用的时候传入“100”,这就是个实际参数。 形参和实参有如下基本规则: (1)形参和实参的类型必须要一致,或者要符合隐含转换规则 (2)形参类型不是引用类型时,在调用该方法时,是按值传递的。在该方法运行时, 形参 和实参是不同的变量,它们在内存中位于不同的位置,形参将实参的值复制一份,在该方法运行 结束的时候形参被释放,而实参内容不会改变。 11 (3)形参类型是引用类型时,在调用该方法时,是按引用传递的。运行时,传给方法的是实 参的地址,在方法体内部使用的也是实参的地址,即使用的就是实参本身对应的内存空间。所以 在函数体内部可以改变实参的值。 参数可变的方法 从 JDK5.0 开始,提供了参数可变的方法。 当不能确定一个方法的入口参数的个数时,5.0 以前版本的 Java 中,通常的做法是将多 个参数放在一个数组或者对象集合中作为参数来传递,5.0 版本以前的写法是: Int sum(Integer[] numbers){…} //在另一个方法中调用该方法 sum(new Integer[] {12,13,20}); 而在 5.0 版本中可以写为: //注意:方法定义中是三个点 int sum(Integer... numbers){ //方法内的操作 } //调用该方法 sum(12,13,20);//正确 sum(10,11); //正确 也就是说,传入参数的个数并不确定。但请注意:传入参数的类型必须是一致的,究其本质 就是一个数组。 显然,JDK5.0 版本的写法更为简易,也更为直观,尤其是方法的调用语句,不仅简化很多, 而且更符合通常的思维方式,更易于理解。 三、如何使用一个 Java 类 前面学习了如何定义一个类,下面来学习如何使用一个类。 1、 new 关键字 假如定义了一个表示日期的类,有三个整数变量;日、月和年的意义即由这些整数变量给出。 如下所示: class MyDate { int day; int month; int year; public String toString() { int num=0; return day+","+month+","+year; } 12 } 名称 MyDate 按照类声明的大小写约定处理,而不是由语意要求来定。 在可以使用变量之前,实际内存必须被分配。这个工作是通过使用关键字 new 来实现的。如 下所示: MyDate myBirth; myBirth = new MyDate() 第一个语句(声明)仅为引用分配了足够的空间,而第二个语句则通过调用对象的构造方法为 构成 MyDate 的三个整数分配了空间。对象的赋值使变量 myBirth 重新正确地引用新的对象。 这两个操作被完成后,MyDate 对象的内容则可通过 myBirth 进行访问。 关键字 new 意味着内存的分配和初始化,new 调用的方法就是类的构造方法。 假使定义任意一个 class XXXX,可以调用 new XXXX()来创建任意多的对象,对象之间 是分隔的。就像有一个汽车的类,可以使用 new 关键字创建多个具体的对象,比如有红旗的汽车、 奇瑞的汽车、大众的汽车等等,它们都是独立的单元,相互之间是隔离的。 一个对象的引用可被存储在一个变量里,因而一个变量点成员(如 myBirth.day)可用来访 问每个对象的单个成员。请注意在没有对象引用的情况下,仍有可能使用对象,这样的对象称 作”匿名”对象。 使用一个语句同时为引用 myBirth 和由引用 myBirth 所指的对象分配空间也是可能的。 MyDate myBirth = new MyDate(); 欢迎来到 java 快车学习 免费试学 2、 使用对象中的属性和方法 要调用对象中的属性和方法,使用“.”操作符。 对象创建以后就有了自己的属性,通过使用“.”操作符实现对其属性的访问。例如: myBirth.day = 26; myBirth.month = 7; myBirth.year = 2000; 对象创建以后,通过使用“.”操作符实现对其方法的调用,方法中的局部变量被分配内存 空间,方法执行完毕,局部变量即刻释放内存。例如: myBirth.toString(); 13 0x1fb8ee3myBirth 栈内存 堆内存 0 0 0 day month year 3、 this 关键字 关键字 this 是用来指向当前对象或类实例的,功能说明如下: 点取成员 this.day 指的是调用当前对象的 day 字段,示例如下: public class MyDate { private int day, month, year; public void tomorrow() { this.day = this.day + 1; // 其他代码 } } Java 编程语言自动将所有实例变量和方法引用与 this 关键字联系在一起,因此,使用 关键字在某些情况下是多余的。下面的代码与前面的代码是等同的。 public class MyDate { private int day, month, year; public void tomorrow() { day = day + 1; // 在 day 前面没有使用 this // 其他代码 } } 区分同名变量 在类属性上定义的变量和方法内部定义的变量相同的时候,到底是调用谁?例如: public class Test { int i = 2; public void t() { int i = 3; // 跟属性的变量名称是相同的 System.out.println("实例变量 i=" + this.i); System.out.println("方法内部的变量 i=" + i); } } 也就是说:“this.变量”调用的是当前属性的变量值,直接使用变量名称调用的是相对距 离最近的变量的值。 作为方法名来初始化对象 也就是相当于调用本类的其它构造方法,它必须作为构造方法的第一句。示例如下: public class Test { 14 public Test() { this(3);// 在这里调用本类的另外的构造方法 } public Test(int a) { } public static void main(String[] args) { Test t = new Test(); } } 作为参数传递 需要在某些完全分离的类中调用一个方法,并将当前对象的一个引用作为参数传递时。例如: Birthday bDay = new Birthday (this); 四、引用类型 早些时候的编程语言和初级程序员将每个变量看作相互无关的实体。例如,如果一个程序需 处理某个日期,则要声明三个单独的整数: int day, month, year; 上述语句作了两件事,一是当程序需要日、月或年的有关信息时,它将操作一个整数; 二是 为那些整数分配存储器。 尽管这种作法很容易理解,但它存在两个重大缺陷。首先,如果程序需同时记录几个日期, 则需要三个不同的声明。例如,要记录两个生日,你可能使用: int myBirthDay, myBirthMonth, myBirthYear; int yourBirthDay, yourBirthMonth, yourBirthYear; 这种方法很快就会引起混乱,因为需要的名称很多。 第二个缺陷是这种方法忽视了日、月和年之间的联系并把每个变量都作为一个独立的值,每 个变量都是一个独立单元。 为了解决这个问题,Java 引入了引用类型,允许程序员自己定义类型。 1、 什么是引用类型 引用类型(reference type)指向一个对象,不是原始值,指向对象的变量是引用变量。 在 Java 里面除去基本数据类型的其它类型都是引用数据类型,自己定义的 class 类都是 引用类型,可以像基本类型一样使用。在 Java 程序运行时,会为引用类型分配一定量的存储空 间并解释该存储空间的内容。 示例如下: 15 public class MyDate { private int day = 8; private int month = 8; private int year = 2008; public MyDate(int day, int month, int year){…} public void print(){…} } public class TestMyDate { public static void main(String args[]) { // 这个 today 变量就是一个引用类型的变量 MyDate today = new MyDate(23, 7, 2008); } } 2、 引用类型的赋值 在 Java 编程语言中,用类的一个类型声明的变量被指定为引用类型,这是因为它正在引 用一个非原始类型,这对赋值具有重要的意义。请看下列代码片段: int x = 7; int y = x; String s = "Hello"; String t = s; 四个变量被创建:两个原始类型 int 和两个引用类型 String。x 的值是 7,而这个值被 复制到 y;x 和 y 是两个独立的变量且其中任何一个的进一步的变化都不对另外一个构成影响。至 于变量 s 和 t,只有一个 String 对象存在,它包含了文本“Hello”,s 和 t 均引用这个单一 的对象。 将变量 t 重新定义为:t=”World”; 则新的对象 World 被创建,而 t 引用这个对象。 上述过程被描述如下: 16 7 7 0x1fb8f25 0x1fb8ee3 x s t y “Hello” “World” 7 7 0x1fb8ee3 0x1fb8ee3 x s t y “Hello” 3、 按值传递还是按引用传递 这个在 Java 里面是经常被提起的问题,也有一些争论,似乎最后还有一个所谓的结论: “在 Java 里面参数传递都是按值传递”。事实上,这很容易让人迷惑,下面先分别看看什么 是按值传递,什么是按引用传递,只要能正确理解,至于称作按什么传递就不是个大问题了。 按值传递 指的是在方法调用时,传递的参数是按值的拷贝传递。示例如下: 1. public class TempTest { 2. private void test1(int a) { 3. // 做点事情 4. a++; 5. } 6. 7. public static void main(String[] args) { 8. TempTest t = new TempTest(); 9. int a = 3; 10. t.test1(a);// 这里传递的参数a就是按值传递。 11. System.out.println("main方法中的a===" + a); 12. } 13. } 按值传递重要特点:传递的是值的拷贝,也就是说传递后就互不相关了。第 9 行的 a 和第 2 行 的 a 是两个变量,当改变第 2 行 a 的值,第 9 行 a 的值是不变的,所以打印结果是 3。 我们把第 9 行的 a 称之为实参,第 2 行的 a 称之为形参;对于基本数据类型,形参数据的改 变,不影响实参的数据。 按引用传递 指的是在方法调用时,传递的参数是按引用进行传递,其实传递的引用的地址,也就是变量 所对应的内存空间的地址。 示例如下: 1. public class TempTest { 2. private void test1(A a) { 3. a.age = 20; 4. System.out.println("test1方法中的age="+a.age); 5. } 6. public static void main(String[] args) { 7. TempTest t = new TempTest(); 8. A a = new A(); 17 3main 方法中的 a 4test1 方法中的 a 9. a.age = 10; 10. t.test1(a); // 这里传递的参数a就是按引用传递 11. System.out.println("main方法中的age="+a.age); 12. } 13. } 14. class A { 15. public int age = 0; 16. } 运行结果如下: test1 方法中的 age=20 main 方法中的 age=20 按引用传递的重要特点: 传递的是值的引用,也就是说传递前和传递后都指向同一个引用(也就是同一个内存空间)。 要想正确理解按引用传递的过程,就必须学会理解内存分配的过程,内存分配示意图可以辅 助我们去理解这个过程。 用上面的例子来进行分析: (1)运行开始,运行第 8 行,创建了一个 A 的实例,内存分配示意图如下: (2)运行第 9 行,是修改 A 实例里面的 age 的值,运行后内存分配示意图如下: (3)运行第 10 行,是把 main 方法中的变量 a 所引用的内存空间地址,按引用传递给 test1 方法中的 a 变量。请注意:这两个 a 变量是完全不同的,不要被名称相同所蒙蔽,但它们指向 了同一个 A 实例。内存分配示意图如下: (4)运行第 3 行,为 test1 方法中的变量 a 指向的 A 实例的 age 进行赋值,完成后形 18 0x1fb8ee3main 方法中的 a age=0 这是一个 A 的实例 0x1fb8ee3main 方法中的 a age=10 这是一个 A 的实例 0x1fb8ee3 main 方法中的 a age=10 这是一个 A 的实例 0x1fb8ee3 test1 方法中的 a 成新的内存示意图如下: 此时 A 实例的 age 值的变化是由 test1 方法引起的。 (5)运行第 4 行,根据此时的内存示意图,输出 test1 方法中的 age=20 (6)运行第 11 行,根据此时的内存示意图,输出 main 方法中的 age=20 对上述例子的改变 理解了上面的例子,可能有人会问,那么能不能让按照引用传递的值,相互不影响呢? 就 是 test1 方法里面的修改不影响到 main 方法里面呢? 方法是在 test1 方法里面新 new 一个实例就可以了。改变成下面的例子,其中第 3 行为 新加的: 1. public class TempTest { 2. private void test1(A a) { 3. a = new A();// 新加的一行 4. a.age = 20; 5. System.out.println("test1方法中的age=" + a.age); 6. } 7. public static void main(String[] args) { 8. TempTest t = new TempTest(); 9. A a = new A(); 10. a.age = 10; 11. t.test1(a); 12. System.out.println("main方法中的age=" + a.age); 13. } 14. } 15. class A { 16. public int age = 0; 17. } 运行结果为: test1 方法中的 age=20 main 方法中的 age=10 为什么这次的运行结果和前面的例子不一样呢,还是使用内存示意图来理解一下: (1)运行开始,运行第 9 行,创建了一个 A 的实例,内存分配示意图如下: 19 0x1fb8ee3main 方法中的 a age=20 这是一个 A 的实例 0x1fb8ee3test1 方法中的 a 0x1fb8ee3main 方法中的 a age=0 这是一个第 9 行 创建的 A 实例 (2)运行第 10 行,是修改 A 实例里面的 age 的值,运行后内存分配示意图如下: (3)运行第 11 行,是把 main 方法中的变量 a 所引用的内存空间地址,按引用传递给 test1 方法中的 a 变量。请注意:这两个 a 变量是完全不同的,不要被名称相同所蒙蔽。 也就是说:是两个变量都指向同一个空间。 (4)运行第 3 行,为 test1 方法中的变量 a 重新生成了新的 A 实例的,完成后形成的新 的内存示意图如下: (5)运行第 4 行,为 test1 方法中的变量 a 指向的新的 A 实例的 age 进行赋值,完成 后形成的新的内存示意图如下: 注意:这个时候 test1 方法中的变量 a 的 age 被改变,而 main 方法中的是没有改变的。 (6)运行第 5 行,根据此时的内存示意图,输出 test1 方法中的 age=20 (7)运行第 12 行,根据此时的内存示意图,输出 main 方法中的 age=10 说明: ➢ “在 Java 里面参数传递都是按值传递”这句话的意思是:按值传递是传递的值的拷贝, 按引用传递其实传递的是引用的地址值,所以统称按值传递。 20 0x1fb8ee3main 方法中的 a age=10 这是一个第 9 行 创建的 A 实例 0x1fb8ee3main 方法中的 a age=10 这是一个第 9 行 创建的 A 实例0x1fb8ee3test1 方法中的 a 0x1fb8ee3main 方法中的 a age=10 这是一个第 9 行 创建的 A 实例0x1fb81f3test1 方法中的 a age=0 这是一个第 3 行 创建的 A 实例 0x1fb8ee3main 方法中的 a age=10 这是一个第 9 行 创建的 A 实例0x1fb81f3test1 方法中的 a age=20 这是一个第 3 行 创建的 A 实例 ➢ 在 Java 里面只有基本类型和按照下面这种定义方式的 String 是按值传递,其它的 都是按引用传递。就是直接使用双引号定义字符串方式:String str = "Java 快车"; 五、 类中的变量 1、 实例变量和局部变量 在方法外定义的变量主要是实例变量,它们是在使用 new Xxxx ()创建一个对象时被分配 内存空间的。每当创建一个对象时,系统就为该类的所有实例变量分配存储空间;创建多个对象 就有多份实例变量。通过对象的引用就可以访问实例变量。 在方法内定义的变量或方法的参数被称为局部 (local) 变量 ,有时也被用为自动 (automatic)、临时(temporary)或栈(stack)变量。 方法参数变量定义在一个方法调用中传送的自变量,每次当方法被调用时,一个新的变量就 被创建并且一直存在到程序的运行跳离了该方法。当执行进入一个方法遇到局部变量的声明语句 时,局部变量被创建,当执行离开该方法时,局部变量被取消,也就是该方法结束时局部变量的 生命周期也就结束了。 因而,局部变量有时也被引用为“临时或自动”变量。在成员方法内定义的变量对该成员变 量是“局部的”,因而,你可以在几个成员方法中使用相同的变量名而代表不同的变量。该方法 的应用如下所示: public class Test { private int i; // Test类的实例变量 public int firstMethod() { int j = 1; // 局部变量 // 这里能够访问i和j System.out.println("firstMethod 中 i=" + i + ",j=" + j); return 1; } // firstMethod()方法结束 public int secondMethod(float f) { // method parameter int j = 2; // 局部变量,跟firstMethod()方法中的j是不同的 // 这个j的范围是限制在secondMethod()种的 // 在这个地方,可以同时访问i,j,f System.out.println("secondMethod中 i=" + i + ",j=" + j + ",f=" +f); return 2; } public static void main(String[] args) { Test t = new Test(); t.firstMethod(); t.secondMethod(3); 21 } } 2、 变量初始化 在 Java 程序中,任何变量都必须经初始化后才能被使用。当一个对象被创建时,实例变量 在分配内存空间时按程序员指定的初始化值赋值,否则系统将按下列默认值进行初始化: 数据类型 初始值 byte 0 short 0 int 0 long 0L char '\u0000' float 0.0f double 0 boolean false 所有引用类型 null 注意:一个具有空值“null”的引用不引用任何对象。试图使用它引用的对象将会引起一个 异常。异常是出现在运行时的错误,这将在模块“异常”中讨论。 在方法外定义的变量被自动初始化。局部变量必须在使用之前做“手工”(由程序员进行)初 始化。如果编译器能够确认一个变量在初始化之前可能被使用的情形,编译器将报错。 public class Test { private int i; // Test类的实例变量 public void test1() { int x = (int) (Math.random() * 100); int y; int z; if (x > 50) { y = 9; } z = y + x; // 将会引起错误,因为y可能还没有被初始化就使用了 22 } public static void main(String[] args) { Test t = new Test(); t.test1(); } } 3、 变量的范围(scope) Java 变量的范围有四个级别:类级、对象实例级、方法级、块级。 ➢ 类级变量又称全局级变量,在对象产生之前就已经存在,就是用 static 修饰的属性。 ➢ 对象实例级,就是属性变量。 ➢ 方法级:就是在方法内部定义的变量,就是局部变量。 ➢ 块级:就是定义在一个块内部的变量,变量的生存周期就是这个块,出了这个块就消失 了,比如 if、for 语句的块。 示例如下: public class Test { private static String name = "Java快车";// 类级 private int i; // 对象实例级,Test类的实例变量 {// 属性块,在类初始化属性时候运行 int j = 2;// 块级 } public void test1() { int j = 3;// 方法级 if (j == 3) { int k = 5;// 块级 } // 这里不能访问块级的变量,块级变量只能在块内部访问 System.out.println("name=" + name + ",i=" + i + ",j=" + j); } public static void main(String[] args) { Test t = new Test(); t.test1(); Test t2 = new Test(); } } 运行结果: name=Java 快车,i=0,j=3 23 引用变量 t 引用变量 t2 i=0 i=0 name = "Java 快车 " 堆区 方法区 说明 (1)方法内部除了能访问方法级的变量,还可以访问类级和实例级的变量。 (2)块内部能够访问类级、实例级变量,如果块被包含在方法内部,它还可以访问方法级的 变量。 (3)方法级和块级的变量必须被显示地初始化,否则不能访问。 六、 包装类 虽然 Java 语言是典型的面向对象编程语言,但其中的 8 种基本数据类型并不支持面向对象 的编程机制,基本类型的数据不具备“对象”的特性----不携带属性、没有方法可调用。 沿用它 们只是为了迎合人类根深蒂固的习惯,并的确能简单、有效地进行常规数据处理。 这种借助于非面向对象技术的做法有时也会带来不便,比如引用类型数据均继承了 Object 类的特性,要转换为 String 类型(经常有这种需要)时只要简单调用 Object 类中定义的 toString()即可,而基本数据类型转换为 String 类型则要麻烦得多。为解决此类问题 , Java 语言引入了封装类的概念,在 JDK 中针对各种基本数据类型分别定义相应的引用类型,并 称之为包装类(Wrapper Classes)。 下表描述了基本数据类型及对应的包装类 基本数据类型 对应的包装类 byte Byte short Short int Integer long Long char Character float Float double Double 24 boolean Boolean 每个包装类的对象可以封装一个相应的基本类型的数据,并提供了其它一些有用的功能。包 装类对象一经创建,其内容(所封装的基本类型数据值)不可改变。 例,包装类用法程序:Wrapper.java public class Wrapper { public static void main(String args[]) { int i = 500; Integer t = new Integer(i); int j = t.intValue(); // j = 500 String s = t.toString(); // s = "500" System.out.println(t); Integer t1 = new Integer(500); System.out.println(t.equals(t1)); } } 程序运行结果为: 500 true 包装类一个常用的功能就是把字符串类型的数据造型成为对应的基本数据类型,如下示例: String str = "123"; int a = Integer.parseInt(str); 更过的功能还请查看 JDK 文档。 七、 类型转换 在赋值的信息可能丢失的地方,编译器需要程序员用类型转换(type cast)的方法确认 赋值。Java 中的类型转换分成:强制类型转换、自动升级类型转换和后面将会学习到的向上造 型。 1、 强制类型转换 把某种类型强制转换成另外一种类型就叫做强制类型转换。 例如,可以将一个 long 值“挤压”到一个 int 变量中。显式转型做法如下: long bigValue = 99L; int squashed = (int)(bigValue); 25 在上述程序中,期待的目标类型被放置在圆括号中,并被当作表达式的前缀,该表达式 必 须被更改。一般来讲,建议用圆括号将需要转型的全部表达式封闭。否则,转型操作的优先级可能 引起问题。 注意:强制类型转换只能用在原本就是某个类型,但是被表示成了另外一种类型的时候,可 以把它强制转换回来。强制转换并不能在任意的类型间进行转换。 比如上面的例子:99 这个数本来就是一个 int 的数,但是它通过在后面添加 L 来表示 成了一个 long 型的值,所以它才能够通过强制转换来转换回 int 类型。 2、 升级和表达式的类型转换 当没有信息丢失时,变量可被自动升级为一个较长的形式(如:int 至 long 的升级) long bigval = 6; // 6 是 int 类型, OK int smallval = 99L; // 99L 是 long 型, 非法 double z = 12.414F; // 12.414F 是 float 型, OK float z1 = 12.414; // 12.414 是 double 型, 非法 对“+”运算符来说,当两个操作数是原始数据类型时,其结果至少有一个 int,并且有 一个通过提升操作数到结果类型,或通过提升结果至一个较宽类型操作数而计算的值,这可能会 导致溢出或精度丢失。例如: short a,b,c ; a=1; b=2; c=a+b; 上述程序会出错是因为在执行“+”操作前,a 和 b 会从 short 提升至 int,两个 int 相加的结果也是 int,然后把一个 int 的值赋值给 c,但是 c 是 short 型的,所以出错。 如果 c 被声明为一个 int,或按如下操作进行类型转换: c = (short)(a+b); 则上述代码将会成功通过。 尤其在四则运算表达式里面,如果不强制进行类型转换,那么运算最后的结果就是精度最高 的那个操作数决定的。比如:3*5.0 的结果就是 double 型的,应该定义成为:double a = 3 * 5.0; 在第二章的算数运算符部分也有对向上造型的相关介绍。 3、 自动包装和解包 自动包装:就是把基础数据类型自动封装并转换成对应的包装类的对象。 自动解包:就是把 包装类的对象自动解包并转换成对应的基础数据类型。 示例如下: public class Test { 26 public static void main(String args[]) { Integer a1 = 5;// 自动包装 int a2 = new Integer(5);// 自动解包 System.out.println("a1=" + a1 + ",a2=" + a2); } } 运行结果:a1=5,a2=5 八、 Java 类的基本运行顺序 作为程序员,应该对自己写的程序具备充分的掌控能力,应该清楚程序的基本运行过程,否 则糊里糊涂的,不利于对程序的理解和控制,也不利于技术上的发展。 我们以下面的类来说明一 个基本的 Java 类的运行顺序: 1. public class Test { 2. private String name; 3. private int age; 4. 5. public Test() { 6. name = "Tom"; 7. age = 20; 8. } 9. public static void main(String[] args) { 10. Test t = new Test(); 11. System.out.println(t.name + "的年龄是" + t.age ); 12. } 13. } 运行的基本顺序是: (1)先运行到第 9 行,这是程序的入口。 (2)然后运行到第 10 行,这里要 new 一个 Test,就要调用 Test 的构造方法。 (3)就运行到第 5 行,注意:可能很多人觉得接下来就应该运行第 6 行了,错!初始化一个 类,必须先初始化它的属性。 (4)因此运行到第 2 行,然后是第 3 行。 (5)属性初始化完过后,才回到构造方法,执行里面的代码,也就是第 6 行、第 7 行。 (6)然后是第 8 行,表示 new 一个 Test 实例完成。 (7)然后回到 main 方法中执行第 11 行。 (8)然后是第 12 行,main 方法执行完毕。 27 说明:这里只是说明一个基本的运行过程,没有考虑更多复杂的情况。 java 快车 作业 1. 写一个 MyPoint 完全封装类,其中含有私有的 int 类型的 x 和 y 属性,分别用 公有的 getX 和 setX、getY 和 setY 方法访问,定义一个 toString 方法用来显 示这个对象的 x、y 的值,如显示(1,2),最后用 main 方法测试。 2. 在 MyPoint 类中增加 equals()、toString()方法,根据命令行参数个数测试:若不 传参数,则显示(0,0);若传一个参数,则打印(此参数值,0);若传两个参数,则 打印(第一个参数值,第二个参数值)。 3. 有一个序列,首两项为 0,1,以后各项值为前两项值之和。写一个方法来实现求这个序 列的和 4. 请编写一个方法实现如下功能:将 1 至 7 的数字转换为星期日到星期六的字符串。 5. 请编写一个方法实现如下功能:将任意三个整数 a,b,c 按从小到大的顺序输出。 6. 请编写一个方法实现如下功能:用程序找出每位数的立方和等于该数本身值的所有的 3 位数。(水仙花数) 7. 请编写一个方法实现如下功能:计算 1 加到 n(n>=2 的整数)的总和。 8. 请编写一个方法实现如下功能:得到一个整数的绝对值。 28 29
还剩28页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享pdf获得金币 ] 1 人已下载

下载pdf

pdf贡献者

Mrxit

贡献于2013-10-17

下载需要 8 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf