第6章 面向对象编程
910
2012-6-20

第六章 面向对象编程(OOP)

本章要点

面向过程的设计思想

抽象

封装

属性、局部变量/成员属性、
变量的作用域

Java程序执行过程分析

方法

方法调用

方法参数及其传递问题

this关键字

简单的JavaBean类

 

面向对象的编程思想是目前应用最广泛的编程思想。程序设计者考虑的是:对象的描述、对象间的关系、类的管理、何时何地调用对象的哪一种方法等。使用面向对象的编程思想适用于规模较大的应用程序。Java面向对象的编程思想引入了许多概念和机制,归纳总结为四大点:抽象、封装、继承和多态。

6.1  面向过程的设计思想

面向过程的设计思想在考虑问题时,是以一个具体的流程为单位,考虑它的实现办法,关心的是功能的实现。在程序设计过程中一般由各个相关联的函数实现,耦合性比较强。在程序设计过程中,程序有一个明显的开始、明显的中间过程、明显的结束,程序的编制以这个预定好的过程为中心,设计好了开始子程序、中间子程序、结尾子程序,然后按顺序把这些子程序连接起来,一旦程序编制好这个过程就确定了,程序按顺序执行。如果在执行过程中,用户需要输入什么参数或用户做出选择,程序将等待用户的输入。只有用户提供了足够的数据,程序才能继续执行下去。

下面来看一个简单的面向过程的例子。在洗衣机的工作过程中,一般要经过以下几个过程。

(1) 接通电源,按下洗衣机的“启动”按钮后开始供水。

(2) 当水满到“水满传感器”时就停止供水。

(3) 水满之后,洗衣机开始执行漂洗过程,正转5秒,然后倒转5秒,执行此循环动作10分钟。

(4) 漂洗结束之后,出水阀开始放水。

(5) 放水30 秒后结束放水。

(6) 开始脱水操作,脱水持续5分钟。

(7) 脱水结束后,“声光报警器”报警,叫工作人员来取衣服。

(8) 按下“停止”按钮(或10 秒报警超时到),声光报警器停止,并结束整个工作过程。

按照该洗衣机的工作流程可以画出它的状态图来描述其状态转化过程,了解了该洗衣机的状态转化过程后,根据其状态图就可以很容易地为其进行软件设计,并写出相应的程序实现代码。但是这样的设计,每一个环节只关注行为动作和功能实现,没有考虑数据的状态,而且各个行为之间的耦合性比较强,不利于程序的扩展和模块化。

6.2  面向对象的设计思想

面向对象的设计思想在考虑问题时,以具体的事物(对象)为单位,考虑它的属性(特征)及动作(行为),关注整体,就好比观察一个人一样,不仅要关注他怎么说话,怎么走路,还要关注他的身高、体重、长相等属性特征。又比如,用程序来模拟对窗口的操作。使用面向过程的设计思想时,主要就是定义针对窗口的各种操作:隐藏窗口、移动窗口、关闭窗口等功能。而使用面向对象的设计思想时,却是把窗口当作主体来看待,定义它的大小、位置、颜色等属性,同时定义好对应的动作,如隐藏、移动、关闭等。

面向对象的编程思想更加接近于现实的事物,它有以下几点好处。

(1) 使编程更加容易。因为面向对象更接近于现实,所以可以从现实的东西出发,进行适当的抽象。

(2) 在软件工程上,面向对象可以使工程更加模块化,实现更低的耦合和更高的内聚。

(3) 在设计模式上(似乎只有面向对象才涉及设计模式),面向对象可以更好地实现开-闭原则,也使代码更易阅读。

相对而言,面向过程的程序设计是面向对象程序设计的基础。面向对象的程序里面一定会有面向过程的程序片段的。在程序中,面向过程的程序设计,通过函数来实现。面向对象的程序设计,通过对象来封装函数和数据等。

总的来说,面向对象编程(Object Oriented Programming,OOP)是一种计算机编程架构。OOP具有的优点是:使人们的编程与实际的世界更加接近,所有的对象被赋予属性和方法,这样编程就更加富有人性化;它的宗旨在于模拟现实世界中的概念;在现实生活中所有事物全被视为对象;能够在计算机程序中用类似的实体模拟现实世界的实体(实体即实实在在的物体);它是一种设计和实现软件系统的方法。

OOP主要有抽象(Abstract)、封装(Encapsulation)、继承(Inheritance)和多态(Polymorphism) 四大特征。

6.3  抽    象

首先来了解面向对象编程思想的第一个特征:抽象。抽象主要用来把客观世界中真实存在的事物用编程语言描述出来。这也是理解面向对象编程思想的第一步。

在了解抽象这个概念之前,需要先来了解一下对象和类的概念。

6.3.1  了解对象

在了解对象之前,先要了解世界是由什么组成的。客观世界是由事物组成的,现实生活中各个实实在在的事物也叫实体,如图6.1所示。

图6.1  现实生活中的实体

如果以面向对象的编程思想来看客观世界的话,万事万物皆对象。对象就是某一个具体的事物,比如一个苹果、一台计算机都是一个对象。每个对象都是唯一的,两个苹果,无论它们的外观有多么相像,内部成分有多么相似,两个苹果毕竟是两个苹果,它们是两个不同的对象。对象可以是一个实物,也可以是一个概念,比如某个苹果对象是实物,而一项政策可能就是一个概念性的对象了。在现实生活中,万事万物皆对象,OOP是模拟现实生活中的一个个对象来编程的。下面来看一个例子,如图6.2所示。

图6.2  生活中的对象

日常生活中,大家谈论别人的媳妇(也就是对象)时,不仅会谈论她的姓名、年龄、体重等,还会关注她的行为,如:她能摔跤、会柔道等。

通过上面的介绍可以看出:万事万物皆对象,每一个对象都是唯一的,对象具有属性和行为。

6.3.2  Java抽象思想的实现

现实生活中的这么多对象需要进行分类。比如分为:人类、老虎类、猫类、汽车类等。分类的作用主要是为了便于管理和维护。由于面向对象的设计思想主要是通过模拟现实世界的各个对象来编程的。那么这些现实生活中的对象是怎样模拟或映射到计算机中的呢?这要归功于面向对象的一大思想——抽象。其实抽象不是Java语言中面向对象的设计思想所特有的,在其他面向对象的语言中,如C++ 等,在构建对象时也需要抽象建模。但抽象思想之所以归纳进来,是因为它的确对于理解面向对象的编程起到了非常重要的作用。在人类长期的进化过程中,人们之所以叫人类,是因为通过抽象的思想,将具有各个人的共同特征(如人有手、脚,会说话,会直立行走等)和行为的高级动物分为一类叫人类。人类主要是为了区别于其他的动物而抽取出来的。

从上面的理解可以看出,所谓的抽象,即抽取,也叫提炼或归纳总结等。每一个人可以把他当成一个个具体的实体对象,通过抽象,可以很容易地归纳总结出人的共同特征和行为,以便于和其他对象区别开来。这样抽取出来的属性和行为在面向对象的编程中叫属性和方法。属性是指对象具有的各种特征;行为用来描述对象的各种操作,一般用动词来描述。

每个对象的每个属性都拥有特定值,如根据图6.2可知布兰尼和朱丽叶的姓名、体重、年龄都不一样,如图6.3所示。

她们执行的各种操作也不一样,如图6.4所示。

通过抽象将下列对象中的属性和方法抽取出来,如图6.5所示。

图6.3  布兰尼和朱丽叶的属性

图6.4  对象的操作与方法

图6.5  对象的抽象

6.4  封    装

在面向对象的编程过程中为什么需要封装(Encapsulation)呢?因为对象也有隐私,对象的隐私就是对象内部的实现细节。要想对象保持良好的形象就要保护好对象隐私,所谓的封装其实就是保护对象隐私。当然,没有人能完全隐藏自己的隐私,比如现实生活中去转户口时,不得不透露自己的家庭信息和健康状况。另外,在不同的场合所透露隐私的数量也不一样,朋友和家人可能会知道你更多的隐私,同事次之,其他人则知道得更少。面向对象的编程也考虑了这些实际的情况,所以像Java之类的编程语言有public、private、protected、friend等关键字,以适应于不同的情况。

封装可以隔离变化。对象内部是非常容易变化的,比如:电脑在不断升级,机箱还是方的,但里面装的CPU和内存已是今非昔比了。变化是不可避免的,但变化所影响的范围是可以控制的,不管CPU怎么变,它不应该影响用户使用的方式。封装是隔离变化的好办法,用机箱把CPU和内存等封装起来,对外只提供一些标准的接口,如USB接口、网线接口和显示器接口等,只要这些接口不变,不管内部怎么变化,也不会影响用户的使用方式。

封装还可以提高易用性。封装后只暴露最少的信息给用户,对外接口清晰,使用更方便,更具用户友好性。试想,如果普通用户都要知道机箱内部各种芯片和跳线是如何布局的,那是多么恐怖的事情,到现在为止编者甚至还搞不清楚硬盘的跳线设置,幸好也没有必要知道。

封装有两层含义,其一是隐藏内部行为,即隐藏内部函数,调用者只能看到对外提供的公共函数。其二是隐藏内部信息,即隐藏内部数据成员。

为了实现数据的封装,提高数据的安全性,一般建议把类的属性声明为私有的,把类的方法声明为公共的。这样,对象能够直接调用类中定义的所有方法,当对象想要修改或得到自己的属性的时候就必须要调用以定义好的专用的方法才能够实现。在考虑封装的时候,建议读者遵守“对象调方法,方法改属性”的要求即可。

对于面向对象编程而言,读者需要掌握如下几点。

(1) 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的重要的一部分。

(2) 封装就是将属性和方法一起包装到一个程序单元中,并隐藏方法的实现过程。这个程序单元以类的形式实现。

(3) 只关注类的属性和方法就称为数据抽象。

封装一个类,就是根据具体的应用从同一类型对象中抽象出相关的属性(成员变量)和方法(函数)。如:封装人这个类,将张三、李四、王五、布兰尼、朱丽叶等具体对象的重要信息抽出来,如:姓名、体重、身高、说话、行走等,只要有这些特征的高级动物归为一类就称为人类。

6.4.1  对象封装的概念

对象同时具有属性和方法两项特性,对象的属性和方法通常被封装在一起,共同体现事物的特性,二者相辅相成,不能分割。

图6.6很形象地说明了,狗除了有颜色它还能跑动,这就是说对象除了有属性外还要有方法。

图6.6  跑动中的小狗

6.4.2  理解类

现实生活中任何实实在在的具体物体都叫对象,通过面向对象的抽象思想,根据很多实体的行为特征可以抽取出很多很多的对象,如张三、李四、法拉利汽车、投影仪等,然后再通过归纳总结,将这些对象分门别类,如图6.7所示。 200731922411falali

法拉利

图6.7  法拉利汽车

法拉利汽车是一个实体,可以抽象出它们共同的特征:有四个轮子、有颜色、能开、能坐等,具有这些特征的对象可以归为一类叫做汽车类。此外还有很多很多的归类对象,如:人类、狗类、老虎类等。总的来说,类可以理解为对事物的分类。

6.4.3  Java类模板创建

人民币分类就有100元、50元、20元、10元等,这些钱都是通过印钞机的模板印刷出来的。那么相应的就会有100元的模板、50元的模板、20元的模板等,这些模板都是根据现实生活中的一张张钱的共同特征制作出来的,它们都有尺寸、颜色、大小、功能等,如图6.8所示。

类可以理解为对数据的分类类型,是各种数据的模板。在面向对象的编程过程中,类是对象的类型,不同于原始数据类型int等,它有具体的方法。类决定对象将会拥有的特征(属性)和行为(方法)。比如:人类是一个共性的概念,对象是一个个性的概念。人类肯定包含每一个人也就是对象的共同特征:姓名、身高、会说话、能行走等,这些特征都是由人类共同决定的。再比如:人民币的模板决定着印出来的人民币的颜色、大小和功能等。

一个类可以决定多个对象,就好比100元人民币的模板决定印出来的人民币的样式是一样的。编程人员可以定义自己想要的类。类就是现在提到的对对象的分类,如上面提到的对人民币的分类,印刷人民币时要先造印钞的模板,模板决定造出来的钱的样式。在Java中模板即类,100元的人民币在Java中叫做对象。所以,要创建出对象,首先要创建出它的数据类型,即类。

图6.8  印钞机和钞票的关系

在Java中是通过类来封装现实生活中对象的各个信息的,如图6.9所示。

图6.9  类封装图

通过多个具体的人(即对象)的共性特征归纳出(也即抽象出)他们共同的特征和行为:姓名name、性别sex、年龄age、说话speak()、行走tread()等。再将这些对象归为一类叫做Person类。

在Java中如何创建类呢?类是将现实世界中的概念模拟到计算机程序中,这需要由Java程序确定。在Java语言中,所有Java程序都以类为组织单元,构成Java面向对象编程的最小封装单元。在Java中通过关键字class定义自定义的数据类型。

Java类的模板创建语法如下:

public class  类名 {

      //定义属性部分

       属性1的类型 属性1;

       属性2的类型 属性2;

       …

       属性n的类型 属性n;

       //定义方法部分

       方法1;

       方法2;

      …

       方法m;

}

Java类的主体是通过一对大括号“{ }”括起来的。类的主体中,除了属性就是方法。举例如下面的程序Person.java。

public class Person{

 //属性的创建

 private String name;

 private String sex;

 private int age;

 //类中方法的创建

 public void speak(){

   System.out.println("会说话");

 }

 public String tread(){

   System.out.println("会行走");

   return "会行走";

 }

 public static void main(String[ ] args) {            

    System.out.println("Java面向对象的程序设计,类的创建");

 }

}

在Java中创建自定义类型的步骤为:

第1步  定义类名。

第2步  编写类的属性。

第3步  编写类的方法。

【示例1】ClassRoom.java

如果有一个用类的思想输出学校教室信息的问题,在问题中可以提取出教室类,并可以提取出它的属性,如:教室名称、教室数目、机器数目等,方法为显示教室的信息。代码如下:

public class ClassRoom {

      //定义教室属性

     String name;    //教室名称

     int classNum;   //教室的数目 

     int labNum;     //机器数目

      //定义教室的方法

     //定义教室的方法

      public String toString() {

          return "教室名称: " +name+ "\n" + "教室数目:"+ classNum + "\n" + "电脑台数: " + labNum + "\n" ;

}

在前面的练习中class中都包含有一个main方法,但在此类的定义中却未含有该方法,这是为什么呢?在这里要强调的是,Java类并不需要一定含有main方法,除非把此类定义为执行类。

6.4.4  Java中对象的创建和使用

对象是Java程序的核心,在Java程序中“万事万物皆对象”。类描述了对象的属性和对象的行为。类是对象的模板和图纸。对象是类的一个实例,是一个实实在在的个体。类和对象的关系如图6.10所示。

图6.10  类和对象的关系

类是对象的模板,决定着对象的属性和方法。由对象可以抽象出类,类可以实例化成对象,就像印钞机的模板决定印刷出来的钱的大小、颜色。

创建和使用对象的步骤如下。

第1步  使用new关键字创建类的一个对象,格式为:

类名 对象名 = new 类名();

创建一个ClassRoom类的对象,套用以上格式,代码为:

ClassRoom center = new ClassRoom();

此语句类似于基本数据类型的变量的声明并赋初值,如:语句“int center=88;”。

第2步  通过对象名.属性或对象名.方法来使用。

访问对象中封装好的属性和方法是通过“.”点操作符来访问的。访问对象的属性用“对象名.属性名”;调用对象的方法用“对象名.方法名()”。

center.name = "CSDN软件学院";  //给属性赋值

center.toString();         //调用类的方法,该方法中的操作将被执行

【示例2】ClassRoomTest.java

根据示例1创建一个学校中心教室类的对象,并给属性赋值后显示在控制台上。代码如下:

public class ClassRoomTest{

     public static void main(String[ ] args){

           ClassRoom center = new ClassRoom();

          //直接输出对象时,会默认调用对象的toString()方法

           //以下语句相当于System.out.println(center.toString());

           System.out.println(center.toString());

           center.name = " CSDN软件学院";

           center.classNum = 10;

           center.labNum = 10;

           System.out.println(center.toString());

     }

}

输出结果为:

教室名称: null

教室数目:0

电脑台数: 0

教室名称: CSDN软件学院

教室数目:10

电脑台数: 10

当一个对象被创建时,会对其中各种类型的成员变量自动进行初始化赋值。所以在未给center赋值的时候toString()方法显示的是null、0和0,赋值后显示的是“CSDN软件学院”、“10”和“10”。

请思考一下:如果ClassRoom类中的属性设置为私有的,此处的ClassRoomTest类编译能通过吗?

【示例3】编写学生类、教师类及测试类

编写学生类,输出学生相关信息;编写教师类,输出教师相关信息;编写测试类,测试代码的正确性。学生类和教师类的具体属性及方法如图6.11所示。

图 6.11  学生类和教师类的属性及方法

学生类代码Student.java如下:

public class Student{

    //工具自动帮用户创建了一个数据类型叫Student类

    //属性

     String name;//姓名

     int age;//年龄

     String course;//课程

     String interest;//兴趣需要注意它们的数据类型

     

    //方法--显示学员的个人信息

     public void display(){

        System.out.println("姓名:"+name+"年龄:"+age+"课程:"+course+"兴趣:
            "+interest);

     }

}

教师类代码Teacher.java如下:

public class Teacher{

    //属性

    String name;//姓名

    String speciality;//专业

    int age;//年龄

    String course;//教授的课程

    //方法—显示教师的个人信息

    public void display(){

    System.out.println("教师的信息为:姓名:"+name+"专业:"

            + speciality +"年龄:"+age+"课程:"+course);

    }

}

测试类代码Testing.java如下:

public class Testing{

    public static void main(String[] args) {

        //声明并赋初值

        Student zhangSan=new Student ();

        Teacher  laoWang=new Teacher();

        //使用

        zhangSan.name="张三";

        zhangSan.age=22;

        zhangSan.course="JavaEE高级冲刺班";

        zhangSan.interest="对Java老师讲的课比较感兴趣";

        //使用属性为属性赋值

        zhangSan.display();//显示张三的个人信息的方法

       

        laoWang.name="老王";

        laoWang.age=28;//使用老王的属性

        laoWang.course="主攻Java";

        laoWang.speciality =" JavaEE方向";

         laoWang.display();//显示老王的个人信息。

    }

}

在前面已经了解main方法的用法,它的存在为Java应用程序启动器java提供了程序执行的入口点。因此,任何类都可以包含main方法。但一般是基于如下原因才在类中添加main方法。

(1)、为了执行测试程序以测试类的其它方法中包含的程序。

(2)、为了启动应用程序。

本例中是基于第一种情况才在Testing类中添加main方法的。

读者朋友可能对于类和对象的理解已经有初步的印象。面向对象的设计重点是类的设计,而不是对象的设计,下面将详细介绍与类的组成部分:属性和方法。

6.5  属    性

在定义类时,经常需要抽象出它的属性并定义在类的主体中。下面就来介绍和属性相关的内容。

6.6.1  属性的定义

在类中定义的属性有常量属性和成员属性之分。常量属性就是用final修饰的属性,它的值只能赋值一次,以后不能再更改了。并且在类中定义的常量属性一般用大写字母命名,如:

【代码(AttributeDemo1.java)】

public class AttributeDemo1{

   final String ABS="-8";

   final double PI=3.14;   //常量属性,圆周率

非常量属性就是成员属性,它直接定义在类的主体中,如:

【代码(AttributeDemo2.java)】

public class AttributeDemo2{

   private String name;

   private int age;    //成员属性

对于成员属性在Java中提供了三种初始化方式:

(1)、使用缺省值初始化

所有的字段虽然在声明的时候都未包含初始值,在默认的情况下,每个字段都会被赋予缺省值。Java中为不同的类型提供的缺省值如表6.1所示。

表6.1  Java中各类型缺省值表

数据类型

缺省值

                       boolean

false

                       byte

0

                       char

‘\u0000’

                       short

0

                       int

0

                       long

0L

                       float

0.0f

double

0.0d

引用类型

null

 

(2)、使用显示值初始化:

亦即声明的同时并赋值,如定义AttributeDemo1类中的属性即采用此种方式。

(3)、使用构造器初始化:

通过构造器来初始化属性,请读者参看6.6小节内容。

6.6.2  变量

在理解类的属性的同时,还需要理解一个更为常用的概念:变量。变量用来代表内存中的某块区域,这块内存区域中的值可以在程序的执行过程中发生变化。

变量根据它定义的位置分为成员变量和局部变量。直接定义在类的主体中的变量叫成员变量,定义在方法的主体中的变量叫局部变量。上面介绍的类的属性其实就叫成员变量,它们只是两种不同的称谓而已。

Java变量在使用前必须先声明和初始化(赋初值),特别是局部变量,如果它没有被显式初始化并赋值,那么它的值是不可预见的,程序中使用这个变量时就可能会出现异常情况。

定义局部变量的语法格式为:数据类型 变量名=值;。如:

【代码Amethod.java】

import java.ulil.Date;

public class Amethod {

    public void Amethod(){//定义一个方法

        int i;

        int j = i+5 ;     // 编译出错,变量i还未被初始化就使用了

        double d = 3.14;   

        System.out.println(“test the local variable”));

    }

}

变量是有作用域的。所谓变量作用域是指定变量使用的范围,也就是活动范围。变量作用域和现实生活实例类比:鱼要在水中游、鸟要在天空中飞,每一个事物都有它的作用范围。在Java 编程中一般通过{}(大括号)来表示变量的作用范围。

【示例4】Company.java

变量作用域演示示例的代码如下:

public class Company {

    // 公司总经理命令

    String managerSay = "我命令全体员工休假两天";

    // 执行总经理命令

    public void say() {

        System.out.println("总经理发言:" + managerSay);

    }

        //执行湖北地区经理命令

    public void huBeiManagersay() {

        String managersay1 = "我命令湖北地区全体员工休假两天";

        System.out.println("湖北地方区经理发言:" + managersay1);

         System.out.println("总经理发言:" + managerSay);

    }

        //执行湖南地区经理命令

    public void huNanManagersay() {

         String managersay2 = "我命令湖南地区全体员工休假两天";

        System.out.println("总经理发言:" + managerSay);

                // 编译出错

        System.out.println("湖北地方区经理发言:" + managersay1);

System.out.println("湖南地方区经理发言:" + managersay2);

    }

}

在这个例子中,成员变量“managerSay”可以在方法huBeiManagersay范围内使用,也可以在huNanManagersay范围内使用。但湖北某个分公司经理的权利只能在湖北地区得到实施,在其他地区的公司得不到执行。

运行上例结果,如下所示。

Company.java:20: 找不到符号

符号: 变量 managersay

位置: 类 Company

                System.out.println("湖北地方区经理发言:" + managersay1);

                                                  ^

1 错误

变量声明的位置决定变量作用域,变量作用域确定了可以在程序中用变量名来访问该变量的值的区域。

在Company.java类的示例中如图6.12所示。

图6.12  变量的作用域

在图6.12中,变量1可以在方法1、方法2中使用,也可以在方法1和方法2的外面使用,只要在类的主体(也就是类的{})中都可以使用。但变量2只能在方法1中使用,变量3只能在方法2中使用,超出这个范围就不能使用了。

6.6  方    法

在封装一个类时,不仅要定义出该类的属性,更为重要的是封装该类的方法。本节就来详细介绍在类中如何封装方法。

6.6.1  方法的定义

操作的实际实现;方法指定操作对象数据的方式;如何执行所请求的操作规范;在得到操作请求时指定如何做的算法等都要一种办法来完成。对对象的执行操作叫方法。

方法的作用有:使程序变得更简短更清晰;有利于程序维护;可以提高程序开发的效率;提高了代码的重用性。

【示例5】AutoLion.java

下面编写一个电动狮子跑和叫的方法示例。电动狮子的属性和方法如图6.13所示。

图6.13  对象的属性和方法

public class AutoLion {

    String color = "黄色";

    //定义一个方法:public是修饰符,void表示没有返回值

public void run(){

          System.out.println("正在以0.1米/秒的速度向前奔跑");

    }  

    public String bark(){   //叫的方法

          String sound = "吼" ;

          return sound;

    }

}

从以上代码可以看出,方法定义至少需要四要素:方法返回值类型,方法名称、形参列表以及方法体。至于方法体有没有要看情况,一般定义方法的时候都要指定方法体,但在后面学习接口的特性时可以不用指定。方法四要素如图6.14所示。

图6.14  方法四要素

语法:

public   返回值类型  方法名(形式参数列表)  {

           //这里编写方法的主体

}

方法的命名与属性类似,主要归纳为如下四点:

l   以字母、数字、下划线_或$符号组成。

l   由字母、下划线_或$符号开头。

l   区分大小写。

l   不能是Java中的关键字。

方法规范与标识符的也类似,只不过要遵循方法的命名规范:通常方法名是一个动词,如果由两个以上单词组成,第一个单词的首字母小写,其后单词首字母大写,如addStudent等。

方法的返回值有以下两种情况:

(1) 如果方法具有返回值,方法中必须使用关键字return返回该值,返回类型为该返回值的类型,如:

return 表达式; //注意表达式的值必须是确定的值

(2) 如果方法没有返回值,返回类型为void。

下面来了解一下定义方法时常出现的错误。

常见错误1:返回类型不匹配

【代码(Student.java)】

public class Student{

    String name = "张三";

    public void getName(){

        return name;  //本方法定义了没有返回值,这里却用return返回了一个值

        //return;

    }

    …

}

常见错误2:main方法不能有返回值

public class School{

      …

      public static void main(String[ ] args){

           return "我爱java";  //编译出错,main方法没有返回值

      }

}

常见错误3:方法最多只能有一个返回值

public class Student{

     public double getInfo(){

          double weight = 96.5;

          double height = 1.69;

          return weight, height;  //编译出错,返回了多个值

     }

}

类中的方法可以分成好多类别。根据方法的作用可以分为构造方法和自定义方法;根据方法的参数类型可以分为无参方法和有参方法。

无参方法的定义语法为:

  public 返回值类型  方法名(){

  }

例如:

public int add(){

   int i=8;

   int j=9;

   return i+j;//返回值类型必须要与定义方法的返回值类型一致

}

有参方法的定义语法为:

 public返回值类型   方法名(形式参数列表){

 }

例如:

public int add1(int i,int j){

    return i+j;//i和j的值由调用此方法的实际参数赋给

}

方法在定义的过程中只是为此功能定义了一个框架,以及所需要参与运算的变量数据叫形式参数。此形式参数定义在方法的()(小括号)内,只是声明的形式参数的变量并没有给它赋值,如果需要调用此方法,主要通过:对象名.方法(实际参数)的形式来调用。如:

stu.add1(8,9);    //stu为对象名,8、9为实际参数,此参数也可以是已经赋了值的变量

实际参数就是已经定义并赋予了初值的变量或对象,是实实在在存在的数据,也叫实际参与运算的数。

6.6.2  构造方法

一个新对象初始化的最终步骤是通过new关键字去调用对象的构造方法,构造方法必须满足以下几个条件:

(1) 方法名必须与类名称完全相匹配。

(2) 不要声明返回类型。

(3) 不能被static、final、synchronized、abstract、native修饰,且不能有return语句返回值。

创建某类的对象要遵循:类名 对象名 = new 类名(); 使用new关键字创建对象要注意以下三个方面:

(1)为对象实例分配内存空间

(2)调用构造方法

(3)返回对象实例的引用

当一个对象被创建时,会对其中各种类型的成员变量自动进行初始化赋值。除了基本数据类型之外,其余的变量类型都是引用类型。创建对象在内存的示意图如图6.15所示。

图6.15  对象内存位置示意图

构造方法和方法的类似之处在于它们都包含可执行代码。然而,它们的区别在于只有当JVM实现在创建类的实例时才执行构造器。构造器通常包含字段初始化代码。严格上讲构造方法并不是方法,因为它不会返回任何值。

根据参数不同,构造方法又可以分为以下三类。

1. 隐式无参构造方法(默认构造方法)

在定义类的时候如果没有给类定义一个构造方法,Java编译器在编译它时会默认提供一个隐式的构造方法,它没有任何参数并且有一个空主体。

下面代码中的Person1类中隐含一个无参构造方法,在main方法中可以通过new关键字来调用此默认的构造方法,代码如下。

【代码(Person1.java)】

public class Person1 {

    private String name;        //姓名

    private boolean sex;        //性别

    private int age;             //年龄

    public void speak(String word){   //说话

        System.out.println( name + "说:" + word);

    }

    public void tread(){  //行走

        System.out.println("走走走…");

    }

    public static void main(String [] args){

        Person1 person = new Person1();  //调用系统提供的一个隐式无参构造方法

        person.tread();

    }

}

2. 显示无参构造方法

在定义类的时候为它定义一个显示无参的构造方法,如:

public Test(){

}

下面代码中的Person2类中定义了一个显示无参的构造方法,代码如下。

【代码(Person2.java)】

public class Person2 {

    private String name;        //姓名

    private boolean sex;        //性别

    private int age;             //年龄

    public Person2(){   //显示定义一个无参的构造方法

       System.out.println("我是显示无参构造方法");     

    }

    public void speak(String word){  //说话

        System.out.println( name + "说:" + word);

    }

    public void tread(){  //行走

        System.out.println("走走走…");

    }

    public static void main(String [] args){

        Person2 person = new Person2();

        person.tread();

    }

}

3. 显示有参构造方法

在定义类的时候为它定义一个显示有参的构造方法,如:

public Test(int i){

}

下面代码中的Person3类中定义了一个有三个参数的显示构造方法,代码如下。

【代码(Person3.java)】

public class Person3 {

    private String name;        //姓名

    private boolean sex;        //性别

    private int age;             //年龄

    public Person3(String n, boolean b, int a){    //参数化的构造方法

        name = n;

        sex = b;

        age = a;

    }

    public void speak(String word){  //说话

        System.out.println( name + "说:" + word);

    }

    public void tread(){  //行走

        System.out.println("走走走…");

    }

public static void main(String [] args){

    //用new来调用该类的显示有参构造方法,注意参数的匹配问题

        Person3 person = new Person3("张三",true,18);

        person.speak("你好");

    }

}

【示例6】Test1.java

下面Test1类中可以定义一个显示无参构造方法和一个显示有参构造方法,代码如下:

public class Test1{

    public Test1(){ //显示无参构造方法

    }

    public Test1(int i){//显示有参构造方法

    }

}

如果类中有一个自己编写的构造方法时,编译器就不会再提供那个默认的构造方法。此时如果希望还可以用默认构造方法来创建类的实例时,那么就必须在类中明确添加这个默认构造方法,否则编译报错。

【示例7】Person4.java

如果显示定义了一个有参的构造方法,系统就不能调用无参的构造方法,如果通过new关键字来调用无参的构造方法就会出错,代码如下:

public class Person4 {

    private String name;        //姓名

    private boolean sex;        //性别

    private int age;            //年龄

    public Person4(String n, boolean b, int a){

        name = n; sex = b;  age = a;

    }

    public void speak(String word){  //说话

        System.out.println( name + "说:" + word);

    }

    public void tread(){  //行走

        System.out.println("the people can tread every where");

    }

    public static void main(String [] args){

        Person4 person = new Person4();  //编译报错

        person.speak("你好");

    }

}

6.6.3  方法重载

前面的练习中,我们曾多次使用print()方法。按照常理如果要打印int、float和String类型,则需要提供三个方法如printInt()、printFloat()和printString(),显然这样做的话很繁琐。Java编程语言允许为多个方法复用一个方法名。这种方式只有在能区分调用的所需方法的情况下才有效。对于三个打印方法的情况,可以根据参数的数量和类型的不同来做区分。比如API文档中在对此的设计如图6.16所示。

图6.16  PrintStream类中print方法 示意图

当编写代码调用其中一个方法时,会根据所传递的参数类型选择合适的方法。这也就是本小节要探讨的方法重载。

方法重载指的是一个类中可以定义有相同的名字,但参数列表(参数的类型、个数、顺序)不同的多个方法。调用时,会根据不同的参数列表来选择对应的方法。这里要说明的是方法的返回类型可以不同。类中定义的普通方法、构造方法都可以重载。

【示例8】Person5.java

下面Person5类定义了两个构造方法,构成构造方法重载,其代码如下:

public class Person5 {

    private String name;        //姓名

    private boolean sex;        //性别

    private int age;            //年龄

    public Person5(String n, boolean s, int a){    //显式带参数的构造方法

        name = n;

        sex = s;

        age = a;

    }

public Person5(){  //显式不带参数的构造方法

}

    public void speak(String word){  //说话

        System.out.println(name + "说:" + word);

    }

    public void speak(){

        System.out.println("无语…");

    }

    public static void main(String [] args){

        Person5 person = new Person5();

        person.speak("你好");

        person.speak();

    }

}

对于方法中有相同类型却不同数量的参数情形,罗列多个方法并不是最恰当的设计,如要创建一个计算一组整数平均数值的方法,通常会如下设计。

public class AverageDemo {

    public float average(int n1, int n2) {

        return (float) (n1 + n2) / 2;

    }

    public float average(int n1, int n2, int n3) {

        return (float) (n1 + n2 + n3) / 3;

    }

    public float average(int n1, int n2, int n3, int n4) {

        return (float) (n1 + n2 + n3 + n4) / 4;

    }

}

这三个重载的方法有相同的功能,Java SE 5.0或以后的版本提供了一个新功能,称之为可变参数,可以简化上述代码,写出更通用的方法。

public class AverageDemo {

    public float average(int ...nums) {

        int sum=0;

        for(int x:nums){

            sum+=x;

        }

        return (float) (sum /nums.length);

    }

}

新的可变参数可以按照重载方法的方式被调用,length属性为内建特征,用以返回参数的数量。

6.6.4  自定义方法

自定义方法是在类中为了解决某个问题而编写的一段功能代码片段。自定义方法必须满足方法的三要素:返回值类型、方法名和行参列表,至于方法体有没有需要根据情况而定。

自定义方法的语法为:

 public 返回值类型 方法名(行参列表){ ...}

【示例9】ZiMethod.java

一般很多系统提供的方法不能满足项目的业务需求,这就需要自己定义满足业务需求的方法,下面是自定义方法的相关代码。

public class ZiMethod{

   public void add(){//自定义无参无返回值的方法

    //执行的语句都写在方法体里面

    }

    public void add1(int a,double b){//自定义有两个参数无返回值的方法

    }

    public String add2(){//自定义无参有返回值的方法

     return "";

}

    public int add3(int a,int b){//自定义有参有返回值类型的方法

      return a+b;

}   

public int[] add4(int a[]){//自定义有数组参数有数组返回值类型的方法

      return a;

}

}

代码改错:方法不能嵌套定义

下面是计算1到n的各整数的和的代码,编译时会出错。

public class Method{

     public int add(int shouSu,int weiSu){

           public void Suan(){}      //编译错误:方法不能嵌套定义

           int geSu=weiSu;

           int he=(shouSu+weiSu)*geSu/2;

           return he;

     }

}

6.6.5  系统提供的方法

Java 之所以流行的原因之一就是在于它的最大的可重用性,JDK 中包含了很多开源组织已经写好了的大部分功能的方法类,即创建好的引用数据类型的类或帮助我们解决问题的类,如:Scanner Random Math System类(也叫API应用程序编程接口类)。只要学会使用或重用相应的类,会使程序开发速度有个质的飞跃。

系统引用类型变量的使用步骤如下。

第1步  通过查阅说明书API,将类引入到用户自己的程序中。如:

import java.util.Scanner;

第2步  声明此类型的变量。如:

Scanner input;

第3步  通过new关键字对变量进行初始化。如:

input=new Scanner(System.in);

第4步  通过查API知道类的方法功能,然后通过对象名.方法(参数列表)的形式来使用。如:

String a=input.next();

第1步和第3步可以合并到一起写。如:

Scanner input=new Scanner(System.in); 

【示例10】ScannerTest.java

下面通过JDK中的Scanner类来实现键盘输入字符串功能。其代码如下:

import java.util.Scanner;

//第1步.通过查说明书API,将类引入到用户自己的程序中

public class ScannerTest {

public static void main(String[] args) {

        //第2步.声明此类型的变量,在此也叫对象名

        Scanner input;

        //第3步.通过new关键字对变量进行初始化

        input = new Scanner(System.in);

//第4步.通过查API知道类的方法功能,然后通过对象名.方法(参数列表)的形式来使用

        String a = input.next();

        System.out.println(a);

}

}

6.6.6  方法的调用

学习编程的一个很重要的基本技能就是要灵活运用变量和类的方法调用。自定义方法的调用语法格式是:对象变量名.方法名(实参列表);。其中实参列表由定义的方法的形式参数决定,形式参数和实际参数两者间一定要匹配。而且调用哪个方法,程序会到被调用的方法处运行,运行完后回到调用处,被调用的方法有返回值就返回所需要的值,没有返回值也会返回到调用处,例如:

main()方法可以调用其他的方法,调用规则如图6.15所示。

图6.15  方法的调用

main()方法调用a()方法,a()方法又调用b()方法,程序一般是从main()方法开始运行的,main()方法是程序的入口。当程序从main()方法开始运行后,遇到调用a()方法的语句会跳到a()方法的方法体中运行,在a()方法的执行体中如果有调用b()方法的语句,进而会跳到b()方法去运行b()方法的方法体。直到b()方法的方法体运行完为止,有返回值返回给a()方法的变量或对象,没有返回值返回a()方法执行体语句调用处。然后a()方法执行体下面的语句继续运行,直到a()方法的方法体运行完后回到main()方法,有返回值返回给main()方法的变量或对象,没有返回值返回main方法的调用处,执行main()方法余下的语句,直到main()方法的方法体执行完后结束程序运行。

【示例11】MethodInVoke.java

下面将实现在同一个类中方法a来调用方法b,其代码如下:

/** 同一个类中main方法调用其他方法 */

public class MethodInVoke {

    /**方法间可以相互调用,但不能嵌套定义 */

public void a(){

        System.out.println("a()方法开始");

        String name=b();

        System.out.println(name);//打印输出调用b()方法返回来的值

        System.out.println("b()方法调用完后返回调用处,执行 a()方法余下的语句");

}

public String b(){

        System.out.println("b()方法开始");      

        System.out.println("b()方法执行完毕回到a()方法");

        return "返回值给a()方法的name变量";

}

public static void main(String[] args) {

        System.out.println("main方法开始执行");

        MethodInVoke m=new MethodInVoke();

        m.a();  //调用a()方法将到a()方法的执行体{}内开始执行,结束后回到此调用处

        //在main()方法中因为此方法是静态的所以只能通过此类的对象来调用a()方法

        System.out.println("main()方法下面的语句后遇到)括号结束main()方法,
            结束整个应用程序");

}

}

输出结果为:

main方法开始执行

a()方法开始

b()方法开始

b()方法执行完毕回到a()方法

返回值给a()方法的name变量

b()方法调用完后返回调用处,执行 a()方法余下语句

main()方法下面的语句后遇到}括号结束main()方法,结束整个应用程序

前面学习了变量的使用,方法调用也可以根据有无参数分为以下两类。

1. 无参方法的调用

无参方法是个“黑匣子”,用于完成某个特定的应用程序功能。

语法:

对象名.方法名();

【示例12】AutoLion1.java

小明过生日,爸爸送给他一个电动狮子玩具,下面编程测试这个狮子能否正常工作。

public class AutoLion1 { 

      String color = "黄色";

       public void run(){       //方法1:跑

              System.out.println("正在以0.1米/秒的速度向前奔跑。");

        }

        public String bark(){   //方法2:叫

              String sound = "吼" ;

              return sound;

        }

        public String getColor(){   //方法3:获得颜色属性

              return color;

        }

       public String showLion() {    //方法4:描述狮子特性

             return "这是一个" + getColor() + "的玩具狮子!" ;

       }

}

测试类代码如下:

【代码(TestLion.java)】

public class TestLion {

    public static void main(String[ ] args) {

           AutoLion lion = new AutoLion();

           System.out.println(lion.showLion());

           lion.run();

           System.out.println(lion.bark());

     }

}

输出结果:

这是一个黄色的玩具狮子!

正在以0.1米/秒的速度向前奔跑。

无参方法调用小结如表6.1所示。

表6.1  无参方法调用

情  况

举  例

在同一个类中

类Student 的方法a()调用Student类的方法b(),直接调用

public void a(){

      b();    //调用b()

}

 

                                                                                                                                                         续表

情  况

举  例

在不同类中

类Student的方法a()调用类Teacher的方法b(),先创建类对象,然后使用“.”调用

public void a(){

      Teacher t = new Teacher();

      t.b();  //调用Teacher类的b()

}

 

2. 有参方法的调用

语法:

[修饰符1 修饰符2 …] 返回值类型 方法名(形式参数列表){

  程序代码;

  return 返回值;

}

其中的形式参数是在方法被调用时用于接收外界输入的数据的。而实际参数是调用方法时实际传给方法的数据。返回值是指方法在执行完毕后返还给调用者的数据。返回值类型是方法要返回的结果的数据类型。若一个方法没有返回值,则必须给出返回值类型void。return语句用来终止方法的运行并指定要返回的数据。

【示例13】AutoLion2.java

下面是有参方法的调用示例,代码如下。

package org.shan.Test;

public class AutoLion2 {

String color = "黄色";

    public void run(){       //方法1:跑

        System.out.println("正在以0.1米/秒的速度向前奔跑。");

    }

    public String bark(String sound){   //方法2:叫

        sound = "吼" ;         

        return sound;

    }

    public String getColor(){   //方法3:获得颜色属性

        return color;

    }

    public String showLion() {    //方法4:描述狮子特性

        return "这是一个" + getColor() + "的玩具狮子!" ;

    }

}

【代码(TestLion2.java)】

public class TestLion2 {

/*在测试类TestLion中的main()方法中调用类AutoLion2的有参方法 */

    public static void main(String[ ] args) {

        AutoLion2 lion = new AutoLion2();

        System.out.println(lion.showLion());    

        lion.run();

        System.out.println(lion.bark("哄"));

    }

}

输出结果为:

这是一个黄色的玩具狮子!

正在以0.1米/秒的速度向前奔跑。

理解清楚了如何调用方法之后,可以经常阅读一些编程高手写的优秀源代码,这是提高编程能力的一个很好的办法。看程序的时候就是从main()方法开始看,调用哪个方法程序就会跳到哪个方法中去执行,执行完后返回调用处,直到main()方法体结束为止。

6.6.7  方法参数及其传递问题

方法参数传递的示意图如图6.16所示。

图6.16  方法的参数传递

方法可以把相对独立的某个功能抽象出来,使之成为程序中的一个独立实体,可以在同一个程序或其他程序中多次重复使用。

在前面已经知道Java数据的分类有基本数据类型数据传递和引用数据类型数据传递两种。不管方法中的参数是哪种类型的数据,Java语言在给被调用方法的参数赋值时,只采用传值的方式,下面分别来介绍这两种参数传递的原理和实质。

1. 基本数据类型传递

指的是在方法调用时,传递的是值的拷贝。对方不管怎么改值都不影响原来的值。就如同第一个同学的作业写的比较好,其他同学要参考他的作业,他要求只允许复制一份,那么将复制的作业给其他同学一份,其他同学爱怎么修改就怎么修改,不会改变原版的作业,如:

【代码(ParameterPassValue.java)】

public class ParameterPassValue{

    public static void main(String[] args){

        int x = 5;

        System.out.println("方法调用之前x==: "+ x);

        change(x);

        System.out.println("方法调用之后x==" + x);

    }

    public static void change(int x){

        x = 100;

        System.out.println("方法中x==" + x);

    }

}

输出结果为:

方法调用之前x==: 5

方法中x==100

方法调用之后x==5

从结果可以看出主方法中x的值前后是没有变化的。也就是说无法在被调用方法内部改变调用方法的参数值。在此例中不管change()方法怎么修改x的值,其值都不会改变。

2. 引用数据类型传递

指的是在方法调用时,传递的参数是按引用进行传递,其实传递的引用的地址,也就是变量所对应的内存空间的地址。引用数据类型的参数传递又分为数组传递和对象传递。

1) 数组传递

方法的形式参数可以定义为数组类型,此方法就可以接受一组数据,如:

【代码(ArrayParameter.java)】

public class ArrayParameter {

    /** 数组传递 */

    public static void main(String[] args) {

        //排序

         int a[]={3,8,5,0,7,2};

         System.out.println("排序之前的数据为:");

         for(int temp:a){

             System.out.println(temp);

         }

         //通过调用一个方法来实现

         //在main方法要调用paiXu的方法四步

         ArrayParameter m=new ArrayParameter();

         

         m.sort(a);//传递的是一个实际存在并有无序数据的数组的名字

                      //数组传递也是引用数据类型的传递能改变原来的值

         

         System.out.println("排序之后的数据为:");

         for(int temp:a){

             System.out.println(temp);

         }

    }

    //定义一个方法来实现冒泡排序

    int temp;

    public void sort(int b[]){//传递一个赋了值的数组

        for(int i=0;i<b.length-1;i++){

            for(int j=0;j<b.length-i-1;j++){

                if(b[j]>b[j+1]){

                    temp=b[j];

                    b[j]=b[j+1];

                    b[j+1]=temp;//交换数据

                }

            }

        }

}

}

输出结果为:

排序之前的数据为:

3

8

5

0

7

2

排序之后的数据为:

0

2

3

5

7

8

可见数组传递也是引用数据类型的参数传递。

2) 对象传递

类是属于引用类型,那么通过类实例化出的对象的传递也是引用类型,如:

【代码(ParameterPassValue2.java)】

public class ParameterPassValue2{

    int x;

    public static void main(String[] args){

        ParameterPassValue2 a=new ParameterPassValue2();

        //原版作业

        a.x=5;

        

        System.out.println("方法调用之前x==" + a.x);//原版作业是5

        change(a);//传递的是引用数据类型的值,只是将原版直接给change()方法

        System.out.println("方法调用之后x==" + a.x);//打印输出原版作业

    }

    public static void change(ParameterPassValue2 a){

        a.x = 100;//将原版作业改成100

        System.out.println("方法中x==" + a.x);//100

    }

}

输出结果为:

方法调用之前x==5

方法中x==100

方法调用之后x==100

对象实例作为参数传递给方法时,参数的值不是对象本身,而是对象引用的复制。可以在被调用方法中改变对象的内容,但不能改变原对象的引用。就好比这份作业不是复制一份给其他同学,而是直接将作业给其他同学使用,其他同学觉得有问题,直接改原版的这份作业,这份作业拿回来时值已经改变了。

对于许多读者朋友而言,对象实例作为参数传递给方法看起来很像引用传递,而且行为上和引用传递有很多的共同处。但我们要抓住实质,来佐证Java语言是值传递的观点。

(1)、改变传入内容的能力只适用于传递对象,不适用于基本类型值。

(2)、与对象类型相关的实际值是对象的引用,而非对象本身。

总之,“在Java 里面参数传递都是按值传递”亦即:按值传递是传递的值的拷贝,按引用传递其实传递的是引用的地址值,所以统称按值传递。

6.6.8  理解main()方法语法及命令行参数

main()方法入口是一个数组类型的参数,也可以给main()方法传递参数,通过命令行在运行时接着参数即可。如:

【代码(CMDParameter.java)】

public class CMDParameter {

/** 命令行参数 */

public static void main(String[] args) {

        System.out.println("参数1:"+args[0]);

        System.out.println("参数2:"+args[1]);

        System.out.println("参数3:"+args[2]);

}

}

编译上面的代码,在运行的过程中输入:

java CMDParameter 中 国 人

当运行上面的命令时,将“中”、“国”、“人”三个参数分别赋值给main方法参数数组元素args[0]、args[1]、args[2],然后打印输出。

输出结果为:

参数1:中

参数2:国

参数3:人

args数组中元素的个数就是在命令行中给类传递的参数的个数,每个参数间用空格分开,如果某个参数中含有空格,将这个参数用双引号引起来。参数与args数组对应关系如图6.17所示。

图6.17  参数与args数组对应关系图

6.7  this关键字

每个类的每个非静态方法(没有被static修饰)都会隐含一个this关键字,它指向调用这个方法的对象。当在方法中使用本类的属性时,都会隐含地使用this关键字,当然也可以明确指定。this可以看作是一个变量,它的值就是当前对象的引用。

例如:

//Person类的构造方法

public Person(String n, boolean s, int a){

    this.name = n;

    this.sex = s;

    this.age = a;

}

【示例15】JuXing.java

为了区分属性与局部变量可以通过this关键字来调用。代码如下:

class  JuXing{

    int x;

    int y;

    void init (int x, int y) {

        this.x = x;  //用this来显示调用当前对象的成员变量

        this.y = y;

    }

    public static void main (String args[]) {

        JuXing p = new JuXing();

        p.init (4,3);

    }

}

this关键字只能在方法内部使用,表示对“调用方法的那个对象”的引用,如果是在同一个类中调用另外一个方法则可以不用写this,直接调用就行。

总体上看,this关键字有以下几种用法:

(1) 当类中某个非静态方法的参数名和类的某个成员变量名相同时,为了避免参数的作用范围覆盖了成员变量的作用范围,必须明确地使用this关键字来指定。

【示例16】Employee.java

显示调用成员变量或本类中的其他方法,代码如下:

public class Employee {

    private String name;      //姓名

    private int age;          //年龄

    private double salary;   //薪水

    public Employee(String name, int age, double salary){    //构造方法

        this.name = name;

        this.age = age;

        this.salary = salary;

    }

 }

(2) 如果某个构造方法的第一条语句具有形式this(...),那么这个构造方法将调用本类中的其他构造方法。

【示例17】Employee1.java

调用本类中的其他构造方法,代码如下:

public class Employee1.java {

    private String name;        //姓名

    private int age;            //年龄

    private double salary;     //薪水

    public Employee1(String name, int age, double salary){    //构造方法1

        this.name = name;

        this.age = age;

        this.salary = salary;

    }

    public Employee1(){    //构造方法2

        this("无名", 18, 800.0);    //调用到了构造方法1

    }

}

(3) 如果某个方法需要传入当前对象,则可以将当前的对象作为参数传递给它。

【示例18】Employee2.java

例如CSDN软件学院新入职了三位员工,院长要查看入职员工的信息,把这个过程用代码描述,代码如下:

import java.util.*;

public class Employee2{

// List列表在后面的章节会学习到,此处暂时理解为员工信息资源库对象

static List plist = new ArrayList();

String name="";//姓名

int age=0;      //年龄

int sex=0;      //性别

public Employee2(){

}

public Employee2(String name,int age,int sex){

    this.name=name;

    this.age=age;

    this.sex=sex;

    }

//显示员工信息       

public void show(){

    for(int i=0;i<plist.size();i++){

     Person p = (Person)plist.get(i);

   System.out.println("name="+p.name+" , age="+p.age+" ,sex="+p.sex);

  }

}

//登记注册

public void put(){

     plist.add(this);

}

public static void main(String[] args){

    Employee2 zhangsan = new Employee2("zhangsan",5,8);

    zhangsan.put();

    Employee2 lisi = new Employee2("lisi",8,8);

    lisi.put();

    Employee2 wangwu = new Employee2("wangwu",9,8);

    wangwu.put();

    Employee2 dean= new Employee2();

    dean.show();

    }

}

6.9 

为了便于管理大型软件系统中数目众多的类,解决类命名冲突的问题,Java引入了包(package)。在使用许多类时,类和方法的名称很难决定。有时需要使用与其他类相同的名称。包基本上避免了名称上的冲突。

日常生活中用文档袋有如下好处:①文档分门别类易于查找;②易于管理;③不同内容的文档可以放在不同的文档袋中,这样即使不同文档袋中的文档拥有相同的名字也不会产生冲突。Java中包的作用与文档袋的作用类似。

6.9.1  为什么需要包

树形文件系统主要的目的是使用目录可以解决文件命名的冲突问题,如图6.18所示。比如在编程的过程中要保存两个相同名字的Sort.java源代码。如果在同一个目录下Windows是不会让我们保存的,通过建立不同的文件夹就可以将相同的文件进行分类存储。

在Java编程中,包类似于文件系统中的文件夹。包的作用如下:

l   允许类组成较小的单元(类似文件夹),易于找到和使用相应的文件。

l   更好地保护类、数据和方法。

l   防止命名冲突。

图6.18  文件夹结构图

JDK 中定义的类就采用了“包”机制进行层次式管理,图6.19显示了其组织结构的一部分:

图6.18  JDK中部分包的组织结构图

从图中可以看出,一个名为java 的包中又包含了两个子包:io 包和lang 包。lang 包中包含了System, String, Object三个类的定义。事实上,Java 包中既可以包含类的定义,也可以包含子包,或同时包含两者。简而言之:从逻辑上讲,包是一组相关类的集合;从物理上讲,同包即同目录。

6.9.2  如何创建包

在Java中用关键字package 来创建包,如:

【代码(School.java)】

package com.tjitcast.chapter5;   //声明包

public class School{

   …

   public void toString(){

       …

}

}

创建包时需要注意的地方如下:

l   创建包时用package关键字。

l   如果有包声明,那么它一定作为源代码的第一行。

l   包的名称一般为小写,而且要有意义。

l   如果不加package语句,则指定为缺省包或无名包。

6.9.3  编译并生成包

带有包的类的源代码,在编译成字节码时,不能直接用javac.exe编译,需要带上“-d”这个参数来编译。以下是两个编译带包的类时使用的编译命令。

(1) 带包编译:

javac -d destpath 类名.java

归入该包的类的字节代码文件应放在java的类库所在路径的destpath 子目录下。现在包的相对位置已经决定了,但java 类库的路径还是不定的。事实上,java可以有多个存放

类库的目录,其中的缺省路径为java 目录下的lib子目录,你可以通过使用-classpath选项来确定你当前想选择的类库路径。除此之外,你还可以在CLASSPATH 环境变量中设置类库路径。destpath为目标路径,可以是本地的任何绝对或相对路径。

javac –d . Employee.java

javac –d D:\share Employee.java

则编译器会自动在destpath目录下建立相关子目录,并将生成的.class文件自动保存到子目录中去。

(2) 带包运行

带有包的类,在运行它时需要指定包名、类名,即通常所说的使用全限定名,格式如下:

java 包名.类名

包的命名规范有如下几点:

(1) 包名由小写字母组成,不能以圆点开头或结尾。

(2) 用户自己设定包名之前最好加上唯一的前缀,通常使用组织倒置的网络域名。如package net.javagroup.mypackage;。

(3) 用户自己设定的包名部分依不同的机构各自内部的规模不同而不同。包的命名规范如图6.20所示。

图6.20  包的命名规范

6.9.4  使用带包的类

为了使用不在同一个包中的类,需要在Java程序中使用import关键字导入这个类。包的导入语法如图6.21所示。

图6.21  包的导入语法

使用示例如下:

import java.util.Scanner;    //导入java.util包中的所有类。*代表所有

import java.util.Date;    //导入java.lang包中的所有类

在java 源文件中import 语句应位于package 语句之后,所有类的定义之前,可以有0~多条import 语句。

java 运行时环境将到CLASSPATH + package路径下寻找并载入相应的字节码文件。比如在示例中,import语句标明要引入java.util包中的Date类,假定环境变量CLASSPATH 的值为

“.;C:\jdk6\lib;D:\xmh”,java 运行环境将依次到下述可能的位置寻找并载入该字节码文件Date.class:

.\java\util\Date.class

C:\jdk6\lib\java\util\Date.class

D:\ex\java\util\Date.class

其中,“.”代表当前路径,如果在第一个路径下就找到了所需的类文件,则停止搜索。否则依次搜索后续路径,如果在所有的路径中都未找到所需的类文件,则编译或运行出错。

同一个包中的类不需要被导入,从外部包中使用的每个类都需要import语句。

import语句的替代方法是使用类的完全限定名称引用需要导入的类。如在上例中若省略导入Scanner语句,则需要在引用Scanner类的位置使用java.util.Scanner。

Java语言提供了从单个包导入所有类的语法,例如示例可以改写为:import java.util.*;

6.9.5  JDK中常用包介绍

JDK 1.6版本中提供了丰富的类库,通过借助它提供的说明文档,可以方便地解决编程过程中的很多问题。JDK 1.6中常用的包提供的功能主要有:

l   java.lang:包含一些Java语言的核心类,如String、Math、Integer、System和Thread,提供常用功能。此包因为非常常用,所以在任何类中不用导入就可能直接使用。

l   java.util:包含一些实用工具类,如定义系统特性、日期时间、日历、集合类等。

l   java.io:包含能提供多种输入输出的流类。

l   java.net:包含执行网络相关的操作的类。

l   java.sql:Java操作数据库的一些API。

l   java.text:包含了一些用来处理文本、日期、数字和消息的类和接口。

l   java.awt:包含了构成抽象窗口工具集的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。

l   javax.swing:包含了构成“轻量级”窗口的组件。

在后面的章节中将陆续学习上面包中的类提供的各种功能。

java 编译器默认为所有的java 程序引入了JDK 的java.lang 包中所有的类(import

java.lang.*;),其中定义了一些常用类:System、String、Object、Math等。因此我们可以直接使用这些类而不必显式引入。但使用其它非无名包中的类则必须先引入、后使用。