JAVA基础09对象和类

本文详细介绍了JAVA中的对象和类的概念,包括如何定义类、使用构造方法创建对象、通过引用变量访问对象的方法和数据域。此外,讲解了Java库中如Date、Random和Point2D类的使用,以及静态变量、常量和方法的特点。文章还讨论了对象的可见性修饰符、数据域封装、向方法传递对象参数以及对象数组等核心概念。

9. 对象和类

9.1 为对象定义类

类为对象定义属性和行为

面向对象程序设计OOP) 就是使用对象进行程序设计对象object) 代表现实世界中可以明确标识的一个实体。例如一个学生一张桌子一个圆一个按钮甚至一笔贷款都可以看作是一个对象。每个对象都有自己独特的标识状态和行为

一个对象的状态(state也称为特征property) 或属性attribute)) 是由具有当前值的数据域来表示的。例如圆对象具有一个数据域 radius, 它是标识圆的属性个矩形对象具有数据域 width height, 它们都是描述矩形的属性

个对象的行为behavior, 也称为动作action))是由方法定义的调用对象的一个方法就是要求对象完成一个动作。例如可以为圆对象定义一个名为 getAreaO getPerimeter) 的 方法圆对象可以调用 getArea() 返回圆的面积调用 getPerimeter) 返回它的周长还可以定义 setRadius(radius) 方法圆对象可以调用这个方法来修改它的半径。

使用一个通用类来定义同一类型的对象类是一个模板蓝本或者说是合约用来定义对象的数据域是什么以及方法是做什么的。一个对象是类的一个实例可以从一个类中创建多个实例。创建实例的过程称为实例化instantiation)对象object) 和实例instance)经常是可以互换的。类和对象之间的关系类似于苹果派配方和苹果派之间的关系。可以用一种配方做出任意多的苹果派来。显示名为 Circle 的类和它的三个对象

Java 类使用变量定义数据域使用方法定义动作除此之外类还提供了一种称为构造方法(constructor) 的特殊类型的方法调用它可以创建一个新对象构造方法本身是可以完成任何动作的,但是设计构造方法是为了完成初始化动作例如初始化对象的数据域

 

 

9.2 使用构造方法构造对象

构造方法在使用 new 操作符创建对象的时候被调用

构造方法是一种特殊的方法它们有以下三个特殊性

  • 构造方法必须具备和所在类相同的名字
  • 构造方法没有返回值类型甚至连 void 也没有
  • 构造方法是在创建一个对象使用 new 操作符时调用的构造方法的作用是初始化对象

构造方法具有和定义它的类完全相同的名字和所有其他方法一样构造方法也可以重载(也就是说可以有多个同名的构造方法但它们要有不同的签名这样更易于用不同的初始数据值来构造对象。

一个常见的错误就是将关键字 void 放在构造方法的前面例如

public void Circle() { 
}

在这种情况下Circle( )是一个方法而不是构造方法

构造方法是用来构造对象的为了能够从一个类构造对象使用 new 操作符调用这个类的构造方法,如下所示

new ClassName(arguments);

例如new Circle( )使用Circle 类中定义的第一个构造方法创建一个 Circle 对象。new Circle(25)调用 Circle 类中定义的第二个构造方法创建一个 Circle 对象

通常一个类会提供一个没有参数的构造方法例如Circle( ) )这样的构造方法称 为无参构造方法(no - arg 或 no - argument constructor)。— 个类可以不定义构造方法在这种情况下类中隐含定义一个方法体为空的无参构造方法。这个构造方法称为默认构造方法default constructor)当且仅当类中没有明确定义任何构造方法时才会自动提供它。

 

 

9.3 通过引用变量访问对象

9.3.1 引用变量和引用类型

对象是通过对象引用变量reference variable)来访问的该变量包含对对象的引用使用如下语法格式声明这样的变量:

ClassName objectRefVar;

本质上来说一个类是一个程序员定义的类型类是一种引用类型reference type),这意味着该类类型的变量都可以引用该类的一个实例。下面的语句声明变量 myCircle 的类型是 Circle 类型

Circle myCircle;

变量 myCircle 能够引用一个 Circle 对象下面的语句创建一个对象并且将它的引用赋给变量 myCircle:

myCircle = new Circle();

采用如下所示的语法可以写一条包括声明对象引用变量创建对象以及将对象的引用赋值给这个变量的语句。

ClassName objectRefVar = new ClassNaroe():

下面是一个例子

Circle myCircle = new Circle();

变量 myCircle中放的是对 Circle 对象的一个引用

注意从表面上看对象引用变量中似乎存放了一个对象但事实上它只是包含了对该对象的引用。严格地讲对象引用变量和对象是不同的但是大多數情况下这种差异是可以忽略的。因此可以简单地说 myCircle 是一个 Circle 对象而不用冗长地描述说, myCi rcle 是一个包含对 Circle 对象引用的变量

注意Java 数组被看作是对象數组是用 new 操作符创建的一个数组变量实际上是一个包含数组引用的变量。

 

9.3.2 访问对象的数据和方法

在面向对象编程中对象成员可以引用该对象的数据域和方法在创建一个对象之后,它的数据和方法可以使用点操作符( 来访问和调用该操作符也称为对象成员访问操作符 (object member access operator)

  • objectRefVar.dataField 引用对象的数据域
  • objectRefVar.method(arguments)调用对象的方法

例如myCircle.radius引用 myCircle的半径myCircle.getArea( ) 调用 myCircle的 getArea 方法方法作为对象上的操作被调用

数据域 radius 称作实例变量instance variable), 因为它依赖于某个具体的实例基于同样的原因,getArea 方法称为实例方法instance method), 因为只能在具体的实例上调用它。调用对象上的实例方法的过程称为调用对象calling object)

警告回想一下我们曾经使用过 Math.methodName (参数例如Math.pow(3,2.5) 来调用 Math 类中的方法那么能否用 Circle.getAreaO 来调用 getArea 方法呢答案是不能。Math 类中的所有方法都是用关键字 static 定义的静态方法但是getArea()是实例方法,因此它是非静态的它必须使用 objectRefVar.methodName(参教的方式例如:myCircle.getAreaO )从对象调用

注意通常我们创建一个对象然后将它赋值给一个变量之后就可以使用这个变量来引用对象。有时候一个对象在创建之后并不需要引用在这种情况下可以创建一个对象,而并不将它明确地赋值给一个变量如下所示

new Circle():

或者

System.out.println("Area is " + new Circle(5).getArea());

前面的语句创建了一个 Circle 对象后面的语句创建了一个 Circle 对象然后调用它的 getArea 方法返回其面积这种方式创建的对象称为匿名对象anonymous object)

 

9.3.3 引用数据域和 null

数据域也可能是引用型的例如下面的 Student 类包含一个 String 类型的 name 数据域,String 是一个预定义的 Java

class Student {
    String name; // name has the default value null
    int age; // age has the default value 0
    boolean isScienceMajor;// isScienceMajor has default value false
    char gender; // gender has default value '\u00001
}

如果一个引用类型的数据域没有引用任何对象那么这个数据域就有一个特殊的 Java 值 null null true false 样都是一个直接量true false boolean 类型直接量,而 mill 是引用类型直接量

引用类型数据域的默认值是 rum, 数值类型数据域的默认值是 0, boolean 类型数据域的默认值是 false, char 类型数据域的默认值是 '\u0000'但是Java 没有给方法中的局部变置赋默认值。下面的代码显示 Student 对象中数据域 nameage、isScienceMajor 和 gender 的默认值

class Test {
    public static void main(String[] args){
        Student student = new Student();
        System.out.println("name?" + student.name);
        System.out.println("age?" + student.age);
        System.out.println("isScienceMajor?" + student.isScienceMajor);
        System.out.println("gender?" + student.gender);
    }
}

下面代码中的局部变量 X y 都没有被初始化所以它会出现编译错误

class Test {
    public static void main(String[] args){
    int x;// x has no default value
    String y;// y has no default value
    System.out.println("x is " + x);
    System.out.println("y is " + y); 
    } 
}

警告NullPointerException 是一种常见的运行时错误当调用值为 null 的引用变量上的方法时会发生此类异常。在通过引用变量调用一个方法之前确保先将对象引用赋值给这个变量。

 

9.3.4 基本类型变量和引用类型变量的区别

每个变量都代表一个存储值的内存位置声明一个变量时就是在告诉编译器这个变量可以存放什么类型的值。对基本类型变量来说对应内存所存储的值是基本类型值对引用类型变量来说,对应内存所存储的值是一个引用是对象的存储地址例如:如下图所示, int 型变量 i 的值就是 int 1Circle 对象 c 的值存的是一个引用它指明这个 Circle 对象的内容存储在内存中的什么位置。

将一个变量赋值给另一个变量时另一个变量就被赋予同样的值。对基本类型变量而言,就是将一个变量的实际值赋给另一个变量对引用类型变量而言,就是将一个变量的引用赋给另一个变量。如下图所示赋值语句 i - j 将基本类型变量 j 的内容复制给基本类型变量 i

如下图所示对引用变量来讲,赋值语句 cl=c2 是将 c2 的引用赋给 cl陚值之后,变量 cl C2 指向同一个对象

注意如上图所示执行完赋值语句 C1-C2 之后cl指向 C2 所指的同一个对象cl 以前引用的对象就不再有用,因此现在它就成为垃圾garbage)垃圾会占用内存空间。Java 运行系统会检测垃圾并自动回收它所占的空间这个过程称为垃圾回收garbage collection)。

提示如果你认为不再需要某个对象时可以显式地给该对象的引用变量赋 null 如果该对象没有被任何引用变量所引用,Java 虚拟机将自动回收它所占的空间

 

 

9.4 使用 Java 库中的类

JavaAPI包含了丰富的类的集合用于开发 Java 程序

9.4.1 Date

Java 在 java.util.Date 类中还提供了与系统无关的对日期和时间的封装,如下图所示:

可以使用 Date 类中的无参构造方法为当前的日期和时间创建一个实例它的 getTime( ) 方法返回自从 GMT 时间 1970 1 1 日算起至今流逝的时间它的 toString( ) 方法返回日期和时间的字符串。例如下面的代码:

java.util.Date date = new java.util.Date();
System.out.println("The elapsed time since Jan 1,1970 is " + date.getTime() + " milliseconds");
System.out.println(date.toString());

显示输出为

The elapsed time since Jan 1, 1970 is 1324903419651 milliseconds
Mon Dec 26 07:43:39 EST 2011

Date 类还有另外一个构造方法Date(long elapseTime), 可以用它创建一个 Date 对象。该对象有一个从 GMT 时间 1970 年 1 月 1 日算起至今流逝的以毫秒为单位的给定时间

 

9.4.2 Random

可以使用 Math .random( ) 获取一个 0.0 1.0(不包括1.0) 之间的随机 double 型值。另一种产生随机数的方法是使用如下图所示的 java.util.Random 它可以产生一个 int、longdoublefloat boolean 型值

创建一个 Random 对象时必须指定一个种子或者使用默认的种子种子是一个用于初始化一个随机数字生成器的数字。无参构造方法使用当前已经逝去的时间作为种子创建一个 Random 对象如果这两个 Random 对象有相同的种子那它们将产生相同的数列例如:下面的代码都用相同的种子 3 来产生两个 Random 对象

Random randoml = new Random(3);
System.out.print("From random1:");
for (int i = 0; i < 10; i++)
System.out.print(randoml.nextInt(1000) + " ");

Random random2 = new Random(3);
System.out.print("\nFrom random2: ");
for (int i = 0; i < 10; i++)
System.out.print(random2.nextInt(1000) + " ");

这些代码产生相同的 int 类型的随机数列

From randoml: 734 660 210 581 128 202 549 564 459 961
From random2: 734 660 210 581 128 202 549 564 459 961

注意产生相同随机值序列的能力在软件测试以及其他许多应用中是很有用的在软件测试中,经常需要从一组固定顺序的随机数中来重复生成测试案例

 

9.4.3 Point2D

Java API javafx.geometry 包中有一个便于使用的 Point2D 用于表示二维平面上的点。该类的 UML 图如下图所示:

 

 

9.5 静态变置常置和方法

静态变量被类中的所有对象所共享静态方法不能访问类中的实例成员

Circle 类的数据域 radius 称为一个实例变量实例变是绑定到类的某个特定实例的,它是不能被同一个类的不同对象所共享的例如假设创建了如下的两个对象:

Circle circlel = new Circle();
Circle circle2 = new Circ1e(5);

circlel中的 radius circle2 中的 radius 是不相关的它们存储在不同的内存位置circlelradius 的变化不会影响 circle2 中的 radius, 反之亦然

如果想让一个类的所有实例共享数据就要使用静态变量static variable), 也称为类变量( class variable)静态变量将变量值存储在一个公共的内存地址因为它是公共的地址, 所以如果某一个对象修改了静态变量的值,那么同一个类的所有对象都会受到影响Java 支持静态方法和静态变童,无须创建类的实例就可以调用静态方法static method)

修改 Circle 添加静态变量 numberOfObjects 统计创建的 Circle 对象的个数。当该类的第一个对象创建后mimberOfObjects 的值是1当第二个对象创建后numberOfObjects 的值是 2Circle 类的UML图如下图所示Circle 类定义了实例变量 radius 和静态变量 numberOfObjects , 还定义了实例方法 getRadiussetRadius getArea 以及静态方法 getNumberOfObjects(注意UML 类图中静态变量和静态方法都是以下划线标注的。

要声明一个静态变量或定义一个静态方法就要在这个变量或方法的声明中加上修饰符 static: 静态变量 numberOfObjects 和静态方法 getNumberOfObjects() 可以如下声明

static int numberOfObjects;
static int getNumberObjects(){
    return numberOfObjects; 
}

类中的常量是被该类的所有对象所共享的因此常量应该声明为 final static, 例如Math 类中的常量 PI 是如下定义的

final static double PI = 3.14159265355979323846:

提示使用 类名 方法名参数)的方式调用静态方法使用 类名 .静态变量的方式访问静态变量。这会提高可读性因为可以很容易地识别出类中的静态方法和数据

实例方法可以调用实例方法和静态方法以及访问实例数据域或者静态数据域静态方法可以调用静态方法以及访问静态数据域。然而静态方法不能调用实例方法或者访问实例数据域,因为静态方法和静态数据域不属于某个特定的对象静态成员和实例成员的关系总结在下图中。

警告一个常见的设计错误就是将一个本应该声明为静态的方法声明为实例方法例如:方法 factohaKint n) 应该定义为静态的如下所示因为它不依赖于任何具体的实例

 

 

9.6 可见性修饰符

可见性修饰符可以用于确定一个类以及它的成员的可见性

可以在类方法和数据域前使用 public 修饰符表示它们可以被任何其他的类访问。如果没有使用可见性修饰符,那么则默认类方法和数据域是可以被同一个包中的任何一个类访问的。这称作包私有package - private)或包内访问package - access )

注意包可以用来组织类。为了完成这个目标,需要在程序中首先出现下面这行语句在这行语句之前不能有注释也不能有空白:

package packageName:

如果定义类时没有声明包就表示把它放在默认包中

Java 建议最好将类放入包中而不要使用默认包

除了 public 和默认可见性修饰符Java 还为类成员提供 private protected 修饰符。 本节介绍 private 修饰符

private 修饰符限定方法和数据域只能在它自己的类中被访问。下图示类 C1中的公共的、默认的和私有的数据域或方法能否被同一个包内的类 C2 访问以及能否被不在同一个包内的类 C3 访问

 

如果一个类没有被定义为公共类那么它只能在同一个包内被访问如下图所示,C2 可以访问 C1C3 不能访问 C1

可见性修饰符指明类中的数据域和方法是否能在该类之外被访问在该类之内对数据域和方法的访问是没有任何限制的。如下图 b 所示C 类的对象 c 不能引用它的私有成员,因为 c Test 类中如下图 a 所示C 类的对象:( 可以访问它的私有成员因为 c 在自己的类内定义。

警告修钸符 private 只能应用在类的成修饰符 public 可以应用在类或类的成员上。在局部变量上使用修饰符 public private 都会导致编译错误

注意:大多数情况下,构造方法应该是公共的但是如果想防止用户创建类的实例就该使用私有构造方法。例如因为 Math 类的所有数据域和方法都是静态的,所以没必要创建 Math 类的实例为了防止用户从 Math 类创建对象在  java.lang.Math 中的构造方法定义为如下所示:

 

 

9.7 数据域封装

将数据域设为私有保护数据并且使类易于维护

为了避免对数据域的直接修改应该使用 Private 修饰符将数据域声明为私有的这称为数据域封装(data field encapsulation)

在定义私有数据域的类外的对象是不能访问这个数据域的但是经常会有客户端需要存取、修改数据域的情况为了能够访问私有数据域可以提供一个 get 方法返回数据域的值。为了能够更新一个数据域可以提供一个 set 方法给数据域设置新值get 方法也被称为访问器(accessor), set 方法称为修改器mutator)

get 方法有如下签名

public returnType getPropertyName()

如果返回值类型是 boolean 习惯上如下定义 get 方法

public boolean isPropertyName()

set 方法有如下签名

public void setPropertyName(dataType propertyValue)

 

 

9.8 向方法传递对象参数

给方法传递一个对象是将对象的引用传递给方法

可以将对象传递给方法同传递数组一样传递对象实际上是传递对象的引用下面的代码将 myCi rcl e 对象作为参数传递给 printCircle 方法

public class Test { 
    public static void main (String[] args) { 
        // CircleWithPrivateDataFields is defined in Listing 9.8
        CircleWithPrivateDataFields myCircle =new
            CircleWithPrivateDataFields(S •0); 
        printCircle(myCircle); 
    } 
 
    public static void printCircle(CircleWithPrivateDataFields c) {
        System.out.pri ntl n ("The area of the circle of radius " + c.getRadius() + " is " + c.getArea());
    }
}

Java 只有一种参数传递方式值传递pass - by - value)在上面的代码中myCircle 的值被传递给 printCircle 方法这个值就是一个对 Circle 对象的引用值

 

 

9.9 对象数组

数组既可以存储基本类型值也可以存储对象

下面的语句声明并创建了 10 Circle 对象的数组

Circle[] circleArray = new Circle[10];

为了初始化数组 circleArray可以使用如下的 for 循环

for (int i = 0;i<circleArray.length;i++){
    circleArray[i] = new Circle(); 
}

对象的数组实际上是引用变量的数组因此调用 circleArray[1].getArea( ) 实际上调用了两个层次的引用,如下图所示circleArray 引用了整个数组circleArray[1引用了一个 Circle 对象

注意当使用 new 操作符创建对象教组后,这个数组中的每个元素都是默认值为 null 的引用变量。

 

 

9.10 不可变对象和类

可以定义不可变类来产生不可变对象不可变对象的内容不能被改变

通常创建一个对象后它的内容是允许之后改变的有时候也需要创建一个一旦创建其内容就不能再改变的对象。我们称这种对象为一个不可变对象immutable object), 而它的类就称为不可变类(immutable class)例如String 类就是不可变的

如果一个类是不可变的那么它的所有数据域必须都是私有的而且没有对任何一个数据域提供公共的 set 方法一个类的所有数据都是私有的且没有修改器并不意味着它一定是不可变类。例如下面的 Student 它的所有数据域都是私有的而且也没有 set 方法,但它不是一个不可变的类。

public class Student {
    private int id;
    private String name ;
    private java.util.Date dateCreated;

    public Student(int ssn, String newName) {
        id = ssn;
        name = newName;
        dateCreated = new java.util.Date();
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }
    public java.util.Date getDateCreated() {
        return dateCreated;
    }
}

如下面的代码所示使用 getDateCreated( ) 方法返回数据域 dateCreated它是对 Date 对象的一个引用通过这个引用可以改变 dateCreated 的值

public class Test {
    public static void main(String[] args){
        Student student = new Student(111223333 , "John");
        java.util.Date dateCreated = student.getDateCreated();
        dateCreated.setTime(200000); // Now dateCreated field is changed!
    }
}

要使一个类成为不可变的它必须满足下面的要求

  • 所有数据域都是私有的
  • 没有修改器方法
  • 没有一个返回指向可变数据域的引用的访问器方法

 

 

9.11 变量的作用域

实例变量和静态变量的作用域是整个类无论变量是在哪里声明的

局部变量的声明和使用都在一个方法的内部。

个类的实例变量和静态变量称为类变量classs variables)或教据域data field)在方法内部定义的变量称为局部变量。无论在何处声明类变量的作用域都是整个类类的变量和方法可以在类中以任意顺序出现,如下图 a 所示但是当一个数据域是基于对另一个数据域的引用来进行初始化时则不是这样。在这种情况下必须首先声明另一个数据域如下图 b 所示为保持一致性,我们在类的开头就声明数据域

类变量只能声明一次但是在一个方法内不同的非嵌套块中可以多次声明相同的变量名。

如果一个局部变量和一个类变量具有相同的名字那么局部变量优先. 而同名的类变量将被隐藏( hidden)例如在下面的程序中x 被定义为一个实例变量也在方法中被定义为局部变量。

public class F {
    private int x = 0; // Instance variable
    private int y = 0;
    public F() { 
    }
    public void p() {
        int x = 1; // Local variable
        System.out.println("x = " + x);
        System.out.println("y = " + y); 
    } 
}

假设 f F 的一个实例那么 f.p( ) 的打印输出是什么呢f.pO 的打印输出是X 为 1, y 0其原因如下

  • x 被声明为类中初值为 0的数据域但是它在方法 p()中又被声明了一次初值为1System.out. println 语句中引用的 x 是后者。 
  • y 在方法 P)的外部声明但在方法内部也是可访问的

提示为避免混淆和错误除了方法中的参數不要将实例变量或静态变量的名字作为局部变量名

 

 

9.12 this 引用

关键字 this 引用对象自身它也可以在构造方法内部用于调用同一个类的其他构造方法。

关键字 this 是指向调用对象本身的引用名可以用 this 关键字引用对象的实例成员。例如,下图 a 的代码使用 this 来显式地引用对象的 radius以及调用它的 getArea( ) 方法this引用通常是省略掉的如下图 b 所示然而在引用隐藏数据域以及调用一个重载的构造方法的时候,this 引用是必须的

9.12.1 使用 this 引用隐藏数据域

this 关键字可以用于引用类的隐藏数据域例如在数据域的 set 方法中,经常将数据域名用作参数名。在这种情况下这个数据域在 set 方法中被隐藏为了给它设置新值,需要在方法中引用隐藏的数据域名。隐藏的静态变量可以简单地通过 类名 .静态变量的方式引用。隐藏的实例变量就需要使用关键字 this 来引用如下图 a 所示

关键字 this 给出一种引用调用实例方法的对象的方法调用f1.setI(10)执行了 this.i=1, 将参数 i 的值赋给调用对象 f1的数据域 i关键字 this 是指调用实例方法 setl 的对象,如上图 b 所示F.k = k 这一行的意思是将参数 k 的值赋给这个类的静态数据域 k,k 是被类的所有对象所共享的

 

9.12.2 使用 this 调用构造方法

关键字 this 可以用于调用同一个类的另一个构造方法例如可以如下改写 Circle :

在第二个构造方法中this(1.0)这一行调用带 double 值参数的第一个构造方法

注意Java 要求在构造方法中语句 this( 参数列表应在任何其他可执行语句之前出现

提示如果一个类有多个构造方法最好尽可能使用 this( 参数列表实现它们通常,无参数或参数少的构造方法可以用 this( 参数列表调用参数多的构造方法这样做通常可以简化代码,使类易于阅读和维护。

 

 

 

 

 

 

编译运算练习

1.

 

 

2.

 

 

3.

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值