Java基础(三)对象与类
1.类的概念:类是构造对象的模板或蓝图。由类构造对象的过程称为创建类的实例。
2.封装的概念:封装(有时称为数据隐藏)是与对象有关的一个重要概念。对象中的数据称为实例域,操纵数据的过程称为方法。对于每个特定的类实例(对象)都有一组特定的实例域值。这些值的几个就是这个对象的当前状态。无论何时,只要向对象发送一个消息,它的状态就有可能发生改变。实现封装的关键在于绝对不能让类中的方法直接地访问其他类的实例域。程序仅通过对象的方法与对象数据进行交互。封装给对象赋予了“黑盒”特征,这是提高重用性和可靠性的关键。这意味着一个类可以全面地改变存储数据的方式,只要仍旧使用同样的方法操作数据,其他对象就不会知道或介意所发生的变化。
3.对象的三个主要特性:
- (1)行为--可以对对象施加哪些操作,或可以对对象施加哪些方法
- (2)状态--当施加那些方法时,对象如何响应
- (3)标识--如何辨别具有相同行为与状态的不同对象
4.类之间的关系:
- 依赖(uses-a):如果一个类的方法操纵另一个类的对象,就说一个类依赖于另一个类。
- 聚合(has-a):聚合关系意味着类A的对象包含类B的对象。
- 继承(is-a):如果类A扩展类B,类
5.封装的优点
如果需要获得或设置实例域的值,应该提供下面三项内容:
- 一个私有(private)的数据域
- 一个公有的(public)域访问器方法(get())
- 一个公有的(public)域更改器方法(set())
这样做的好处是,私有的数据域一旦在构造器中设置完毕,就只能通过公有的域访问器方法对这个私有域进行访问,同时也只能通过公有的域更改器对这个私有域进行修改。一旦这个域值出现了错误,只用调试这个方法就可以了,如果域是public的,破坏这个域的捣乱者可能会出现在任何地方。
6.私有方法
如果希望将一个计算代码划分成若干个独立的辅助部分,通常,这些辅助方法不应该成为公有接口的一部分,这是因为它们往往与当前的实现机制非常紧密,或者需要一个特别的协议以及一个特别的调用次序。最好将这样的方法设计为private的,因为private方法只能当前类的方法调用,而外部类无法调用。
对于私有方法,如果改用其他方法实现相应的操作,则不必保留原有的方法。只要方法是私有的,类的设计者就可以确信:它不会被外部的其他类操作调用,可以将其删去。如果方法是公有的,就不能将其删去,因为其他的代码很可能依赖它。
7.final实例域
将实例域定义为final时,在构造对象时必须初始化这样的域。也就是说,必须确保在每一个构造器执行之后,这个域的值被设置,并且在后面的操作中,不能够再对它进行修改。
8.静态域
如果将域定义为static,每个类中只有一个这样的域,而每一个对象对于所有的实例域却都有自己的一份拷贝。
class Employee{
private static int nextId = 1; private int id; }
例如,每一个雇员对象都有自己的id域,但这个类的所有实例将共享一个nextId域。也就是说,如果有1000个Employee类的对象,则有1000个实例域id。但是,只有一个静态域nextId。即使没有一个雇员对象,静态域nextId也存在。它属于类,而不属于任何独立的对象。
9.静态常量
public class Math{ public static final double PI = 3.14159265358979323846; ... }
如果关键字static被省略,PI就变成了Math类的一个实例域。需要通过Math类的对象访问PI。并且每一个Math对象都有它自己的一份拷贝。如果使用了static,PI就属于Math类,也就是说,任何调用Math类中PI的值都是唯一的,只有一个。也不用通过Math类的对象才能访问。同时,前面提到过,最好将实例域设计为private,但是公有常量(final域)设置为public却没有问题,因为PI被声明为final为不可变,所以,不允许再将其他值赋给PI。
10.静态方法
静态方法是一种不能向对象实施操作的方法。使用静态方法时不需要任何类的对象。
public static int getNextId(){ return nextId; }
Employee类的静态方法不能访问Id实例域,因为它不能操作对象。但是,静态方法可以访问自身类中的静态域。
可以通过类名调用这个方法。如果省略了static,那么需要通过Employee类对象的引用调用这个方法。
int n = Employee.getnextId();
在下面的两种情况下使用静态方法:
- 一个方法不需要访问对象状态,其所需参数都是通过显示参数提供的。(例如,Math.pow(x,a))
- 一个方法只需要访问类的静态域(例如,Employee.getnextId())
11.静态代码块
29 // 静态初始化块 30 static 31 { 32 Random generator = new Random(); 33 // 设置nextId为0到9999的随机数 34 nextId = generator.nextInt(10000); 35 }
在类的构造方法之前,如果对类的静态域进行初始化的代码比较复杂,那么可以使用静态的初始化块。将初始化代码放在一个块中,并标记关键字static。
这样,在类第一次加载的时候,将会对静态域进行初始化。与实例域一样,所有的静态初始化代码块都将依照类定义的顺序执行。
12.包作用域
- 标记为public的类、方法或常量可以被任意的类使用。
- 标记为private的类、方法或常量只能被定义它们的类使用。
- 如果没有指定public或private,这个类、方法或常量可以被同一个包中所有方法访问。
13.类设计技巧
- 一定要保证数据私有
绝对不要破坏封装性。有时候需要编写一个访问器或修改器方法,但是最好还是保持实例域的私有性。数据的表示形式很可能会改变,但它们的使用方式却不会经常发生变化。当数据保持私有时,它们的表示形式的变化不会对类的使用者产生影响,即使出现bug也易于检测。
- 一定要对数据初始化
最好不要依赖于系统的默认值,而是应该显示地初始化所有的数据。具体的初始化方式可以是提供默认值,也可以是在所有构造器中设置默认值。
- 不要在类中使用过多的基本类型
即用其他的类代替多个相关的基本类型的使用,这样会使类更加易于理解且易于修改。
例如:Customer类中包括很多实例域,其中有实例域street,city和state,可以将这三个实例域拿出来用一个Address的新的类替换,从而可以减少Customer类中的实例域个数。
- 不是所有的域都需要独立的域访问器和域修改器
如果有一些不希望别人访问或者修改的域,就不要设置域访问器或域修改器。要根据实际情况和实际需求来设置。
- 将职责过多的类进行分解
一种情况是将实现很多功能的类分解成几个不同的类,这些类分担原来的类的所有的职责。
- 类名和方法名要能够体现它们的职责
- 优先使用不可变的类
更改对象的问题在于,如果多个线程试图同时更新一个对象,就会发生并发更改。其结果是不可预料的。如果类是不可变的,就可以安全地在多个线程间共享其对象。