在标题为面向对象的编程概念课程中对面向对象概念的介绍以自行车课为例,以赛车,山地自行车和双人自行车为子类,下面是可能实现Bicycle
类的示例代码,为你提供类声明的概述,本课程的后续部分将逐步备份和解释类声明,目前,不要关心细节。编程
public class Bicycle { // the Bicycle class has // three fields public int cadence; public int gear; public int speed; // the Bicycle class has // one constructor public Bicycle(int startCadence, int startSpeed, int startGear) { gear = startGear; cadence = startCadence; speed = startSpeed; } // the Bicycle class has // four methods public void setCadence(int newValue) { cadence = newValue; } public void setGear(int newValue) { gear = newValue; } public void applyBrake(int decrement) { speed -= decrement; } public void speedUp(int increment) { speed += increment; } }
做为Bicycle
的子类的MountainBike
类的类声明可能以下所示:segmentfault
public class MountainBike extends Bicycle { // the MountainBike subclass has // one field public int seatHeight; // the MountainBike subclass has // one constructor public MountainBike(int startHeight, int startCadence, int startSpeed, int startGear) { super(startCadence, startSpeed, startGear); seatHeight = startHeight; } // the MountainBike subclass has // one method public void setHeight(int newValue) { seatHeight = newValue; } }
MountainBike
继承了Bicycle
的全部字段和方法,并增长了seatHeight
和设置它的方法(山地自行车有座位,能够根据地形要求上下移动)。数组
你已经看到如下列方式定义的类:app
class MyClass { // field, constructor, and // method declarations }
这是一个类声明,类主体(大括号之间的区域)包含为从类建立的对象的生命周期提供的全部代码:用于初始化新对象的构造函数,提供类及其对象状态的字段的声明,以及实现类及其对象行为的方法。编程语言
前面的类声明是最小的,它仅包含类声明所需的那些组件,你能够在类声明的开头提供有关该类的更多信息,例如其超类的名称,是否实现任何接口等等,例如:ide
class MyClass extends MySuperClass implements YourInterface { // field, constructor, and // method declarations }
表示MyClass
是MySuperClass
的子类,它实现了YourInterface
接口。函数
你也能够在最开始添加public
或private
等修饰符 — 这样你就能够看到类声明的开头行可能变得很是复杂,public
和private
修饰符决定了其余类能够访问MyClass
的内容,本课程稍后将对此进行讨论。关于接口和继承的课程将解释如何以及为何在类声明中使用extends
和implements
关键字,目前你不须要担忧这些额外的。this
一般,类声明能够按顺序包含这些组件:rest
public
、private
以及稍后你将遇到的许多其余修饰符。extends
开头,一个类只能extend
(子类)一个父类。implements
,一个类能够implement
多个接口。{}
。有几种变量:code
Bicycle
类使用如下代码行来定义其字段:
public int cadence; public int gear; public int speed;
字段声明按顺序由三个部分组成:
public
或private
。Bicycle
的字段被命名为cadence
、gear
和speed
,而且都是整数数据类型(int),public
关键字将这些字段标识为公共成员,可由任何能够访问该类的对象访问。
使用的第一个(最左侧)修饰符容许你控制哪些其余类能够访问成员字段,目前,只考虑public
和private
,其余访问修饰符将在后面讨论。
public
修饰符 — 能够从全部类访问该字段。private
修饰符 — 该字段只能在其本身的类中访问。本着封装的精神,将字段设为private
是很常见的,这意味着它们只能从Bicycle
类直接访问,可是,咱们仍然须要访问这些值,这能够经过添加为咱们获取字段值的公共方法间接完成:
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; } }
全部变量都必须具备类型,你可使用原始类型,如int
、float
、boolean
等,或者你可使用引用类型,例如字符串、数组或对象。
全部变量(不管是字段、局部变量仍是参数)都遵循“语言基础”课程“变量命名”中介绍的相同命名规则和约定。
在本课程中,请注意相同的命名规则和约定用于方法和类名称,除了:
如下是典型方法声明的示例:
public double calculateAnswer(double wingSpan, int numberOfEngines, double length, double grossTons) { //do the calculation here }
方法声明中惟一必需的元素是方法的返回类型、名称、一对圆括号()
和大括号之间的主体{}
。
更通常地,方法声明有六个组件,顺序以下:
public
、private
和其余你将在稍后了解的内容。void
。()
,若是没有参数,则必须使用空括号。修饰符、返回类型和参数将在本课程的后面部分讨论,异常将在后面的课程中讨论。
定义:方法声明的两个组件包括方法签名 — 方法的名称和参数类型。
上面声明的方法的签名是:
calculateAnswer(double, int, double, double)
虽然方法名称能够是任何合法标识符,但代码约定限制方法名称,按照惯例,方法名称应该是小写的动词或以小写的动词开头的多单词名称,后跟形容词、名词等。在多单词名称中,第二个和后面每一个单词的第一个字母应该大写,这里有些例子:
run runFast getBackground getFinalData compareTo setX isEmpty
一般,方法在其类中具备惟一名称,可是,因为方法重载,方法可能与其余方法具备相同的名称。
Java编程语言支持重载方法,Java能够区分具备不一样方法签名的方法,这意味着若是类中的方法具备不一样的参数列表,则它们能够具备相同的名称(有一些条件,将在标题为“接口和继承”的课程中讨论)。
假设你有一个类可使用书法来绘制各类类型的数据(字符串、整数等),而且包含绘制每种数据类型的方法,为每一个方法使用新名称很麻烦 — 例如,drawString
、drawInteger
、drawFloat
等等。在Java编程语言中,你能够对全部绘图方法使用相同的名称,可是为每一个方法传递不一样的参数列表,所以,数据绘图类可能会声明四个名为draw
的方法,每一个方法都有一个不一样的参数列表。
public class DataArtist { ... public void draw(String s) { ... } public void draw(int i) { ... } public void draw(double f) { ... } public void draw(int i, double f) { ... } }
重载方法由传递给方法的参数的数量和类型区分,在代码示例中,draw(String s)
和draw(int i)
是不一样且惟一的方法,由于它们须要不一样的参数类型。
你不能声明具备相同名称和相同数量和类型的参数的多个方法,由于编译器没法区分它们。
在区分方法时编译器不考虑返回类型,所以即便它们具备不一样的返回类型,也不能声明具备相同签名的两个方法。
注意:应谨慎使用重载方法,由于它们会使代码的可读性下降。
类包含被调用以从类蓝图建立对象的构造函数,构造函数声明看起来像方法声明 — 除了它们使用类的名称而且没有返回类型,例如,Bicycle
有一个构造函数:
public Bicycle(int startCadence, int startSpeed, int startGear) { gear = startGear; cadence = startCadence; speed = startSpeed; }
要建立一个名为myBike
的新Bicycle
对象,new
运算符将调用构造函数:
Bicycle myBike = new Bicycle(30, 0, 8);
new Bicycle(30, 0, 8)
为对象建立内存空间并初始化其字段。
虽然Bicycle
只有一个构造函数,但它可能有其余构造函数,包括一个无参构造函数:
public Bicycle() { gear = 1; cadence = 10; speed = 0; }
Bicycle yourBike = new Bicycle();
调用无参构造函数来建立一个名为yourBike
的新Bicycle
对象。
两个构造函数均可以在Bicycle
中声明,由于它们具备不一样的参数列表,与方法同样,Java平台根据列表中的参数数量及其类型来区分构造函数。你不能为相同类编写两个具备相同参数数量和类型的构造函数,由于平台没法区分它们,这样作会致使编译时错误。
你没必要为你的类提供任何构造函数,但在执行此操做时必须当心,编译器自动为没有构造函数的任何类提供无参数的默认构造函数,此默认构造函数将调用超类的无参数构造函数,在这种状况下,若是超类没有无参数构造函数,编译器将会报错,所以你必须验证它是否有,若是你的类没有显式的超类,那么它有一个隐式的超类Object,它有一个无参数的构造函数。
你能够本身使用超类构造函数,本课开头的MountainBike
类就是这样作的,稍后将在有关接口和继承的课程中对此进行讨论。
你能够在构造函数的声明中使用访问修饰符来控制哪些其余类能够调用构造函数。
注意:若是另外一个类不能调用MyClass
构造函数,则没法直接建立MyClass
对象。
方法或构造函数的声明声明该方法或构造函数的参数的数量和类型,例如,如下是根据贷款金额、利率、贷款期限(期数)和贷款的将来价值计算住房贷款的每个月付款的方法:
public double computePayment( double loanAmt, double rate, double futureValue, int numPeriods) { double interest = rate / 100.0; double partial1 = Math.pow((1 + interest), - numPeriods); double denominator = (1 - partial1) / interest; double answer = (-loanAmt / denominator) - ((futureValue * partial1) / denominator); return answer; }
此方法有四个参数:贷款金额、利率、将来价值和期数,前三个是双精度浮点数,第四个是整数,参数在方法体中使用,而且在运行时将采用传入的参数的值。
注意:参数是指方法声明中的变量列表,参数是调用方法时传递的实际值,调用方法时,使用的参数必须与声明参数的类型和顺序匹配。
你能够将任何数据类型用于方法或构造函数的参数,这包括原始数据类型,如在computePayment
方法中看到的双精度数、浮点数和整数,以及引用数据类型,如对象和数组。
这是一个接受数组做为参数的方法示例,在此示例中,该方法建立一个新的Polygon
对象,并从Point
对象数组中初始化它(假设Point
是一个表示x,y坐标的类):
public Polygon polygonFrom(Point[] corners) { // method body goes here }
注意:若是要将方法传递给方法,请使用
lambda
表达式或方法引用。
你可使用名为可变参数的构造将任意数量的值传递给方法,当你不知道将多少特定类型的参数传递给该方法时,你可使用可变参数,这是手动建立数组的快捷方式(前一种方法可使用可变参数而不是数组)。
要使用可变参数,你经过省略号跟随最后一个参数的类型(三个点,...
),而后是空格和参数名称,而后可使用任何数量的参数调用该方法,包括无参数。
public Polygon polygonFrom(Point... corners) { int numberOfSides = corners.length; double squareOfSide1, lengthOfSide1; squareOfSide1 = (corners[1].x - corners[0].x) * (corners[1].x - corners[0].x) + (corners[1].y - corners[0].y) * (corners[1].y - corners[0].y); lengthOfSide1 = Math.sqrt(squareOfSide1); // more method body code follows that creates and returns a // polygon connecting the Points }
你能够看到,在方法内部,corners
被视为数组,可使用数组或参数序列调用该方法,在任何一种状况下,方法体中的代码都会将参数视为数组。
你最多见的是使用打印方法的可变参数,例如,这个printf
方法:
public PrintStream printf(String format, Object... args)
容许你打印任意数量的对象,它能够像这样调用:
System.out.printf("%s: %d, %s%n", name, idnum, address);
或者像这样:
System.out.printf("%s: %d, %s, %s, %s%n", name, idnum, address, phone, email);
或者还有不一样数量的参数。
向方法或构造函数声明参数时,为该参数提供名称,此名称在方法体内用于引用传入的参数。
参数的名称在其范围内必须是惟一的,它不能与同一方法或构造函数的另外一个参数的名称相同,也不能是方法或构造函数中的局部变量的名称。
参数能够与类的某个字段具备相同的名称,若是是这种状况,则称该参数遮蔽该字段,遮蔽字段可能使你的代码难以阅读,而且一般仅在设置特定字段的构造函数和方法中使用,例如,考虑如下Circle
类及其setOrigin
方法:
public class Circle { private int x, y, radius; public void setOrigin(int x, int y) { ... } }
Circle
类有三个字段:x
,y
和radius
,setOrigin
方法有两个参数,每一个参数与其中一个字段具备相同的名称,每一个方法参数都会影响共享其名称的字段,所以,在方法体内使用简单名称x或y是指参数,而不是字段。要访问该字段,你必须使用限定名称,这将在本课程后面的“使用this
关键字”一节中讨论。
原始参数(如int
或double
)按值传递给方法,这意味着对参数值的任何更改都仅存在于方法的范围内,方法返回时,参数消失,对它们的任何更改都将丢失,这是一个例子:
public class PassPrimitiveByValue { public static void main(String[] args) { int x = 3; // invoke passMethod() with // x as argument passMethod(x); // print x to see if its // value has changed System.out.println("After invoking passMethod, x = " + x); } // change parameter in passMethod() public static void passMethod(int p) { p = 10; } }
运行此程序时,输出为:
After invoking passMethod, x = 3
引用数据类型参数(如对象)也按值传递给方法,这意味着当方法返回时,传入的引用仍然引用与之前相同的对象,可是,若是对象的字段的值具备适当的访问级别,则能够在该方法中更改它们的值。
例如,考虑任意类中移动Circle
对象的方法:
public void moveCircle(Circle circle, int deltaX, int deltaY) { // code to move origin of circle to x+deltaX, y+deltaY circle.setX(circle.getX() + deltaX); circle.setY(circle.getY() + deltaY); // code to assign a new reference to circle circle = new Circle(0, 0); }
使用这些参数调用该方法:
moveCircle(myCircle, 23, 56)
在方法内部,circle
最初引用的是myCircle
,该方法将circle
引用的对象(即myCircle
)的x和y坐标分别改变23和56,方法返回时,这些更改将保持不变。而后circle
被赋予新的Circle
对象的引用,其中x = y = 0
,可是,这种从新分配没有永久性,由于引用是按值传递的,不能更改,在该方法中,circle
指向的对象已更改,可是,当方法返回时,myCircle
仍然引用与调用方法以前相同的Circle
对象。