笨办法学 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


相关文章
|
8月前
|
人工智能 自然语言处理 物联网
阿里万相重磅开源,人工智能平台PAI一键部署教程来啦
阿里云视频生成大模型万相2.1(Wan)重磅开源!Wan2.1 在处理复杂运动、还原真实物理规律、提升影视质感以及优化指令遵循方面具有显著的优势,轻松实现高质量的视频生成。同时,万相还支持业内领先的中英文文字特效生成,满足广告、短视频等领域的创意需求。阿里云人工智能平台 PAI-Model Gallery 现已经支持一键部署阿里万相重磅开源的4个模型,可获得您的专属阿里万相服务。
|
7月前
|
消息中间件 Cloud Native 程序员
为什么公共云的弹性能力很难被发挥出来?
为什么公共云的弹性能力很难被发挥出来?
|
存储 数据库 Android开发
10分钟手把手教你用Android手撸一个简易的个人记账App(二)
接下来就来讲解,如何从0到1实现一个简易的个人记账系统。
10分钟手把手教你用Android手撸一个简易的个人记账App(二)
|
6天前
|
存储 关系型数据库 分布式数据库
PostgreSQL 18 发布,快来 PolarDB 尝鲜!
PostgreSQL 18 发布,PolarDB for PostgreSQL 全面兼容。新版本支持异步I/O、UUIDv7、虚拟生成列、逻辑复制增强及OAuth认证,显著提升性能与安全。PolarDB-PG 18 支持存算分离架构,融合海量弹性存储与极致计算性能,搭配丰富插件生态,为企业提供高效、稳定、灵活的云数据库解决方案,助力企业数字化转型如虎添翼!
|
17天前
|
弹性计算 关系型数据库 微服务
基于 Docker 与 Kubernetes(K3s)的微服务:阿里云生产环境扩容实践
在微服务架构中,如何实现“稳定扩容”与“成本可控”是企业面临的核心挑战。本文结合 Python FastAPI 微服务实战,详解如何基于阿里云基础设施,利用 Docker 封装服务、K3s 实现容器编排,构建生产级微服务架构。内容涵盖容器构建、集群部署、自动扩缩容、可观测性等关键环节,适配阿里云资源特性与服务生态,助力企业打造低成本、高可靠、易扩展的微服务解决方案。
1318 7
|
4天前
|
存储 人工智能 Java
AI 超级智能体全栈项目阶段二:Prompt 优化技巧与学术分析 AI 应用开发实现上下文联系多轮对话
本文讲解 Prompt 基本概念与 10 个优化技巧,结合学术分析 AI 应用的需求分析、设计方案,介绍 Spring AI 中 ChatClient 及 Advisors 的使用。
291 128
AI 超级智能体全栈项目阶段二:Prompt 优化技巧与学术分析 AI 应用开发实现上下文联系多轮对话
|
3天前
|
监控 JavaScript Java
基于大模型技术的反欺诈知识问答系统
随着互联网与金融科技发展,网络欺诈频发,构建高效反欺诈平台成为迫切需求。本文基于Java、Vue.js、Spring Boot与MySQL技术,设计实现集欺诈识别、宣传教育、用户互动于一体的反欺诈系统,提升公众防范意识,助力企业合规与用户权益保护。
|
16天前
|
机器学习/深度学习 人工智能 前端开发
通义DeepResearch全面开源!同步分享可落地的高阶Agent构建方法论
通义研究团队开源发布通义 DeepResearch —— 首个在性能上可与 OpenAI DeepResearch 相媲美、并在多项权威基准测试中取得领先表现的全开源 Web Agent。
1386 87