成员对象:当一个类的成员是另一个类的对象时,这个对象就叫成员对象。概括的说,就是一个类的成员是一个对象,即成员对象。
例如:
class Date{ ...}; //创建一个对象Date,类体略
class Time
{ Date d1 , d2;}; //创建一个对象Time,该对象有两个数据成员d1和d2,这两个成员是Date类的两个对象
在这个例子中,Time的成员d1和d2是Date的对象,那么d1和d2就是成员对象。
方案的重复使用
为重复使用一个类,最简单的办法是仅直接使用那个类的对象。但同时也能将那个类的一个对象置入一个新
类。我们把这叫作“创建一个成员对象”。新类可由任意数量和类型的其他对象构成。无论如何,只要新类
达到了设计要求即可。这个概念叫作“组织”——在现有类的基础上组织一个新类。有时,我们也将组织称
作“包含”关系,比如“一辆车包含了一个变速箱”。
对象的组织具有极大的灵活性。新类的“成员对象”通常设为“私有”(Private),使用这个类的客户程序员不能访问它们。这样一来,我们可在不干扰客户代码的前提下,从容地修改那些成员。也可以在“运行期”更改成员,这进一步增大了灵活性。后面要讲到的“继承”并不具备这种灵活性,因为编译器必须对通过继承创建的类加以限制。
由于继承的重要性,所以在面向对象的程序设计中,它经常被重点强调。作为新加入这一领域的程序员,或
许早已先入为主地认为“继承应当随处可见”。沿这种思路产生的设计将是非常笨拙的,会大大增加程序的
复杂程度。相反,新建类的时候,首先应考虑“组织”对象;这样做显得更加简单和灵活。利用对象的组
织,我们的设计可保持清爽。一旦需要用到继承,就会明显意识到这一点。
继承:重新使用接口
就其本身来说,对象的概念可为我们带来极大的便利。它在概念上允许我们将各式各样数据和功能封装到一
起。这样便可恰当表达“问题空间”的概念,不用刻意遵照基础机器的表达方式。在程序设计语言中,这些
概念则反映为具体的数据类型(使用class 关键字)。
我们费尽心思做出一种数据类型后,假如不得不又新建一种类型,令其实现大致相同的功能,那会是一件非
常令人灰心的事情。但若能利用现成的数据类型,对其进行“克隆”,再根据情况进行添加和修改,情况就
显得理想多了。“继承”正是针对这个目标而设计的。但继承并不完全等价于克隆。在继承过程中,若原始
类(正式名称叫作基础类、超类或父类)发生了变化,修改过的“克隆”类(正式名称叫作继承类或者子
类)也会反映出这种变化。在Java 语言中,继承是通过 extends关键字实现的
使用继承时,相当于创建了一个新类。这个新类不仅包含了现有类型的所有成员(尽管private 成员被隐藏
起来,且不能访问),但更重要的是,它复制了基础类的接口。也就是说,可向基础类的对象发送的所有消
息亦可原样发给衍生类的对象。根据可以发送的消息,我们能知道类的类型。这意味着衍生类具有与基础类
相同的类型!为真正理解面向对象程序设计的含义,首先必须认识到这种类型的等价关系。
由于基础类和衍生类具有相同的接口,所以那个接口必须进行特殊的设计。也就是说,对象接收到一条特定
的消息后,必须有一个“方法”能够执行。若只是简单地继承一个类,并不做其他任何事情,来自基础类接
口的方法就会直接照搬到衍生类。这意味着衍生类的对象不仅有相同的类型,也有同样的行为,这一后果通
常是我们不愿见到的。
有两种做法可将新得的衍生类与原来的基础类区分开。第一种做法十分简单:为衍生类添加新函数(功
能)。这些新函数并非基础类接口的一部分。进行这种处理时,一般都是意识到基础类不能满足我们的要
求,所以需要添加更多的函数。这是一种最简单、最基本的继承用法,大多数时候都可完美地解决我们的问
题。然而,事先还是要仔细调查自己的基础类是否真的需要这些额外的函数。
java-继承-实例
//建一个A类
class
A{
//成员变量
其中num是实参
int
num=0;
//成员方法,其中
i 是型参
public
A(int i){
//输入int型文件输出下面
System.out.println("aaaaaaaaaaaaaaaaaaa");
System.out.println("i=
"+i);
//把输入的i赋值给成员变量num
num=i;
}
}
//B继承A
class
B extends A{
int
num=0;
//成员方法B.
public
B(){
//继承类A的方法.因为B继承了A必然继承了A的特性.所以输入int值10令方法实现.
super(10);
System.out.println("bbbbbbbbbbbbbbbbbbbb");
//这里的num是类B的.
System.out.println("num=
"+num);
//如果想显示类A的num则需要用下列的super.num.这里注意num在类A中需是成员变量才可以.
System.out.println("super.num=
"+super.num);
}
}
//建立测试类C
public
class C{
//main
public
static void main(String aa[]){
//new
一个对象B()
new
B();
}
}
(1)改善基础类
尽管extends关键字暗示着我们要为接口“扩展”新功能,但实情并非肯定如此。为区分我们的新类,第二
个办法是改变基础类一个现有函数的行为。我们将其称作“改善”那个函数。
为改善一个函数,只需为衍生类的函数建立一个新定义即可。我们的目标是:“尽管使用的函数接口未变,
但它的新版本具有不同的表现”。31
(2)等价与类似关系
针对继承可能会产生这样的一个争论:继承只能改善原基础类的函数吗?若答案是肯定的,则衍生类型就是
与基础类完全相同的类型,因为都拥有完全相同的接口。这样造成的结果就是:我们完全能够将衍生类的一
个对象换成基础类的一个对象!可将其想象成一种“纯替换”。在某种意义上,这是进行继承的一种理想方
式。此时,我们通常认为基础类和衍生类之间存在一种“等价”关系——因为我们可以理直气壮地说:“圆
就是一种几何形状”。为了对继承进行测试,一个办法就是看看自己是否能把它们套入这种“等价”关系
中,看看是否有意义。
但在许多时候,我们必须为衍生类型加入新的接口元素。所以不仅扩展了接口,也创建了一种新类型。这种
新类型仍可替换成基础类型,但这种替换并不是完美的,因为不可在基础类里访问新函数。我们将其称作
“类似”关系;新类型拥有旧类型的接口,但也包含了其他函数,所以不能说它们是完全等价的。举个例子
来说,让我们考虑一下制冷机的情况。假定我们的房间连好了用于制冷的各种控制器;也就是说,我们已拥
有必要的“接口”来控制制冷。现在假设机器出了故障,我们把它换成一台新型的冷、热两用空调,冬天和
夏天均可使用。冷、热空调“类似”制冷机,但能做更多的事情。由于我们的房间只安装了控制制冷的设
备,所以它们只限于同新机器的制冷部分打交道。新机器的接口已得到了扩展,但现有的系统并不知道除原
始接口以外的任何东西。
认识了等价与类似的区别后,再进行替换时就会有把握得多。尽管大多数时候“纯替换”已经足够,但您会
发现在某些情况下,仍然有明显的理由需要在衍生类的基础上增添新功能。通过前面对这两种情况的讨论,
相信大家已心中有数该如何做。
对这样的一系列类,我们要进行的一项重要处理就是将衍生类的对象当作基础类的一个对象对待。这一点是
非常重要的,因为它意味着我们只需编写单一的代码,令其忽略类型的特定细节,只与基础类打交道。这样
一来,那些代码就可与类型信息分开。所以更易编写,也更易理解。此外,若通过继承增添了一种新类型,
如“三角形”,那么我们为“几何形状”新类型编写的代码会象在旧类型里一样良好地工作。所以说程序具
备了“扩展能力”,具有“扩展性”。
以上面的例子为基础,假设我们用 Java 写了这样一个函数:
void doStuff(Shape s) {
s.erase();
// ...
s.draw();32
}
这个函数可与任何“几何形状”(Shape)通信,所以完全独立于它要描绘(draw)和删除(erase)的任何
特定类型的对象。如果我们在其他一些程序里使用 doStuff()函数:
Circle c = new Circle();
Triangle t = new Triangle();
Line l = new Line();
doStuff(c);
doStuff(t);
doStuff(l);
这实际是一个非常有用的编程技巧。请考虑下面这行代码:
doStuff(c);
此时,一个 Circle(圆)句柄传递给一个本来期待 Shape(形状)句柄的函数。由于圆是一种几何形状,所
以doStuff()能正确地进行处理。也就是说,凡是 doStuff()能发给一个
Shape的消息,Circle也能接收。
所以这样做是安全的,不会造成错误。
我们将这种把衍生类型当作它的基本类型处理的过程叫作“Upcasting”(上溯造型)。其中,“cast”(造
型)是指根据一个现成的模型创建;而“Up”(向上)表明继承的方向是从“上面”来的——即基础类位于
顶部,而衍生类在下方展开。所以,根据基础类进行造型就是一个从上面继承的过程,即“Upcasting”。
在面向对象的程序里,通常都要用到上溯造型技术。这是避免去调查准确类型的一个好办法。请看看
doStuff()里的代码:
s.erase();
// ...
s.draw();
注意它并未这样表达:“如果你是一个Circle,就这样做;如果你是一个Square,就那样做;等等”。若那
样编写代码,就需检查一个Shape 所有可能的类型,如圆、矩形等等。这显然是非常麻烦的,而且每次添加
了一种新的 Shape类型后,都要相应地进行修改。在这儿,我们只需说:“你是一种几何形状,我知道你能
将自己删掉,即erase();请自己采取那个行动,并自己去控制所有的细节吧。”
面向对象的多态性
1、应用程序不必为每个子类编写单独的功能调用,只需要对抽象基类(父类)进行处理即可。以不变应万变
2、子类的功能可以被父类的引用变量调用,这个叫做向后兼容。
例如:
class A
{
void Func1()
{
System.out.println("A func1 is calling");
}
void Func2()
{
Func1();
}
}
class B extends A
{
void Func1()
{
System.out.println("B func1 is calling");
}
void Func3()
{
System.out.println("B func1 is calling");
}
}
class Test
{
public static void
main(String [] args)
{
B b = new B();
CallA(b)
}
public static void CallA(A
a)
{
a.Func1();
a.Func2();
}
}
在这段代码里面可以看到B继承了A并且覆盖了A中的Func1
在main中new了一个B的对象,调用了CaiiA(),同时传递了一个类B的一个对象b给了CallA(b),并且b被上溯造型为A在CallAde(b)的内部将会调用a.Func1是调用的A的Func1还是B的func1呢?
答案是调用的是b的Func1,因为传递到CallA里面的是一个B类的对象 //根据传进来的对象自动调用对应的方法
同时,运行a.Func2(),因为B中没有这个方法,所以将会运行A中的这个方法。
但是A中的这个方法要调用Func1,这个时候还是不会调用A中已经有的Func1,而是继续调用B中的这个Func1的因为子类的功能可以被父类的引用变量调用,这个叫做向后兼容
Q:Criterion[]
criterions = new Criterion[criterionList.size()];
这样写算是实例化一个对象吗?
还是只是定义一个数组而已。
A:在JVM虚拟机里,new关键字如同C的 malloc函数。
new Object();表示直接对Object类型开辟一个空间,包括的内存空间有:非static成员变量(方法和静态成员在静态内存区)+其他初始化空间。
例如:
class Test{
private int a;
public List list;
public void aaa(){}
}
创建实例new Test()则开辟a(4字节)+list指针(Java叫对象引用,这时只是一个null)+其他使用空间。
同样:
Test[] t=new Test[100];
只开辟了100个连续的对象引用地址。这些地址的指向是null.就就是他们都是没有实际内容的对象(null,没有实例化。)
如图:
0x10000000 --------> t[0x10000004,0x10000008...]
0x10000004 --------> NULL
0x10000008 --------> NULL
......
所以上面的循环输出数组全部是null.