Logo

郎哥编程

Java对象

2021-06-10 178

学习目标:学习对象的创建、对象的访问、对象的引用、对象的比较和销毁。

对象的创建

类是抽象的概念集合,表示的是一个共性的产物,类中定义的是属性和方法,而对象是类的实例。

例如前面课程中的水果被归纳为类,而苹果、香蕉、葡萄为水果类的实例或对象。水果是人们赋予具有苹果、香蕉、葡萄等共同特点的名称,不单指某一事物;对象是指具体的实物或概念,如苹果、香蕉、葡萄等对象是实物,而一项政策可能就是一个概念性的对象了,在现实生活中,万事万物皆对象,面向对象编程就是模拟现实生活中的一个个对象来编程的。

类也可以看做是对象的模板,它描述一类对象的行为和状态,决定着对象的属性和方法。由对象可以抽象出类,类也可以实例化成对象,就像水果类决定了苹果、香蕉、葡萄等对象具备糖分、汁液、芳香度都基本特征,也可以通过抽取香蕉、葡萄等对象的共同特征抽象为水果类。下图给出了水果类与水果对象的关系。

 26.png

由苹果、葡萄等对象抽象出水果类,水果类属性有water(汁液含量)、sugar(糖分含量)、fragrance(芳香度),这些属性是水果类所具有的共同特点。当在程序中需要使用苹果对象时,将水果类实例化苹果,同时初始化苹果对象的water(汁液含量)、sugar(糖分含量)、fragrance(芳香度)属性。

创建对象

对象是根据类来创建的。在Java语言中,可以通过new运算符、反射机制和序列化来创建对象,最常用的是通过new运算符来创建对象。本课主要讲述通过new运算符来创建对象,使用反射机制和序列化来创建对象的内容,将在Java 核心技术课程中讲述。

使用new运算符创建对象语法如下:

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

案例5:创建FruitsObj类。

在PUnit5项目unit包下创建Java类FruitsObj。代码如下:

package unit;
public class FruitsObj {
    //水果名称
    private String  name;
    //水果类汁液含量属性
    private String  water;
    //水果类糖分含量属性
    private String  sugar;
    //水果类芳香度属性
    private  String  fragrance;
 
    //水果类的构造方法
    public FruitsObj(String name,String water,String sugar,String fragrance)
    {
        System.out.println("正在实例化" + name);
        this.name = name;
        this.water = water;
        this.sugar = sugar;
        this.fragrance = fragrance;
    }
 
    /**
     * Function  showFruit
     * Description: 输出水果的水分含量、糖分含量、芳香度
     * input: 无输入参数
     * return: 无返回
     */
    public void  showFruit()
    {
        System.out.printf("----%s的属性----\n", getName());
        System.out.println("水分含量:" + getWater());
        System.out.println("糖分含量:" + getSugar());
        System.out.println("芳香度:" + getFragrance());
    }
 
 
    // 水果类的开花行为
    public void flower()
    {
        System.out.println("开花");
    }
 
    // 水果类的成熟行为
    public void ripe()
    {
        System.out.println("成熟");
    }
 
    // 水果类的落果行为
    public void drop ()
    {
        System.out.println("落果");
    }
 
    /**
     * @return the water
     */
    public String getWater() {
        return water;
    }
 
    /**
     * @param water the water to set
     */
    public void setWater(String water) {
        this.water = water;
    }
 
    /**
     * @return the sugar
     */
    public String getSugar() {
        return sugar;
    }
 
    /**
     * @param sugar the sugar to set
     */
    public void setSugar(String sugar) {
        this.sugar = sugar;
    }
 
    /**
     * @return the fragrance
     */
    public String getFragrance() {
        return fragrance;
    }
 
    /**
     * @param fragrance the fragrance to set
     */
    public void setFragrance(String fragrance) {
        this.fragrance = fragrance;
    }
 
    /**
     * @return the name
     */
    public String getName() {
        return name;
    }
 
    /**
     * @param name the name to set
     */
    public void setName(String name) {
        this.name = name;
    }
 
}

FruitsObj类是水果类。它有四个成员变量,分别是name、water、sugar、fragrance,并提供了成员变量的get和set方法。它提供了一个显示构造方法,该构造方法用传入的参数来初始化类的成员变量。showFruit()是Fruits类的成员方法,用于输出类成员变量的信息。

在Punit5项目src目录下创建manager包,在包manager下创建Java类FruitsManager。代码如下:

package manager;
import unit.FruitsObj;
public class FruitsManager {
    public static void main(String[] args) {
        // 实例化Fruits类,创建一个apple对象
        FruitsObj apple = new FruitsObj("苹果","80%", "60%", "30%");
        // 调用apple对象的showFruit方法
        apple.showFruit();
 
        // 实例化Fruits类,创建一个peach对象
        FruitsObj peach = new FruitsObj("桃","60%", "30%", "50%");
        // 调用peach对象的showFruit方法
        peach.showFruit();
 
    }
}

FruitsManager类是程序的主类,在类中提供了main()方法。程序执行入口从main()方法开始。

在main()方法内部使用new运算符创建了两个Fruits类对象,一个对象是apple(苹果),一个对象是peach(桃),最后调用对象的showFruit()方法,输出对象的成员变量信息。因为FruitsManager类和Fruits类不是同一个包,因此FruitsManager类需要使用import语句导入Fruits类。

在Fruits类实例化对象的过程中,Java虚拟机会为apple和peach对象分配内存,同时也为apple和peach对象内的成员变量和成员方法分配存储空间。apple和peach对象是相互独立的,在内存中占据独立的内存地址,并且每个对象都有自己的生命周期,当一个对象的生命周期结束时,对象变成了垃圾,由Java 虚拟机自带的垃圾回收机制进行处理,该对象就不能再使用了。

访问对象的属性和方法

对象被创建后,对象可以通过“.”操作符来访问对象的成员变量和成员方法。例如:

Fruits  apple = new Fruits("80%”,“60%”,”30%”);
apple. showFruit();

在上面的代码中,apple对象通过“.”操作符调用apple对象的showFruit()方法。同样的道理,apple对象也可以通过“.”操作符来访问对象的成员变量,在这种情况下,应确认对象所在的类具有访问对象成员变量的权限,否则只能调用成员变量的get和set方法来访问成员变量。

案例6:创建一个Rectangle类,定义成员变量widht(宽度)和height(高度),定义一个getArea()方法。

在PUnit5项目unit包下创建Java类Rectangle。代码如下:

package unit;
public class Rectangle {
    // 成员变量width
    public int width;
    // 成员变量height
    public int height;
 
    // 计算面积的成员方法
    public int getArea()
    {
        return width * height;
    }
}

Rectangle类矩形类。它有两个成员变量width和height,被修饰为public权限。成员方法getArea()用于计算矩形的面积并返回计算结果。

在PUnit5项目manager包下,创建Java类RectangleTest。代码如下:

package manager;
import unit.Rectangle;
 
public class RectangleTest {
    public static void main(String[] args) {
        // 创建Rectangle对象
        Rectangle rect = new Rectangle();
        // rect对象的成员变量赋值
        rect.width = 30;
        rect.height = 20;
        // 计算矩形的面积
        int  nArea = rect.getArea();
        System.out.print("矩形的面积为:" + nArea);
 
    }
}

在RectangleTest类的main()方法内,首先实例化Rectangle对象,实例化的对象名称为rect,然后使用“.”操作符对成员变量width和height赋值。因为成员变量width和height的访问权限为public,因此rect对象所在的类具有直接访问rect对象成员变量的权限。最后使用“.”操作符调用getArea()方法计算机圆的面积。

对象的引用

类在通过new运算符实例化对象的过程中,Java虚拟机会为对象在堆(系统存储区域,用于存储应用程序创建的对象)中分配内存,并返回存储对象的内存地址,对象的内存地址就被称为对象的引用。

例如:

public static void main(String[] args) {
       // 创建Rectangle对象
       Rectangle  rect = new Rectangle();
       // rect对象的成员变量赋值
       rect.width = 30;
       rect.height = 20;
       // 计算矩形的面积
       int  nArea = rect.getArea();
       System.out.print("矩形的面积为:" + nArea);
   }

上面的代码使用new运算符创建了rect对象,此时rect存储的不是Rectangle类的实例化对象,而是对象在内存中的地址。

Rectangle  rect = new Rectangle();

上面的语句可以分成两条语句来看:

 Rectangle  rect;
 rect = new Rectangle();

第1条语句声明了一个Rectangle类型的变量rect,它存放在栈空间(用来存储局部变量的存储空间),此时rect不指向任何内存地址,它的值是null。

第2条语句由new运算符创建了一个Rectangle对象,并将创建的Rectangle对象放在堆中,然后将Rectangle对象在堆中的地址赋值给rect变量,此时rect变量存储的就是Rectangle对象在堆中的地址。

既然rect变量存储的Rectangle对象在堆中的地址,自然可以使用rect来访问对象的成员变量和成员方法了。

一个对象的引用可以理解为指向一个具体的、已经创建的对象,如果没有指向任何对象,在该引用为null。一个对象的引用不能同时指向多个对象,这一点也容易理解,为某一对象分配的内存只能存储一个对象,这段内存的地址也是唯一的。

多个对象引用变量可以指向同一个对象,看下面的代码:

Rectangle  rect;
rect = new Rectangle();
Rectangle  rect1 = rect;

rect1也是Rectangle类型的变量,rect把对象的引用赋值给rect1,此时rect和rect1指向同一个Rectangle对象。

对象的比较

在实际编程中,有时需要比较两个对象是否相等,如比较两个字符串对象。

比较两个对象是否相等,可以使用“==”运算符和对象的equals()方法。这两种方法在比较方式上是不同的。“==”运算符比较的两个对象的内存地址,equals()方法比较的是两个对象的内容。

String对象的比较在前面的课程已经讲过了,本课主要讲述自定义类的比较。

“==”运算符的比较。

“==”运算符主要是比较两个对象的内存地址是否相同。前面讲过实例化的对象变量存储的是对象的引用,使用“==”运算符可以比较两个对象变量存储的对象引用是否相同,如果这两个对象变量存储的对象引用相同,则说明它们指向同一个存储在堆中的对象,否则指向存储在堆中两个不同的对象。

使用equals()方法比较

equals()方法用于判断两个对象的内容是否相等。前面课程判断String对象的内容是否相等时,就使用了equals()方法。

equals()方法是Object类提供的方法,Object类是Java所有类(包括自定义类)的父类,所有的类都继承于Object类(关于类的继承内容将在后面的课程讲述)。String类重写了Object类equals()方法,所以可以使用equals()方法来判断两个字符串对象的内容是否相等。

要实现两个自定义类实例化对象的内容比较,就需要在自定义类中重写Object类equals()方法,在equals()方法内判断两个对象的成员变量内容是否相等,如果这两个对象的成员变量内容相等,可以认为这两个对象的内容是相等的。

例如:

public class ExtendRectangle {
      // 成员变量width
      public int width;
      // 成员变量height
      public int height;
            
      // 构造方法
      public ExtendRectangle(int width,int height)
      {
             this.width = width;
             this.height = height;
      }
     
      // 计算面积的成员方法
      public int getArea() {
             return width * height;
      }
 
      @Override
      public boolean equals(Object obj) {
             // 比较两个对象的引用是否相等
             if( this == obj )
                     return true;
          // 判断obj是否是ExtendRectangle类的实例
             if( !(obj instanceof ExtendRectangle) )
                     return false;
             // 声明boolean变量,标记对象内容是否相等
             boolean bEqual = true;
             // obj强制转换为ExtendRectangle类型
             ExtendRectangle rect = (ExtendRectangle)obj;
             // 比较成员变量的值是否相等
             if( this.width != rect.width )
                     bEqual = false;
             if( this.height != rect.height )
                     bEqual = false;
             return bEqual;
      }
            
}

在equals()方法内,首先判断传入的对象引用obj和当前对象的引用是否相等,如果两个对象的引用相等,说明这两个对象是同一个对象,可以直接返回true。

instanceof是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例。如果测试对象是类的实例返回true,否则返回false。代码使用instanceof判断obj是否是ExtendRectangle的实例对象,主要是防止传入的obj不是一个合法的ExtendRectangle类的实例化对象。

传入的obj是Object类型,需要将obj强制转换为ExtendRectangle类型,才能访问ExtendRectangle类实例化对象的成员变量。

代码最后判断当前对象与obj对象的成员变量的值是否相等,如果不相等将局部变量bEqual设置为false。

对象的销毁

类在实例化的过程中,Java虚拟机会为实例化的对象分配内存,同时也为对象内的成员变量和成员方法分配存储空间。实例化的每个对象都是相互独立的,在内存中占据独立的内存地址,并且每个对象都有自己的生命周期,当一个对象的生命周期结束时,对象变成了垃圾,由Java 虚拟机自带的垃圾回收机制进行处理,该对象就不能再使用了。

Java虚拟机是如何判断对象可以被回收呢?

对象的生命周期与对象的有效范围有关。在《变量的有效范围》一课中谈到了变量分为全局变量和局部变量:类的成员变量都是全局变量;在类方法内部声明的变量是局部变量。全局变量的有效范围为整个类,局部变量的有效范围为类方法内部。

如果在类方法内部创建的实例化对象,当这个类方法执行完成后,将会被回收。因为类方法执行完成后,类方法执行时分配的栈空间(用来存储局部变量的存储空间)被清理,方法内部的实例化对象已经失去了有效范围。

例如下面的代码,当方法执行完成后,String对象s将被Java 虚拟机自动回收。

public void mthod()
{
   String  s = new String("这是一个方法");
   System.out.println(s);
}

还有一种情况就是程序员主动将已实例化的对象设置为null,在这种情况下,虽然实例化的对象可能还在有效范围之内,Java虚拟机也会自动回收这类对象。

public void mthod()
{
   String  s = new String("这是一个方法");
   s = null;
   ……
}

垃圾对象由Java 虚拟机自动回收。如果程序员希望在垃圾对象回收之前做一些对象的资源清理工作,可以重写Object类的finalize()方法,在finalize()方法内部做对象资源的清理工作。

@Override
 protected void finalize() throws Throwable {
  //  清理资源的代码
  …………
  super.finalize();
}

类重写Object类的finalize()方法后,Java 虚拟机会判断对象是否重写了finalize()方法,如果没有重写,则直接将其回收。否则,会在垃圾回收时调用该方法,并且在下一次进行垃圾回收时,再回收对象占有的内存资源。

由于Java虚拟机的垃圾自动回收不受人为控制,具体执行时间也不确定。为此,Java提供了System.gc()方法来强制启动垃圾回收器,让Java虚拟机进行对象的回收工作。

 

代码在线纠错(通义千问 qwen-max)

支持粘贴多个代码文件,提交后由阿里云通义千问自动分析代码漏洞、语法错误、逻辑问题并给出修改建议。
您已解锁 AI 代码纠错功能,可正常使用!

评论区

登录 后发表评论
暂无评论