码迷,mamicode.com
首页 > 编程语言 > 详细

JAVA面向对象 对象/引用

时间:2016-07-19 11:02:04      阅读:228      评论:0      收藏:0      [点我收藏+]

标签:

本页面更新日期: 2016年07月17日

对象/引用

在前面 PersonTest.java 代码中, 有这样一行代码: Person p = new Person();
这行代码创建了一个 Person 实例, 也被称为 Person对象,这个Person对象被赋给 p 变量.

这行代码实际产生了两个东西: 一个是 p 变量, 一个是 Person 对象.

从 Person 类定义来看, Person 对象应包含两个实例变量, 而变量时需要内存来存储的.
因此, 当创建 Person 对象时, 有对应的内存来存储 Person 对象的实例变量.
画个图给你看看. (Person对象在内存中的存储示意图)

技术分享

从上图可以看出, Person 对象由多块内存组成, 不同内存块分别存储了 Person 对象的不同成员变量.
当把这个 Person 对象复制给一个 引用变量 时, 系统如何处理呢?
难道系统会把这个 Person 对象在内存里重新复制一份嘛? 当然不会, Java才没有那么笨.
Java 让引用变量指向这个对象即可.
也就是说, 引用变量里存放的仅仅是一个引用, 它指向实际的对象.

与之前介绍的数组类型类似, 类也是一种引用数据类型.
因此程序中定义的 Person 类型的变量实际上时一个引用, 它被存放在栈内存里, 指向实际的 Person 对象.
而真正的 Person 对象则存放在 堆内存中.
再画个图给你看, 显示 Person 对象赋值给一个引用变量的示意图.

技术分享

栈内存里的引用变量并未真正存储对象的成员变量.
对象的成员变量数据实际存放在堆内存里.
而引用变量只是指向该堆内存里的对象.

当一个对象被创建出来以后, 这个对象将保存在堆内存中.
Java程序不允许直接访问堆内存中的对象
只能通过该对象的引用来操作该对象.
也就是说, 不管时数组还是对象, 都只能通过引用来访问它们.

堆内存里的对象可以有多个引用.
即多个引用变量可以指向同一个对象. 例如下面代码:

//将 p 变量的值赋给 p2 变量
Person p2 = p;

上面这行代码把 p 变量的值赋给 p2 变量.
也就是将 p 变量保存的地址赋给 p2 变量.
这样 p2 变量 和 p 变量 将指向堆内存里同一个 Person 对象. 可以理解吧?
这样的话, 不管访问 p2 变量的成员变量和方法, 还是访问 p 变量的成员变量和方法.
它们实际上时访问同一个 Person 对象的成员变量和方法, 将会返回相同的访问结果.

小知识: 如果堆内存里的对象没有任何变量指向该对象, 那么程序将无法在访问该对象, 这个对象也就变成了垃圾, Java的垃圾回收机制会将该对象回收.
并且释放其所占用的内存.

因此, 如果希望让一个对象被回收, 只需要切断该对象的所有引用变量和它之间的关系即可. 也就是把这些引用变量的值赋为: null

对象的 this 引用

Java提供了一个 this 关键字, this 关键字总是指向调用该方法的对象.
根据this 出现位置的不同, this 作为对象的默认引用有两种情形.

  • 构造器中引用该构造器正在初始化的对象.
  • 在方法中引用调用该方法的对象.

this 关键字最大的作用就是让类中的一个方法, 访问该类里的另一个方法或实例变量.
我们假设定义了一个 Dog 类, 这个 Dog 对象的 run() 方法需要调用它的 jump() 方法.
那么应该如何做?
是否应该这样做?

public class Dog
{
    //定义 jump() 方法
    public void jump()
    {
        System.out.println("狗狗要跳跳");
    }
    //定义 run() 方法, run() 方法需要借助 jump()方法
    public void run()
    {
        Dog d = new Dog();
        d.jump();
        System.out.println("狗狗跑啊跑");
    }
}

使用这种方式来定义这个 Dog 类, 确实可以实现 run() 方法中调用 jump() 方法.
下面提供一个程序来创建 Dog 对象, 并调用该对象的 run() 方法

public class DogTest
{
    public static void main(String[] args)
    {
        //创建Dog对象
        Dog dog = new Dog();
        //调用Dog对象的 run() 方法
        dog.run();
    }
}

在上面程序中, 一共产生了两个 Dog 对象.
在 Dog 类的 run() 方法中, 程序创建了一个 Dog 对象.
并使名为 d 的引用变量来指向该 Dog 对象.

在 DogTest 的main() 方法中, 程序再次创建了一个 Dog 对象.
并使名为 dog 的引用变量来指向该 Dog 对象.

这里产生了两个问题:

  • 在run() 方法中调用 jump() 方法时是否一定需要一个 Dog 对象?
  • 是否一定需要重新创建一个 Dog 对象?

第一个问题的答案是肯定的, 因为没有使用 static 修饰的成员变量和方法 都必需使用对象来调用.
第二个问题的答案时否定的. 因为当程序调用 run() 方法时, 一定会提供一个 Dog 对象.
这样就可以直接使用这个已经存在的 Dog 对象, 无需重新创建 Dog 对象.

因此, 需要在 run() 方法中获得调用该方法的对象,通过 this 关键字就可以满足这个需求.
this可以代表任何对象, 当this出现在某个方法体中时, 它所代表的对象是不确定的.
但它的类型是确定的, 它所代表的对象只能是当前类.
只有当这个方法被调用是, 它所代表的对象才被确定下来.
谁在调用这个方法, this 就代表谁.

所以, 将前面的 Dog 类的 run() 方法改为如下形式更合适:

//定义一个 run() 方法, run() 方法需要借助 jump() 方法
public void run()
{
    //使用 this 引用调用run() 方法的对象
    this.jump();
    System.out.println("狗狗跑啊跑");
}   

上面的代码更好, 当一个 Dog 对象调用 run() 方法时, run() 方法需要依赖它自己的 jump() 方法.

在现实中, 对象的一个方法依赖于另一个方法的情形很常见:

  • 吃饭方法依赖于拿筷子方法
  • 写程序方法依赖于敲键盘方法

这种依赖都是同一个对象两个方法之间的依赖.
因此, Java允许对象的一个成员直接调用另一个成员. 可以省略 this 前缀.
也就是说, 将上面的 run() 方法改为如下形式也完全正确.


public void run()
{
    jump();
    System.out.println("狗狗跑啊跑");
}

大部分时候,一个方法访问该类中定义的其它方法/成员变量时不加 this 前缀的效果是完全一样的.

对于 static 修饰的方法而言, 则可以使用类来直接调用该方法.
如果在 static 修饰的方法中使用 this 关键字, 则这个关键字就无法指向合适的对象.
所以, static 修饰的方法中不能使用this引用.
由于 static 修饰的方法不能使用 this 引用, 所以 static 修饰的方法不能访问不使用 static 修饰的普通成员.
因此, Java 语法规定: 静态成员不能直接访问非静态成员.

下面写个代码演示一下这种错误用法:

public class StaticAccessNonStatic
{
    public void info()
    {
        System.out.println("简单的info方法");
    }
    public static void main(String[] args)
    {
        //因为 main() 方法是静态方法, 而 info() 是非静态方法
        //调用 main() 方法的是类本身, 而不是类的实例
        //因此省略的 this 无法指向有效的对象(你加上 this 也不行)
        info();
    }
}

上面的错误因为 info() 方法是属于 实例的方法. 而不是属于类的方法.
因此必需使用对象来调用该方法.
在上面的 main() 方法中直接调用 info() 方法时, 系统相当于使用 this 作为该方法的调用者.
而 main() 方法是一个 static 修饰的方法, static 修饰的方法属于类, 而不属于对象.
因此调用 static 修饰的方法的主调总是类本身.
如果允许在 static 修饰的方法中出现 this 引用, 那将导致 this 无法引用有效的对象, 因此上面的程序出现编译错误.
如果你混淆了这个概念, 最简单的解决方法就是你以后写程序, static 修饰的成员你都用类来调用, 不要用实例来调用. 你能懂不?

如果你确实需要在静态方法中访问一个非静态方法.
则只能重新创建一个对象.
例如上面的 info() 调用改为如下形式:

new StaticAccessNonStatic().info();

大部分时候, 普通方法访问其他方法/成员变量时 无需使用 this 前缀.
但如果方法里有个 局部变量和成员变量重名
程序又需要在该方法里访问这个被覆盖的成员变量, 则必需使用 this 前缀.
关于局部变量覆盖成员变量这个知识, 以后会说.

除此之外, this 引用也可以用于构造器中作为默认引用.
由于构造器是直接使用 new 关键字来调用, 而不是使用对象来调用.
所以 this 在构造器中代表该构造器正在初始化的对象.

public class ThisInConstructor
{
    //定义一个名为 foo 的成员变量
    public int foo;

    public ThisInConstructor()
    {
        //在构造器里定义一个 foo 变量
        int foo = 0;
        //使用 this 代表该构造器正在初始化的对象
        //下面代码将会把该构造器正在初始化的对象的 foo 成员变量设为 6
        this.foo = 6;
    }

    public static void main(String[] args)
    {
        //所有使用 ThisInConstructor 创建的对象的 foo 成员变量
        //都将被设为6, 所以下面代码将输出 6
        System.out.println(new ThisInConstructor().foo);
    }
}

在 ThisInConstructor 构造器中使用 this 引用时, this 总是引用该构造器正在初始化的对象.
this.foo = 6 这句话将正在执行初始化的 ThisInConstructor 对象的 foo 成员变量设为6.
这意味着该构造器返回的所有对象的 foo 成员变量都等于 6

与普通方法类似的是, 大部分时候, 在构造器中访问其他成员变量和方法时都可以省略 this 前缀.
但如果构造器中有一个与成员变量同名的局部变量, 又必需在构造器中访问这个被覆盖的成员变量, 则必需使用 this 前缀.

当 this 作为对象的默认引用使用时, 程序可以像访问普通引用变量一样来访问这个 this 引用.
甚至可以把 this 当成普通方法的返回值.
写个程序看看

public class ReturnThis
{
    public int age;
    public ReturnThis grow()
    {
        age++;
        //return this 返回调用该方法的对象
        return this;
    }
    public static void main(String[] args)
    {
        ReturnThis rt = new ReturnThis();
        //可以连续调用同一个方法
        rt.grow()
            .grow()
            .grow();
        System.out.println("rt 的age成员变量的值是:" + rt.age);
    }
}

从上面的程序可以看出, 如果在某个方法中把 this 作为返回值. 则可以多次连续调用同一个方法.
从而使代码更加简洁.
但是, 这种把 this 作为返回值的方法可能造成实际意义的模糊, 例如上面的 grow 方法, 用于表示对象的生长, 即 age 成员变量的值加 1 . 实际上不应该有返回值.

JAVA面向对象 对象/引用

标签:

原文地址:http://blog.csdn.net/tmdlife/article/details/51933335

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!