本文是《Java学习指南》原书的网络版,作者邵发,拥有本书的全部权利。相关视频课程在此查看。
目录
第8章 当前对象
8.1 当前对象 this
引例:用Screen表示一个屏幕,width, height表示宽和高。要求写一个方法pixel() 计算它的像素数。
( 像素计算公式: p = width * height )
按我们现在的所学,可以新建一个Screen类,
public class Screen
{
public int width;
public int height;
public int pixel ( int w, int h )
{
int result = w * h;
return result;
}
}
然后再main()方法里按如下的方式调用,
Screen s = new Screen();
s.width = 1366;
s.height = 768;
int p = s.pixel( s.width, s.height );
System.out.println("像素数: " + p );
这样是可以实现需求的,但是也存在一个显示的问题:前面已经设置了s的属性s.width和s.height,后面在调用s.pixel()方法的时候却又传递的一遍。
这就好比,Screen类当于我们编写的一个工具,明明已经给它设好了宽高,在让它计算像素的时候,却又还要我们传递一遍。显然,这么设计的工具不能算是好用的。
下面,我们可以做一个小改进,
public class Screen
{
public int width;
public int height;
public int pixel ( Screen that ) // 此处改进
{
int result = that.width * that.height;
return result;
}
}
再按如下方式调用,
Screen s = new Screen();
s.width = 1366;
s.height = 768;
int p = s.pixel( s ); // 此处改进
System.out.println("像素数: " + p );
经此改进之后,在调用s.pixel() 时,只需要对s对象本身传递过去就行了。
虽然如此,但是像s.pixel( s ) 的调用仍然显得有点奇怪:为什么调用调用对象s的方法要传递它自己作为参数呢?
8.1.1 this参数
在Java的方法里,默认传递了一个this参数,该参数指向自身对象。例如,
在这里,pixel() 看起来并未带任何参数,但实际上有一个隐含的参数this,指向当前对象。
那么,什么是当前对象呢?
当在外面调用s.pixel() 时,s即为当前对象。在进入pixel()方法,this所指向的对象,就是当前对象。
8.1.2 调用自己的方法
使用this不仅可以访问当前对象的属性,也可以调用当前对象的方法。通过下面的例子来演示。
要求输出m,n之间所有的质数,例如,求400~500之间所有的质数。
所谓质数,是指只能被1和自身整除的,如2,3,5,7,11。
观察以下代码,
public class MyMath
{
// 判断n是否为质数; true,是质数; false, 不是质数
public boolean isPrime( int n )
{
for(int i=2; i<n ; i++)
{
if( n % i == 0)
{
return false;
}
}
return true;
}
// 输出m,n之间所有的质数
public void showPrimes (int m, int n)
{
for(int i=m; i<=n; i++)
{
if( this.isPrime( i )) //
{
System.out.println("质数: " + i );
}
}
}
}
在此例中,在showPrimes() 方法里,通过this.isPrime() 调用当前对象的方法。
这说明,通过this可以访问当前对象的属性和方法。
8.2 省略与重名
8.2.1 省略this
使用this可以访问当前对象的属性和方法,一般情况下,this是可以省略的。
例如,
public class Example
{
public int number = 10;
public void showNumber()
{
System.out.println("当前值: " + this.number);
}
}
在showNumber()方向当前对象的属性,可以用this.number。为了简化书写,此处的this可省略,形如,
初学者应坚持使用this,再熟练使用this之后才开始省略this的写法。
8.2.2 重名
再看看以下的例子,
1 2 3 4 5 6 7 8 9 | public class Example { public int number = 10; public void test() { int number = 12; System.out.println("值为:" + number); } } |
在此例中,
第3行,定义了一个属性 number,值为10
第6行,定义了一个变量 number,值为12
第7行,输出number的值。问题是,这个到底是第3行的number还是第6行的number ?
在 Java里,把定义在方法中的变量,称为局部变量。所以,第6行定义的number就是一个局部变量。
当局部变量与类的属性重名时,局部变量的优先显示。所以,第7行中的number指的是局部变量。此例将输出的值为12。
在重名的情况下,如果要访问类的属性,则必须指定this前缀,不能省略。
以后,我们会经常看来类似如下的写法,
1 2 3 4 5 6 7 8 | public class Example { public int number = 10; public void setNumber (int number) { this.number = number; } } |
第4行,在参数列表里定义的变量也叫局部变量
第6行,左侧的this.number指的是属性,右侧的number指的是参数里的局部变量。
8.3 类的设计示范
下面通过几个例子,来演示讲解如何按面象对象的思路来设计一个类。
8.3.1 示例1
有一个换游戏币的机器。可以投1元、5元、10元的人民币。最后按一下出货按钮,可以吐出游戏币。
(每个游戏币=1元人民币)
首先,创建一个类Machine来表示这种机器,
public class Machine
{
}
然后,这种机器可以投入现金,所以添加一个方法用于投入现金,
public class Machine
{
// 人民币:1,5,10
public void insertCash ( int cash )
{
}
}
当把现金投进去后,应该有一个属性记录一共投了多少现金,
public class Machine
{
public int money = 0; // 机器里投入了多少钱
public void insertCash ( int cash )
{
}
}
现在来完成insertCash() 方法,每投入一些现金后,应该把现金数加到余额上,
public void insertCash ( int cash )
{
this.money += cash;
System.out.println("当前余额: " + this.money);
}
现在,该完成交易了,添加exchange() 方法,
public class Machine
{
public int money = 0;
public void insertCash ( int cash )
{
... 篇幅原因, 省略此处代码 ..
}
public int exchange ()
{
int numOfCoin = this.money / 1;
this.money = 0;
System.out.println("交易完成, 当前余额: " + this.money);
return numOfCoin;
}
}
最后看看怎么调用这个类,
Machine m = new Machine();
m.insertCash( 5 );
m.insertCash( 10);
m.insertCash( 50);
int coins = m.exchange(); // 按一下按钮
System.out.println("拿到了" + coins + "个游戏币");
这便是一个最简单的面向对象的设计。
可以发现,类的属性是由于存储数据的,而类的方法则是用于计算处理的。类就是属性和方法的综合体。
8.3.2 示例2
有两个数组
int[] a1 = { 123, 38, 103, 89 };
int[] b1 = { 34, 8, 11, 29 };
求这两个数组中的所有的质数。
首先,设计一个类PrimeFilter。
添加一个put () 方法,将需要处理的数组传给它,内部检出所有的质数,存在属性result里。
添加一个value()方法,用于取出最后的结果。
示例代码如下,
public class PrimeFilter
{
// 存储
public int[] result = new int[512];
public int total= 0;
// 用户输入: 数组data
// 把data数组里面,所有的质数都放到result
public void put ( int[] data)
{
for (int i=0; i<data.length; i++)
{
if ( this.isPrime( data[i] ))
{
this.result[total] = data[i];
this.total += 1;
}
}
}
// 取出最终过滤得到 所有的质数
public int[] values()
{
int[] r = new int[total];
for(int i=0; i< this.total; i++)
{
r[i] = this.result[i];
}
return r;
}
// 判断n是否为质数; true,是质数; false, 不是质数
public boolean isPrime( int n )
{
for(int i=2; i<n ; i++)
{
if( n % i == 0)
{
return false;
}
}
return true;
}
}
再来看一下怎么调用,
PrimeFilter filter = new PrimeFilter();
int[] a1 = { 123, 38, 103, 89 };
int[] b1 = { 34, 8, 11, 29 };
filter.put (a1);
filter.put (b1);
int[] numbers = filter.values();
小结一下,所谓的对象可以视为属性和方法的综合体,即:
对象 = 属性 + 方法
其中,属性就是数据,方法就是算法。创建一个对象、给它所需的数据、让它干活、取出结果,这就是一般的面向对象的设计方法。
8.4 特殊形式的属性
下面,再了解一种相对来说有点奇怪的写法。用Human表示人类,每个人类都应该有名字(name),有一个配偶(mate)。用代码表示如下,
public class Human
{
public String name; // 名字
public Human mate; // 配偶 (指向另外一个Human对象)
}
对于初学者来说,这种写法可能会有点奇怪:一个属性的类型可以是它的同类?
如果觉得奇怪的话,我们可以大胆的改一下,改成我们熟悉的形式,
public class Human
{
public String name;
public Monkey mate; // Monkey, 猴子
}
如此改动,在字面形式上我们会觉得很熟悉,对吧?但是,从逻辑上理解却是不合常理的,一个人类(Human)的配偶(mate)竟然是一个猴子?
一个Human的mate属性是另一个Human对象,才是符合逻辑的,也是非常自然的。虽然语法形式上大家会觉得有点生疏,但看多了就习惯了。
下面,再添加一个方法merryWith(),表示与某个结婚,示例如下,
public class Human
{
public String name; // 名字
public Human mate; // 配偶 (指向另外一个Human对象)
// 与另一人结婚
public void merryWith ( Human someone)
{
this.mate = someone; // 我的伴侣是Ta
someone.mate = this; // Ta的伴侣是我
}
// 自我介绍
public void introduce()
{
System.out.println("我叫" + this.name + ", 我的爱人叫" + mate.name);
}
}
在merryWith()的时候,需要指定另一个人类对象someone作为参数。同样的,如果不喜欢的话可以换成一只猴子!
下面再来看一下如何使用这个Human类,示例如下,
public class HelloWorld
{
public static void main(String[] args)
{
Human a = new Human(); // 小张
a.name = "张";
Human b = new Human("王"); // 小王
b.name = "王";
a.merryWith( b ); // a与b结婚
a.introduce(); // a自我介绍
b.introduce(); // b自我介绍
}
}
显然,要谈婚论嫁,必需得有两个人(a 和 b ),然后再调用a.merryWith(b) 使之结为夫妻。结论之后,再自我介绍(introduce)的时候就得把爱人的名字( mate.name) 也报一下了!