笨办法学 Java(四)(1)

简介: 笨办法学 Java(四)(1)

练习 55:记录数组

记录很棒,数组更好,但是当你把记录放入数组时,这个生活中几乎没有你不能编码的东西。

1 class Student
 2 {
 3     String name;
 4     int credits;
 5     double gpa;
 6 }
 7 
 8 public class StudentDatabase
 9 {
10     public static void main( String[] args )
11     {
12         Student[] db;
13         db = new Student[3];
14 
15         db[0] = new Student();
16         db[0].name = "Esteban";
17         db[0].credits = 43;
18         db[0].gpa = 2.9;
19 
20         db[1] = new Student();
21         db[1].name = "Dave";
22         db[1].credits = 15;
23         db[1].gpa = 4.0;
24 
25         db[2] = new Student();
26         db[2].name = "Michelle";
27         db[2].credits = 132;
28         db[2].gpa = 3.72;
29 
30         for ( int i=0; i<db.length; i++ )
31         {
32             System.out.println("Name: " + db[i].name);
33             System.out.println("\tCredit hours: " + db[i].credits);
34             System.out.println("\tGPA: " + db[i].gpa + "\n");
35         }
36 
37         int max = 0;
38         for ( int i=1; i<db.length; i++ )
39             if ( db[i].gpa > db[max].gpa )
40                 max = i;
41 
42         System.out.println(db[max].name + " has the highest GPA.");
43     }
44 }

你应该看到什么

Name: Esteban
Credit hours: 43
GPA: 2.9
Name: Dave
Credit hours: 15
GPA: 4.0
Name: Michelle
Credit hours: 132
GPA: 3.72
Dave has the highest GPA.

当你看到变量定义中某物的右侧有方括号时,那就是“某物的数组”。实际上,由于这本书快要结束了,也许我应该解释一下public static void main的业务。至少部分地。

public static void main( String[] args )

这一行声明了一个名为 main 的函数。该函数需要一个参数:名为 args 的字符串数组(缩写为“arguments”)。该函数不返回任何值;它是void

无论如何。

第 12 行声明了db作为一个可以容纳“学生数组”的变量。还没有数组,只是一个可能容纳数组的变量。就像我们说…

int n;

…还没有整数。变量n可能容纳一个整数,但它里面还没有数字。n被声明但未定义。同样,一旦第 12 行执行完毕,db是一个可能指向学生数组的变量,但仍未定义。

幸运的是,我们不必等太久;第 13 行通过创建一个实际的具有三个槽的学生数组来初始化 db。此时,db 被定义,db.length3,db 有三个合法索引:012

好吧,在这一点上,db是一个学生记录的数组。除了它不是。db是一个学生变量的数组,每个变量都可能容纳一个学生记录,但没有一个变量是这样的。数组中的所有三个槽都未定义。

(从技术上讲,它们包含值null,这是 Java 中引用变量在其中没有对象时具有的特殊值。)

因此,在第 15 行,重要的是创建一个学生对象并将其存储到数组的第一个槽(索引0)中。然后在第 16 行,我们可以将一个值存储到数组 db 中索引0的学生记录的名字字段中。

让我们从外到内追踪它:

表达式 类型 描述
db students[] 一组学生记录
db[0] students 一个单独的学生记录(第一个)
db[0].name String 数组中第一个学生的name字段
db.name 错误 整个数组没有一个名字字段

因此,第 16 行将一个值存储到数组中第一个记录的name字段中。第 17 和 18 行将值存储到该记录中的其余字段中。第 20 到 28 行创建并填充数组中的其他两个记录

尽管在第 30 到 34 行,我们使用循环在屏幕上显示所有的值。

然后,第 37 到 42 行找到了 GPA 最高的学生。这值得更详细解释。在第 37 行,定义了一个名为 max 的int。但 max 不会保存最高 GPA 的值;它只会保存它的索引。

所以当我把0放入 max 时,我的意思是“在代码的这一点上,就我所知,最高分的学生

在槽0中。”这可能不是真的,但由于我们还没有查看数据库中的任何值,这是一个很好的起点。

然后在第 38 行,我们设置循环来查看数组的每个槽。然而,请注意,循环从索引1(第二个槽)开始。为什么?

因为 max 已经是0。所以如果 i 也从0开始,那么if语句将进行以下比较:

if ( db[0].gpa > db[0].gpa )

…这是浪费。因此,通过从1开始,第一次循环时,if语句将进行以下比较:

if ( db[1].gpa > db[0].gpa )

“如果戴夫的 GPA 大于埃斯特万的 GPA,则将 max 从0更改为 i(1)的当前值。”

因此,当循环结束时,max包含具有最高 GPA 的记录的索引。这正是我们在第 42 行显示的内容。

学习演练

  1. 将数组的容量更改为4而不是 3。不改变任何其他内容,编译并运行程序。你明白为什么程序会崩溃吗?
  2. 现在添加一些代码,将值放入新学生的字段中。给这个新学生一个比“Dave”更高的 GPA,并确认代码正确地将他们标记为具有最高的 GPA。
  3. 更改代码,使其查找具有最少学分的人,而不是具有最高 GPA 的人。

练习 56:从文件中读取记录的数组(温度重访)

这个练习从互联网上的一个文件中填充了一个记录数组。到目前为止,您应该知道您是否需要下载此文件的副本,还是您的计算机可以直接从互联网上打开它。

与本书中迄今为止使用的所有其他文件不同,这个数据文件正是我从戴顿大学的平均日温度档案中下载的。这意味着三件事:

  1. 文件的第一行没有数字告诉我们有多少记录。
  2. 除了温度之外,每个记录还包括样本的月份、日期和年份。
  3. 文件中有错误数据。特别是,“当数据不可用时,我们使用‘-99’作为无数据标志。”

因此,有些天的温度是-99。我们将不得不在代码中处理这个问题。

1 import java.util.Scanner;
 2 
 3 class TemperatureSample
 4 {
 5     int month, day, year;
 6     double temperature;
 7 }
 8 
 9 public class TemperaturesByDate
10 {
11     public static void main(String[] args) throws Exception
12     {
13         String url = 
"http://learnjavathehardway.org/txt/avg­daily­temps­with­dates­atx.txt";
14         Scanner inFile = new Scanner((new java.net.URL(url)).openStream());
15 
16         TemperatureSample[] tempDB = new TemperatureSample[10000];
17         int numRecords, i = 0;
18 
19         while ( inFile.hasNextInt() && i < tempDB.length )
20         {
21             TemperatureSample e = new TemperatureSample();
22             e.month = inFile.nextInt();
23             e.day   = inFile.nextInt();
24             e.year  = inFile.nextInt();
25             e.temperature = inFile.nextDouble();
26             if ( e.temperature == ­99 )
27                 continue;
28             tempDB[i] = e;
29             i++;
30         }
31         inFile.close();
32         numRecords = i;
33 
34         System.out.println(numRecords + " daily temperatures loaded.");
35 
36         double total = 0, avg;
37         int count = 0;
38         for ( i=0; i<numRecords; i++ )
39         {
40             if ( tempDB[i].month == 11 )
41             {
42                 total += tempDB[i].temperature;
43                 count++;
44             }
45         }
46 
47         avg = total / count;
48         avg = roundToOneDecimal(avg);
49         System.out.println("Average daily temperature over " + count + " days 
in November: " + avg);
50     }
51 
52     public static double roundToOneDecimal( double d )
53     {
54         return Math.round(d*10)/10.0;
55     }
56 }

你应该看到什么

6717 daily temperatures loaded.
Average daily temperature over 540 days in November: 59.7

第 3 到 7 行声明了我们的记录,它将存储单个平均日温度值(一个

double),还有月份、日期和年份的字段。

第 16 行定义了一个记录数组。但是我们有一个问题。我们无法在不提供容量的情况下定义数组,而在看到文件中有多少记录之前,我们不知道需要多大的容量。这个问题有三种可能的解决方案:

  1. 不要使用数组。使用其他东西,比如一个可以在添加条目时自动增长的数组。这实际上可能是正确的解决方案,但是“其他东西”超出了本书的范围。
  2. 读取文件两次。首先只计算记录的数量,然后使用完美大小创建数组。然后再次读取文件将所有值读入数组。这样做很慢,但有效。
  3. 不要担心使数组的大小合适。只需使其“足够大”。然后在读取它们时计算实际拥有的记录数量,并在任何循环中使用该计数,而不是数组的容量。这并不完美,但它有效且简单。编写软件有时需要妥协,这就是其中之一。

因此,第 16 行声明了数组并定义为有一万个槽位:“足够大”。

在第 19 行,我们开始一个循环,读取文件中的所有值。我们使用索引变量i来跟踪数组中下一个需要填充的槽位。因此,只要文件中还有更多整数,并且我们的数组容量还没有用完,我们的循环就会继续。

仅仅因为我们通过使数组“足够大”来节省了一些步骤,并不意味着我们会对此感到愚蠢。如果文件最终比我们的数组容量大,我们希望尽早停止读取文件,而不是因为 ArrayIndexOutOfBounds 异常而使程序崩溃。

21 行定义了一个名为e的 TemperatureSample 记录。22 到 25 行将文件中的下几个值加载到该记录的适当字段中。

但是!请记住,我们的文件中有“缺失”的值。有些天的温度读数是

-99,所以我们在第 26 行放置了一个if语句来检测它,然后将它们放入我们的数据库中。

然后在第 27 行有一些新东西:Java 关键字continuecontinue只能在循环体内合法。它的意思是“跳过循环体中剩余的代码行,然后返回顶部进行下一次迭代。”

这实际上丢弃了当前(无效)记录,因为它跳过了第 28 和 29 行,这两行将当前记录存储在数组中的下一个可用槽位中,然后增加索引。

有些人不喜欢使用continue,他们会这样写:

if ( e.temperature != ­99 )
{
    tempDB[i] = e; i++;
}

这也完全没问题。只有当温度不是-99时,才将此条目放入数组中。我更喜欢使用continue,因为这样的代码对我来说更清晰,但是理智的人可能会有不同意见。选择对你来说最有意义的方式。

一旦在第 31 行完成循环,我们确保关闭文件,然后将最终索引存储到 numRecords 中,以便我们可以在任何循环中使用它,而不是tempDB.length。毕竟,我们使数组比我们需要的大,最后的 3283 个槽(在这个例子中)是空的。仅循环到 numRecords 会更有效一些,我们可以通过这种方式避免检查任何无效的记录。

在第 34 行,我们在屏幕上显示记录的数量,这可以帮助您查看是否在读取时出现了任何问题。

第 36 至 45 行循环遍历所有我们的记录。任何月份字段为11(11 月)的记录都会被添加到一个运行总数中,我们也在此过程中计算匹配记录的总数。

然后,当循环结束时,我们可以通过将总和除以计数来获得数据库中所有 11 月份每日温度的平均值。

现在,我的程序的第一个版本的整体平均温度是59.662962962963。这不仅看起来不好,而且不正确:所有输入温度只精确到十分之一度。因此,显示具有十几个有效数字的结果看起来比实际更准确。

因此,在第 52 至 55 行,您将找到一个小小的函数,用于将数字四舍五入到小数点后一位。据我所知,Java 没有内置的此功能,但它确实有一个内置的将数字四舍五入到最接近的整数的函数:Math.round()。所以我将数字乘以十,四舍五入,然后再除以十。也许有更好的方法,但我喜欢这样做。

第 48 行将平均温度作为参数传递给我的函数,然后取舍返回值并将其存储为avg的新值。

学习演练

  1. 访问戴顿大学的温度档案,并下载一个附近城市的温度数据文件!让你的代码从该文件中读取数据。
  2. 更改代码以查找其他内容,比如二月份的最高温度或其他你感兴趣的内容。
  3. 尝试在屏幕上打印整个 TemperatureSample 记录。类似于这样:
TemperatureSample ts = tempDB[0]; System.out.println( ts );

请注意,它不会打印像 ts.year 这样的整数或像ts.temperature这样的双精度;它试图在屏幕上显示整个记录。编译并运行文件。屏幕上显示了什么?

尝试更改索引以从数组中提取不同的值,并查看它如何改变打印出来的内容。


笨办法学 Java(四)(2)https://developer.aliyun.com/article/1481923


相关文章
|
6月前
|
存储 Java Go
笨办法学 Java(四)(3)
笨办法学 Java(四)(3)
34 1
|
6月前
|
存储 Java
笨办法学 Java(三)(1)
笨办法学 Java(三)(1)
39 0
|
6月前
|
存储 Java 索引
笨办法学 Java(四)(2)
笨办法学 Java(四)(2)
38 0
|
6月前
|
存储 安全 Java
笨办法学 Java(三)(2)
笨办法学 Java(三)(2)
46 0
|
6月前
|
存储 Java 程序员
笨办法学 Java(二)(3)
笨办法学 Java(二)(3)
54 0
|
6月前
|
存储 Java 编译器
笨办法学 Java(二)(1)
笨办法学 Java(二)(1)
57 0
|
6月前
|
存储 Java 程序员
笨办法学 Java(一)(2)
笨办法学 Java(一)(2)
76 0
|
6月前
|
存储 Java 程序员
笨办法学 Java(一)(1)
笨办法学 Java(一)
71 0
|
6月前
|
存储 人工智能 网络协议
笨办法学 Java(二)(2)
笨办法学 Java(二)
57 0
|
6月前
|
存储 Java 程序员
笨办法学 Java(三)(3)
笨办法学 Java(三)(3)
96 0
下一篇
无影云桌面