Java基础教程(10)--类
一.声明类
你已经见过了以如下方式定义的类:
class MyClass {
// field, constructor, and method declarations
}
上面是声明类的最基本的语法。可以在声明类时提供更多的信息,例如它继承的父类,或实现的接口等,例如:
class MyClass extends MySuperClass implements MyInterface {
// field, constructor, and method declarations
}
这意味着MyClass类继承自MySuperClass类并是实现了MyInterface接口。
还可以在开头加上修饰符,例如public、private、final等。有关这些修饰符的作用将在下文中进行详细的介绍。一般来说,类声明由以下几部分组成:
- 修饰符。例如public、private以及其他将会在后面见到的修饰符;
- 类名。按照惯例使用首字母大写的驼峰命名法。
- 继承的父类。使用关键字extend声明,一个类只能直接继承一个父类。
- 一个或多个实现的接口。使用关键字implements声明,多个接口之间使用逗号隔开。
- 类体。定义在一对大括号中。
二.域的声明
Bicycle类使用以下几行代码声明它的域:
public int cadence;
public int gear;
public int speed;
域的声明由以下几部分组成:
- 修饰符,例如private、public、static、final等;
- 域的类型;
- 域的名称。
权限修饰符
权限修饰符用来控制其他类对一个成员的访问权限。现在我们只讨论public和private,其他权限修饰符将在后面的文章中介绍。
- public:可以从所有类访问该字段;
- private:只能在自己的类中访问。
本着封装的精神,应该尽可能地将域的访问权限设为private,这意味这只能在自己的类中访问它的值。但是,其他类仍有访问它的需要,可以通过添加访问域的方法来供其他类访问,就像下面这样:
public class Bicycle {
private int cadence;
private int gear;
private int speed;
public Bicycle(int startCadence, int startSpeed, int startGear) {
gear = startGear;
cadence = startCadence;
speed = startSpeed;
}
public int getCadence() {
return cadence;
}
public void setCadence(int newValue) {
cadence = newValue;
}
public int getGear() {
return gear;
}
public void setGear(int newValue) {
gear = newValue;
}
public int getSpeed() {
return speed;
}
public void applyBrake(int decrement) {
speed -= decrement;
}
public void speedUp(int increment) {
speed += increment;
}
}
三.定义方法
下面定义了一个方法:
public int add(int a, int b) {
return a + b;
}
方法定义中必需的是返回值类型,方法名,一对小括号,以及大括号和方法体。更一般地来说,方法的声明由六个部分组成:
- 修饰符——例如public、private、static等;
- 返回值类型——方法返回的数据的类型。若方法无返回值则为void;
- 方法名称——和变量的命名规则相同,但惯例有一点不同;
- 参数列表——位于小括号中,用逗号隔开。方法也可以没有参数;
- 方法抛出的异常——有关这部分的内容将在后续教程中进行介绍;
- 方法体——位于大括号中,方法的具体代码。
签名:方法的签名由方法名称和参数列表组成。签名用来在一个类中唯一地标识一个方法,也就是说,一个类中不能存在两个签名相同的方法。上面的add方法的签名为add(int a, int b)。
方法的名称可以是任何合法的标识符。但是按照惯例,方法名的第一个单词应该是动词并且小写它的首字母。例如run、compareTo等。
方法的重载
重载是指一个类中的两个或多个方法具有相同的名称但具有不同的参数列表。Java支持方法的重载,并且可以通过方法的签名区分它们。
假设有一个计算器类Calculator。为不同类型的操作数的加法设计不同的名字是一件很麻烦的事,例如addInt、addDouble、addFloat等。在Java中,可以为这些方法使用相同的名称,只要它们的参数列表是不同的。例如:
public class Calculator {
public static add(int a, int b) {return a + b;}
public static add(double a, double b) {return a + b;}
...
}
不能声明名称和参数列表都相同的方法,因为编译器无法区分它们。编译器在区分方法时只考虑签名,所以名称和参数列表相同,但返回值不同的两个方法并不算重载,编译器将会给出错误。
四.构造器
构造器(也称构造方法)是将类实例化为对象是自动调用的方法。但与普通方法不同的是,它的名字与类名相同且没有返回值。例如,Bicycle类有一个构造方法:
public Bicycle(int startCadence, int startSpeed, int startGear) {
gear = startGear;
cadence = startCadence;
speed = startSpeed;
}
在创建对象时,new运算符将会调用构造方法:
Bicycle myBike = new Bicycle(30, 0, 8);
一个类可以有多个构造方法,只要它们的参数列表不同。还可以有无参构造方法:
public Bicycle() {
gear = 1;
cadence = 10;
speed = 0;
}
如果没有提供构造方法,编译器将会自动提供一个无参构造方法,这个无参构造方法会自动调用父类的无参构造方法。如果父类没有无参构造方法,编译器将会给出错误。因此在不提供构造方法时要保证父类有无参构造方法。如果你的类没有明确的父类,那么它有一个隐含的父类Object,这个类有一个无参构造方法。
可以在构造方法中手动调用父类的构造方法,有关这部分的内容将会在接口和继承的教程中介绍。还可以在构造方法的声明中使用访问修饰符来控制哪些类可以调用构造函数。如果某个类不能调用本类的构造方法,则无法在该类内部直接创建本类对象。
五.向方法或构造器传递参数
方法的参数分为形式参数和实际参数。形式参数(简称形参)是方法声明中的参数列表,实际参数(简称实参)是指在调用方法时传递的具体的值。调用方法时,实际参数的顺序和类型必须与形式参数完全匹配。例如之前定义的add方法:
int add(int a, int b) {
return a + b;
}
下面的语句调用了这个方法并把返回值赋值给变量sum,2和3就是调用方法时传递的实参:
int sum = add(2, 3);
还可以传递任意数量的参数。例如让add方法可以计算任意数量的整数的和:
int add(int... a) {
int sum = 0;
for(int i = 0; i < a.length; i++) {
sum += a[i];
}
return sum;
}
可以看到,在方法内部,这个参数被视为一个数组,可以通过数组的访问形式去访问每一个参数。需要注意的是,参数列表中只能有一个这样的参数,并且它必须放在最后一个位置上。
形参的名称如果与类的某个域的名称相同,那么在这个方法内,这个形参将会屏蔽与它同名的域。可以使用this关键字来区分同名的形参和域,有关this关键字的内容将会在后面的文章中讨论。
基本数据类型参数
基本数据类型的参数按值传递到方法中。这意味着对参数值的任何更改都仅存在于方法的范围内。方法返回时,参数消失,对它们的任何更改都将丢失。下面是一个例子:
public class PassPrimitiveByValue {
public static void main(String[] args) {
int a = 1;
increment(a);
System.out.println(a);
}
public static void increment(int x) {
x = x + 1;
}
}
这个程序会输出1而不是2。x只是接收了a的值,对x的操作与a完全没有关系,所以函数结束后,a的值还是1。
引用类型参数
引用类型的参数将引用传递到方法中。对形参的操作就相当于对实参进行同样的操作。例如:
public class PassPrimitiveByReference {
public static void main(String[] args) {
Foo foo = new Foo(1);
changeBar(foo);
System.out.println(foo.getBar());
}
public static void changeBar(Foo f) {
f.setBar(2);
}
}
class Foo {
private int bar;
public Foo(int bar) {this.bar = bar;}
public int getBar() {return bar;}
public void setBar(int bar) {this.bar = bar;}
}
这个程序会输出2。因为形参f和实参foo引用了同一个对象。