类与对象
面向对象的想法是模拟现实世界。
把某样东西的状态数据和它的行为封装起来,从而达到易用、重用、隐藏内部状态的目的。
一般的变量,如: int a, double b 等,
我们定义它之后,它就是一个被动的数据,等特着其它代码来操作它。
而对象,不仅含有数据,还有在数据上操作的方法,这样,数据本身就可能隐藏起来了。
外界只需要调用对象的方法来达到操控对象的目的。
这对外界而言,增加了易用性,因为这些专门针对该对象的方法,进行了精心设计,可以避免复杂度,避免一些误用。
对对象而言,因为不直接操作它的数据,就达到了保护内部数据的目的,增加了安全性。
这样看来,相比于普通的变量,对象就好比是更智能化的数据。
对象是一种复合类型,或说复杂类型。
因为对象中,可能会包含多个数据,以刻画对象当前所处的状态,又包含多个方法,描述对象可以做出的行为。
定义一个普通的数据, int a = 100; 就好了,这个类型很简单。
如果还需要第 2 个, int b = ... 也就搞定。
但如果直接定义一个对象,无论怎么设计语法,要给出多个数据,多个方法,都肯定会冗长。
刚写好,然后老板又要再定义 10 个这样的对象,那就要悲剧了。
为避免重复与冗长,一个很自然的想法,是先定义一自己的新“类型”,地位相当于 int,
在这个类型中,把对象应该包含的数据、方法一次性描述清楚。
然后用这个新的类型再去定义对象,不就很清晰自然了吗?
这个新类型,就是我们所说的----类
如下代码定义了平面上的"点",它包含 x, y 坐标,还有方法,比如:到原点的距离, distance。
1 class MyPoint 2 { 3 private double x; 4 private double y; 5 6 public double distance(){ 7 return Math.sqrt(x * x + y * y); 8 } 9 public void set(double x1, double y1){ 10 x = x1; 11 y = y1; 12 } 13 public double getx(){ 14 return x; 15 } 16 public double gety(){ 17 return y; 18 } 19 }
这段代码中对 MyPoint 的描述比较简陋,只有 2 个数据,3 个方法。
数据前边以 private修饰,表示外界不能直接存取这些数据,达到保护的目的。
但这样一来,总得给外界提供某种能够访问 x, y 的方式,否则这些数据就没什么价值了。
所以才有下面的 set, getx, gety 方法,它们负责对 x y 设定新值,以及读取 x y 的值。
下面看看怎样使用 MyPoint 这个新类型。
1 public class A0411 2 { 3 public static void main(String[] args){ 4 MyPoint p1 = new MyPoint(); 5 p1.set(10,20); 6 System.out.println(p1.distance()); 7 System.out.println(p1.getx()); 8 System.out.println(p1.gety()); 9 } 10 }
你可能会觉得,这么绕着弯去操作 x, y 到底有什么好处呢,看起来还不如直接定义 x, y 更舒服呢。
的确,面向对象在效率上确实有些陨失,但换来了安全性。当软件变大,变复杂后,安全性、隔离性就很重要了。
对这么个简单情况,当然看不出什么优势来。
如果一定要看优势,这样想吧,比如我们需要对 x y 的值进行限幅操作,x y 的值被限定在 -1000 到 1000 之间,
超出的,取范围内与设定最接近的值。
此时,我们只需要对 set 方法进行修改就可以了,不需要更动其它代码,尤其是不需要惊动使用 MyPoint 的代码,这是个很大的优势,
因为在大型项目中,MyPoint 和使用 MyPoint 的代码很可能是两个不同的人,甚至是两个不同的团队写出来的。
public void set(double x1, double y1){ if(x1 < -1000) x1 = -1000; if(x1 > 1000) x1 = 1000; if(y1 < -1000) y1 = -1000; if(y1 > 1000) y1 = 1000; x = x1; y = y1; }
这也正是“封装”的含义。
外界只能调用方法,提供必要的参数,而无权干涉对象内部如何处理。
更理论化地说,外界只能向对象发出请求,至于对象如何响应这个请求,外界无法左右。
外界: 求求你,快把你的坐标改为 1200,900 吧。
对象: 才不呢! 1200超范围了,我就设为 1000,900 吧,爱咋地咋地。
构造方法
很多时候,我们希望对象刚刚创建以后就持有正确的状态,而不是每次都很繁琐地去初始化它的状态。
比如,MyPoint,可能由于某种特殊的要求,每次对象默认的初始位置在(100,100)。
这可以由一个特殊的方法---构造方法来实现。
1 class MyPoint 2 { 3 private double x; 4 private double y; 5 6 public MyPoint(){ 7 x = 100; 8 y = 100; 9 } 10 11 public double distance(){ 12 return Math.sqrt(x * x + y * y); 13 } 14 public void set(double x1, double y1){ 15 if(x1 < -1000) x1 = -1000; 16 if(x1 > 1000) x1 = 1000; 17 if(y1 < -1000) y1 = -1000; 18 if(y1 > 1000) y1 = 1000; 19 x = x1; 20 y = y1; 21 } 22 public double getx(){ 23 return x; 24 } 25 public double gety(){ 26 return y; 27 } 28 } 29 30 public class A0416 31 { 32 public static void main(String[] args){ 33 MyPoint p1 = new MyPoint(); 34 System.out.println(p1.getx() + ", " + p1.gety()); 35 System.out.println(p1.distance()); 36 } 37 }
我们可以注意到构造方法与普通方法的不同之处。
首先是名字,构造方法与类同名。
然后是调用,构造方法被自动调用了。每次创建对象的时候都会自动地调用构造方法。
还有就是,在对象的整个生存期间,构造方法只会被调用一次。
当然,像普通方法一样,构造方法也可以有参数。这些参数在创建对象的时候传入。
1 class MyPoint 2 { 3 private double x; 4 private double y; 5 6 public MyPoint(){ 7 x = 100; 8 y = 100; 9 } 10 11 public MyPoint(int x1, int y1){ 12 this(); 13 set(x1,y1); 14 } 15 16 public double distance(){ 17 return Math.sqrt(x * x + y * y); 18 } 19 public void set(double x1, double y1){24 if(x>=-1000 && x<=1000) x = x1; 25 if(y>=-1000 && y<=1000) y = y1; 26 } 27 public double getx(){ 28 return x; 29 } 30 public double gety(){ 31 return y; 32 } 33 } 34 35 public class A0416 36 { 37 public static void main(String[] args){ 38 MyPoint p1 = new MyPoint(10,20); 39 System.out.println(p1.getx() + ", " + p1.gety()); 40 System.out.println(p1.distance()); 41 } 42 }
这里,新增加了一个含有参数的构造方法,与原来的没有参数的构造方法并存。
这种不同的方法具有相同的名字的现象,叫“方法重载”。编译器根据你传入的参数的个数和类型可以很容易区分出你想调用的是哪个方法,
实际上,在编译器看来,这两个方法与不同名字的方法根本就没什么大不同。
还有一个奇怪的语法, this(),这是去调用了不含参数的那个构造方法。
之所以这样做,是因为 set 方法改变了策略,它对不符合要求的参数,直接忽略了。
为了防止对象刚创建的时候传入了不好的参数,我们先用默认的构造方法保个底,再去试着设置新的值。
那为什么不写 MyPoint(); 而是搞这么个怪模样的 this() 呢?
个中苦衷外人不知。
原来系统对构造方法特殊对待,要保证它不会随便被调用,怎么保证?根本不让你写出 MyPoint() 这样的语句来,会直接编译错。
当然在一个构造方法中调用另一个构造方法应该允许,毕竟这个构造方法自身也保证了不会被调用多次,
这样一来就需要网开一面,需要弄出个新的语法来。
那为什么选个 this,有什么典故不?
有,这是后面要讲的 this 引用,一言难尽,暂按下不表。
对象与对象引用
类与对象的关系就好比“图纸”与“房屋”的关系。
类描述了对象的构成,对象的行为。但毕竟它只是“描述”,如果不创建对象,还都只是构想。
类是创建对象所遵照的“施工图纸”。
由一个类,我们可以创建任意多的对象。
最常见的创建对象的手段是 new 关键字。每次new 都创建一个对象。
创建对象后会在这个对象上,立即调用构造方法,来初始化对象的内部状态。
为了更精确地理解对象的行为,还是要区分两个概念:对象和对它的引用。
在java中,我们永远都无法直接接触到对象本身,我们所持有的所有变量,都只可能是对象的引用。
引用的本质就是c 语言中的指针概念。但它要更容易使用和管理,当然功能会弱一些。
c 语言中的对指针的许多操作,在 java 中是禁止的。比如: p++ 这样的常见操作。
MyPoint p = new MyPoint();
这句话中,p 实质是引用,并不是对象本身。
我们可以通过p 访问对象的方法,或访问它的数据(如果其数据定义为 public的话)
也可以把p 值赋值给另一个变量。
MyPoint q = p;
注意,些时,Mypoint 对象只有一个,q 只是个指针,因为赋值,使得p, q 都指向了同一个对象。