@Darcy
2017-08-11T11:17:42.000000Z
字数 11813
阅读 2148
JavaForKids
在前面的章节中,我们使用了大量的Java元素,还创建了一个Tic-Tac-Toe的游戏。但是我跳过了部分重要的Java元素,现在是时候来学习这些元素了。
每台电脑都有一个内部时钟。每个Java程序都能在其中找到系统当前的日期和时间,并用不同的格式如06/15/2004
或者是June 15, 2004
来输出展示。尽管Java有非常多的类来处理日期和时间,但只要其中的两个: java.util.Date
和 java.text.SimpleDateFormat
就可以满足你操作日期和时间的大部分需求了。
创建一个对象来存储系统当前的日期和时间是很容易的:
Date today = new Date();
System.out.println( "The date is " + today );
上面程序的输出结果大致如下:
The date is Fri Feb 27 07:18:51 EST 2004
SimpleDateFormat
类让你可以用不同的格式来显示日期和时间。具体的做法是先用你所需的格式创建SimpleDateFormat
类的实例,然后将Date
对象作为其方法 format()
的参数,并调用 format()
。下面的程序采用了几种不同的格式来输入当前日期。
import java.util.Date;
import java.text.SimpleDateFormat;
public class MyDateFormat {
public static void main(String[] args) {
// 创建一个日期对象, 输出它的默认格式
Date today = new Date();
System.out.println("The date is " + today);
// 格式化输出日期,让它像02-27-04格式一样输出
SimpleDateFormat sdf = new SimpleDateFormat("MM-dd-yy");
String formattedDate = sdf.format(today);
System.out.println("The date(dd-mm-yy) is " + formattedDate);
// 格式化输出日期,让它像27-02-2004格式一样输出
sdf = new SimpleDateFormat("dd-MM-yyyy");
formattedDate = sdf.format(today);
System.out.println("The date(dd-mm-yyyy) is " + formattedDate);
// 输出日期格式为: Fri, Feb 27, ‘04
sdf = new SimpleDateFormat("EEE, MMM d, ''yy");
formattedDate = sdf.format(today);
System.out.println("The date(EEE, MMM d, ''yy) is " + formattedDate);
// 输出日期格式为: 07:18:51 AM
sdf = new SimpleDateFormat("hh:mm:ss a");
formattedDate = sdf.format(today);
System.out.println("The time(hh:mm:ss a) is " + formattedDate);
}
}
编译并运行 MyDateFormat
类,它将打印出如下内容:
The date is Fri Feb 27 07:34:41 EST 2004
The date(dd-mm-yy) is 02-27-04
The date(dd-mm-yyyy) is 27-02-2004
The date(EEE, MMM d, ''yy) is Fri, Feb 27, '04
The time(hh:mm:ss a) is 07:34:41 AM
Java的API文档的SimpleDateFormat
部分介绍了更多的日期格式。你还能在文档中找到另一个类java.util.Calendar
,它的很多方法都能用于处理日期。
方法重载是指在一个类中定义多个同名的方法,但要求每个方法具有不同的参数类型或参数个数。比如说,类System
里面的println()
方法就可以分别带有String
,int
,char
或其他类型的参数来调用:
System.out.println(“Hello”);
System.out.println (250);
System.out.println (‘A’);
尽管看起来似乎我们是调用了同一个方法println()
三次,实际上,每次我们都是调用的不同的方法。你可能会疑惑为什么不直接创建不同名称的方法,比如printString()
,printInt()
,printChar()
?原因之一是记住一个print
的方法名要比记几个名字容易。使用方法重载还有其他的原因,解释起来会比较复杂,所以留给进阶型的书里讨论吧。
还记得我们在第四章提到的Fish
类吗?这个类有个带有一个参数的dive()
方法:
public int dive(int howDeep)
我们来创建另一个不带有任何参数的dive()
方法。这个方法会让鱼下潜5尺,除非当前深度已经超过了100尺。另外,这个新的Fish
类还声明了一个新的final
类型变量DEFAULT_DIVING
,,它的值是5尺。
现在Fish
类有两个重载方法dive()
。FishMaster
类现在可以调用任意一个重载方法dive()
:
public class FishMaster {
public static void main(String[] args) {
Fish myFish = new Fish(20);
myFish.dive(2);
myFish.dive(); // 一个新的重载方法
myFish.dive(97);
myFish.dive(3);
myFish.sleep();
}
}
构造方法同样可以重载,但是创建对象时JVM
只会选择一个参数列表匹配的来调用。 举个例子来说,如果你为Fish类添加一个不带参数的构造方法,FishMaster
类可以使用下列任一方式来创建它的实例:
Fish myFish = new Fish(20);
或者
Fish myfish = new Fish();
在这个部分你将学习程序如何在命令窗口输出问题,并懂得让用户从键盘去输入回应。这次我们将FishMaster
传递给类Fish
的硬编码(固定值)移除。现在程序将会提出问题 How Deep? ,鱼会根据用户的输入来决定下潜多少步。
你现在应该能很顺手地使用标准输入流System.out
。顺便提一句,out
变量是java.io.OutputStream
的数据类型。现在我会向你解释如何使用标准输入流System.in
,你应该也能猜到,in
变量是java.io.InputStream
的数据类型。
下一个版本的FishMaster
类展示了在系统控制台等待用户输入的命令。在用户输入一个或更多的字符并按下回车键后,JVM将输入的字符放入InputStream
流并传递给程序。
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class FishMaster {
public static void main(String[] args) {
Fish myFish = new Fish(20);
String feetString = "";
int feets;
// 创建InputStreamReader连接System.in,并把它传递给缓冲流
BufferedReader stdin = new BufferedReader(new InputStreamReader(
System.in));
// 接着往下潜直到用户按下'Q'键盘
while (true) {
System.out.println("Ready to dive.How deep?");
try {
//程序暂停,等待用户输入一段文字,按回车之后结束
feetString = stdin.readLine();
if (feetString.equals("Q")) {
// 退出程序
System.out.println("Good bye!");
System.exit(0);
} else {
// 把feetString转化成整数类型
feets = Integer.parseInt(feetString);
//鱼下潜的步数
myFish.dive(feets);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
用户和FishMaster
之间输出的对话大致如下:
Ready to dive.How deep?
14
Diving for 14 feet
I'm at 34 feet below the sea level
30
Diving for 30 feet
I'm at 64 feet below the sea level
Ready to dive.How deep?
Q
Good bye!
首先,FishMaster
通过BufferedReader
流与标准输入流System.in
相关联,但是BufferedReader
只能接受Reader
类型的参数,而InputStreamReader
是一个Reader
类型的类,它可以把二进制的字节流转化成字符流,让我们可以按字符读出,你可以认为InputStreamReader
是连接二进制流(InputStream
)和字符流(Reader
)的一个桥梁。接着程序输入一条信息 Ready to dive.How deep?
, readLine()
方法让程序暂停直到用户按下回车键。由于输入的值是String
类型,所以FishMaster
将其转为整型并调用Fish
中的dive()
方法。这些操作一直循环直到用户按下字母Q来退出程序。feetString.equals(“Q”)
是将String
类型的变量feetString
与字母Q
比较。
刚才我们使用方法readLine()
来一次获取用户输入的整行数据,你也可以用另一个方法System.in.read()
用于处理用户输入的单个字符。
当程序员参与的是一个包含了大量类的项目中时,他们通常会将这些类放在不同的包中分类。比如某个包中可以包含所有展示界面的类,而另一个包中可以包含所有事件监听器相关的类,java.io就是提供给所有输入/输出类的包,javax.swing
是图形工具操作类的包。
我们在Eclipse中创建一个新的项目PingPong
。这个项目将类分别放在两个包中:screens
和engine
。现在我们创建一个PingPongTable
类, 将Package
这行填上screens
:
按下Finish键后Eclipse生成的代码中就会包含有包名。
package screens;
public class PingPongTable {
public static void main(String[] args) {
}
}
顺便提一句,如果你的类中有一行带有关键字package
,那么除了注释之外任何内容都不能写在这行上面。
每一个包都会存放在磁盘的不同文件夹中,Eclipse
创建了文件夹screens
并将文件PingPongTable.java
放在里面。你可以到磁盘中查看到文件夹c:\eclipse\workspace\PingPong\screens
,里面有PingPongTable.java
和PingPongTable.class
文件。
再来创建另一个PingPingEngine
类并将engine
作为包名。现在项目PingPong
就有了两个包:
既然我们的两个类分别存放在不同的包(文件夹)中。那么PingPongTable
类是无法直接访问到PingPongEngine
类的,除非你添加import
语句。
package screens;
import engine.PingPongEngine;
public class PingPongTable {
public static void main(String[] args) {
PingPongEngine gameEngine = new PingPongEngine();
}
}
Java的包机制不仅能帮助你更好的管理类,还能严格控制外部包对类的访问权限。
Java中的类,方法和成员变量都可以指定它的访问级别为public
, private
, protected
或者 包级访问权限的其中一种。我们的类PingPongEngine
使用public
修饰,这意味着任意类都可以访问到它。我们来做个实验——声明 PingPongEngine
时不带有关键字public
。现在PingPongTable
类甚至不能编译通过,会报PingPongEngine can not be resolved or is not a type
(PingPongEngine 不能被转换或不是一个类型)的错误。这意味着PingPongTable
无法再访问到PingPongEngine
。
如果某个类没有指定访问级别,那么它将具备包访问权限,即这个类仅对同一个包中的类可见。
同样的,如果你忘了给 PingPongEngine
中的方法public
访问级别,那么PingPongTable
也会报出这些方法不可见的错误。下一章节中我们会写一个乒乓球游戏,让你能更好了解访问级别。
private
访问修饰词用来向外部隐藏类的成员变量或方法。就像一辆汽车:大部分人不知道汽车外壳下面有什么,也压根不知道当司机踩下刹车的时候汽车内部是如何工作的。
看下面的程序代码。在Java中,我们可以说对象Car
只向外部提供了一个public
方法:brake()
,而在brake()
内部,则可能调用了其他一些我们不需要知道的方法。比如说,如果司机刹车踩的太厉害,那么汽车的电脑装置可能会提供特殊的防抱死机制。在前面我已经提到过,Java程序可以控制像火星探测器这样复杂的机器人,更不用说简单的汽车装置了。
public class Car {
// 这个私有的成员变量只能在这个类中使用
private String brakesCondition;
// 公开类型的brake()方法里面调用了私有的不同刹车方法
public void brake(int pedalPressure) {
boolean useRegularBrakes;
useRegularBrakes = checkForAntiLockBrakes(pedalPressure);
if (useRegularBrakes == true) {
useRegularBrakes();
} else {
useAntiLockBrakes();
}
}
//这个私有的方法只能在本类中被调用
private boolean checkForAntiLockBrakes(int pressure) {
if (pressure > 100) {
return true;
} else {
return false;
}
}
//这个私有的方法只能在本类中被调用
private void useRegularBrakes() {
//正常刹车的代码
}
//私有方法
private void useAntiLockBrakes() {
// 防抱死刹车
}
}
还有一个Java关键字protected
来控制访问级别。如果你在方法声明时使用这个关键字,那么这个方法将只对当前类,当前类的子类以及同一个包中的类可见。其他包中的类都无法访问到这个方法。
面向对象语言的特性之一就是封装性,封装性可以用来隐藏或者保护类中的元素。
当你设计一个类时,隐藏的方法和成员变量对于外部是不可见的。如果汽车的设计者不把汽车外壳下的各种控制装置隐藏起来,那么司机将要处理成百的按钮,开关和计量表。
在下个部分,你将看到Score类将它的属性都用private
方式声明。
在第九章,程序ScoreWriter
创建了一个String
类型的数组来存放在文件中玩家的名字和分数。
现在是时候来学习怎样用数组存放任意对象了。
这次我们将创建一个对象展示游戏分数,这个对象将包括运动员的姓名,得分,以及最后一次游戏时间。
Score
类在下列代码片段中,它的每个属性都被声明为private
,但都有getter
和setter
方法。当然,你 可能不太明白为什么Score
对象不直接像下面那样为它的属性赋值:
Score.score = 250;
而要使用
Score.setScore(250);
试试打破常规来思考。如果稍后我们决定当玩家达到500的分数时,程序就会播放音乐。如果Score
里面有方法setScore()
,那么你需要修改的仅仅是这个方法, 直接在这个方法里面加入代码来检查分数决定是否需要播放音乐即可, 原来调用setScore()
方法的类就不需要有任何的改动了。如果调用类直接改变属性值,音乐播放的切换就必须在调用类中实现。那当你需要在两个不同的游戏程序中使用Score
的时候该怎么办呢?直接改变属性值,你需要在两个调用类中实现这些改变,但是如果本身就带有setter
方法,这些操作就能被封装起来并很便捷地被每个调用类使用。
import java.util.Date;
public class Score {
private String firstName;
private String lastName;
private int score;
private Date playDate;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public Date getPlayDate() {
return playDate;
}
public void setPlayDate(Date playDate) {
this.playDate = playDate;
}
// 拼接各种属性值,并且在最后加一个换行符。
@Override
public String toString() {
String scoreString = firstName + " " + lastName + " " + score + " " +
playDate + System.getProperty("line.separator");
return scoreString;
}
}
程序ScoreWriter2
将创建多个Score
实例对象,并一 一为其赋值。
import java.io.FileWriter;
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.Date;
public class ScoreWriter2 {
/**
* 这个main方法执行了一下的动作:
* 1. 创建了一个数组实例
* 2. 创建各个Score对象并填入数组
* 3. 把数组中的数据写入文件中
*/
public static void main(String[] args) {
FileWriter myFile = null;
BufferedWriter buff = null;
Date today = new Date();
Score scores[] = new Score[3];
// 玩家 #1
scores[0] = new Score();
scores[0].setFirstName("John");
scores[0].setLastName("Smith");
scores[0].setScore(250);
scores[0].setPlayDate(today);
// 玩家 #2
scores[1] = new Score();
scores[1].setFirstName("Anna");
scores[1].setLastName("Lee");
scores[1].setScore(300);
scores[1].setPlayDate(today);
// 玩家 #3
scores[2] = new Score();
scores[2].setFirstName("David");
scores[2].setLastName("Dolittle");
scores[2].setScore(190);
scores[2].setPlayDate(today);
try {
myFile = new FileWriter("c:\\scores2.txt");
buff = new BufferedWriter(myFile);
for (int i = 0; i < scores.length; i++) {
// 通过toString()方法来输出对象的有关信息,并输出到文件中
buff.write(scores[i].toString());
System.out.println("Writing " + scores[i].getLastName());
}
System.out.println("File writing is complete");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
buff.flush();
buff.close();
myFile.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
如果一个程序试图访问超出数组长度的数组元素,比如:score[5].getLastName()
,Java就会抛出ArrayIndexOutOfBoundsException
的异常。
ava.util
包中包含了很多用来在内存中存放多个实例对象(集合)的类。 一些比较常用的类比如ArrayList
, Vector
, HashTable
, HashMap
和List
。我将向你展示怎么使用java.util.ArrayList
。
普通数组的缺点在于你必须得提前知道数组元素的数量。记得吗?为了创建一个数组实例,你必须得在中括号中放入数字来指定数组的容量。
String[] myFriends = new String[5];
ArrayList
类没有这个限制--在不知道这个集合需要存放多少对象的情况下你就能创建它的实例,需要的时候直接添加元素就可以了。
那为什么还要使用数组,一直用ArrayList
就够了!不幸的是,没有事情是绝对完美的,你需要为ArrayList
的便捷性付出代价:ArrayList
的存取要比普通的数组慢,而且你只能在其中存入对象类型[1] ,比如你不能存放一组int型
的数字在ArrayList
中。
为了构建一个ArrayList
对象,你首先需要进行实例化。创建你打算存放在ArraList
中的对象实例,并通过调用ArrayList
的方法add()
来添加对象实例。下面这个简短的程序将一组String
对象存放到一个ArrayList
中并将这个集合打印出来。
import java.util.ArrayList;
public class ArrayListDemo {
public static void main(String[] args) {
// 创建一个存放String类型的ArrayList对象,并填充它
ArrayList<String> friends = new ArrayList<>();
friends.add("Mary");
friends.add("Ann");
friends.add("David");
friends.add("Roy");
// 朋友的数量
int friendsCount = friends.size();
// 输出ArrayList中的内容
for (int i = 0; i < friendsCount; i++) {
System.out.println("Friend #" + i + " is " + friends.get(i));
}
}
}
这个程序将打印出下面几行:
Friend #0 is Mary
Friend #1 is Ann
Friend #2 is David
Friend #3 is Roy
请注意ArrayList<String>
中的尖括号,它表示了在ArrayList
里面只能存放String
类型的对象,无论你通过add
来添加对象,或是通过get
来获取一个对象,它都是String
类型的。当然,尖括号里面可以是任意的对象类型,你声明为哪种类型,ArrayList
之后就和哪种类型绑定了,这种技术在编程中成为泛型技术。例如在下面的FishTank
例子中,当你声明为ArrayList<Fish>
之后,add
方法和get
方法ArrayList
里面的其他方法,都只能接受Fish
类型的对象了,如果你试图添加其他类型的对象,那将不能通过编译。
import java.util.ArrayList;
public class FishTank {
public static void main(String[] args) {
ArrayList<Fish> fishTank = new ArrayList<>();
Fish aFish = new Fish(20);
aFish.color = "Red";
aFish.weight = 2;
fishTank.add(aFish);
aFish = new Fish(10);
aFish.color = "Green";
aFish.weight = 5;
fishTank.add(aFish);
//迭代输出fishTank中的内容
for (Fish theFish : fishTank) {
System.out.println("Got the " + theFish.color
+ " fish that weighs " + theFish.weight + " pounds. Depth:"
+ theFish.currentDepth);
}
}
}
下面是程序FishTank
的输出内容:
Got the Red fish that weighs 2.0 pounds. Depth:20
Got the Green fish that weighs 5.0 pounds. Depth:10
在FishTank
中我们看到一个新的for
循环的语法结构,这是一种加强版的循环语法,你可以在数组或者各种集合中使用它来迭代输出容器里面的内容。
既然你已经了解过Java的访问权限, 那么, Pet
和Fish
类可以做一些小修改了。age
, color
, weight
和height
这些成员变量可以声明为protected
,而变量currentDepth
应该声明为private
。你应该添加public
的方法比如getAge()
来返回当前变量age
的值,而setAge()
用来设置age
的值,其他变量同理。
编程习惯良好的程序员会为类添加方法来供其他类调用来修改变量,而不是让其他类直接修改它的成员变量。这就是为什么在前面的部分我们将Score
类的变量声明为private
,再提供setter
和getter
方法来修改变量。
在这一章中我向你展示了不同的Java元素,这些元素看起来没有关联,但是专业的Java程序员都会经常用到这些元素。在完成这一章节的实践作业后,你应该能对这些元素如何共同使用有更好的理解。
1.Java 集合
http://t.cn/RLnvu06
2.ArrayList类
http://t.cn/RLnvEq1
3.Vector类
http://t.cn/RLnvefQ
4.Calendar类
http://t.cn/RLnP7Iv
1.为Fish
类添加一个重载的没有参数的构造方法。这个构造方法要将初始位置设为10步。FishMaster
类要创建一个如下所示的Fish
对象的实例:
Fish myFish = new Fish();
2.为Score
类添加带有四个参数的构造方法。创建一个程序ScoreWriter3
在创建Score
对象实例时就给其属性赋值,而不是通过setter
方法,比如:
Score aScore = new Score("John", "Smith", 250, today);
在网上学习如何使用Vector
类,尝试仿造ArrayListDemo
创建一个VectorDemo
程序。
本著作係採用創用 CC 姓名標示-非商業性-禁止改作 2.5 中國大陸 授權條款授權.