方法的签名包含如下两个方面:方法名和参数列表。
Java语法规定,一个类中不可以有两个方法签名完全相同的方法,即:一个类中不可以有两个方法的方法名和参数列表都完全相同,但是,如果一个类的两个方法只是方法名相同而参数列表不同,是可以的。下面看如下代码:
public class Cashier { public boolean pay(double money) { … } public boolean pay(double money) { …} }
分析如上代码,结论会出现编译错误,因为在同一个类Cashier中的两个方法,签名相同,这在java语法中是不允许出现的。而下面的代码就是正确的:
public class Cashier { public boolean pay(double money) { … } public boolean pay(String cardId, String cardPwd) { … } }
可以看到上面的代码,在类Cashier中,虽然pay方法名相同,但是参数列表不同,这样是被允许的,可以正常编译通过。
假想收款窗口的设计,可以采用两种方式:
图- 8
图- 9
通过对如上两种方式的分析,请问:哪种方式更好一些呢?常规情况下认为,相对于A的方式,B的设计可以降低用户的负担,用户去付款时不需要去找对应的窗口,只需要到收款窗口就可以了, 减少了用户使用时的错误,B的设计更加优雅一些。
按B的设计方式即为方法重载,在Java语言中,允许多个方法的名称相同,但参数列表不同,此种方式称为方法的重载(overload)。
下面的代码即为收款窗口的两种设计方式,可以看出B方式即为方法的重载方式。
public class PayMoney { //----------A方式 …payByCash(double money) { } …payByCard(String cardId,StringcardPwd) { } …payByCheck(String compayName,double money) { } } public class PayMoney { //-----------B方式 …pay(double money) { } …pay(String cardId,StringcardPwd) { } …pay(String compayName,double money) { } }
通过如上的代码可以看出,按照A的方式若想付款,需在三个方法之中进行选择,不同的方法即为不同的付款方式,而B的方式即为重载方式,若想付款,只需要找到pay方法,只不过,传递不同的参数即可。
当调用重载的方法时,编译器会在编译时根据签名的不同来绑定调用不同的方法,可以把重载的方法看成是完全不同的方法,只不过,恰好方法名称相同而已。请看下面的两组代码:
重载方法:
…pay(double money) { } …pay(String cardId,StringcardPwd) { } …pay(String compayName,double money) { }
调用方法:(只管调用即可,由编译器来根据签名绑定不同的方法)
pay(8888.88); pay(“12345678”,”666666”); pay( “tarena”, 8888.88);
构造方法是在类中定义的方法, 但不同于其他的方法,构造方法的定义有如下两点规则:
如下所示为构造方法的语法:
【访问修饰符】类名( ) { //构造方法体 }
Java语言中的构造方法常常用于实现对对象成员变量的初始化,如下代码展示了构造方法的使用。
class Cell { int row ; int col ; public Cell (int row1 , int col1){ row = row1; col = col1; } } class TestCell { public static void main(String args[ ] ){ Cell c1 = new Cell( 15 , 6 ); printCell(c1); } }
可以看出,在创建对象时,构造方法写在new关键字之后,可以理解为:“new”创建了对象,而构造方法对该对象进行了初始化。
在上面的代码中,为该构造方法定义了两个参数,分别表示行和列。为了区分于Cell类的成员变量row和col,为两个参数分别取名为row1和col1,这显然不是一种好方法,因为,我们会希望依然使用变量row来表示行,col来表示列,而不是row1和col1。为了解决这个问题,需要使用到this关键字。
this关键字用在方法体中,用于指代调用该方法的当前对象,简单的说:哪个对象调用方法,this指的就是哪个对象。严格来讲,在方法中需要通过this关键字指明当前对象。请看如下代码:
public void drop ( ) { this.row ++; }
上面的drop()方法可以解释为:将调用该方法对象的成员变量的row加1。很多时候,为了方便起见,在没有歧义的情况下可以省略this,如下所示:
public void drop ( ) { row ++; }
在构造方法中,用来初始化成员变量的参数一般和成员变量取相同的名字,这样会有利于代码的可读性,但此处就必须通过this关键字来区分成员变量和参数了,而不能省略this了,如下代码所示:
public Cell (int row , int col ) { this . row = row ; this . col = col ; }
如上的代码中,this.row表示的为Cell类的成员变量,而row为参数变量。
JAVA语法规定,任何一个类都必须含有构造方法,假如源程序中没有定义,则编译器在编译时将为其添加一个无参的空构造方法(此方法称之为“默认的构造方法”)。
例如:先前所定义的Cell类源文件中没有写构造方法,但也可以编译成功,因为在编译时由编译器为其添加了如下的构造方法:
Cell( ) { }
但是有一个问题需要注意,当类定义了构造方法后,Java编译器将不再添加默认的构造方法,看如下代码:
class Cell{ int row; int col; Cell (int row,int col){ this.row = row; this.col = col; } } public class CellGame { public static void main(String[] args) { Cell cell = new Cell( ); //编译错误 } }
可以看出,在创建Cell类对象时,发生了一个编译期错误,那是因为,在Cell类中已经定义了Cell(int,int)的构造方法,此时,编译器将不会再提供无参的构造方法了。所以此处发生了编译期错误。
很多时候,为了使用的方便,可以对一个类定义多个构造方法,这些构造方法都有相同的名称(类名),只是方法的参数不同,称之为构造方法的重载。
在创建对象时,Java编译器会根据不同的参数调用来不同构造方法。看如下的几组构造方法的声明及调用。
声明: Cell ( int row , int col ) { } 调用: Cell c1 = new Cell ( 5 , 6 ); 声明: Cell ( ) { } 调用: Cell c1 = new Cell ( ) ; 声明: Cell (int row) { this(row , row ); } 调用: Cell c1 = new Cell ( 5 ) ;
可以注意到,在第三段声明的构造方法中,使用了this关键字,在构造方法中可以通过this关键字来调用另外的一个重载的构造方法。this(row,row)调用了第一段声明Cell(int row, int col)构造方法。
在java中,数组属于引用数据类型,数组对象存放在堆中存储,数组变量属于引用类型,存储数组对象的地址信息,指向数组对象。而数组的元素可以看成数组对象的成员变量(只不过类型全部相同)。看如下代码:
int[ ] arr = new int [ 3 ]
内存的分配如下图– 4所示:
图- 4
说明: 在堆内存中分配了数组对象,分配三个int型空间,并将每个元素赋初始值为0,栈中存储对堆中数据的引用,即堆中int数组的首地址。
刚刚声明的数组为基本类型数组,除了基本类型数组以外,也可以声明引用类型数组。所谓引用类型数组,即数组元素的类型不是基本类型(int,char,float。。) , 而是引用类型,看如下代码:
Cell [ ] cells = new Cell [ 4 ] ;
其内存分配如下图– 5所示:
图- 5
从上图示可以看出,new Cell[4]实际是分配了4个空间用于存放4个Cell类型的引用,并赋初始值为null,而并非是分配了4个Cell类型的对象。
前面所介绍的基本类型数组的默认值同成员变量默认的初始值(如int类型的数组初始值为0),而引用类型数组的默认初始值都是null。
如果希望每一个元素都指向具体的对象,则需要针对每一个数组元素进行“new”运算。与基本类型数组一样,也可以采用静态初始化的方式进行初始化操作。如下代码所示:
Cell[ ] cells = new Cell[4]; cells[0] = new Cell(0,4); cells[1] = new Cell(1,3); cells[2] = new Cell(1,4); cells[3] = new Cell(1,5);
等价于:
Cell[ ] cells = new Cell[ ] { new Cell(0,4) , new Cell(1,3) , new Cell(1,4) , new Cell(1,5) } ;
如上数组内存分配图如下图– 6 所示:
图- 6
前面所介绍的数组的元素或为基本类型int,或为引用类型Cell,而数组本身也是一种数据类型,当然也可以作为数组的元素。看下面的代码:
int [ ][ ] arr = new int[3][ ]; arr[0] = new int[2]; arr[1] = new int[3]; arr[2] = new int[2]; arr[1][1] = 100;
分析如上代码可以看出,变量arr指向一个数组,该数组有三个元素,每个元素都是int类型数组,长度分别为2,3,2,arr[1][1]=100表示将arr数组中的第2个元素(数组)的第2个元素赋值为100, 其内存分配如图– 7所示:
图- 7
对于元素为数组的数组,如果每个数组元素的长度相同,也可以采用如下的方式声明:
int row = 3, col = 4; int[][] arr = new int[row][col]; for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { arr[i][j] = i * j; } }
如上形式的数组可以用来表示类似“矩阵”这样的数据结构(3行4列),如下图-8所示,arr[i][j] 可以认为访问行号为i,列号为j的那个元素。在其他的一些语言中有专门表示这样结构的所谓二维数组;但严格的讲,Java语言中没有真正的二维数组。
图- 8