3.1 类和对象
Java语言是一种面向对象的高级程序设计语言。OOP是目前最接近人类思维的计算机语言之一,它是计算机语言朝着人类自然语言方向上发展的研究成果。对问题的求解过程实际上是模拟人类社会对事物的处理过程。所有的物体都可以看做对象,对象有一定的框架结构,具有一定的功能,完成一定的任务,而且这些对象之间可以建立起联系,从而就像人类社会那样处理各种各样的事务。在Java语言中这种对象都是通过类来构造的。
Java语言中的类就是一块模板。对象是在其类模板上建立起来的,就像根据建筑图纸来建楼。同样的图纸可以画许多楼房,而每栋楼房则只是建筑图纸的一个对象。
3.1.1 Java类定义
把众多的事物归纳、划分成一些类是人类在认识客观世界时经常采用的思维方法。分类的原则是抽象。类是具有相同属性和方法的一组对象的集合,它为属于该类的所有对象提供了统一的抽象描述,其内部包括属性和方法两个主要部分。
在面向对象的编程语言中,为了符合编程人员的思维习惯及更好地使客观世界与程序世界相对应,提出了类的概念。类是一个独立的程序单位,是Java程序的基本组成单位,它应该有一个类名并包括属性说明和方法说明两个主要部分。在面向对象编程语言中,要使用类的概念,首先必须进行类定义,类的具体定义格式如下。
1.类的定义格式
Java中类的定义格式如下:
<类首声明>{ <类主体> }
其中:
❑ 类首声明定义了类的名字、访问权限及与其他类的关系。
❑ 类主体定义了类的成员,包括变量(属性)和方法(行为)。
2.类首声明
类首声明的格式如下:
[<修饰符>] class <类名> [extends <父类名>] [implements <接口名>]
其中
❑ class:类定义的关键字。
❑ extends:表示定义的类和另外一个类的继承关系的关键字。
❑ implements:表示类实现了某些接口的关键字。
❑ 修饰符:表示类的访问权限(public、private等)和一些其他特性(abstract、final等)。
3.类主体
类主体的结构如下:
<类首声明>{//以下为类主体 <成员变量的声明> <成员方法的声明及实现> }
其中
❑ 成员变量即类的属性,其主要反映了类的状态。
❑ 成员方法即类的行为,主要定义了对属性的操作。
【实例3-1】定义一个用户类。
01 public class User{ 02 //定义成员变量 03 String name; 04 int age; 05 //定义成员方法 06 public void show() 07 { 08 System.out.println(“用户姓名是:”+ name +”,年龄是:”+ age); 09 } 10 }
【代码说明】在实例3-1定义的类中,第3~4行的成员变量name、age分别表示用户的姓名和年龄,第6行的成员方法show()用来对属性进行操作,即打印出用户的信息。
说明
使用类的形式进行定义,将有助于程序员编写出更加容易维护和修改的程序。
3.1.2 类的成员变量和成员方法
从程序设计的角度来看,类的成员变量用来表示事物的属性,类的成员方法用来表示事物的行为。例如,现实生活中每个人都有自己的行为和属性,一个人的属性根据需要可以包括有姓名、性别、年龄、身高和体重等,一个人的行为可以有行走、跑步、开车等。
1.成员变量
类的成员变量的声明要给出变量名、变量类型及其他特性,其格式如下:
[<修饰符>][static][final][transient][volatile] <变量类型><变量名>
其中
❑ static:表示是一个类成员静态变量。
❑ final:表示是一个常量。
❑ transient:表示是一个临时变量。
❑ volatile:用于并发线程的共享变量。
❑ 修饰符:表示变量的访问权限(默认访问、public、private和protected),关于访问权限将在后续章节中详细说明。
在实例3-1的用户类定义中,定义的成员变量name是字符串类型变量,age是整型变量,有默认的访问权限。
2.成员方法
类的成员方法是类的行为,通过它实现了对类的属性的操作。此外,其他类也可以通过某个类的方法对其变量进行访问。
对于习惯了C语言等面向过程的读者而言,特别需要注意的是,Java中的方法必须属于某个类,不可能定义一个不属于任何类的方法。
类的成员方法的声明格式如下:
[<修饰符>][static][final | abstract][native][synchronized]<返回类型> <方法名> ([<参数列表>]) [throws<异常类>]{ 方法体 }
其中
❑ 修饰符:表示方法的访问权限(默认访问、public、private和protected)。
❑ static:静态方法,可通过类名直接调用。
❑ abstract:抽象方法,只有方法声明,没有方法体。
❑ final:最终方法,方法不能被重写。
❑ native:集成其他语言代码的方法。
❑ synchronized:控制多个并发线程访问的方法。
❑ 返回类型:为该方法返回值的类型,若该方法没有返回值,则方法的返回类型为void。
❑ 参数列表:如果有数据要传递到该方法中,参数列表中的参数变量用于接收数据。
在实例3-1 的用户类定义中,定义的成员方法show()没有返回值,也不需要输入参数,有public的访问权限。
3.1.3 类的构造函数
在类定义中有一类特殊的成员方法,这类成员方法的名字与类名完全一致,在创建对象时用来对成员变量进行初始化。这类方法被称做构造方法。
创建一个构造方法和创建其他成员方法是一样的。但是需要注意的是类中的构造方法的名字必须和这个类的名字一模一样,此外,构造方法不能有返回值,特别需要注意的是,在构造方法名字前面连void也不能加。
那么,是不是在类中必须定义一个构造方法呢?答案是否定的。因为,如果在类中没有创建用户自定义的构造方法,Java会提供一个默认的构造方法,默认的构造方法没有参数,因此不能对成员变量进行初始化。但是,这里需要注意一点,如果类中有了用户自定义的构造方法后,Java就不会给出默认的构造方法了。所以,用户如果想在程序中继续使用无参的构造方法,就必须在类中再自己定义一个无参的构造方法。
实际上,构造方法是可以重载的(关于重载,将在后续章节中详细介绍),也就是说,可以在一个类中创建多个同名但参数不一样的构造方法。
【实例3-2】在用户类中添加构造方法。
01 public class User{ 02 //定义成员变量 03 String name; 04 int age; 05 //用户自定义的构造方法 06 public User(String name,int age) 07 { 08 this.name=name; 09 this.age=age; 10 } 11 //无参构造方法 12 public User() 13 {} 14 //定义成员方法 15 public void show() 16 { 17 System.out.println(“用户姓名是:”+ name + ”,年龄是:”+ age); 18 } 19 }
【代码说明】在实例3-2定义的类中,第6~10行定义了类的构造函数,在该构造函数中对类的成员变量进行初始化。
3.1.4 对象的创建和使用
对象是系统中用来描述客观事物的一个实体,它是构成系统的一个基本单位。一个对象由一组属性和对这组属性进行操作的一组服务组成。从更抽象的角度来说,对象是问题域或实现域中某些事物的一个抽象,它反映该事物在系统中需要保存的信息和发挥的作用;它是一组属性和有权对这些属性进行操作的一组服务的封装体。客观世界是由对象和对象之间的联系组成的。
当我们定义了一个类之后,这个类就与int、float、char等基本数据类型一样是Java的一种数据类型。类被认为是Java中的抽象数据类型。它是给对象的特殊类型提供定义。它规定对象内部的数据,创建该对象的特性,以及对象在其自己的数据上运行的功能。因此类就是一块模板。对象是在其类模块上建立起来的。类与对象的关系就如模具和铸件的关系,类的实例化结果就是对象,而对一类对象的抽象就是类。
应该注意,类定义了对象是什么,但它本身不是一个对象。在程序中只能有一个类定义,但可以有几个对象作为该类的实例。因此,在程序中不能直接使用类,而是要首先实例化一个对象,然后通过该对象调用类中定义的成员变量和成员方法,否则将无法调用类中定义的成员变量和成员方法(类中的静态成员变量和成员方法除外,这个稍候会详细介绍)。
1.对象的创建
在Java编程语言中使用运算符new来实例化一个对象。创建对象的形式有两种。
第一种形式的创建步骤如下。
1)声明对象
声明一个对象的具体格式如下:
<类名> <对象名>
例如:
User user;
表示将user声明为类User的对象,但是这样并没有将该对象实例化,而仅仅是通知Java编译器,user是类User类型的一个对象。
2)实例化对象
用运算符new来实例化对象,具体格式如下:
<对象名>=new构造方法( )
例如:
user=new User( );
表示对User类的对象user进行实例化,使用的是默认的构造方法。
第二种创建对象的形式是在声明对象的同时进行实例化。具体格式如下:
<类名> <对象名>=new构造方法( )
例如:
User user=new User( );
表示在声明对象时,就向内存申请分配存储空间,同时对对象进行实例化。
说明
在程序中可以创建同一个类的若干个对象。
【实例3-3】应用程序中创建对象。
01 public class User{ 02 //定义成员变量 03 String name; 04 int age; 05 //用户自定义的构造方法 06 public User(String name,int age) 07 { 08 this.name=name; 09 this.age=age; 10 } 11 //无参构造方法 12 public User() 13 {} 14 //定义成员方法 15 public void show() 16 { 17 System.out.println(“用户姓名是:”+ name + ”,年龄是:”+ age); 18 } 19 public static void main(String[ ] args) 20 { 21 User user1=new User( ); //用默认的构造方法创建对象 22 User user2=new User(“sun”,28); //用用户自定义的构造方法创建对象,并同时进行 初始化 23 } 24 }
【代码说明】在实例3-3定义的类中,第21行使用默认的构造方法创建对象user1,第22行使用用户自定义的构造方法创建对象user2。
2.对象的使用
创建了对象之后,就可以根据对象和对象成员的访问权限对成员变量进行访问,或对成员方法进行调用。
引用成员变量或成员方法时要用“.”运算符。
1)成员变量的引用
成员变量的引用格式如下:
<对象名>.<变量名>
例如,在实例3-4中,创建user2实例后,可以通过user2.name引用成员变量name。
2)成员方法的引用
成员方法的调用格式如下:
<对象名>.<方法名([参数])>
例如,在实例3-4中,创建user2实例后,可以通过user2.show()调用成员方法show()。
【实例3-4】调用对象的方法。
01 public class User{ 02 //定义成员变量 03 String name; 04 int age; 05 //用户自定义的构造方法 06 public User(String name,int age) 07 { 08 this.name=name; 09 this.age=age; 10 } 11 //无参构造方法 12 public User() 13 {} 14 //定义成员方法 15 public void show() 16 { 17 System.out.println(“用户姓名是:”+ name + ”,年龄是:”+ age); 18 } 19 public static void main(String[ ] args) 20 { 21 User user1=new User( ); //用默认的构造方法创建对象 22 User user2=new User(“sun”,28); //用用户自定义的构造方法创建对象,并同时进行 初始化 23 System.out.println(“用户姓名是:”+user2.name+ ”,年龄是:”+user2.age); //引用成员变量 24 user2.show(); //调用成员方法 25 } 26 }
图3.1 程序运行结果
【代码说明】在实例3-4定义的类中,第23行中调用对象的属性,第24行中调用对象的方法。
【运行结果】根据第一章中介绍的MyEclipse开发Java应用程序的步骤,在MyEclipse集成开发环境中编写并运行上述代码,最后的运行结果如图3.1所示。
3.1.5 类的封装
封装性就是把类的属性和方法结合成一个独立的单位,并尽可能地隐蔽类内部的实现细节,这主要包含两层含义。
❑ 把类的全部属性和全部方法结合在一起,形成一个不可分割的独立单位,即类。
❑ 信息隐蔽,即尽可能隐蔽类的内部细节,对外形成一道屏障,只保留有限的对外接口使之与外部发生联系。
通过前面有关章节的学习,读者已经能够体会到类定义中的封装性,因此,如何对类中的成员进行有效的访问控制就将是本小节重点要讨论的内容了。
在前面的例子中,对类的成员变量和成员方法都没有设定访问权限,因此,类外的代码可以直接访问类的成员。但是,这样将降低类中数据的安全性。
例如,在User类的main()方法中,在公共类中user2.name和user2.age就是对User类的内部成员变量的直接访问,如果在类Test中使用如下语句:
user2.name=”Tom”; user2.age=18;
就可以直接修改User类中的成员变量。这样是非常危险的,因为这意味着类的外部可以没有限制地直接访问类中的变量。
因此,必须限制类的外部程序对类内部成员的访问,这就是类封装的目的。但是,封装并不是意味着不允许外部程序访问类的成员变量,而是需要创建一些允许被外部程序调用的方法,通过这样的方法来访问类的成员变量。这样的方法被称为公共接口。而访问权限就是用来控制这些公共接口能够被哪些外部程序调用的。
1.访问权限
Java语言中有4 种不同的限定词,提供了4 种不同的访问权限:公有的(public)、受保护的(protected)、默认的、私有的(private)。各种权限的访问级别如表3.1所示。
表3.1 各种权限的访问级别
2.类的访问权限的设置
类的访问权限有两种:默认的和public。因此,在声明一个类时,其权限关键字要么没有,要么就是public。在同一个源文件中,可以声明多个类,但是其中只能有一个类的权限关键字是public,这个类的名字必须和源文件的名字相同,main()方法也应该在这个公共类中。
【实例3-5】一个源文件中包含多个类。
01 package com.sun.qdu; 02 //定义普通类 03 class User{ 04 //定义成员变量 05 String name; 06 int age; 07 //用户自定义的构造方法 08 public User(String name,int age) 09 { 10 this.name=name; 11 this.age=age; 12 } 13 //无参构造方法 14 public User() 15 {} 16 //定义成员方法 17 public void show() 18 { 19 System.out.println("用户姓名是:"+ name + ",年龄是:"+ age); 20 } 21 22 } 23 //定义公共类 24 public class Test { 25 public static void main(String[ ] args) 26 { 27 User user1=new User( ); //用默认的构造方法创建对象 28 User user2=new User("sun",28); //用用户自定义的构造方法创建对象,并同时进行初始化 //引用成员变量 29 System.out.println("用户姓名是:"+user2.name+ ",年龄是:"+user2.age); 30 user2.show(); //调用成员方法 31 } 32 }
【代码说明】在实例3-5中包含两个类定义,其中第24行开始定义的公共类Test包含主函数main(),并且与源文件名相同。
说明
一个Java源文件中只可以有一个公共类。
3.类的成员的访问权限的设置
用权限关键字设置类的成员的权限,可以决定是否允许类外部的代码访问这些成员。各种权限关键字的含义如下。
❑ public:该类的成员可以被其他任何所有的类访问。
❑ protected:该类的成员可以被同一包中的类或其他包中的该类的子类访问。
❑ 默认的:该类的成员能被同一包中的类访问。
❑ private:该类的成员只能被同一类中的成员访问。
【实例3-6】演示类中的私有成员。在该类中,将User类中的成员变量name和age的访问权限设置为private。
01 package com.sun.qdu; 02 //普通类的定义 03 class User{ 04 //定义成员变量 05 private String name; 06 private int age; 07 //用户自定义的构造方法 08 public User(String name,int age) 09 { 10 this.name=name; 11 this.age=age; 12 } 13 //无参构造方法 14 public User() 15 {} 16 //定义成员方法 17 public void show() 18 { 19 System.out.println("用户姓名是:"+ name + ",年龄是:"+ age); 20 } 21 22 } 23 //定义公共类 24 public class Test { 25 public static void main(String[ ] args) 26 { 27 User user1=new User( ); //用默认的构造方法创建对象 28 User user2=new User("sun",28); //用用户自定义的构造方法创建对象,并同时进行初始化 29 System.out.println("用户姓名是:"+user2.name+ ",年龄是:"+user2.age); //引用成员变量 30 user2.show(); //调用成员方法 31 } 32 }
【代码说明】在实例3-6中,第5~6行定义的成员变量被声明为private,所以在第29行直接调用该成员变量时,将显示错误信息。程序运行错误的原因是,在User类中将成员变量name和age设置为私有的,因此,这两个成员变量只能够被User类内部的成员方法直接访问,而在类外部不能够被直接访问,而本程序中在公共类Test中直接访问了User类内部的私有成员变量,违反了变量的访问控制权限。
【运行结果】在MyEclipse集成开发环境中编写并运行上述代码,将显示如图3.2所示的错误信息。
图3.2 显示运行错误信息
4.类的静态成员
在3.1.2节中介绍过,一般情况下,在类中定义的成员方法和成员变量都是属于一个个由类产生的对象的,而类中有一种特殊的成员,它不属于类的某个对象,而是属于类本身的。这种成员在声明时只需在前面加上关键字static,它们被称做静态成员变量和静态成员方法。如果在声明时不用static关键字修饰,则声明的变量和方法为实例变量和实例方法。
1)实例变量和类的静态成员变量
每个对象的实例变量都分配内存,通过该对象来访问这些实例变量,不同的实例变量是不同的。
类的静态成员变量仅在生成第一个对象时分配内存,相当于全局变量,所有实例对象共享同一个类的静态成员变量,每个实例对象对类的静态成员变量的改变都会影响到其他的实例对象。类的静态成员变量可通过类名直接访问,无须先生成一个实例对象,也可以通过实例对象访问类的静态成员变量。
类的静态成员变量引用格式如下:
<类名>.<类的静态成员变量>
2)实例方法和类方法
实例方法可以对当前对象的实例变量进行操作,也可以对类的静态成员变量进行操作,实例方法由实例对象调用。但类的静态成员方法不能访问实例变量,只能访问类的静态成员变量。类的静态成员方法可以由类名直接调用,也可由实例对象进行调用。类的静态成员方法中不能使用this或super关键字。
类的静态成员方法引用格式如下:
<类名>.<类的静态成员方法> <对象名>.<类的静态成员方法>
通过上述介绍,可以看到类的静态成员方法可以不用实例化直接通过类名就可以调用,因此,用第一种格式调用非常方便。
3.1.6 包的创建和使用
包是类的逻辑组织形式。在程序中可以声明类所在的包。同一包中类的名字不能重复。通过包可以对类的访问权限进行控制。此外,包是有层次结构的,即包中可以包含子包。
除了Java提供的用于程序开发的系统类被存放在各种系统包中之外,用户也可以创建自己的用户包。
1.自定义包
如果在程序中没有声明包,类就将被存放在默认的包中,这个默认包是没有名字的。对于包含类比较多的程序,不建议采用默认包的形式,而是建议开发者创建自己的包。
在程序中声明包的格式如下:
package <包名>
说明
声明一个包的语句必须写在源程序中的第一行。
例如,在类中的第一行添加如下代码:
package com.sun.qdu;
表示创建一个包com.sun.qdu,在该源文件中定义的所有的类都存放在这个包中。
2.包的导入
如果要使用Java中存在的包,要在源程序中使用import语句导入包。
在程序中导入包的格式如下:
import <包名>.<类名> import <包名>.*
如果要导入一个包中的多个类时,可以用“*”表示包中所有的类。
例如:
import javax.swing.*; //导入javax.swing包中所有的类 import java.awt.Button; //导入java.awt包中的Button类
3.包的层次结构
在操作系统中,包对应于一个文件夹,而类则是文件夹中的一个文件。包路径同样可以有层次结构,例如:
package com.sun.qdu;
其中,用“.”将包的层次分开,同时形成包路径的层次,qdu是sun文件夹的子文件夹,sun是com文件夹的子文件夹。
当使用多层次包结构时,要了解父包和子包在使用上是否有联系。当用“*”导入一个包中的所有类时,并不会导入这个包的子包中的类,如果需要用到子包中的类,就需要将子包单独再导入一次。例如,在后续章节中,经常会看到以下代码:
import javax.servlet.*; import javax.servlet.http.*;
4.包的访问权限
一个包中只有访问权限为public的类,才能被其他包中的类引用,其他包中具有默认访问权限的类只能在同一包中使用。
关于在同一包中,类成员的访问权限在前面已经详细讲述过了,下面着重讲一下在不同包中类成员的访问权限。
1)public访问权限的类成员
public类中的public成员可以被其他包中的类访问。public类中的protected成员可以被由它派生的在其他包中的子类访问。
2)默认访问权限的类成员
无论类的访问权限修饰符为什么,类中的默认访问权限的成员,都不能被其他包中的类访问。