问题引出:在前面的章节中,我们已经学会用数组、循环等方法解决一些编程问题,但是这些Java的特性还不足够用来开发图形用户界面和大型软件系统。如果需要开发一个GUI(图形用户界面),该如何用程序实现。
解决方式:使用对象和类
9.2为对象定义类
面向对象编程(OOP)就是使用对象进行程序设计的。对象(Object)代表现实世界中可以明确标识的一个实体。例如:一个学生、一张桌子。每个对象都有自己独特的表示、状态和行为。
一个对象的状态:(state)是由具有当前值的数据域来表示的。例如:圆对象有一个数据域radius,它是标识圆的属性。
一个对象的行为:(behavior)是由方法定义的。调用对象的一个方法就是要求对象完成一个任务。例如:可以为圆对象定义一个名为getArea的方法返回圆的面积。
通常使用一个通用类来定义同一类型的对象。类是一个模板、蓝本或者合约。一个对象是类的一个实例。创建实例的过程被称为实例化。对象(object)和实例(instance)经常是可以互换的。类和对象之间的关系类似于苹果和苹果派之间的关系。可以使用一种配方制作出任意多的苹果来。
Java类使用变量定义数据域,使用方法定义动作。除此之外,类还提供了一种称为构造方法(constructor)的特殊类型的方法,调用它可以创建一个新的对象。构造方法本身是可以完成任何动作的,但涉及构造方法的初衷是为了完成初始化动作。例如初始化对象的数据域。
Circle类与目前所见的所有类都不通,它没有main方法,因此不能运行。涉及main方法的类我们称为主类。
类的模板和对象的图示可以使用统一建模语言(UML)的图像符号进行标准化。如下图:
9.3示例:定义类和创建对象
程序包括两个类。第一个类TestSimpleCircle是主类。它的唯一目的就是测试第二个类SimpleCircle。使用这样的类的程序通常被称为该类的客户(client),运行这个程序时,Java运行系统会调用这个主类的main方法。
可以把两个类放在同一个文件中,但是一个文件只能有一个类是公共(public)的。此外,公共类必须与文件同名。源代码中每个类编译成.class文件。当编译TestSimpleCircle.java时,产生两个类文件TestSimpleTest.class和SimpleCircle.class。
编译Java程序的方法有很多种。例如,可以把例子中的两个类合成一个类。
由于组合后的类中有一个main方法,所以它可以由Java解释器执行。
9.4使用构造方法构造对象
构造方法是一种特殊的方法。它们具有以下三个特殊性:
1.构造方法必须具备和所在类相同的名字
2.构造方法没有返回值。甚至连void也没有。
3.构造方法是在创建一个对象使用new操作符时调用的。构造犯法的作用是初始化对象。
构造犯法具有和定义它的类完全相同的名字。和所有其他方法一样,构造方法也可以重载(也就是说,可以有多个同名的构造方法,但它们要有不同的签名),这样更易于用不同的初始数据值来构造对象。
通常,一个类会提供一个没有参数的构造方法。这样的构造方法称为无参构造方法(no-arg)。
一个类可以不定义构造方法,这事,类中隐含定义一个方法体为空的无参构造方法。这个构造方法称为默认构造方法(default constructor)。当且仅当类中没有明确定义任何构造方法时才会提供它。
9.5通过引用变量访问对象
9.5.1引用变量和引用类型
对象是通过对象引用变量(reference variable)来访问的,该变量包含对对象的引用,使用如下语法格式声明这样的变量。
ClassName objectRefVar
本质上来说,一个类一个程序员定义的类型。类是一种引用变量(reference type),这意味着该类类型的变量都可以引用该类的一个实例,下面语句声明变量myCircle的类型是Circle类型。
Circle myCircle
变量myCircle能够引用一个Circle对象。下面的语句创建一个对象,并且把它的引用赋给变量myCircle。
myCircle=new Circle()
采用下面的语法,可以写一条包括声明对象引用变量、创建对象以及将对象的引用赋值给这个变量的语句。
ClassName objectRefVal=new ClassName()
变量myCircle中放的是对Circle对象的一个引用。
注意:从表面上看,对象引用变量中似乎放了一个对象,但事实上,它只是包含了该对象的引用。严格地讲,对象引用变量和对象是不同的。
9.5.2访问对象的数据和方法
在面向对象编程中,对象成员可以引用该对象的数据域和方法。在创建一个对象后,它的数据和方法可以使用点操作符(.)来访问和调用,该操作符也称为对象成员访问操作符(object member access operator):
objectRefVal.dataField引用对象的数据域
objectRefVal.method(arguments)调用对象的方法
数据域radius称作实例变量(instance variable),因为它依赖于某个具体的实例。基于同样的原因,getArea方法称为实例方法(Instance method),因为只能在具体的实例上调用它。调用对象上的实例方法的过程被称为调用对象(calling object)。
9.5.3引用数据域和null值
数据域也可以是引用型的。例如:下面的Student类包含一个String类型的name数据域,String name是一个预定义的Java类。
如果一个引用类型的数据域中没有任何引用对象,那么这个数据域就有一个特殊的Java值null。null同true和false一样都是一个直接量。true和false是boolean类型直接量,而null是引用类型直接量。
引用类型数据域的默认值是null,数值类型数据域的默认值是0,boolean类型数据域的默认值是false,而char类型数据域的默认值是‘\u0000’。但是,Java没有给方法中的局部变量赋默认值。
9.5.4基本类型变量和引用类型变量的区别
每一个变量都代表一个存储至的内存位置。声明一个变量时,就是在告诉编译器这个变量可以存放什么类型的值。对基本类型变量来说,对应内存所存储的值是基本类型值。对引用变量类型来说,对应内存存储的值是一个引用,是对象的存储地址。例如。Int变量i的值就是int值1,而Circle对象c的值存的是一个引用,它芝麻这个Circle对象的内容存储在内存中的什么位置。
讲一个变量赋值给另一个变量时,另一个变量就被赋予了同样的的值。对基本数据类型而言,就是键一个变量的实际值赋给了另一个变量。对引用类型变量而言,就是键一个变量的引用赋给另一个变量。
如果你认为不再需要某个对象时,可以显式的给该对象锁定在图层上的引用变量赋null值。如果该对象没有被任何引用变量引用,Java虚拟机将自动回收它所占的空间。
9.6使用Java中的类
9.6.1Date类
Java在java.util.Date类中还提供了与系统无关的对日期和时间的封装。
可以使用Date类中的无参构造犯法为当前的日期和时间创建一个实例,它的getTime()方法返回自GMT时间1970年1月1日算起至今流逝的时间,它的toString方法返回日期和时间的字符串。例如:
9.6.2Random类
可以使用Math.random()来获取一个0.0到1.0(不包括1.0)之间的随机double型的值。另一只产生随机数的方法是使用java.util,Random类,它可以产生一个int/long/double/float和boolean型值。
创建一个Random对象时,必须指定一个种子或使用默认种子。种子是一个用于初始化一个随机数字生成器的数字。无参构造方法使用当前已经逝去的时间作为种子,创建一个Random对象。如果这两个Random对象有相同的种子,那它们将产生相同的数列。例如:
9.6.3Print2D类
JavaAPI在javafx.geometry包中有一个便于使用的Print2D类,用于表示二维平面上的点。
可以为给定x和y坐标的点来创建一个Point2D对象,使用distance方法计算该点到另一个点的距离,并且使用toStirng()方法来返回该点的字符串表示。
9.7静态变量、常量和方法
Circle类中的数据域radius称为一个实例变量。实例变量是绑定到类中某个特定实例的,它是不能被同一个类的不同对象所共享的。例如:假设创建了如下两个对象:
Circle circle1=new Circle()
Circle circle2=new Circle(5)
这两个radius是不相关的,它们存储在不同的内存位置。
如果想让一个类的所有实例数据共享数据,就要使用静态变量(static vsriable),也称为类变量(class variable)。静态变量将变量值存储在一个公共的内存位置。因为它是公共的地址,所以如果某一个对象修改了静态变量的值,那么同一个类的所有对象都会受到影响。Java支持静态变量和静态太方法,无序创建类的实例就能调用静态方法(static variable)。
类中的常量是被该类所有对象所共享的。因此,常量应该声明为final static。
9.8可见性修饰符
可以在类、方法和数据域前面使用public修饰符,表示它们可以被任何其他类访问。若果没有使用可见性修饰符,那么则默认类、方法和数据域是可以被同一个包中的任何一个类访问的。这称作包私有(package-private)或包内访问(package-access)。
除了public和默认可见性修饰符,Java还为类成员提供private和protected修饰符。private修饰符限定方法和数据域只能在它自己的类中被访问。如果一个类没有被定义为共欧诺个类,那么它只能在同一个包内被访问。
可见性修饰符指明类中的数据域和方法是否能在该类之外被访问。在该类之内,对数据域和方法的访问是没有限制的。
9.9数据域的封装
在之前的程序中,数据域可以直接修改。这不是一个好的做法。原因有两点:
1.数据可能被篡改
2.它使类变得难于维护
为了避免对数据域的直接修改,应该使用private修饰符将数据域声明为私有,这称作数据域封装(date field encapsulation)。
在定义私有数据域的类外的对象是不能访问这个数据域的,可以提供一个get()方法返回数据域的值。为了能够更新数据域,可以提供一个set()方法给数据域设置新值。get方法也成为访问器(accessor),而set方法也称为修改器(mutator)。
9.10向方法传递对象参数
可以将对象传递给方法。同传递数组一样,传递对象实际上是传递对象的引用。下面展示了传递基本类型值和传递引用值的差异:
当传递基本数据类型参数时,传递的是实参的值。在这种情况下,n(5)的值就被传递给times。在printArea方法内,times的内容改变,这不会影响n的内容。
传递引用类型的参数时,传递的是对象的引用。在这种情况下,c具有与myCircle相同的引用值。因此,,通过printArea方法内部的c与在方法外的myCircle来改变对象的属性,效果一样。引用上的传值在语义上最好描述为传共享(pass-by-sharing),也就是说,在方法中引用的对象和传递的对象是一样的。
9.11对象数组
对象的数组实际上是引用变量的数组。因此,调用circleArray[2].getArea()实际上调用了两个层次的引用。circleArray引用了整个数组,circleArray[1]引用了一个Circle对象。
注意:当使用new操作符创建对象数组后,这个数组中的每个元素都是默认值为null的引用变量。
9.12不可变对象和类
即可以定义不可变类来产生不可变对象。不可变对象的内容不能被改变。
同程艺龙,创建一个对象hour,它的内容是允许之后改变的。有时候也需要创建一个一旦创建其内容就不能改变的对象。称这种对象为一个不可变对象(immutable object),而它的类就称为不可变类(immutable class)。例如:String类就是不可变的。
如果一个类是不可变的,那么它所有数据域都必须为私有的,而且没有对任何一个数据域提供公共的set方法。一个类的所有数据都是私有的且没有修改器并不意味着它一定是不可变类。
9.13变量的作用域
一个类的实例变量和静态变量称为类变量或数据域。在方法内部定义的变量称为局部变量。无论在何处声明,类变量的作用域都是整个类
类变量只能声明一次,但是在一个方法内不同的非嵌套块中,可以多次声明相同的变量名。如果一个局部变量和一个类变量具有相同的名字,那么局部变量优先,而同名的类变量将被隐藏。
9.14this引用
关键字this是指向调用对象本身的引用名。可以用htis关键字引用对象的实例成员。例如,可以使用this来显式地引用对象的radius以及调用它的getArea()方法。this引用通常是省略掉的。然而,在隐藏数据域以及调用一个重载的构造方法时,this引用是必须的。
9.14.1使用this引用隐藏数据域
this关键字可以用于引用类的隐藏数据域。例如,在数据域的set方法中,经常将数据域名用作参数名。在这种情况下,这个数据域在set方法中被隐藏。为了给它设新值,需要在方法中引用隐藏的数据域名。隐藏的静态变量可以简单通过“类名.静态变量”的方式引用,隐藏的实例变量就需要使用关键字this来引用。
9.14.2使用this调用构造方法
关键字this可以调用同一个类的另一个构造方法。Java要求在构造方法中,语句this(参数列表)应在任何其他可执行语句之前出现。