05_面向对象基础篇_01-基本概念、类与对象、类的封装、内部类、引用数据

 

 

本章章节

> 5.1 面向对象程序设计的基本概念
> 5.2类与对象
> 5.3类的封装性
> 5.4在类内部调用方法
> 5.5引用数据类型的传递
> 5.6构造方法
> 5.7匿名对象
> 5.8对象的比较
> 5.9 this 关键字的使用
> 5.10 static 关键字的使用
> 5.11构造方法的私有
> 5.12对象数组的使用
> 5.13 Java基本数据类型的包装类
> 5.14 String类的常见方法
> 5.15 Java文档注释
.本章摘要:

到目前为止,前面所学习到的Java语法都属于Java语言的最基本功能,其中包括了数据类型和程序控制语句、循环语句、方法、数组和数据结构等。但随着计算机的发展,面向对象的概念也随之孕育而生。类(class)是面向对象程序设计最重要的概念之一,要深入了解Java程序语言,一定要了解面向对象程序设计的观念,从本章将开始学习Java程序中类的设计!

5.1 面向对象程序设计的基本概念

早期的程序设计经历了“面向问题”、“面向过程”的阶段,随着计算机技术的发展,以及所要解决问题的复杂性的提高,以往的程序设计方法已经不能适应这种发展的需求。于是,从20世纪70年代开始,相继出现了多种面向对象的程序设计语言(如图5-1所示),并逐渐产生了面向对象的程序设计方法。面向对象的程序设计涉及到对象、封装、类、继承及多态等几个基本概念。

 

图5-1  计算机语言的发展过程

5.1.1 对象

面向对象是什么意思呢?

面向对象程序设计是将人们认识世界过程中普遍采用的思维方法应用到程序设计中。对象是现实世界中存在的事物,它们是有形的,如某个人、某种物品;也可以是无形的,如某项计划、某次商业交易。对象是构成现实世界的一个独立单位,人们对世界的认识,是从分析对象的特征入手的。

对象的特征分为静态特征动态特征两种。静态的特征指对象的外观、性质、属性(如某个人具有名字、身高、体重等);动态的特征指对象具有的功能、行为(如人具有吃饭、睡觉、打人、偷税、漏税)等。客观事物是错综复杂的,但人们总是从某一目的出发,运用抽象分析的能力,从众多的特征中抽取最具代表性、最能反映对象本质的若干特征加以详细研究。

人们将对象的静态特征抽象为属性,用数据来描述,在Java语言中称之为变量;人们将对象的动态特征抽象为行为,用一组代码来表示,完成对数据的操作,在Java语言中称之为方法,。一个对象由一组属性和一组对属性进行操作的方法构成。

5.1.2 类

将具有相同属性及相同行为的一组对象称为类。广义地讲,具有共同性质的事物的集合就称为类。如:博拉图对人作如下定义:人是没有毛能直立行走的动物。在博拉图的定义中“人”是一个类,具有"没有毛、直立行走"等一些区别于其它事物的共同特征;而张三、李四、王五等一个个具体的人,是"人"这个类的一个个"对象"。如图5-2所示:

 

图5-2  类与对象的关系

在面向对象程序设计中,类是一个独立的单位,它有一个类名,其内部包括成员变量,用于描述对象的属性;还包括类的成员方法,用于描述对象的行为。在Java程序设计中,类被认为是一种抽象数据类型,这种数据类型,不但包括数据,还包括方法。这大大地扩充了数据类型的概念。

类是一个抽象的概念,要利用类的方式来解决问题,必须用类创建一个实例化的类对象,然后通过类对象去访问类的成员变量,去调用类的成员方法来实现程序的功能。这如同“汽车”本身是一个抽象的概念,只有使用了一辆具体的汽车,才能感受到汽车的功能。

一个类可创建多个类对象,它们具有相同的属性模式,但可以具有不同的属性值。Java程序为每一个类对象都开辟了内存空间,以便保存各自的属性值。对象与对象之间也可以相互联系和相互作用,我们把这种方式称为“消息”。 一个消息主要由5部分组成:发送消息的对象、接收消息的对象、消息传递办法、消息内容(参数)、反馈。

面向对象的程序设计有四个主要特征:抽象、封装、继承、多态。

5.1.3 抽象

类的定义中明确指出类是一组具有内部状态和运动规律对象的抽象,抽象是一种从一般的观点看待事物的方法,它要求我们集中于事物的本质特征(内部状态和运动规律),而非具体细节或具体实现。面向对象鼓励我们用抽象的观点来看待现实世界,也就是说,现实世界是一组抽象的对象——类组成的。

5.1.4 封装

封装是面向对象的方法所应遵循的一个重要原则。它有两个含义:一是指把对象的属性和行为看成一个密不可分的整体,将这两者“封装”在一个不可分割的独立单位(即对象)中。另一层含义指“信息隐蔽”,把不需要让外界知道的信息隐藏起来,有些对象的属性及行为允许外界用户知道或使用,但不允许更改,而另一些属性或行为,则不允许外界知晓;或只允许使用对象的功能,而尽可能隐蔽对象的功能实现细节。

封装机制在程序设计中表现为,把描述对象属性的变量及实现对象功能的方法合在一起,定义为一个程序单位,并保证外界不能任意更改其内部的属性值,也不能任意调动其内部的功能方法。

封装机制的另一个特点是,为封装在一个整体内的变量及方法规定了不同级别的“可见性”或访问权限。

5.1.5 继承

继承是面向对象方法中的重要概念,并且是提高软件开发效率的重要手段。首先拥有反映事物一般特性的类,然后在其基础上派生出反映特殊事物的类。如已有的汽车的类,该类中描述了汽车的普遍属性和行为,进一步再产生轿车的类,轿车的类是继承于汽车类,轿车类不但拥有汽车类的全部属性和行为,还增加轿车特有的属性和行为。

在Java程序设计中,已有的类可以是Java开发环境所提供的一批最基本的程序——类库。用户开发的程序类是继承这些已有的类。这样,现在类所描述过的属性及行为,即已定义的变量和方法,在继承产生的类中完全可以使用。被继承的类称为父类或超类,而经继承产生的类称为子类或派生类。根据继承机制,派生类继承了超类的所有成员,并相应地增加了自己的一些新的成员

面向对象程序设计中的继承机制,大大增强了程序代码的可复用性,提高了软件的开发效率,降低了程序产生错误的可能性,也为程序的修改扩充提供了便利。

若一个子类只允许继承一个父类,称为单继承;若允许继承多个父类,称为多继承。目前许多面向对象程序设计语言不支持多继承。而Java语言通过接口(interface)的方式来弥补由于Java不支持多继承而带来的子类不能享用多个父类的成员的缺憾

5.1.6 多态

多态是面向对象设计语言的基本特征。仅仅是将数据和方法捆绑在一起,进行类的封装,使用一些简单的继承,还不能算是真正应用了面向对象的设计思想。多态性是面向对象的精髓。多态性可以简单地概括为“一个接口,多种方法”。

多态性是指同名的方法可在不同的类中具有不同的运动规律。在父类演绎为子类时,类的运动规律也同样可以演绎,演绎使子类的同名运动规律或运动形式更具体,甚至子类可以有不同于父类的运动规律或运动形式。不同的子类可以演绎出不同的运动规律。如动物都会吃,而羊和狼吃的方式和内容都不一样。如:警车鸣笛,普通人反应一般,逃犯听见会大惊失色。警车鸣笛(同一种行为),导致普通人和逃犯不同反应(多种形态)。再如,画出昆虫的图片,对蚂蚁和对蜘蛛这2种昆虫画出的是不同的图片。通常是指对于同一个消息、同一种调用,在不同的场合,不同的情况下,执行不同的行为。

Java语言中含有方法重载与成员覆盖两种形式的多态。

方法重载:也叫静态多态。在一个类中,允许多个方法使用同一个名字,但方法的参数不同,完成的功能也不同。

成员覆盖:也叫动态多态。子类与父类允许具有相同的变量名称,但数据类型不同,允许具有相同的方法名称,但完成的功能不同。

5.2 类与对象

面向对象的编程思想力图使在计算机语言中对事物的描述与现实世界中该事物的本来面目尽可能地一致,类(class)对象(object)就是面向对象方法的核心概念。类是对某一类事物的描述,是抽象的、概念上的定义;对象是实际存在的该类事物的个体,因而也称实例(Instance)。可以这么说:类是对象的抽象;对象是类的实例。如图5-3就是一个说明类与对象的典型范例:

 

图5-3  类与对象的实例化说明

上图中,汽车设计图就是“类”,由这个图纸设计出来的若干的汽车就是按照该类产生的“对象”。可见,类描述了对象的属性和对象的行为,类是对象的模板。对象是类的实例,是一个实实在在的个体,一个类可以对应多个对象。可见,如果将对象比作汽车,那么类就是汽车的设计图纸,所以面向对象程序设计的重点是类的设计,而不是对象的设计。

同一个类按同种方法产生出来的多个对象,其开始的状态都是一样的,但是修改其中一个对象的时候,其他的对象是不会受到影响的,比如改装第一辆汽车的时候,其他的汽车是不会受到影响的。

5.2.1 类的声明

在使用类之前,必须先定义它,然后才可利用所定义的类来声明变量,并创建对象。类定义的语法如下:

 

下面举一个Person类的例子,来让大家清楚认识类的组成。

范例:Person.java

class Person
{
  String name;
  int age;
  
void talk()   {     System.out.println("我是:"+name+",今年:"+age+"岁");   } }

程序说明:

1、程序首先用class声明了一个名为Person的类,这里Person是类的名称。

2、在第3、4行,先声明了两个属性name和age,name为String(字符串类型)型、age为int(整型)型。

3、在第5~8行,声明了一个talk()方法,此方法用于向屏幕打印信息。

为了更好的说明类的关系,请参见图5-4。

 

图5-4  Person类图

小提示:

可以发现在本例中,声明类 Person 时,类名中单词的首字母是大写的,这是规定的一种符合标准的写法,在以后的范例中都将采用这种写法。另外,如果一个类声明为public class 则文件名称必须与类名称一致,而且在一个Java源文件中只能有一个public class。而使用 class声明一个类,则文件名称可以与类名称不一致,但是执行的时候必须执行生成的class文件名称。除了这些之外,public class和class还在包的访问上有所限制。如果一个类只在本包中访问,不需要被外包访问,则声明成class即可,而如果一个类需要被外包访问,则必须声明为public class。一般在开发中对于一个*.java文件往往都只定义一个类:public class。

5.2.2 创建新的对象

在上面的范例中,已经创建好了一个Person类,相信类的基本形式应该已经很清楚了,但是在实际中单单有类是不够的,类提供的只是一个摸板,是一种数据类型。如果不用该数据类型是定义变量(实例化对象)就没有任何的意义。必须依照它创建出对象之后才可以使用。了解了上述的概念之后,便可动手编写程序了。创建属于某类的对象,需要通过下面两个步骤来实现:

1、声明指向"由类所创建的对象"的变量。

2、利用new创建新的对象,并指派给先前所创建的变量。

下面定义了由类产生对象的基本形式:

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

举例来说,如果要创建Person类的对象,可用下列的语句来实现:

Person p; // 先声明一个Person类的对象p

p = new Person();   // 用new 关键字实例化Person的对象p

当然也可以用下面这种形式来声明变量:

  Person p = new Person();  // 声明Person对象p并直接实例化此对象

小提示:

对象只有在实例化之后才能被使用,而实例化对象的关键字就是new

关于对象实例化的过程,请参见图5-5:

 

图5-5  Person类对象的实例化过程

由图中可以看出,当语句执行到Person p的时候,只是在栈内存中声明了一个Person的对象p,但是这个时候p并没有在堆内存中开辟空间,所以这个时候的p是一个未实例化的对象,用new关键字实际上就是开辟堆内存,把堆内存的地址赋给了p,这个时候的p才称为实例化对象。由此可见:对象的引用是保存在栈内存之中,属性是保存在堆内存之中,在程序中所有的方法都是保存在全局代码区之中的,此区中的内容是所有对象共享的。

匿名对象:没有名字的对象,在Java 中如果一个对象只使用一次,则就可以将其定义成匿名对象。所谓的匿名对象就是比之前的对象少了一个栈内存的引用关系。声明匿名对象的方式与具体的构造函数有关。例如:有两个参数的构造函数的匿名对象:new Person("张三", 24);

如果要访问对象里的某个成员变量或方法时,可以通过下面语法来实现:

访问属性:对象名称.属性名

访问方法:对象名称.方法名()

例如:如果想访问Person类中的name和age属性,可以用如下方法来访问:

p.name; // 访问Person类中的name属性

p.age; // 访问Person类中的age属性

因此:若想将Person类的对象p中的属性name赋值为"张三",年龄赋值为25,则可以采用下面的写法:

 p.name = "张三";

 p.age = 25;

如果想调用Person中的talk()方法,可以采用下面这种写法:

p.talk(); // 调用Person类中的talk()方法

请看下面的完整的程序:

范例:Person.java

class Person
{
  String name;
  int age;
  
void talk()   {     System.out.println("我是:"+name+",今年:"+age+"岁");   } }

范例:TestPersonDemo.java

//下面这个范例说明了使用Person类的对象调用类中的属性与方法的过程
class TestPersonDemo
{
  public static void main(String[] args)
  {
    Person p = new Person();
    p.name = "张三";
    p.age = 25;
    p.talk();
  }
}

输出结果:

我是:张三,今年:25岁

程序说明:

1、第6行声明了一个Person类的实例对象p,并直接实例化此对象

2、第7、8行给p对象中的属性赋值

3、第9行调用talk()方法,在屏幕上输出信息

可以参照上述程序代码与图5-6的内容,即可了解到Java是如何对对象成员进行访问操作的。

 

图5-6  对Person对象p的访问操作过程

5.2.3 创建多个新对象

在上面的TestPerson.java程序中,只建立了一个Person的对象p,如果需要创建多个对象的话,如下范例所示:

范例:TestPersonDemo1.java

class Person
{
  String name;
  int age;
  
void talk()   {     System.out.println("我是:"+name+",今年:"+age+"岁");   } } public class TestPersonDemo1 {   public static void main(String[] args)   {     Person p1 = new Person();// 声明并实例化一Person对象p1     Person p2 = new Person();// 声明并实例化一Person对象p2
    
// 给p1的属性赋值     p1.name = "张三";     p1.age = 25;     // 给p2的属性赋值     p2.name = "李四";     p2.age = 30;     // 分别用p1、p2调用talk()方法     p1.talk();     p2.talk();   } }

输出结果:

我是:张三,今年:25岁

我是:李四,今年:30岁

程序说明:

1、1~9行声明了一个Person类,类中有name、age两个属性,还有一个talk()方法用于输出信息。

2、14~15声明了Person的两个实例对象p1、p2。

3、17、18行给p1对象的属性赋值。

4、20、21行给p2对象的属性赋值。

5、23、24行分别用p1、p2调用Person类中的talk()方法,用于在屏幕上打印信息。

6、在程序中声明了两个对象p1和p2,之后为p1与p2分别赋值,可以发现p1与p2赋的值互不影响,此关系可由图5-7表示出来。

 

图5-7  Person中p1与p2的内存分配图

可以发现p1与p2各自占有一块内存空间,p1、p2中各有自己的属性值,所以p1、p2不会互相影响。

5.3 类的封装性

封装是面向对象的四大特征之一。为什么要封装?封装是面向对象语言对客观世界的模拟,客观世界里的属性都是被隐藏在对象内部,外界无法直接操作和修改。实现良好的封装,需要从两个方面考虑:

1、将对象的属性和实现细节隐藏起来,不允许外部直接访问

2、把方法暴露出来,让外界能通过方法来操作或访问这些属性。

封装实际有两个方面含义:该隐藏的隐藏起来,该暴露的暴露出来。

为什么需要封装?对于程序而言,不封装有时会带来一些隐含的问题,可以先看看下面的程序:

范例:TestPersonDemo2.java

class Person
{
  String name;
  int age;

  void talk()
  {
    System.out.println("我是:"+name+",今年:"+age+"岁");
  }
}

public class TestPersonDemo2
{
  public static void main(String[] args)
  {
    Person p = new Person();// 声明并实例化一Person对象p
    p.name = "张三";// 给p中的属性赋值
    p.age = -25; // 在这里将对象p的年龄属性赋值为-25岁
    p.talk();// 调用Person类中的talk()方法
  }
}

输出结果:

我是:张三,今年:-25岁

由上面的程序可以发现,在程序的第16行,将年龄(age)赋值为-25岁,这明显是一个不合法的数据,最终程序在调用talk()方法的时候才会打印出了这种错误的信息。这就好比要加工一件产品一样,本身加工的原料就有问题,那么最终加工出来的产品也一定是一个不合格的产品。而导致这种错误的原因,就是因为程序在原料的入口出,并没有加以检验,而加工的原料原本就是变质的,这样加工出来的产品也必然是一个不合要求的产品。

之前所列举的程序都是用对象直接访问类中的属性,这在面向对象法则中是不允许的,在实际的工作中也是不可取的。所以为了避免程序中这种错误的情况的发生,在一般的开发中往往要将类中的属性封装(即加上private修饰)

如何实现封装

属性封装:private 属性类型 属性名称=初始值;

方法封装:private 方法返回值 方法名称(参数列表){//方法体}

注意:

用private声明的属性或方法只能在其类的内部被调用,而不能在类的外部被调用

范例:TestPersonDemo3_1.java

class Person
{
  private String name;
  private int age;
  
void talk()   {     System.out.println("我是:"+name+",今年:"+age+"岁");   } } public class TestPersonDemo3_1 {   public static void main(String[] args)   {     Person p = new Person();// 声明并实例化一Person对象p     p.name = "张三";// 给p中的属性赋值     p.age = -25; // 在这里将p对象中的年龄赋值为-25岁     p.talk();// 调用Person类中的talk()方法   } }

编译结果:

TestPersonDemo3.java:15: name has private access in Person

p.name = "张三" ;

^

TestPersonDemo3.java:16: age has private access in Person

p.age = -25 ;

^

2 errors

可以发现本程序与上面的范例除了在声明属性上有些区别之外,并没有其它的区别,而就是这一个小小的关键字,却可以发现程序连编译都无法通过,而所提示的错误为:属性(name、age)为私有的,所以不能由对象直接进行访问。这样就可以保证对象无法直接去访问类中的属性,但是如果非要给对象赋值的话,而这一矛盾该如何解决呢?程序设计人员一般在类的设计时,会对属性增加一些方法,如:setXxx()、getXxx()这样的公有方法来解决这一矛盾。

属性被封装之后如何访问:编写setter或者getter方法完成。

public 属性类型  get首字母大写的属性名称(){return 属性值;}

public  void  set首字母大写的属性名称(属性类型 参数){属性=参数值;}

范例:TestPersonDemo3_2.java

class Person
{
  private String name;
  private int age;

  void talk()
  {
    System.out.println("我是:"+name+",今年:"+age+"岁");
  }

  public void setName(String str)
  {
    name = str;
  }

  public void setAge(int a)
  {
    if(a > 0)
    age = a;
  }

  public String getName()
  {
    return name;
  }

  public int getAge()
  {
    return age;
  }
}

public class TestPersonDemo3_2
{
  public static void main(String[] args)
  {
    Person p = new Person();// 声明并实例化一Person对象p
    p.setName("张三");// 给p中的属性赋值
    p.setAge(-25); // 在这里将p对象中的年龄赋值为-25岁
    p.talk();// 调用Person类中的talk()方法
  }
}

输出结果:

我是:张三,今年:0岁

可以发现在本程序中,传进了一个-25的不合理的数值,这样在设置Person中属性的时候因为不满足条件而不能被设置成功,所以age的值依然为自己的默认值——0。这样在输出的时候可以发现,那些错误的数据并没有被赋到属性上去,而只输出了默认值。

由此可以发现,用private可以将属性封装起来,当然private也可以封装方法。

下面这道程序修改自上面的程序(TestPersonDemo3_2.java),在这里,将talk()方法封装了起来。

范例:TestPersonDemo4.java

class Person
{
  private String name;
  private int age;

  private void talk()
  {
    System.out.println("我是:"+name+",今年:"+age+"岁");
  }

  public void setName(String str)
  {
    name = str;
  }

  public void setAge(int a)
  {
    if(a>0)
    age = a;
  }

  public String getName()
  {
    return name;
  }

  public int getAge()
  {
    return age;
  }
}

public class TestPersonDemo4
{
  public static void main(String[] args)
  {
  Person p = new Person();// 声明并实例化一Person对象p
  p.setName("张三");// 给p中的属性赋值
  p.setAge(-25); // 在这里将p对象中的年龄赋值为-25岁
  p.talk();// 调用Person类中的talk()方法
  }
}

编译结果:

TestPersonDemo4.java:34: talk() has private access in Person

p.talk();

^

1 error

程序说明:

在程序第5行,将talk()方法用private来声明。此时在类外不能访问。

说明private也是同样可以用来声明方法的,只是该方法就只能在类的内部被访问了。

小提示:

到底什么时候需要封装,什么时候不用封装?关于封装与否并没有一个明确的规定,不过从程序设计角度来说,一般设计较好的程序的类中的属性都是需要封装的。此时,要设置或取得属性值,可以利用setXxx()、getXxx()方法集,这是一个明确且标准的规定。即“属性私有,方法公开”的原则。

主要注意的是:setter方法体里可以加上数据的相关校验。getter方法不要返回可变的引用对象,应该使用克隆。例如:

class AAA
{   
private int a;   public AAA(int a)
  {     
this.a = a;   }   public void disp()
  {     System.out.println(
this.a);   }   public void setA(int a)
  {     
this.a = a;   } } class CCC
{   
private AAA aaa;
  
public CCC()
  {     aaa
= new AAA(10);   }   public AAA fun()
  {     
return aaa;   }   public void disp()
  {     aaa.disp();   } }
public class BBB
{   
public static void main(String args[])
  {     CCC c
= new CCC();     AAA a = c.fun();     a.setA(30);     a.disp();     c.disp();   } }

从实例中可以看出,CCC类中的fun方法返回的是引用对象,而在BBB类中利用a来接收,但是对a修改之后,结果却影响到aaa了。

5.4 在类内部调用方法

通过上面的几个范例,应该可以清楚,在一个java程序中是可以通过对象去调用类中的方法的,当然类的内部也能互相调用各自的方法,比如下面的程序,在下面的程序中,修改了以前的程序代码,新增加了一个公有的say()方法,并用这个方法去调用私有的talk()方法。

范例:TestPersonDemo5.java

class Person
{
  private String name;
  private int age;
  private void talk()
  {
    System.out.println("我是:"+name+",今年:"+age+"岁");
  }

  public void say()
  {
    talk();
  }

  public void setName(String str)
  {
    name = str;
  }

  public void setAge(int a)
  {
    if(a>0)
    age = a;
  }

  public String getName()
  {
    return name;
  }

  public int getAge()
  {
    return age;
  }
}

public class TestPersonDemo5
{
  public static void main(String[] args)
  {
    Person p = new Person(); // 声明并实例化一Person对象p
    p.setName("张三"); // 给p中的属性赋值
    p.setAge(30); // 在这里将p对象中的年龄属性赋值为-25岁
    p.say(); // 调用Person类中的say()方法
  }
}

输出结果:

我是:张三,今年:30岁

程序说明:

1、程序9~12行声明一公有方法say(),此方法用于调用类内部的私有方法talk()。

2、在程序第38行调用Person类中的say()方法,其实也就是调用了Person类中的talk()方法。

注意:

这个时候say()方法调用talk()方法,如果非要强调对象本身的话,也可以写成如下形式: 

this.talk();

也许你会觉得这样写有些多余,当然this的使用方法很多,在以后的章节中会有更加完整的介绍。

5.5 引用数据类型的传递

在前面就已经提到过,java中使用引用来取代C++中的指针,那么什么是引用?java又是怎样通过引用来取代C++中指针的呢?请先看一下下面程序的代码。

范例:TestRefDemo1.java

class Person
{
  String name;
  int age;
}

public class TestRefDemo1
{
  public static void main(String[] args)
  {
    Person p1 = null;// 声明一对象p1,此对象的值为null,表示未实例化
    Person p2 = null; // 声明一对象p2,此对象的值为null,表示未实例化
    p1 = new Person();// 实例化p1对象
    p1.name = "张三";// 为p1对象中的属性赋值
    p1.age = 25;
    p2 = p1; // 将p1的引用赋给p2

    // 输出p2对象中的属性
    System.out.println("姓名:"+p2.name);
    System.out.println("年龄:"+p2.age);
    p1 = null;
  }
}

输出结果:

姓名:张三

年龄:25

程序说明:

1、1~5行声明一个Person类,有name与age两个属性。

2、程序10、11行,分别声明两个Person的对象p1和p2,但这两个对象在声明时都同时赋值为null,表示此对象未实例化。

3、程序第12行为对对象p1进行实例化。

4、13、14行分别为p1对象中的属性赋值。

5、15行,将p1的引用赋给p2,此时相当于p1与p2都同时指向同一块堆内存

6、第17、18行分别调用p2.name和p2.age输出p2对象中的属性。

7、第19行把p1对象赋值为null,表示此对象不再引用任何内存空间。

8、程序执行到第19行时,实际上p1断开了对其之前实例化对象的引用,而p2则继续指向p1原先的引用。

由程序中可以发现,在程序中并未用new关键字为对象p2实例化,而到最后依然可以用p2.name与p2.age方式输出属性的内容,而且内容与p1对象中的内容相似,也就是说在这道程序之中p2是通过p1对象实例化的,或者说p1将其自身的引用传递给了p2。可以参考图5-8:

 

图5-8  引用数据类型的传递

注意:

如果在程序最后又加了一段代码,令p2=null,则之前由p1创建的实例化对象不再有任何对象使用它,则此对象称为垃圾对象,如图5-9 所示: 

 

图5-9  垃圾对象的产生

所谓垃圾对象,就是指程序中不再使用的对象引用,关于垃圾回收的概念,在后面会有介绍。

范例:TestRefDemo2.java

class Change
{
  int x = 0;
}

public class TestRefDemo2
{
  public static void main(String[] args)
  {
    Change c = new Change();
    c.x = 20;
    fun(c);
    System.out.println("x = "+c.x);
  }

  public static void fun(Change c1)
  {
    c1.x = 25;
  }
}

输出结果:

x = 25

程序说明:

1、第1~4行,声明一名为Change的类,里面有一个属性x。

2、程序第9行实例化了一个Change类的对象c。

3、程序第10行将对象c中的x属性赋值为20。

4、程序第11行调用fun()方法,将对象c传递到方法之中。

5、14~17行声明这个fun方法,接收参数类型为Change类型。

6、第16行将对象c1中的x属性赋值为25。

可以发现程序最后的输出结果为“x = 25”,而程序只有在fun()方法中,才将x的值赋为25,为什么方法完成之后值还依然被保留下来了呢?在程序第14行,fun()方法接收的参数是Change c1,也就是说fun()方法接收的是一个对象的引用。所以在fun方法中所做的操作,是会影响原先的参数,可以参考图5-10,来了解整个过程:

 

图5-10  操作过程

从图中可以发现,最开始声明的对象c将其内部的x赋值为20(图A),之后调用fun()方法,将对象赋值给c1,c1再继续修改x的值,此时c1与c同时指向同一内存空间,所以c1操作了x也就相当于c操作了x,所以fun()方法执行完毕之后,x的值为25。所以,在方法调用的过程中,如果想要形参的改变能够影响实参的变化,在Java中需要利用引用数据类型

感谢阅读。如果感觉此章对您有帮助,却又不想白瞟

                                 

原文地址:https://www.cnblogs.com/springl/p/8963082.html