第三章 Java类和对象
学习目标:
2 面向对象基础
2 掌握对象的三大特性
2 掌握 Java 类的构建
2 掌握如何使用 Java 类
2 理解引用类型
2 理解按值传递和按引用传递
2 深入理解变量
2 掌握包装类
2 理解类型转换
2 理解 Java 类的基本运行顺序
一:面向对象初步
1:什么是对象
对象是真实世界中的物体在人脑中的映象,包括实体对象和逻辑对象。实体对象指的是
我们能在现实生活中能看得见、摸得着,实际存在的东西,比如:人,桌子,椅子等。逻辑
对象是针对非具体物体,但是在逻辑上存在的东西的反映,比如: 人与人的关系。为了简单,
这里讨论的对象都是实体对象。
2:对象的基本构成
初次接触对象,我们从实体对象入手,因为看得见、摸得着会比较容易理解。
分析实体对象的构成,发现有这样一些共同点,这些实体对象都有自己的属性,这些属
性用来决定了对象的具体表现,比如:人有身高、体重等。
除了这些静态的,用于描述实体对象的基本情况外,实体对象还有自己的动作,通过这
些动作能够完成一定的功能,我们称之为方法,比如:人的手能动,能够写字,能够刷牙等。
对象同时具备这些静态属性和动态的功能。
3:如何进行对象抽象
抽象是在思想上把各种对象或现象之间的共同的本质属性抽取出来而舍去个别的非本
质的属性的思维方法。 也就是说把一系列相同或类似的实体对象的特点抽取出来,采用一个
统一的表达方式,这就是抽象。
比如:张三这个人身高 180cm,体重 75kg,会打篮球,会跑步
李四这个人身高 170cm,体重 70kg,会踢足球
现在想要采用一个统一的对象来描述张三和李四, 那么我们就可以采用如下的表述方法
来表述:
人{
静态属性:
姓名;
身高;
体重;
动态动作:
打篮球();
跑步();
踢足球();
}
这个“人”这个对象就是对张三和李四的抽象,那么如何表述张三这个具体的个体呢:
人{
静态属性:
姓名=张三;
身高 = 180cm;
体重 = 75kg;
动态动作:
打篮球(); //相应的打篮球的功能实现
跑步();//相应的跑步的功能实现
踢足球();
}
如何表述李四这个具体的个体呢:
人{
静态属性:
姓名=李四;
身高 = 170cm;
体重 = 70kg;
动态动作:
打篮球();
跑步();
踢足球();//相应的踢足球的功能实现
}
对实体对象的抽象一定要很好的练习,可以把你所看到的任何物体都拿来抽象, “一切
皆对象” 。要练习到,你看到的没有物体,全是对象就好了。
4:抽象对象和实体对象的关系
仔细观察上面的抽象对象——“人” ,和具体的实体对象: “张三” 、 “李四” 。你会发现,
抽象对象只有一个,实体对象却是无数个,通过对抽象对象设置不同的属性,赋予不同的功
能,那么就能够表示不同的实体对象。
这样就大大简化了对象的描述工作,使用一个对象就可以统一地描述某一类实体了,在
需要具体的实体的时候,分别设置不同的值就可以表示具体对象了。
5:Java中的类和对象
5.1:Java中的类
把抽象出来的对象使用Java 表达出来,那就是类 class。类在 Java编程语言中作为定义
新类型的一种途径,类声明可定义新类型并描述这些类型是如何实现的。接下来将会学习许
多关于类的特性。
比如前面讨论过的“人”使用Java表达出来就是一个类。
5.2:Java中的对象
Java 中的对象是在Java 中一个类的实例,也称实例对象。实例就是实际例子。
类可被认为是一个模板------你正在描述的一个对象模型。 一个对象就是你每次使用的
时候创建的一个类的实例的结果。
比如前面讨论的张三和李四,他们就是“人”这个类的实例。
二:面向对象三大特征
1:封装
封装这个词听起来好象是将什么东西包裹起来不要别人看见一样,就好象是把东西装进
箱子里面,这样别人就不知道箱子里面装的是什么东西了。其实 JAVA 中的封装这个概念也
就和这个是差不多的意思。
封装是 JAVA 面向对象的特点的表现,封装是一种信息隐蔽技术。它有两个含义:即把
对象的全部属性和全部服务结合在一起,形成一个不可分割的独立单位;以及尽可能隐藏对
象的内部结构。也就是说,如果我们使用了封装技术的话,别人就只能用我们做出来的东西
而看不见我们做的这个东西的内部结构了。
封装的功能
- 隐藏对象的实现细节
- 迫使用户去使用一个界面访问数据
- 使代码更好维护
封装迫使用户通过方法访问数据能保护对象的数据不被误修改, 还能使对象的重用变得
更简单。数据隐藏通常指的就是封装。它将对象的外部界面与对象的实现区分开来,隐藏实
现细节。迫使用户去使用外部界面,即使实现细节改变,还可通过界面承担其功能而保留原
样,确保调用它的代码还继续工作。封装使代码维护更简单。
2:继承
is a 关系——子对象
在面向对象世界里面,常常要创建某对象(如:一个职员对象) ,然后需要一个该基本
对象的更专业化的版本,比如,可能需要一个经理的对象。显然经理实际上是一个职员,经
理和职员具有is a 的关系,经理只是一个带有附加特征的职员。因此,需要有一种办法从
现有对象来创建一个新对象。这个方式就是继承。
“继承”是面向对象软件技术当中的一个概念。如果一个对象A继承自另一个对象B,
就把这个A称为"B的子对象",而把B 称为"A的父对象"。继承可以使得子对象具有父对
象的各种属性和方法,而不需要再次编写相同的代码。在令子对象继承父对象的同时,可
以重新定义某些属性,并重写某些方法,即覆盖父对象的原有属性和方法,使其获得与父
对象不同的功能。
3:多态
同一行为的多种不同表达,或者同一行为的多种不同实现就叫做多态。
还是用刚才经理和职员这个例子来举例:人事部门需要对公司所有职员统一制作胸卡
(一般也就是门禁卡,进出公司证明身份使用) ,制作的师傅说,只要告诉我一个人员的信
息,就可以制作出一份胸卡,简化一下就是:一位职员的信息对应一份胸卡。
这个时候,对胸卡制作的师傅而言,所有的人都是职员,无所谓是经理还是普通职员。
也就是说,对于传递职员信息这样一个行为, 存在多种不同的实现, 既可以传递经理的信息,
也可以传递普通职员的信息。这就是多态的表现。
再举一个例子:比如我们说“笔”这个对象,它就有很多不同的表达或实现,比如有钢
笔、铅笔、圆珠笔等等。那么我说“请给我一支笔” ,你给我钢笔、铅笔或者圆珠笔都可以,
这里的“笔”这个对象就具备多态。
三:Java 类的基本构成
1: Java类的定义形式
一个完整的Java 类通常由下面六个部分组成:
包定义语句
import语句
类定义{
成员变量
构造方法
成员方法
}
其中:只有类定义和“{}”是不可或缺的,其余部分都可以根据需要来定义。
下面分别来学习各个部分的基本规则,看看如何写Java 的类。
2: 包
2.1:包是什么
在 Java 中,包是类、接口或其它包的集合,包主要用来将类组织起来成为组,从而对
类进行管理。
2.2:包能干什么
包对于下列工作非常有用:
(1) :包允许您将包含类代码的文件组织起来,易于查找和使用适当的类。
(2) :包不止是包含类和接口,还能够包含其它包。形成层次的包空间。
(3) :它有助于避免命名冲突。当您使用很多类时,确保类和方法名称的唯一性是非常困难
的。包能够形成层次命名空间,缩小了名称冲突的范围,易于管理名称。
为便于管理数目众多的类,Java 语言中引入了“包”的概念,可以说是对定义的 Java
类进行“分组” ,将多个功能相关的类定义到一个“包”中,以解决命名冲突、引用不方便、
安全性等问题。
就好似当今的户籍制度,每个公民除有自己的名字“张三” 、 “李四”外还被规定了他的
户籍地。假定有两个人都叫张三,只称呼名字就无法区分他们,但如果事先登记他们的户籍
分别在北京和上海,就可以很容易的用“北京的张三” 、 “上海的张三”将他们区分开来。如
果北京市仍有多个张三,还可以细分为“北京市.海淀区的张三” 、 “北京市.西城区.平安大街
的张三”等等,直到能惟一标识每个“张三”为止。
JDK 中定义的类就采用了“包”机制进行层次式管理,下图显示了其组织结构的一部
分:
从图中可以看出,一个名为 java 的包中又包含了两个子包:io包和 lang包。lang 包中
包含了System, String, Object三个类的定义。事实上,Java 包中既可以包含类的定义,也可
以包含子包,或同时包含两者。
简而言之:从逻辑上讲,包是一组相关类的集合;从物理上讲,同包即同目录。
2.1:JDK中常用的包
java.lang----包含一些Java 语言的核心类,包含构成Java 语言设计基础的类。在此包中
定义的最重要的一个类是“Object” ,代表类层次的根,Java 是一个单根系统,最终的根就
是“Object” ,这个类会在后面讲到。
Java 并不具有“*”的方法,例如,不属于任何类的方法,Java 中的所有方法必须
始终属于某个类。经常需要使用数据类型转换方法。Java 在 Java.lang 包中定义了“包装对
lang包
java 包
io 包 System类 String类
Object类
象”类,使我们能够实现数据类型转换。如 Boolean、Character、Integer、Long、Float 和
Double,这些在后面会讲到。
此包中的其它类包括:
l Math——封装最常用的数学方法,如正弦、余弦和平方根。
l String,StringBuffer——封装最常用的字符串操作。
你不必显示导入该包,该Java 包通常已经导入。
java.awt----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被
用来构建和管理应用程序的图形用户界面(GUI)。
javax.swing----完全Java 版的图形用户界面(GUI)解决方案,提供了很多完备的组件,可
以应对复杂的桌面系统构建。
java.net----包含执行与网络相关的操作的类,如URL, Socket, ServerSocket等。
java.io----包含能提供多种输入/输出功能的类。
java.util----包含一些实用工具类,如定义系统特性、使用与日期日历相关的方法。还有
重要的集合框架。
2.2:Java中如何表达包——package语句
Java 语言使用package语句来实现包的定义。package 语句必须作为Java 源文件的第一
条语句, 指明该文件中定义的类所在的包。若缺省该语句,则指定为无名包,其语法格式为:
package pkg1[.pkg2[.pkg3…]]; //“[]”表示可选
Java 编译器把包对应于文件系统的目录管理,因此包也可以嵌套使用,即一个包中可
以含有类的定义也可以含有子包,其嵌套层数没有限制。package 语句中,用‘.’来指明
包的层次;
程序 package的使用:Test.java
1 package p1; 2 3 public class Test{ 4 5 public void display(){ 6 7 System.out.println("in method display()"); 8 9 } 10 11 }
Java 语言要求包声明的层次和实际保存类的字节码文件的目录结构存在对应关系, 以便
将来使用该类时能通过包名(也就是目录名)查找到所需要的类文件。简单地说就是包的层
次结构需要和文件夹的层次对应。
注意:每个源文件只有一个包的声明,而且包名应该全部小写。
具体来说,程序员要做以下工作:
2.3:编译和生成包
如果在程序Test.java 中已定义了包p1,就必须将编译生成的字节码文件 Test.class保存
在与包名同名的子目录中,可以选用下述两种方式之一:
采用下述命令编译:
javac Test.java
则编译器会在当前目录下生成Test.class文件, 再在适合位置手动创建一个名为 p1的子
目录,将Test.class复制到该p1 目录下。
采用简化的编译命令,就是可以带包编译
javac -d destpath Test.java
归入该包的类的字节代码文件应放在 java的类库所在路径的 destpath 子目录下。 现在
包的相对位置已经决定了,但 java类库的路径还是不定的。事实上,java可以有多个存放
类库的目录,其中的缺省路径为java 目录下的lib子目录,你可以通过使用-classpath选
项来确定你当前想选择的类库路径。除此之外,你还可以在 CLASSPATH环境变量中设置类库
路径。destpath为目标路径,可以是本地的任何绝对或相对路径。则编译器会自动在 destpath
目录下建立一个子目录p1,并将生成的.class文件自动保存到 destpath/p1 下。例如:
javac -d .\ Test.java
javac -d C:\test\ Test.java
2.4:带包运行
运行带包的程序,需要使用类的全路径,也就是带包的路径,比如上面的那个程序,就使
用如下的代码进行运行:
java p1.Test
3: import
为了能够使用某一个包的成员,我们需要在Java 程序中明确导入该包。使用“import”
语句可完成此功能。在 java 源文件中 import 语句应位于 package 语句之后,所有类的定义
之前,可以有0~多条,其语法格式为:
import package1[.package2…].(classname|*);
java 运行时环境将到CLASSPATH + package1.[package2…]路径下寻找并载入相应的字
节码文件classname.class。 “*”号为通配符,代表所有的类。也就是说 import语句为编译器
指明了寻找类的途径。
例,使用 import语句引入类程序:TestPackage.java
1 import p1.Test; //或者import p1.*; 2 3 public class TestPackage{ 4 5 public static void main(String args[]){ 6 7 Test t = new Test(); //Test类在 p1包中定义 8 9 t.display(); 10 11 } 12 13 }
java 编译器默认为所有的 java 程序引入了 JDK 的 java.lang 包中所有的类(import
java.lang.*;) ,其中定义了一些常用类:System、String、Object、Math等。因此我们可以直
接使用这些类而不必显式引入。但使用其它非无名包中的类则必须先引入、后使用。
3.1: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
其中, “.”代表当前路径,如果在第一个路径下就找到了所需的类文件,则停止搜索。
否则依次搜索后续路径,如果在所有的路径中都未找到所需的类文件,则编译或运行出错。
4: 访问修饰符
Java 语言允许对类中定义的各种属性和方法进行访问控制, 即规定不同的保护等级来限
制对它们的使用。为什么要这样做?Java 语言引入类似访问控制机制的目的在于实现信息
的封装和隐藏。 Java 语言为对类中的属性和方法进行有效地访问控制, 将它们分为四个等级:
private, 无修饰符, protected, public,具体规则如下:
表 Java 类成员的访问控制
变量和方法可以处于四个访问级别中的一个:公共,受保护,无修饰符或私有。类可以
在公共或无修饰级别。
变量、方法或类有缺省(无修饰符)访问性,如果它没有显式受保护修饰符作为它的声
明的一部分的话。这种访问性意味着,访问可以来自任何方法,当然这些方法只能在作为对
象的同一个包中的成员类当中。
以修饰符protected标记的变量或方法实际上比以缺省访问控制标记的更易访问。 一个
protected 方法或变量可以从同一个包中的类当中的任何方法进行访问,也可以是从任何子
类中的任何方法进行访问。当它适合于一个类的子类但不是不相关的类时,就可以使用这种
受保护访问来访问成员。
5: 类定义
Java 程序的基本单位是类,你建立类之后,就可用它来建立许多你需要的对象。Java
把每一个可执行的成分都变成类。
类的定义形式如下:
<权限修饰符> [一般修饰符] class <类名> {
[<属性定义>]
[<构造方法定义>]
[<方法定义>]
}
这里,类名要是合法的标识符。在类定义的开始与结束处必须使用花括号。你也许想建
立一个矩形类,那么可以用如下代码:
public class Rectangle{
......//矩形具体的属性和方法
}
6:构造方法
6.1:什么是构造方法
类有一个特殊的成员方法叫作构造方法,它的作用是创建对象并初始化成员变量。在创
建对象时,会自动调用类的构造方法。
6.2:构造方法定义规则
Java 中的构造方法必须与该类具有相同的名字,并且没有方法的返回类型(包括没有
void) 。另外,构造方法一般都应用public类型来说明,这样才能在程序任意的位置创建类
的实例--对象。
6.3:示例
下面是一个 Rectangle类的构造方法,它带有两个参数,分别表示矩形的长和宽:
1 public class Rectangle{ 2 3 public Rectangle(int w,int h) { 4 5 width=w; 6 7 height=h; 8 9 } 10 11 }6.4:说明
每个类至少有一个构造方法。如果不写一个构造方法, Java 编程语言将提供一个默认的,
该构造方法没有参数,而且方法体为空。
注意:如果一个类中已经定义了构造方法则系统不再提供默认的构造方法。
7:析构方法
析构方法finalize 的功能是:当对象被从内存中删除时,该成员方法将会被自动调用。
通常,在析构方法内,你可以填写用来回收对象内部的动态空间的代码。
特别注意:当我们去调用析构方法的时候,并不会引起该对象实例从内存中删除,而是
不会起到任何作用。
在Java编程里面,一般不需要我们去写析构方法,这里只是了解一下就可以了。
8:属性
8.1:属性是什么
简单点说,属性就是对象所具有的静态属性。
8.2:定义规则
Java 类中属性的声明采用如下格式:
访问修饰符 修饰符 类型 属性名称=初始值;
访问修饰符:可以使用四种不同的访问修饰符中的一种,包括 public(公共的)、
protected(受保护的) ,无修饰符和 private(私有的) 。public 访问修饰符表示属性可以
从任何其它代码调用。private 表示属性只可以由该类中的其它方法来调用。protected 将
在以后的课程中讨论。
修饰符:是对属性特性的描述,例如后面会学习到的:static、final等等。
类型:属性的数据类型,可以是任意的类型。
属性名称:任何合法标识符
初始值:赋值给属性的初始值。如果不设置,那么会自动进行初始化,基本类型使用缺
省值,对象类型自动初始化为null。
8.3:说明
属性有时候也被称为成员变量、实例变量、域,它们经常被互换使用。
9:方法
9.1:方法是什么
方法就是对象所具有的动态功能。
9.2:定义规则
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 异常列表:子句导致一个运行时错误(异常)被报告到调用的方法中,以便以
合适的方式处理它。异常在后面的课程中介绍。
花括号内是方法体,即方法的具体语句序列。
9.3:示例
比如现在有一个“车”的类——Car, “车”具有一些基本的属性,比如四个*,一个
方向盘,车的品牌等等。当然,车也具有自己的功能,也就是方法,比如车能够“开动”—
—run。要想车子能够开动,需要给车子添加汽油,也就是说,需要为 run 方法传递一些参
数“油”进去。车子跑起来过后,我们需要知道当前车辆运行的速度,就需要run方法具有
返回值“当前的速度” 。
1 package cn.javass.javatest; 2 3 public class Car {// 车这个类 4 5 private String make;// 一个车的品牌 6 7 private int tyre;// 一个车具有轮胎的个数 8 9 private int wheel;// 一个车具有方向盘的个数 10 11 12 13 public Car() { 14 15 // 初始化属性 16 17 make = "BMW";// 车的品牌是宝马 18 19 tyre = 4;// 一个车具有4个轮胎 20 21 wheel = 1;// 一个车具有一个方向盘 22 23 } 24 25 /** 26 27 * 车这个对象所具有的功能,能够开动 28 29 * @param oil 为车辆加汽油的数量 30 31 * @return 车辆当前运行的速度 32 33 */ 34 35 public double run(int oil) { 36 37 // 进行具体的功能处理 38 39 return 200.0; 40 41 } 42 43 }
9.4:形参和实参
形参:就是形式参数的意思。是在定义方法名的时候使用的参数,用来标识方法接收的
参数类型,在调用该方法时传入。
实参:就是实际参数的意思。是在调用方法时传递给该方法的实际参数。
比如:上面的例子中“int oil”就是个形式参数,这里只是表示需要加入汽油,这个方
法才能正常运行,但具体加入多少,要到真正使用的时候,也就是调用这个方法的时候才具
体确定,加入调用的时候传入“80” ,这就是个实际参数。
形参和实参有如下基本规则:
(1) :形参和实参的类型必须要一致,或者要符合隐含转换规则
(2) :形参类型不是引用类型时,在调用该方法时,是按值传递的。在该方法运行时,
形参和实参是不同的变量,它们在内存中位于不同的位置,形参将实参的值复制一份,在该
方法运行结束的时候形参被释放,而实参内容不会改变。
(3) :形参类型是引用类型时,在调用该方法时,是按引用传递的。运行时,传给方法
的是实参的地址,在方法体内部使用的也是实参的地址,即使用的就是实参本身对应的内存
空间。所以在函数体内部可以改变实参的值。
9.5:参数可变的方法
从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关键字
假如定义了一个表示日期的类,有三个整数变量;日、月和年的意义即由这些整数变量
给出。如下所示:
1 class MyDate { 2 3 int day; 4 5 int month; 6 7 int year; 8 9 }
名称 MyDate按照大小写的有关约定处理,而不是由语意要求来定。
那么怎么来使用这个类呢:在你可以使用变量之前,实际内存必须被分配。这个工作是
通过使用关键字new 来实现的。如下所示:
MyDate today;
today = new MyDate();
第一个语句(声明)仅为引用分配了足够的空间, 而第二个语句则通过调用对象的构造方
法为构成 MyDate 的三个整数分配了空间。对象的赋值使变量 today 重新正确地引用新的对
象。这两个操作被完成后,MyDate对象的内容则可通过today 进行访问。
关键字new意味着内存的分配和初始化,new 调用的方法就是类的构造方法。
使用一个语句同时为引用today 和由引用 today所指的对象分配空间也是可能的。
MyDate today = new MyDate();
2:如何使用对象中的属性和方法
要调用对象中的属性和方法,使用“.”操作符。
例如:
today.day = 26; today.month = 7; today.year = 2008;
3:this关键字
关键字this是用来指向当前对象或类实例的,功能说明如下:
3.1:点取成员
this.day 指的是调用当前对象的 day 字段,示例如下:
1 public class MyDate { 2 3 private int day, month, year; 4 5 public void tomorrow() { 6 7 this.day = this.day + 1; 8 9 //其他代码 10 11 } 12 13 }
Java 编程语言自动将所有实例变量和方法引用与 this 关键字联系在一起,因此,使用
关键字在某些情况下是多余的。下面的代码与前面的代码是等同的。
1 public class MyDate { 2 3 private int day, month, year; 4 5 public void tomorrow() { 6 7 day = day + 1; // 在 day 前面没有使用this 8 9 //其他代码 10 11 } 12 13 }3.2:区分同名变量
也有关键字this 使用不多余的情况。如,需要在某些完全分离的类中调用一个方法,并
将当前对象的一个引用作为参数传递时。例如:
Birthday bDay = new Birthday (this);
还有一种情况,就是在类属性上定义的变量和方法内部定义的变量相同的时候,到底是
调用谁呢?例如:
也就是说:“this.变量”调用的是当前属性的变量值,直接使用变量名称调用的是相
对距离最近的变量的值。
3.3:作为方法名来初始化对象
也就是相当于调用本类的其它构造方法,它必须作为构造方法的第一句。示例如下:
1 public class Test { 2 3 public Test(){ 4 5 this(3);//在这里调用本类的另外的构造方法 6 7 } 8 9 public Test(int a){ 10 11 12 13 } 14 15 public static void main(String[] args) { 16 17 Test t = new Test(); 18 19 } 20 21 }
五:引用类型
1:引用类型是什么
一般引用类型(reference type)指向一个对象,不是原始值,指向对象的变量是引用
变量。
在Java 里面除去基本数据类型的其它类型都是引用数据类型。Java 程序运行时,会为
引用类型分配一定量的存储空间并解释该存储空间的内容。
示例如下:
1 public class MyDate{ 2 3 private int day=8; 4 5 private int month=8; 6 7 private int year=2008; 8 9 public MyDate(int day, int month, int year){…} 10 11 public void print(){…} 12 13 } 14 15 public class TestMyDate{ 16 17 public static void main(String args[]){ 18 19 MyDate today = new MyDate(23,7,2008);//这个today 变量 20 21 //就是一个引用类型的变量 22 23 } 24 25 }
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 引用这个对象。上
述过程被描述如下
3:按值传递还是按引用传递
这个在Java 里面是经常被提起的问题,也有一些争论,似乎最后还有一个所谓的结论:
“在Java 里面参数传递都是按值传递” 。事实上,这很容易让人迷惑,下面先分别看看什么
是按值传递, 什么是按引用传递, 只要能正确理解, 至于称作按什么传递就不是个大问题了。
3.1:按值传递是什么
指的是在方法调用时,传递的参数是按值的拷贝传递。示例如下:
public class TempTest { private void test1(int a){ //做点事情 } public static void main(String[] args) { TempTest t = new TempTest(); int a = 3; t.test1(a);//这里传递的参数a就是按值传递 } }
按值传递重要特点:传递的是值的拷贝,也就是说传递后就互不相关了。
示例如下:
1 public class TempTest { 2 3 private void test1(int a){ 4 5 a = 5; 6 7 System.out.println("test1方法中的a==="+a); 8 9 } 10 11 public static void main(String[] args) { 12 13 TempTest t = new TempTest(); 14 15 int a = 3; 16 17 t.test1(a);//传递后,test1方法对变量值的改变不影响这里的a 18 19 System.out.println("main方法中的a==="+a); 20 21 } 22 23 }
运行结果是:
test1方法中的a===5
main方法中的a===3
3.2:按引用传递是什么
指的是在方法调用时,传递的参数是按引用进行传递,其实传递的引用的地址,也就是
变量所对应的内存空间的地址。
示例如下:
1 public class TempTest { 2 3 private void test1(A a){ 4 5 6 7 } 8 9 public static void main(String[] args) { 10 11 TempTest t = new TempTest(); 12 13 A a = new A(); 14 15 t.test1(a); //这里传递的参数a就是按引用传递 16 17 } 18 19 } 20 21 22 23 class A{ 24 25 public int age = 0; 26 27 }
3.3:按引用传递的重要特点
传递的是值的引用,也就是说传递前和传递后都指向同一个引用(也就是同一个内存
空间) 。
示例如下:
1 第1行 public class TempTest { 2 第2行 private void test1(A a){ 3 第3行 a.age = 20; 4 第4行 System.out.println("test1方法中的age="+a.age); 5 第5行 } 6 第6行 public static void main(String[] args) { 7 第7行 TempTest t = new TempTest(); 8 第8行 A a = new A(); 9 第9行 a.age = 10; 10 第10行 t.test1(a); 11 第11行 System.out.println("main方法中的age="+a.age); 12 第12行 } 13 第13行 } 14 第14行 class A{ 15 第15行 public int age = 0; 16 第 16行 }
运行结果如下:
test1方法中的age=20
main方法中的age=20
3.4:理解按引用传递的过程——内存分配示意图
要想正确理解按引用传递的过程,就必须学会理解内存分配的过程,内存分配示意图可
以辅助我们去理解这个过程。
用上面的例子来进行分析:
(1) :运行开始,运行第8行,创建了一个 A的实例,内存分配示意如下:
(2) :运行第9行,是修改A实例里面的age的值,运行后内存分配示意如下:
这是一个A的实例
此时 age = 0;
这是一个A的实例
此时 age = 10;
(3) :运行第10 行,是把main方法中的变量 a所引用的内存空间地址,按引用传递给 test1
方法中的a 变量。请注意:这两个a 变量是完全不同的,不要被名称相同所蒙蔽。
内存分配示意如下:
由于是按引用传递,也就是传递的是内存空间的地址,所以传递完成后形成的新的内存
示意图如下:
也就是说:是两个变量都指向同一个空间。
(4) :运行第3行,为 test1 方法中的变量 a指向的A实例的 age进行赋值,完成后形成的
新的内存示意图如下:
此时 A实例的age值的变化是由 test1 方法引起的
(5) :运行第4行,根据此时的内存示意图,输出test1方法中的age=20
(6) :运行第11行,根据此时的内存示意图,输出 main方法中的age=20
3.5:对上述例子的改变
理解了上面的例子,可能有人会问,那么能不能让按照引用传递的值,相互不影响呢?
就是test1 方法里面的修改不影响到main方法里面呢?
方法是在test1方法里面新new一个实例就可以了。改变成下面的例子,其中第 3行为
新加的:
1 第1行 public class TempTest { 2 第2行 private void test1(A a){ 3 第3行 a = new A();//新加的一行 4 第4行 a.age = 20; 5 第5行 System.out.println("test1方法中的age="+a.age); 6 第6行 } 7 第7行 public static void main(String[] args) { 8 这是一个A的实例 9 此时 age = 10; 10 这是一个A的实例 11 此时 age = 10; 12 这是一个A的实例 13 此时 age = 20; 14 第8行 TempTest t = new TempTest(); 15 第9行 A a = new A(); 16 第10行 a.age = 10; 17 第11行 t.test1(a); 18 第12行 System.out.println("main方法中的age="+a.age); 19 第13行 } 20 第14行} 21 第15行class A{ 22 第16行 public int age = 0; 23 第 17行}运行结果为:
test1方法中的age=20
main方法中的age=10
为什么这次的运行结果和前面的例子不一样呢,还是使用内存示意图来理解一下
3.6:再次理解按引用传递
(1) :运行开始,运行第9行,创建了一个 A的实例,内存分配示意如下:
(2) :运行第10行,是修改A实例里面的age的值,运行后内存分配示意如下:
(3) :运行第11行,是把main方法中的变量 a 所引用的内存空间地址,按引用传递给 test1
方法中的a 变量。请注意:这两个a 变量是完全不同的,不要被名称相同所蒙蔽。
内存分配示意如下:
由于是按引用传递,也就是传递的是内存空间的地址,所以传递完成后形成的新的内存
这是一个A的实例
此时 age = 0;
这是一个A的实例
此时 age = 10;
这是一个A的实例
此时 age = 10;
示意图如下:
也就是说:是两个变量都指向同一个空间。
(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
3.7:说明
(1) : “在Java 里面参数传递都是按值传递”这句话的意思是:按值传递是传递的值的
拷贝,按引用传递其实传递的是引用的地址值,所以统称按值传递。
(2) :在 Java 里面只有基本类型和按照下面这种定义方式的 String 是按值传递,其它
的都是按引用传递。就是直接使用双引号定义字符串方式:String str = “Java 私塾”;
这是一个A的实例
此时 age = 10;
这是一个A的实例
此时 age = 10;
这是一个新的 A的实例
此时 age = 0;
这是一个A的实例
此时 age = 10;
这是一个新的 A的实例
此时 age =2 0;
六:再谈变量
1:实例变量和局部变量
在方法外定义的变量主要是实例变量,它们是在使用new Xxxx ()创建一个对象时被分
配内存空间的。每当创建一个对象时,系统就为该类的所有实例变量分配存储空间;创建多
个对象就有多份实例变量。通过对象的引用就可以访问实例变量。
在方法内定义的变量或方法的参数被称为局部(local)变量,有时也被用为自动
(automatic) 、临时(temporary)或栈(stack)变量。
方法参数变量定义在一个方法调用中传送的自变量,每次当方法被调用时,一个新的变
量就被创建并且一直存在到程序的运行跳离了该方法。
当执行进入一个方法遇到局部变量的声明语句时,局部变量被创建,当执行离开该方法
时,局部变量被取消,也就是该方法结束时局部变量的生命周期也就结束了。
因而,局部变量有时也被引用为“临时或自动”变量。在成员方法内定义的变量对该成
员变量是“局部的” ,因而,你可以在几个成员方法中使用相同的变量名而代表不同的变量。
该方法的应用如下所示:
1 public class Test { 2 3 private int i; // Test类的实例变量 4 5 6 7 public int firstMethod() { 8 9 int j = 1; // 局部变量 10 11 // 这里能够访问i和j 12 13 System.out.println("firstMethod 中 i="+i+",j="+j); 14 15 return 1; 16 17 } // firstMethod()方法结束 18 19 20 21 public int secondMethod(float f) { //method parameter 22 23 int j = 2; //局部变量,跟firstMethod()方法中的j是不同的 24 25 // 这个j的范围是限制在secondMethod()中的 26 27 // 在这个地方,可以同时访问i,j,f 28 29 System.out.println("secondMethod 中 i="+i+",j="+j+",f="+f); 30 31 return 2; 32 33 } 34 35 public static void main(String[] args) { 36 37 Test t = new Test(); 38 39 t.firstMethod(); 40 41 t.secondMethod(3); 42 43 } 44 45 }2:变量初始化
在 Java 程序中,任何变量都必须经初始化后才能被使用。当一个对象被创建时,实例
变量在分配内存空间时按程序员指定的初始化值赋值,否则系统将按下列默认值进行初始
化:
注意── 一个具有空值“null”的引用不引用任何对象。试图使用它引用的对象将会
引起一个异常。异常是出现在运行时的错误,这将在模块“异常”中讨论。
在方法外定义的变量被自动初始化。局部变量必须在使用之前做“手工”(由程序员进
行)初始化。如果编译器能够确认一个变量在初始化之前可能被使用的情形,编译器将报错。
1 public class Test { 2 3 private int i; //Test类的实例变量 4 5 public void test1() { 6 7 int x = (int) (Math.random() * 100); 8 9 int y; 10 11 int z; 12 13 if (x > 50) { 14 15 y = 9; 16 17 } 18 19 z = y + x; // 将会引起错误,因为y可能还没有被初始化就使用了 20 21 } 22 23 public static void main(String[] args) { 24 25 Test t = new Test(); 26 27 t.test1(); 28 29 } 30 31 }
3:变量的范围(scope)
Java变量的范围有四个级别:类级、对象实例级、方法级、块级
(1) :类级变量又称全局级变量,在对象产生之前就已经存在,就是后面会学到的 static
变量。
(2) :对象实例级,就是前面学到的属性变量
(3) :方法级:就是在方法内部定义的变量,就是前面学到的局部变量。
(4) :块级:就是定义在一个块内部的变量,变量的生存周期就是这个块,出了这个块就消
失了。
示例如下:
1 public class Test { 2 3 private static String name="Java私塾";//类级 4 5 private int i; // 对象实例级,Test类的实例变量 6 7 {//属性块,在类初始化属性时候运行 8 9 int j = 2;//块级 10 11 } 12 13 public void test1() { 14 15 int j = 3;//方法级 16 17 if(j==3){ 18 19 int k = 5;//块级 20 21 } 22 23 //这里不能访问块级的变量,块级变量只能在块内部访问 24 25 System.out.println("name="+name+",i="+i+",j="+j); 26 27 } 28 29 public static void main(String[] args) { 30 31 Test t = new Test(); 32 33 t.test1(); 34 35 } 36 37 }
运行结果:
name=Java私塾,i=0,j=3
3.1:访问说明
(1) :方法内部除了能访问方法级的变量,还可以访问类级和实例级的变量
(2) :块内部能够访问类级、实例级变量,如果块被包含在方法内部,它还可以访问方法级
的变量。
(3)变量当然是要在被访问前被定义和初始化,不能访问后面才定义的变量。
七:包装类
虽然 Java 语言是典型的面向对象编程语言,但其中的 8 种基本数据类型并不支持面向
对象的编程机制,基本类型的数据不具备“对象”的特性----不携带属性、没有方法可调用。
沿用它们只是为了迎合人类根深蒂固的习惯,并的确能简单、有效地进行常规数据处理。
这种借助于非面向对象技术的做法有时也会带来不便,比如引用类型数据均继承了
Object类的特性,要转换为String类型(经常有这种需要)时只要简单调用Object类中定义
的toString()即可,而基本数据类型转换为 String类型则要麻烦得多。为解决此类问题,Java
语言引入了封装类的概念,在 JDK 中针对各种基本数据类型分别定义相应的引用类型,并
称之为包装类(Wrapper Classes) 。
下表描述了基本数据类型及对应的包装类
每个包装类的对象可以封装一个相应的基本类型的数据,并提供了其它一些有用的功
能。包装类对象一经创建,其内容(所封装的基本类型数据值)不可改变。
例,包装类用法程序:Wrapper.java
1 public class Wrapper{ 2 3 public static void main(String args[]){ 4 5 int i = 500; 6 7 Integer t = new Integer(i); 8 9 int j = t.intValue(); // j = 500 10 11 String s = t.toString(); // s = "500" 12 13 System.out.println(t); 14 15 Integer t1 = new Integer(500); 16 17 System.out.println(t.equals(t1)); 18 19 } 20 21 }
程序运行结果为:
500
true
包装类一个常用的功能就是把字符串类型的数据造型成为对应的基本数据类型,如下示例:
String str = "123";
int a = Integer.parseInt(str);
更过的功能还请查看JDK 文档。
八:类型转换
在赋值的信息可能丢失的地方,编译器需要程序员用类型转换(type cast)的方法确
认赋值。Java 中的类型转换分成:强制类型转换、自动升级类型转换和后面将会学习到的
向上造型。
1:强制类型转换
把某种类型强制转换成另外一种类型就叫做强制类型转换。
例如,可以将一个long 值“挤压”到一个int变量中。显式转型做法如下:
long bigValue = 99L;
int squashed = (int)(bigValue);
在上述程序中,期待的目标类型被放置在圆括号中,并被当作表达式的前缀,该表达式
必须被更改。一般来讲,建议用圆括号将需要转型的全部表达式封闭。否则,转型操作的优
先级可能引起问题。
注意: 强制类型转换只能用在原本就是某个类型, 但是被表示成了另外一种类型的时候,
可以把它强制转换回来。强制转换并不能在任意的类型间进行转换。
比如上面的例子: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型, 非法
一般来讲,如果变量类型至少和表达式类型一样大(位数相同) ,则你可认为表达式是
赋值兼容的。
3:表达式的升级类型转换
对 + 运算符来说,当两个操作数是原始数据类型时,其结果至少有一个 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;
4:自动包装和解包
自动包装:就是把基础数据类型自动封装并转换成对应的包装类的对象。
自动解包:就是把包装类的对象自动解包并转换成对应的基础数据类型。
示例如下:
1 public class Test { 2 3 public static void main(String args[]) { 4 5 Integer a1 = 5;//自动包装 6 7 int a2 = new Integer(5);//自动解包 8 9 System.out.println("a1="+a1+",a2="+a2); 10 11 } 12 13 }运行结果:a1=5,a2=5
九:Java 类的基本运行顺序
作为程序员, 应该对自己写的程序具备充分的掌控能力, 应该清楚程序的基本运行过程,
否则糊里糊涂的,不利于对程序的理解和控制,也不利于技术上的发展。
我们以下面的类来说明一个基本的Java类的运行顺序:
1 第1行 public class Test { 2 第2行 private String name = "Java私塾"; 3 第3行 private int age = 2; 4 第4行 public Test(){ 5 第5行 age = 1000;//期望能到1000年,呵呵 6 第6行 } 7 第7行 public static void main(String[] args) { 8 第8行 Test t = new Test(); 9 第9行 System.out.println(t.name+"的年龄是"+t.age+"年"); 10 第10行 } 11 第 11行 }运行的基本顺序是:
(1) :先运行到第7行,这是程序的入口
(2) :然后运行到第8行,这里要new一个 Test,就要调用 Test的构造方法
(3) :就运行到第4行,注意:可能很多人觉得接下来就应该运行第 5 行了,错!初始化一
个类,必须先初始化它的属性
(4) :因此运行到第2行,然后是第 3行
(5) :属性初始化完过后,才回到构造方法,执行里面的代码,也就是第5 行
(6) :然后是第6行,表示new一个 Test实例完成
(7) :然后回到main方法中执行第9行
(8) :然后是第10行
运行的结果是:Java私塾的年龄是1000年
说明:这里只是说明一个基本的运行过程,没有考虑更多复杂的情况。
作业
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:请编写一个方法实现如下功能:将任意三个整数 a,b,c按从小到大的顺序输出。
7:请编写一个方法实现如下功能:用程序找出每位数的立方和等于该数本身值的所有
的3位数。 (水仙花数)
8:请编写一个方法实现如下功能:计算 1 加到 n ( n>=2的整数)的总和。
9:请编写一个方法实现如下功能:得到一个整数的绝对值。