[关闭]
@Darcy 2017-08-11T11:12:20.000000Z 字数 8009 阅读 2985

第三章 宠物和鱼—Java的类

JavaForKids

Java程序中用类来描述真实世界中的物体。尽管大家写程序有着不同的偏好,但大多数人还是觉得面向对象的风格会更好。优秀的程序员总是会先想好程序里面需要包含的对象和描述它们的类,然后才开始写代码。

1. 类和对象

Java的类有方法和属性。
方法定义了一个类可以执行的动作。
属性则用于表达类的状态。

我们来创建和讨论一个叫VideoGame的类。 这个类包含了几个方法,对应着这个游戏相应的动作:开始游戏,停止游戏,保存分数,等等。它还可以有一些属性或性质:价格 、屏幕颜色、 控制手柄数量等等。

在Java语言中,这个类看起来就像下面那样子:

  1. public class VideoGame {
  2. String color;
  3. int price;
  4. void start () {
  5. }
  6. void stop () {
  7. }
  8. void saveScore(String playerName, int score) {
  9. }
  10. }

我们创建的类VideoGame就和很多的视频游戏一样,它们都有不同尺寸、颜色的屏幕,它们都执行相似的动作,它们都需要购买。

我们可以创造另一更特殊的GameBoyAdvance类,它同样属于视频游戏的大家庭,但有些性质是专属GameBoyAdvance,比如说,卡盘类型[1]

  1. public class GameBoyAdvance {
  2. String cartridgeType;
  3. int screenWidth;
  4. void startGame() {
  5. }
  6. void stopGame() {
  7. }
  8. }

在这个例子中GameBoyAdvance定义了两个属性:cartridgeType(卡盘类型)和screenWidth(屏幕宽度),以及两个方法--startGame()stopGame()。然而这些方法仍然不能执行任何动作,因为大括号里没有任何Java代码。

除了类(class)这个词,你还要习惯对象(object)这个词的新含义:
创建一个类的实例所表达的是: 根据这个类的定义,在你电脑的内存里创建一个对象。

工厂里GameBoy模型和真实的游戏的关系,就相当于Java里类和实例。工厂根据游戏模型,生产出真实的游戏;而在Java里,我们则根据类来实例化GameBoy对象。

Gameboy Model

很多情况下,为了使用一个Java的类,我们必须先创建它的实例。在相同描述的基础上,小贩也可以创造上千个翻版游戏来。即使这些副本表现同样的类,它们在自己的属性中还是拥有不同的属性值的:有一些是蓝色的,有一些是银色的,诸如此类。换种方式说,一个程序可以创建多种关于GameBoyAdvance的实例。

2. 数据类型

Java中的变量可能是一个类的属性、方法的参数、或是在方法中用于暂存数据的局部变量。在变量使用之前,你必须先声明它。

还记得像y=x+2 这样的等式吗?在Java中,你需要为变量xy指定相应的数值类型如int或者double类型:

  1. int x;
  2. int y;

下面的两行将展示如何去赋值给这些变量。如果你的程序给变量x赋值为5,那么变量y就会等于7:

  1. x=5;
  2. y=x+2;

这里的两个连续的加法,相当于y = y + 1, 也就是y最后的结果是6:

  1. int y = 5;
  2. y++;

下面这个代码片段运行之后,变量myScore的值也是6:

  1. int myScore = 10;
  2. myScore--;
  3. myScore = myScore *2;
  4. myScore = myScore /3;
  5. System.out.println("My score is " + myScore);

这段代码输出来是什么呢?Eclipse有个很酷的特征叫做剪贴簿 (scrapbook),它甚至不需要创造一个类就可以快速测试任何代码片段(比如刚提及的那个)。选择菜单里的File->new->Scrapbook Page,然后输入Test作为你的剪切簿的文件名。

现在进入Eclipse的剪切簿里,选择这五行代码,然后执行它们 。

Eclipse scrapbook

点击屏幕底部的控制台切换卡,你就可以看到分数运算的结果了。

My sroce is 6

在这个例子中,方法println()的参数分为两个部分——文本My score is和变量myScore的值,这个值是6 。 把多个字符串合并成一个叫做字符串的拼接(concatenation) 。 即使myScore是一个数字,Java会足够智能地将这个变量转换成一个字符串,然后把它附加在My Score is上。

观察下面一些其他改变变量值的方式:

myScore=myScore*2;   相当于 myScore*=2;   
myScore=myScore+2;   相当于 myScore+=2; 
myScore=myScore-2;   相当于 myScore-=2; 
myScore=myScore/2;   相当于 myScore/=2;  

Java里有八种简单的,或者说基本的数据类型。你要根据你打算储存在变量里的数据的类型和大小,来决定使用哪一种数据类型:

  1. 四种储存整数值的数据类型—byteshortintlong
  2. 两种小数类型—floatdouble
  3. 一种存放单个字符的类型—char
  4. 一种逻辑数据类型,boolean,它只允许有两种值:true或者false

你可以在变量的声明中赋予它一个初始值,这个过程叫做变量的初始化。

  1. char grade = 'A';
  2. int chairs = 12;
  3. boolean playSound = false;
  4. double nationalIncome = 23863494965745.78;
  5. float gamePrice = 12.50f;
  6. long totalCars =4637283648392l;

在最后两行中,f 代表着floatl 代表着long

如果你不初始化那些变量,Java会帮你给数值类型赋初值0,给boolean类型赋值false,给char赋值特殊符号\u0000[2]

这里还有一个特殊的关键词final,如果它被用来声明变量,你只能一开始就赋予这个变量一个值,而且以后都不能重新赋值了。在一些语言中, final变量叫做常量。在Java程序中,我们命名final 变量时通常用大写字母:

  1. final String STATE_CAPITAL="Washington";

除了基本数据类型,你还可以用类来声明变量。每一个基本数据类型都有相应的包装类,例如 Integer, Double, Boolean等。这些类提供了很多实用的方法,用来实现数据类型之间的转换。

Java除了可以用char类型来存储单个字符之外,还提供了String类型来处理更长的文本,例如:

  1. String lastName = "Smith";

在Java中,变量的命名不能以数字开头,也不能有任何空格。

bit(比特)是内存所能存储的最小数据单元,它可以用于保存0或1
byte类型的数据占用8个比特
char 类型则占两个字节
intfloat 在内存中占4个字节。
longdouble 各占8个字节。
使用更多字节的数据类型可以存储更大的数字。
1 Kb (千字节) 有 1024 byte
1 Mb (兆字节) 有 1024 Kb
1 Gb (千兆字节) 有 1024 Mb

3. 创建一个宠物类

让我们来设计并创建一个类Pet。首先,我们需要决定我们的宠物会做什么动作,包括怎么吃,怎么睡觉,怎么说话。 我们会在类Pet中的方法中编程描述这些动作。我们还将给我们的宠物这些属性:年龄,高度,重量和颜色。

像第二章所描述的那样,先在我的第一个程序中创建一个新的类Pet, 但是不要勾选main()方法

新建类Pet

你的屏幕将会显示如下:

类Pet

现在我们来声明Pet的属性和方法。类和方法以大括号的形式封装自己。每一个开大括号 {都有一个对应的闭大括号}

  1. class Pet{
  2. }

为了给类的属性声明变量,我们要为它们选定 一些数据类型。我建议年龄用int类型,重量和高度用float,宠物的颜色用String

  1. class Pet {
  2. int age;
  3. float weight;
  4. float height;
  5. String color;
  6. }

下一步就是给这个类增加一些方法。在声明一个方法前,你要决定它是否带有参数和是否需要有返回值:

新版本的类Pet将会显示如下:

  1. class Pet {
  2. int age;
  3. float weight;
  4. float height;
  5. String color;
  6. public void sleep() {
  7. System.out.println("Good night, see you tomorrow");
  8. }
  9. public void eat() {
  10. System.out.println("I’m so hungry…let me have a snack like nachos!");
  11. }
  12. public String say(String aWord) {
  13. String petResponse = "OK!! OK!! " + aWord;
  14. return petResponse;
  15. }
  16. }

这个类代表着真实世界中友善的生物:

友善的生物

让我们来讨论方法sleep()的方法签名:

  1. public void sleep()

它意味着我们能从任意类(public)中调用这个方法,并且它不会返回任何数据(void)。空白的括号意味着这个方法没有任何参数,因为它不需要外部世界的任何数据。

方法say()的方法签名如下:

  1. public String say(String aWord)

这个方法同样能从其它任何类中调用,但是它会返回一些文本,这便是关键词String之所以放在方法的名称前的原因。此外,它需要一些来自外部的文本信息,因此需要参数String aWord

eating

那么你如何决定一个方法是否需要返回一个值呢?如果一个方法需要执行一些操作,并要把最后的结果返回给调用类,那么它就需要返回一个值。你可能会说,类Pet并没有任何调用类!确实是这样,让我们创建一个调用类,称之PetMaster。这个类有方法 main(),它包含着和类Pet交流的代码。好,下面我们创建另一个类PetMaster,并且这次在Eclipse里选择创建方法main()的选项。记住,没有这个方法,你将不能在程序中运行这个类。修改在Eclipse里创建的代码后显示如下:

  1. public class PetMaster {
  2. public static void main(String[] args) {
  3. String petReaction;
  4. Pet myPet = new Pet();
  5. myPet.eat();
  6. petReaction = myPet.say("Tweet!! Tweet!!");
  7. System.out.println(petReaction);
  8. myPet.sleep();
  9. }
  10. }

别忘了按Ctrl-S保存并编译这个类!

运行这个类,会显示以下的文本信息:

I'm so hungry...let me have a snack like nachos!
OK ! ! OK ! ! Tweet ! ! Tweet ! !
Good night, see you tomorrow

PetMaster是一个调用类,它创建了一个Pet的实例对象。它声明了变量myPet,并使用new这个运算符:

  1. Pet myPet = new Pet();

这一行代码声明了类型Pet的一个变量(是的,你可以把任何你创建的类当做新的数据类型。)现在变量myPet知道电脑内存里创建实例Pet的位置,你就可以用这个变量从类Pet中调用任何方法,例如:

  1. myPet.eat();

如果方法有返回值,那么你就应该用另外的方式来调用这个方法 : 声明和方法返回值相同类型的变量,并且把值赋予给这个变量。现在你就可以调用这个方法:

  1. String petReaction;
  2. petReaction = myPet.say("Tweet ! ! Tweet ! !");

好了,现在返回值被储存在变量petReaction中哦,如果你想要看里面有什么,可以用下面的代码输出:

  1. System.out.println(petReaction);

OK!OK!

4. 继承——鱼也是宠物

我们的类Pet将会帮助我们学习Java另一种重要的特征,我们称之为继承。在现实生活里,每个人都从他或她的父母那继承了一些特征。同样的,在Java世界里,你也可以在已存在的类的基础上,创建一个新的类。

Pet所拥有的行为和属性,其它宠物也有—他们吃东西,睡觉,一些会发声音,它们的皮肤有不同的颜色,诸如此类。另一方面,宠物又是不同的—狗会吠,鱼会游泳并不会发出声音,鹦鹉讲话比狗要好。但是所有的这些宠物都吃东西,睡觉,有重量和高度。这就是为什么,比起每次创建DogParrot,或Fish都要重新写一遍,从类Pet继承一些相同的行为和属性来创建一个类Fish会容易的多。

一个特殊关键词extends就能够完成这个小把戏:

  1. class Fish extends Pet{
  2. }

你可以说我们的FishPet的一个子类,而PetFish的一个父类。换句话说,你可以把Pet当做一个模板来创建类Fish

即使你想让Fish跟现在一样,你也能使用从Pet继承的每个方法和属性。比如:

  1. Fish myLittleFish = new Fish();
  2. myLittleFish.sleep();

尽管我们还没有声明Fish的任何方法,我们仍然可以从它的父类中调用方法sleep()

Eclipse中创建一个子类是轻而易举的事!选择File->New->Class,将类命名为Fish。然后将父类中的java.lang.Object替换成Pet

继承

但是千万别忘了,我们创建Pet的子类,是为了增加一些只有鱼有的特征, 当然之前在一般宠物中的代码我们是可以重用的。

是时候来揭露一个秘密了: Java里所有的类都是从根类Object中继承而来的,不管你有没有使用extends这个词。

不是所有的宠物都能潜水,但鱼是可以的。现在让我们给Fish增加一个新的方法dive()

  1. public class Fish extends Pet{
  2. int currentDepth=0;
  3. public int dive(int howDeep){
  4. currentDepth=currentDepth + howDeep;
  5. System.out.println("Diving for " + howDeep +
  6. " feet");
  7. System.out.println("I'm at " + currentDepth +
  8. " feet below sea level");
  9. return currentDepth;
  10. }
  11. }

方法dive()有一个howDeep的属性,这个属性说明了鱼应该游多深。我们还声明了一个变量currentDepth, 它可以在你每次调用完dive()方法后存储和更新当前的深度 。这个方法将变量currentDepth当前的值返给给调用类。

可以创建另一个类FishMaster显示如下:

  1. public class FishMaster {
  2. public static void main(String[] args) {
  3. Fish myFish = new Fish();
  4. myFish.dive(2);
  5. myFish.dive(3);
  6. myFish.sleep();
  7. }
  8. }

方法main()将对象鱼实例化并调用了dive()方法两次,每次传入的参数值都不一样 。然后,它调用方法sleep() 。当你运行程序FishMaster的时候,它会输出以下信息:

Diving for 2 feet
I'm at 2 feet below sea level
Diving for 3 feet
I'm at 5 feet below sea level
Good night, see you tomorrow

你有没有注意到,除了在Fish中定义了方法,FishMaster还从它的父类Pet中调用了其它方法。这就是整个继承的重点了:你不需要从Pet中复制或者粘贴代码,你只需要使用extends,然后Fish就可以使用Pet的方法了!

还有另外一件事,尽管dive() 返回currentDepth的值,FishMaster并没有使用它。这是事实,我们的FishMaster不需要这个值,但是可能会有其它一些类仍需要使用Fish,因此它们会觉得Fish很有用。例如,假设一个类FishTrafficDispatcher,为了避免交通事故,在允许它能潜水之前,我们需要知道其它在海里的鱼的位置。

5. 方法覆盖(Override)

正如你所知道的,鱼是不能说话的(至少它们不能大声说话)。但是类FishPet继承了say() 的方法。这意味着,没有什么可以阻止你写出以下的代码:

  1. myFish.say();

那么,我们的鱼开始说话了......如果你并不想这发生,那么你可以在类Fish中覆盖Pet的方法say() 。它运行方式是这样的:如果你在一个子类中声明了一个方法,而这个方法恰好和它的父类有相同的方法签名,那么我们会使用子类的方法,而不是父类的。让我们来给类Fish增加方法say()

  1. public String say(String something){
  2. return "Don't you know that fish do not talk?";
  3. }

现在增加下面三行代码给FishMaster的方法main()

  1. String fishReaction;
  2. fishReaction = myFish.say("Hello");
  3. System.out.println(fishReaction);

运行这个程序,它会输出:

Don't you know the fish do not talk?

这证明了Pet的方法say()已经被覆盖了,换句话说,就是被禁止了。

如果一个方法的特征包括关键词final,那么这个方法就不能被覆盖,例如:
final public void sleep(){...}

开心吗!我们在这一章已经学了很多了,让我们来休息一下。

6. 扩展阅读

1、Java数据类型
http://java.sun.com/docs/books/tutorial/java/nutsandbolts/datatypes.html

2、关于继承
http://java.sun.com/docs/books/tutorial/java/concepts/inheritance.html

7. 练习

1、 用以下方法创建一个新的类Car

  1. public void start()
  2. public void stop()
  3. public int drive(int howlong)

方法 drive()要返回特定的时间内汽车行驶的距离。使用以下公式来计算距离:

distance = howlong*60;

2、 写另外一个类CarOwner,并且创建对象Car的一个实例,调用它的方法。每一个方法调用的结果的输出方式都用System.out.println()

8. 进一步的练习

创建Car的一个子类,命名为JamesBondCar,覆盖方法drive()。使用以下的公式来计算距离:

distance = howlong*180;

有创造性一点,输出一些有趣的信息!


創用 CC 授權條款
本著作係採用創用 CC 姓名標示-非商業性-禁止改作 2.5 中國大陸 授權條款授權.


[1] 以前的Gameboy游戏机需要插上一个磁卡类似的东西,游戏就存储在上面,我们把它称为卡盘。
[2] \u0000是UTF字符编码的值,每个编码代表一个字符,\u后面跟的是字符的十六进制表示。
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注