Java之六 类设计

多态

普通的方法重载

在Java中,同一个类中的2个或2个以上的方法可以有同一个名字,只要它们的参数声明不同即可。在这种情况下,该方法就被称为重载(overloaded),这个过程称为方法重载(method overloading)。方法重载是Java实现多态性的一种方式。如果你以前从来没有使用过一种允许方法重载的语言,这个概念最初可能有点奇怪。但是你将看到,方法重载是Java最激动人心和最有用的特性之一。

当一个重载方法被调用时,Java用参数的类型和(或)数量来表明实际调用的重载方法的版本。因此,每个重载方法的参数的类型和(或)数量必须是不同的。虽然每个重载方法可以有不同的返回类型,但返回类型并不足以区分所使用的是哪个方法。当Java调用一个重载方法时,参数与调用参数匹配的方法被执行。

下面是一个说明方法重载的简单例子:

// Demonstrate method overloading.

class OverloadDemo {

void test(){

System.out.println("No parameters");

}

// Overloadtest for one integer parameter.

voidtest(int a) {

System.out.println("a: " + a);

}

// Overloadtest for two integer parameters.

voidtest(int a,int b) {

System.out.println("a and b: " + a +" " + b);

}

// overloadtest for a double parameter

doubletest(double a) {

System.out.println("double a: " + a);

return a*a;

}

}

class Overload {

public static void main(String args[]) {

OverloadDemo ob = new OverloadDemo();

double result;

// call all versions of test()

ob.test();

ob.test(10);

ob.test(10,20);

result = ob.test(123.25);

System.out.println("Result of ob.test(123.25):" + result);

}

}

该程序产生如下输出:

No parameters

a: 10

a and b: 10 20

double a: 123.25

Result of ob.test(123.25): 15190.5625

从上述程序可见,test()被重载了四次。第一个版本没有参数,第二个版本有一个整型参数,第三个版本有两个整型参数,第四个版本有一个double型参数。由于重载不受方法的返回类型的影响,test()第四个版本也返回了一个和重载没有因果关系的值。

当一个重载的方法被调用时,Java在调用方法的参数和方法的自变量之间寻找匹配。

但是,这种匹配并不总是精确的。在一些情况下,Java的自动类型转换也适用于重载方法的自变量。例如,看下面的程序:

// Automatic type conversions apply to overloading.

class OverloadDemo {

void test(){

System.out.println("No parameters");

}

// Overloadtest for two integer parameters.

voidtest(int a,int b) {

System.out.println("a and b: " + a +" " + b);

}

// overloadtest for a double parameter

voidtest(double a) {

System.out.println("Inside test(double) a:" + a);

}

}

class Overload {

publicstatic void main(String args[]) {

OverloadDemo ob = new OverloadDemo();

int i = 88;

ob.test();

ob.test(10,20);

ob.test(i); // this will invoke test(double)

ob.test(123.2); // this will invoke test(double)

}

}

该程序产生如下输出:

No parameters

a and b: 10 20

Inside test(double) a: 88

Inside test(double) a: 123.2

在本例中,OverloadDemo 的这个版本没有定义test(int)。因此当在Overload内带整数参数调用test()时,找不到和它匹配的方法。但是,Java可以自动地将整数转换为double型,这种转换就可以解决这个问题。因此,在test(int)找不到以后,Java将i扩大到double型,然后调用test(double)。当然,如果定义了test(int),当然先调用test(int)而不会调用test(double)。

只有在找不到精确匹配时,Java的自动转换才会起作用。

方法重载支持多态性,因为它是Java实现 “一个接口,多个方法”范型的一种方式。

要理解这一点,考虑下面这段话:在不支持方法重载的语言中,每个方法必须有一个惟一的名字。但是,你经常希望实现数据类型不同但本质上相同的方法。可以参考绝对值函数的例子。在不支持重载的语言中,通常会含有这个函数的三个及三个以上的版本,每个版本都有一个差别甚微的名字。例如,在C语言中 ,函数abs( )返回整数的绝对值,labs()返回long型整数的绝对值( ),而fabs( )返回浮点值的绝对值。尽管这三个函数的功能实质上是一样的,但是因为C语言不支持重载,每个函数都要有它自己的名字。这样就使得概念情况复杂许多。尽管每一个函数潜在的概念是相同的,你仍然不得不记住这三个名字。在Java中就不会发生这种情况,因为所有的绝对值函数可以使用同一个名字。确实,Java的标准的类库包含一个绝对值方法,叫做abs
( )。这个方法被Java的math类重载,用于处理数字类型。Java根据参数类型决定调用的abs()的版本。

重载的价值在于它允许相关的方法可以使用同一个名字来访问。因此,abs这个名字代表了它执行的通用动作(general action)。为特定环境选择正确的指定(specific)版本是编译器要做的事情。作为程序员的你,只需要记住执行的通用操作就行了。通过多态性的应用,几个名字减少为一个。尽管这个例子相当简单,但如果你将这个概念扩展一下,你就会理解重载能够帮助你解决更复杂的问题。

当你重载一个方法时,该方法的每个版本都能够执行你想要的任何动作。没有什么规定要求重载方法之间必须互相关联。但是,从风格上来说,方法重载还是暗示了一种关系。这就是当你能够使用同一个名字重载无关的方法时,你不应该这么做。例如,你可以使用sqr这个名字来创建一种方法,该方法返回一个整数的平方和一个浮点数值的平方根。但是这两种操作在功能上是不同的。按照这种方式应用方法就违背了它的初衷。在实际的编程中,你应该只重载相互之间关系紧密的操作。

构造函数重载

除了重载正常的方法外,构造函数也能够重载。实际上,对于大多数你创建的现实的类,重载构造函数是很常见的,并不是什么例外。为了理解为什么会这样,让我们回想上一章中举过的Box类例子。下面是最新版本的Box类的例子:

class Box {

doublewidth;

doubleheight;

doubledepth;

// This isthe constructor for Box.

Box(double w,double h,doubled) {

width = w;

height = h;

depth = d;

}

// computeand return volume

doublevolume() {

return width * height * depth;

}

}

在本例中,Box()构造函数需要三个自变量,这意味着定义的所有Box对象必须给Box()构造函数传递三个参数。例如,下面的语句在当前情况下是无效的:

Box ob = new Box();

因为Box( )要求有三个参数,因此如果不带参数的调用它则是一个错误。这会引起一些重要的问题。如果你只想要一个盒子而不在乎 (或知道)它的原始的尺寸该怎么办?或,如果你想用仅仅一个值来初始化一个立方体,而该值可以被用作它的所有的三个尺寸又该怎么办?如果Box类是像现在这样写的,与此类似的其他问题你都没有办法解决,因为你只能带三个参数而没有别的选择权。

幸好,解决这些问题的方案是相当容易的:重载Box构造函数,使它能处理刚才描述的情况。下面程序是Box的一个改进版本,它就是运用对Box构造函数的重载来解决这些问题的:

/* Here,Box defines three constructors to initialize

thedimensions of a box various ways.

*/

class Box {

doublewidth;

doubleheight;

doubledepth;

//constructor used when all dimensions specified

Box(double w,double h,doubled) {

width = w;

height = h;

depth = d;

}

//constructor used when no dimensions specified

Box() {

width = -1; // use -1 to indicate

height = -1; // an uninitialized

depth = -1; // box

}

//constructor used when cube is created

Box(doublelen) {

width = height = depth = len;

}

// computeand return volume

double volume() {

return width * height * depth;

}

}

class OverloadCons {

publicstatic void main(String args[]) {

// create boxes using the various constructors

Box mybox1 = new Box(10,20,15);

Box mybox2 = new Box();

Box mycube = new Box(7);

double vol;

// get volume of first box

vol = mybox1.volume();

System.out.println("Volume of mybox1 is "+ vol);

// get volume of second box

vol = mybox2.volume();

System.out.println("Volume of mybox2 is "+ vol);

// get volume of cube

vol = mycube.volume();

System.out.println("Volume of mycube is "+ vol);

}

}

该程序产生的输出如下所示:

Volume of mybox1 is 3000.0

Volume of mybox2 is -1.0

Volume of mycube is 343.0

在本例中,当new执行时,根据指定的自变量调用适当的构造函数。

把对象作为参数

到目前为止,我们都使用简单类型作为方法的参数。但是,给方法传递对象是正确的,也是常用的。例如,考虑下面的简单程序:

// Objects may be passed to methods.

class Test {

int a,b;

Test(int i,int j) {

a = i;

b = j;

}

// returntrue if o is equal to the invoking object

booleanequals(Test o) {

if(o.a == a && o.b == b)

return true;

else

returnfalse;

}

}

class PassOb {

publicstatic void main(String args[]) {

Test ob1 = new Test(100,22);

Test ob2 = new Test(100,22);

Test ob3 = new Test(-1,-1);

System.out.println("ob1== ob2: " + ob1.equals(ob2));

System.out.println("ob1== ob3: " + ob1.equals(ob3));

}

}

该程序产生如下输出:

ob1 == ob2: true

ob1 == ob3: false

在本程序中,在Test中的equals()方法比较两个对象的相等性,并返回比较的结果。也就是,它把调用的对象与被传递的对象作比较。如果它们包含相同的值,则该方法返回值为真,否则返回值为假。注意equals中的自变量o指定Test作为它的类型。尽管Test是程序中创建的类的类型,但是它的使用与Java的内置类型相同。

对象参数的最普通的使用涉及到构造函数。你经常想要构造一个新对象,并且使它的初始状态与一些已经存在的对象一样。为了做到这一点,你必须定义一个构造函数,该构造函数将一个对象作为它的类的一个参数。例如,下面版本的Box允许一个对象初始化另外一个对象:

// Here,Box allows one object to initialize another.

class Box {

double width;

doubleheight;

doubledepth;

//construct clone of an object

Box(Box ob){ // pass object to constructor

width = ob.width;

height = ob.height;

depth = ob.depth;

}

// constructor used when all dimensions specified

Box(double w,double h,doubled) {

width = w;

height = h;

depth = d;

}

//constructor used when no dimensions specified

Box() {

width = -1; // use -1 to indicate

height = -1; // an uninitialized

depth = -1; // box

}

//constructor used when cube is created

Box(doublelen) {

width = height = depth = len;

}

// computeand return volume

doublevolume() {

return width * height * depth;

}

}

class OverloadCons2 {

publicstatic void main(String args[]) {

// create boxes using the various constructors

Box mybox1 = new Box(10,20,15);

Box mybox2 = new Box();

Box mycube = new Box(7);

Box myclone = new Box(mybox1);

double vol;

// get volume of first box

vol = mybox1.volume();

System.out.println("Volume of mybox1 is "+ vol);

// get volume of second box

vol = mybox2.volume();

System.out.println("Volume of mybox2 is "+ vol);

// get volume of cube

vol = mycube.volume();

System.out.println("Volume of cube is " +vol);

// get volume of clone

vol = myclone.volume();

System.out.println("Volume of clone is "+ vol);

}

}

在本程序中你能看到,当你开始创建你自己的类的时候,为了方便高效的构造对象,必须为同一构造函数方法提供多种形式。

参数是如何传递的

总的来说,计算机语言给子程序传递参数的方法有两种。第一种方法是按值传递(call-by-value)。这种方法将一个参数值(value)复制成为子程序的正式参数。这样,对子程序的参数的改变不影响调用它的参数。第二种传递参数的方法是引用调用(call-by-reference)。在这种方法中,参数的引用(而不是参数值)被传递给子程序参数。

在子程序中,该引用用来访问调用中指定的实际参数。这样,对子程序参数的改变将会影响调用子程序的参数。你将看到,根据传递的对象不同,Java将使用这两种不同的方法。

在Java中,当你给方法传递一个简单类型时,它是按值传递的。因此,接收参数的子程序参数的改变不会影响到该方法之外。例如,看下面的程序:

// Simple types are passed by value.

class Test {

voidmeth(int i,int j) {

i *= 2;

j /= 2;

}

}

class CallByValue {

publicstatic void main(String args[]) {

Test ob = new Test();

int a = 15,b = 20;

System.out.println("a and b before call:" +

a + "" + b);

ob.meth(a,b);

System.out.println("a and b after call: "+

a + "" + b);

}

}

该程序的输出如下所示:

a and b before call: 15 20

a and b after call: 15 20

可以看出,在meth( )内部发生的操作不影响调用中a和b的值。它们的值没在本例中没有变为30和10。

当你给方法传递一个对象时,这种情形就会发生戏剧性的变化,因为对象是通过引用传递的。记住,当你创建一个类类型的变量时,你仅仅创建了一个类的引用。因此,当你将这个引用传递给一个方法时,接收它的参数将会指向该参数指向的同一个对象。这有力地证明了对象是通过引用调用传递给方法的。该方法中对象的改变确实影响了作为参数的对象。例如,考虑下面的程序:

// Objects are passed by reference.

class Test {

int a,b;

Test(int i,int j) {

a = i;

b = j;

}

// pass anobject

voidmeth(Test o) {

o.a *=  2;

o.b /= 2;

}

}

class CallByRef {

publicstatic void main(String args[]) {

Test ob = new Test(15,20);

System.out.println("ob.a and ob.b before call:" +

ob.a + " " + ob.b);

ob.meth(ob);

System.out.println("ob.a and ob.b after call:" +

ob.a +" " + ob.b);

}

}

该程序产生下面的输出:

ob.a and ob.b before call: 15 20

ob.a and ob.b after call: 30 10

正如你所看到的,在这个例子中,在  meth ( )中的操作影响了作为参数的对象。

有趣的一点是,当一个对象引用被传递给方法时,引用本身使用按值调用被传递。但是,因为被传递的值指向一个对象,该值的拷贝仍然指向它相应的参数所指向的同一个对象。

注意:当一个简单类型传递给一个方法时,使用按值传递。对象传递则按引用传递。

返回对象

方法能够返回任何类型的数据,包括你创建的类的类型。例如,在下面的程序中, incrByTen()方法返回一个对象,在该对象中的值a比调用对象中的值a大10。

// Returning an object.

class Test {

int a;

Test(int i){

a = i;

}

TestincrByTen() {

Test temp = new Test(a+10);

return temp;

}

}

class RetOb {

publicstatic void main(String args[]) {

Test ob1 = new Test(2);

Test ob2;

ob2 = ob1.incrByTen();

System.out.println("ob1.a:" + ob1.a);

System.out.println("ob2.a:" + ob2.a);

ob2 = ob2.incrByTen();

System.out.println("ob2.a after secondincrease: "

+ ob2.a);

}

}

该程序产生的输出如下所示:

ob1.a: 2

ob2.a: 12

ob2.a after second increase: 22

正如你看到的,每次调用incrByTen(),就产生一个新对象,同时将它的引用返回到调用子程序。

上面的程序还有另外重要的一点:既然所有的对象用关键字new动态地分配内存,你不必担心一个对象会出范围,因为它被其创建的方法终止。只要你程序中有它的一个引用,该对象将会继续存在。当没有该对象的引用时,在下一次垃圾回收发生时该对象将被回收。

递   归

Java支持递归(recursion)。递归就是依照自身定义事物的过程。在Java编程中,递归是允许方法调用自身调用的属性。调用自身的方法称为是递归的(recursive)。

递归的典型例子是数字的阶乘。数字N的阶乘是1到N之间所有整数的乘积。例如3的阶乘就是1× 2×3,或者是6。下面的程序使用递归来计算数字的阶乘。

// A simple example of recursion.

class Factorial {

// this isa recursive function

intfact(int n) {

int result;

if(n==1) return 1;

result = fact(n-1) * n;

return result;

}

}

class Recursion {

publicstatic void main(String args[]) {

Factorial f = new Factorial();

System.out.println("Factorial of 3 is " +f.fact(3));

System.out.println("Factorial of 4 is " +f.fact(4));

System.out.println("Factorial of 5 is " +f.fact(5));

}

}

该程序产生的输出如下所示:

Factorial of 3 is 6

Factorial of 4 is 24

Factorial of 5 is 120

如果你对递归的方法比较陌生,那么fact( )的操作可能看起来似乎有点糊涂。它是这样工作的:当fact( )带着参数1被调用时,该方法返回1;否则它返回fact( n-1 )与n的乘积。为了对这个表达式求值,fact()带着参数n-1被调用。重复这个过程直到 n  等于 1,且对该方法的调用开始返回。

为了更好地理解fact( )方法是如何工作的,让我们通过一个短例子来说明。例如当计算 3  的阶乘时,对fact()的第一次调用引起参数2的第二次调用。这个调用将引起fact以参数1的第三次调用,这个调用返回1,这个值接着与2(第二次调用时n的值)相乘。然后该结果(现为2)返回到fact()的最初的调用,并将该结果与3(n的初始值)相乘。这时得到答案,6。如果你在fact()中插入println()语句,显示每次调用的阶数以及中间结果,你会觉得很有意思。

当一个方法调用它自身的时候,堆栈就会给新的局部变量和自变量分配内存,方法代码就带着这些新的变量从头执行。递归调用并不产生方法新的拷贝。只有参数是新的。每当递归调用返回时,旧的局部变量和自变量就从堆栈中清除,运行从方法中的调用点重新开始。递归方法可以说是像“望远镜”一样,可以自由伸缩。

许多子程序的递归版本执行时会比它们的迭代版本要慢一点,因为它们增加了额外的方法调用的消耗。对一个方法太多的递归调用会引起堆栈崩溃。因为自变量和局部变量的存储都在堆栈中,每次调用都创建这些变量新的拷贝,堆栈有可能被耗尽。如果发生这种情况,Java的运行时系统就会产生异常。但是,除非递归子程序疯狂运行,否则你大概不会担心这种情况。

递归的主要优点在于:某些类型的算法采用递归比采用迭代算法要更加清晰和简单。

例如快速排序算法按照迭代方法是很难实现的。还有其他一些问题,特别是人工智能问题,就依赖于递归提供解决方案。最后,有些人认为递归要比迭代简单。

当编写递归方法时,你必须使用if条件语句在递归调用不执行时来强制方法返回。如果你不这么做,一旦你调用方法,它将永远不会返回。这类错误在使用递归时是很常见的。

尽量多地使用println()语句,使你可以了解程序的进程;如果发现错误,立即中止程序运行。

下面是递归的又一个例子。递归方法  printArray ( )打印数组values中的前i个元素。

// Another example that uses recursion.

class RecTest {

intvalues[];

RecTest(inti) {

values = new int[i];

}

// displayarray – recursively

voidprintArray(int i) {

if(i==0) return;

else printArray(i-1);

System.out.println("[" + (i-1) + "]" + values[i-1]);

}

}

class Recursion2 {

publicstatic void main(String args[]) {

RecTest ob = new RecTest(10);

int i;

for(i=0; i<10; i++) ob.values[i] = i;

ob.printArray(10);

}

}

该程序产生如下的输出:

[0] 0

[1] 1

[2] 2

[3] 3

[4] 4

[5] 5

[6] 6

[7] 7

[8] 8

[9] 9

介绍访问控制

理解访问控制符号

我们知道,封装将数据和处理数据的代码连接起来。同时,封装也提供另一个重要属性:访问控制(access control)。通过封装你可以控制程序的哪一部分可以访问类的成员。

通过控制访问,可以阻止对象的滥用。例如,通过只允许适当定义的一套方法来访问数据,你能阻止该数据的误用。因此,如果使用得当,可以把类创建一个“黑盒子”,虽然可以使用该类,但是它的内部机制是不公开的,不能修改。但是,本书前面创建的类可能不会完全适合这个目标。例如,考虑在第6章末尾示例的Stack类。方法push( )和pop()确实为堆栈提供一个可控制的接口,这是事实,但这个接口并没被强制执行。也就是说,程序的其他部分可以绕过这些方法而直接存取堆栈,这是可能的。当然,如果使用不当,这可能导致麻烦。本节将介绍能精确控制一个类各种各样成员的访问的机制。

一个成员如何被访问取决于修改它的声明的访问指示符(access specifier)。Java提供一套丰富的访问指示符。存取控制的某些方面主要和继承或包联系在一起(包,package,本质上是一组类)。Java的这些访问控制机制将在以后讨论。现在,让我们从访问控制一个简单的类开始。一旦你理解了访问控制的基本原理,其他部分就比较容易了。

Java的访问指示符有public(公共的,全局的)、private(私有的,局部的)、和protected(受保护的)。Java也定义了一个默认访问级别。指示符protected仅用于继承情况中。下面我们描述其他两个访问指示符。

让我们从定义public和private开始。当一个类成员被public指示符修饰时,该成员可以被你的程序中的任何其他代码访问。当一个类成员被指定为private时,该成员只能被它的类中的其他成员访问。现在你能理解为什么main( )总是被public指示符修饰。它被在程序外面的代码调用,也就是由Java运行系统调用。如果不使用访问指示符,该类成员的默认访问设置为在它自己的包内为public,但是在它的包以外不能被存取(包将在以后的章节中讨论)。

到目前为止,我们开发的类的所有成员都使用了默认访问模式,它实质上是public。然而,这并不是你想要的典型的方式。通常,你想要对类数据成员的访问加以限制,只允许通过方法来访问它。另外,有时你想把一个方法定义为类的一个私有的方法。

访问指示符位于成员类型的其他说明的前面。也就是说,成员声明语句必须以访问指示符开头。下面是一个例子:

public int i;

private double j;

private int myMethod(int a,char b) { // ...

要理解public和private对访问的作用,看下面的程序:

/* This program demonstrates the difference between

public andprivate.

*/

class Test {

int a; //default access

public intb; // public access

private intc; // private access

// methodsto access c

voidsetc(int i) { // set c‘s value

c = i;

}

int getc(){ // get c‘s value

return c;

}

}

class AccessTest {

publicstatic void main(String args[]) {

Test ob = new Test();

// These are OK,a and b may be accessed directly

ob.a = 10;

ob.b = 20;

// This is not OK and will cause an error

//  ob.c =100; // Error!

// You must access c through its methods

ob.setc(100); // OK

System.out.println("a,b,and c: "+ ob.a + " " +

ob.b +" " + ob.getc());

}

}

可以看出,在Test类中,a使用默认访问指示符,在本例中与public相同。b被显式地指定为public。成员c被指定为private,因此它不能被它的类之外的代码访问。所以,在AccessTest类中不能直接使用c。对它的访问只能通过它的public方法:setc()和getc()。

如果你将下面语句开头的注释符号去掉,

//  ob.c = 100; // Error!

则由于违规,你不能编译这个程序。

为了理解访问控制在实际中的应用,我们来看在第6章末尾所示的Stack类的改进版本。

// This class defines an integer stack that canhold 10 values.

class Stack {

/* Now,both stck and tos are private. Thismeans

that they cannot be accidentally or maliciously

altered in a way that would be harmful to thestack.

*/

private intstck[] = new int[10];

private inttos;

//Initialize top-of-stack

Stack() {

tos = -1;

}

// Push anitem onto the stack

voidpush(int item) {

if(tos==9)

System.out.println("Stack is full.");

else

stck[++tos]= item;

}

// Pop anitem from the stack

int pop() {

if(tos < 0) {

System.out.println("Stack underflow.");

return 0;

}

else

returnstck[tos--];

}

}

在本例中,现在存储堆栈的stck和指向堆栈顶部的下标tos,都被指定为private。这意味着除了通过push()或pop(),它们不能够被访问或改变。例如,将tos指定为private,阻止你程序的其他部分无意中将它的值设置为超过stck 数组下标界的值。

下面的程序表明了改进的Stack类。试着删去注释前面的线条来证明stck和tos成员确实是不能访问的。

class TestStack {

publicstatic void main(String args[]) {

Stack mystack1 = new Stack();

Stack mystack2 = new Stack();

// push some numbers onto the stack

for(int i=0; i<10; i++) mystack1.push(i);

for(int i=10; i<20; i++) mystack2.push(i);

// pop those numbers off the stack

System.out.println("Stack in mystack1:");

for(int i=0; i<10; i++)

System.out.println(mystack1.pop());

System.out.println("Stack in mystack2:");

for(int i=0; i<10; i++)

System.out.println(mystack2.pop());

// these statements are not legal

// mystack1.tos = -2;

// mystack2.stck[3] = 100;

}

}

尽管由类定义的方法通常提供对数据的访问,但情况并不总是这样。当需要时允许一个实例变量为public是完全合适的。例如,为简单起见,本书中大多数的简单类在创建时不关心实例变量的存取。然而,在大多数实际应用的类中,你将有必要仅仅允许通过方法来对数据操作。下一章将回到访问控制的话题。你将看到,在继承中访问控制是至关重要的。

实践问题:

1. 思考现实生活中,我们有没有从其他人那里学到些技术或知识,这是不是多态呢?

2. 如果不想让别人进入你们家,你就锁门;不像让别人看见你的表情,你就带墨镜;以及我们对别人的猜疑、竖起心灵的围墙;等等这些和面向对象的访问控制之间有什么相似的地方呢?

小结:

在本章中,我们主要学习了:

u      面向对象的多态等高级特征;

u      访问修饰符在java中的限定及使用;

英语词汇:

 

英文                    全文                                  中文

 

Overloaded    Overloaded           重载

Recursion     Recursion            递归

Public     Public            公共的,全局的

Private       Private              私有的,局部的

Protected            Protected                           受保护的

练习项目:

你父亲是一个地道的中国农民,只会种地干粗活,没有动过电脑;但是你虽然是你父亲的儿子,具备了宽容、善良、勤劳的美德;但你却会熟练地使用电脑,而且还是地道的软件工程师;然而你有时候又不听你父亲的话,和他有心灵的隔阂;试着用本节学到的面向对象的知识,用程序模拟实现这些特征;

时间: 2024-10-08 09:58:22

Java之六 类设计的相关文章

Java学习---JAVA的类设计

基础知识 JAVA是由C/C++语言发展而来的纯面向对象语言,其基本元素包括:简单数据类型 和 复合数据类型(即类).类是对客观事物的抽象描述,它有面向对象的四个特点,即:封装性.继承性.多态性和通信相关性. 类由属性和方法构成,类.属性.方法都通过修饰符限制如何使用.常用的修饰符包括:public.protected.private.static.final.abstract等等.JAVA语言中除了类以外,还存在抽象类和接口,其中抽象类是使用abstract修饰的类,其中包含了抽象的方法(即只

From C# to Java (2) - 类的设计 (1)

上一篇文章 From C# to Java (1) - 类型.引用与相等关系 对 Java 与 C# 在基本概念上的一些区别进行了简单的叙述,在这里简单做一回顾.第一,Java 的数据类型分为基础数据类型和类类型,类类型均为引用类型:第二,Java 的“==”运算符严格执行引用相等:第三,Java 不支持运算符重载.其中的很多重要特性在本文中也会提到,而且对类设计有重要的意义. 本文试图通过一个实际的 Java 类(Android Open Source Project 中的 BitmapFac

Selenium Webdriver自动化测试设计(webdriver自动化架构设计、定义接口、日志处理、Java Robot类应用)

给各位网友分享一套课程,有兴趣的可以加我 2748165793 更多免费资料,可以查看http://blog.sina.com.cn/sonyandnokia码农的开心乐园 课程大纲第1章节:课程介绍概要介绍selenium介绍webdirver介绍webdriver环境搭建 第2章节Selenium IDE介绍IDE实例讲解录制回放过程录制脚本的导出IDE插件的介绍以及安装方法 第3章节Selenium 识别对象的方法包括:id,name,class name,link text,partia

在线数据库表(sql语句)生成java实体类工具

相信每个做java开发的读者,都接触过SQL建表语句,尤其是在项目开发初期,因为数据库是项目的基石. 在现代项目开发中,出现了许多ORM框架,通过简单的实体映射,即可实现与数据库的交互,然而我们最初设计的一定是数据库表结构,而不是实体类.实体类仅仅是对底层数据结构的有损压缩,它仅仅是数据载体,不具备数据归档能力. 因此,很多时候,我们需要将原始的SQL建表语句转换成java实体类,这项工作看似简单,但若人工完成,工作量也是相当可观的,而且难免会出现差错. 到目前为止,笔者还没有发现比较靠谱的此类

java Timer类

java.util 类 Timer java.lang.Object java.util.Timer public class Timerextends Object 一种工具,线程用其安排以后在后台线程中执行的任务.可安排任务执行一次,或者定期重复执行. 与每个 Timer 对象相对应的是单个后台线程,用于顺序地执行所有计时器任务.计时器任务应该迅速完成.如果完成某个计时器任务的时间太长,那么它会“独占”计时器的任务执行线程.因此,这就可能延迟后续任务的执行,而这些任务就可能“堆在一起”,并且

java基础知识回顾之java Thread类学习(七)--java多线程通信等待唤醒机制(wait和notify,notifyAll)

1.wait和notify,notifyAll: wait和notify,notifyAll是Object类方法,因为等待和唤醒必须是同一个锁,不可以对不同锁中的线程进行唤醒,而锁可以是任意对象,所以可以被任意对象调用的方法,定义在Object基类中. wait()方法:对此对象调用wait方法导致本线程放弃对象锁,让线程处于冻结状态,进入等待线程的线程池当中.wait是指已经进入同步锁的线程,让自己暂时让出同步锁,以便使其他正在等待此锁的线程可以进入同步锁并运行,只有其它线程调用notify方

从零开始自学Java泛型的设计难不难?

引言 泛型是Java中一个非常重要的知识点,在Java集合类框架中泛型被广泛应用.本文我们将从零开始来看一下Java泛型的设计,将会涉及到通配符处理,以及让人苦恼的类型擦除. 泛型基础 泛型类 我们首先定义一个简单的Box类: public class Box { private String object; public void set(String object) { this.object = object; } public String get() { return object;

Java虚拟机类装载的原理及实现(转)

Java虚拟机类装载的原理及实现(转) 一.引言 Java虚拟机(JVM)的类装载就是指将包含在类文件中的字节码装载到JVM中, 并使其成为JVM一部分的过程.JVM的类动态装载技术能够在运行时刻动态地加载或者替换系统的某些功能模块, 而不影响系统其他功能模块的正常运行.本文将分析JVM中的类装载系统,探讨JVM中类装载的原理.实现以及应用. 二.Java虚拟机的类装载实现与应用 2.1 装载过程简介 所谓装载就是寻找一个类或是一个接口的二进制形式并用该二进制形式来构造代表这个类或是这个接口的c

(读书笔记)Java应用架构设计-模块化模式与OSGi

本书主要模块化模式的好处.模块化方法与模式.OSGi简单使用等内容,分3大部分: 第一部分介绍了模块化概念,为什么要模块化,以及一些模块化要考虑的东西,如模块粒度,依赖关系,重用性灵活性等. 第二部分介绍模块化的一些模式,采用了GoF设计模式的格式(模式名称.模式表述.图示.描述.多种实现.效果.样例.小结),看着有些乱,但是收获不少. 第三部分介绍OGSi结合Java如何使用,以及如何模块化现有系统.Java中无法直接模块化(Java SE模块化功能Jigsaw被推迟到了Jave SE 9),