Skip to content

Latest commit

 

History

History
654 lines (413 loc) · 28 KB

File metadata and controls

654 lines (413 loc) · 28 KB

第7节 Java基础

❤️💕💕包含软件工程、算法与架构:设计模式、软件架构、协同开发、质量保障。更多关注我的博客:Myblog:http://nsddd.top


[TOC]

目标

  • 学习基本的 Java 语法和语义
  • 从写 Python 到写 Java 的转变

开始使用 Java

阅读**从 Python 到 Java**的前六节(27 页):

  • 程序结构和执行
  • 数据类型和表达式
  • 简单语句
  • 终端输入和输出
  • 控制语句
  • 对象和接口

在 Eclipse 中的 Java Tutor 中,完成前两个类别:Basic Java 和 Numbers & Strings。

完成这些类别后,Eclipse 中的 Java Tutor 窗格应显示已完全选中的类别,并带有绿色复选标记:

img

基础练习

假设我们在 Java 中编辑一个函数的主体,声明和使用局部变量。

int a = 5;     // (1)
if (a > 10) {  // (2)
    int b = 2; // (3)
} else {       // (4)
    b = 4;     // (5)
}              // (6)
b *= 3;        // (7)

上面从第五步开始就是错误的❌

这段代码声明了第 3 行命名的变量b,但 Java 中的局部变量只在声明它们的花括号块的*范围内。*所以这个声明在第 5 行不再有效,Java 抱怨这b是一个未声明的变量。

假设我们只需要一个名为 的变量b,请选择能够修复错误并允许编译此代码的最小更改集:

int b;`在第 1 行之后声明   //√ 

`b = 0;`在第 2 行之前分配

分配`b = 2;`而不是第 3//√ 

声明并分配`int b = 4;`而不是第 5 行

声明并分配`int b *= 3;`而不是第 7

我们需要b在if-else的作用域之外声明,这样后面的所有使用都b可以看到。但是,我们不需要在声明它时为其赋值。第 3 行的声明必须更改为简单的赋值,但第 7 行的重新赋值没问题。

我们注释掉 else if后面语句

int a = 5;
int b;
if (a > 10) {
    b = 2;
} else {
    // b = 4;
}
b *= 3;

除了要求声明变量之外*,* Java 编译器还必须确保在我们尝试访问每个变量的值之前都已为其分配了一个值。

对于这段代码,编译器看到b它只在 if-else 的一个分支中分配,因此b当我们到达最后一行时可能没有被分配任何值。

这个 Python 代码是否给出了从华氏温度到摄氏温度的准确转换?

fahrenheit = 212.0
celsius = (fahrenheit - 32) * 5 / 9
是的  //√ 

否:整数运算将导致`celsius`为零

否:整数运算将导致`celsius`向下舍入

fahrenheit是一个浮点数,所以fahrenheit - 32给出一个浮点结果。运算顺序表示我们首先将该数字乘以 5(浮点结果),然后除以 9(再次浮点)。

如果5 / 9用括号括起来以便它首先计算,那么结果在 Python 3 中仍然是正确的,因为5/9它是一个浮点数。但是在 Python 2 中,5/9使用整数运算,所以结果总是 0。

让我们用 Java 重写上一个练习中的 Python 代码。

用Java重写第一行:

int fahrenheit = 212.0;
Integer fahrenheit = 212.0;
float fahrenheit = 212.0;
Float fahrenheit = 212.0;  
double fahrenheit = 212.0;
Double fahrenheit = 212.0; //√ 

浮点数最常见的 Java 类型是double,因为它比float.

每个原始类型都有对应的对象包装类型:Floatfor floatDoublefordouble等。Java 要求我们在某些情况下使用对象类型,但我们应该尽可能选择更简单的原始类型。

现在重写第二行,其中???是您上面选择的相同类型。

做出一个既安全又易于理解的选择。

??? celsius = (fahrenheit - 32) * 5/9;
??? celsius = (fahrenheit - 32) * (5 / 9);
??? celsius = (fahrenheit - 32) * (5.0 / 9); //√ 

fahrenheit假设具有 type ,选项 1 在技术上是正确double的,因为 Java 评估表达式的顺序。首先fahrenheit - 32计算,产生一个double值,然后将该值乘以 5,产生另一个double值。所以最后除以 9 是浮点运算,是正确的。但是这种运算顺序与表达式间距所暗示的顺序不同,这似乎表明5/9将首先计算分数。表达式以一种不容易理解的方式编写。

选项 2 产生错误的答案,因为5/9它是通过整数除法完成的,并且总是产生整数 0。

选项 3 是最好的,因为它清楚明确地将除法应用于浮点数。

快照图

为了理解微妙的问题,绘制运行时发生的事情的图片对我们很有用。 快照图表示程序在运行时的内部状态——它的堆栈(正在进行的方法及其局部变量)和它的堆(当前存在的对象)。

这就是我们在 6.031 中使用快照图的原因:

  • 通过图片互相交谈(在课堂和团队会议中)
  • 为了说明基本类型与对象类型、不可变值与不可重新分配的引用、指针别名、堆栈与堆、抽象与具体表示等概念。
  • 为后续课程中更丰富的设计符号铺平道路。例如,快照图在 6.170 中泛化为对象模型。

尽管本课程中的图表使用来自 Java 的示例,但该符号可以应用于任何现代编程语言,例如 Python、JavaScript、C++、Ruby。

最简单的快照图显示了一个变量名和一个指向变量值的箭头:

int n = 1;
double x = 3.5;

当值是对象值(与原始值相反)时,它由一个按其类型标记的圆圈表示:

BigInteger val = new BigInteger("1234567890");

image-20220912093814785

快照图语法旨在灵活,不必一直显示所有细节,以便我们可以绘制简单的图表,专注于我们想要讨论的程序状态的特定方面。

例如,右边的图表都是在快照图中显示字符串变量的合理方式。

String s = "hello";

image-20220912093827814

我们可能会在不同的上下文中使用不同的图表。有时我们关心 的特定值s,有时我们不关心。有时我们想强调 aString是一个对象值(与原始值相反),有时这并不相关。

当我们想要显示有关对象值的更多详细信息时,我们将在对象圆圈内写入字段名称,并用箭头指向它们的值。

Point pt = new Point(5, -3);

image-20220912093841165

更详细地说,变量可以包括它们声明的类型。有些人喜欢写x:int而不是写int x,但两者都很好。

变异值与重新分配变量

快照图为我们提供了一种可视化更改变量和更改值之间区别的方法:

  • 当您分配给变量或字段时,您正在更改变量的箭头指向的位置。您可以将其指向不同的值。
  • 当您更改可变对象(例如数组或列表)的内容时,您正在更改该值内的引用。

重新分配和不可变值

例如,如果我们有一个String变量,我们可以将它的值从s重新赋值。"a" "ab"

String s = "a";
s = s + "b";

image-20220912094422921

String是一个例子不可变type,一种类型,其值一旦创建就永远不会改变。不变性是本课程中的主要设计原则,我们将在以后的阅读中更多地讨论它。

在快照图中,当我们想强调像 一样的对象的不变性时String,我们用双边框来绘制它,如此处的图表所示。

可变值

相比之下,StringBuilder(另一个内置的 Java 类)是一个可变的表示字符串的对象,并且它具有更改对象值的方法:

StringBuilder sb = new StringBuilder("a");
sb.append("b");

这两个快照图看起来非常不同,这很好:可变性和不变性之间的区别将在使我们的代码免受错误影响方面发挥重要作用。

不可重新分配的引用

Java 还为我们提供了引用的不变性:分配一次并且永远不会重新分配的变量。做一个参考不可重新分配, 用关键字声明它final

final int n = 5;

在这段代码中,n永远不能重新分配;它将在其整个生命周期中引用值 5。如果 Java 编译器不相信您的final变量只会在运行时分配一次,那么它将产生编译器错误。因此final,您可以静态检查不可重新分配的引用。

在快照图中,当我们想要关注可重新分配性时,我们将使用双箭头表示不可重新分配的 ( final) 引用。右图显示了一个id永远不会改变的对象(它不能重新分配给不同的数字),但它age可以改变。

请注意,我们可以对可变值进行不可重新分配的引用,即使我们指向同一个对象,其值也会发生变化:

final StringBuilder sb = new StringBuilder("a");
sb.append("b");

我们也可以有一个可重新分配的参考到一个不可变的值,其中变量的值可以改变,因为它可以重新指向不同的对象:

String s = "a";
s = "ab";

双线有助于强调快照图部分的不可更改性。但是当可重新分配或可变性很明显,或者与讨论无关时,我们将保持图表简单,只使用单线箭头和对象边框。

== 与 equals()

Java 有两种不同的方法来测试值的相等性,具体取决于值是基元还是对象:

  • 运算符比较基元的==值。例如,5 == 5返回 true,如'a' == 'a'(这个是char类型但是不是String类型)
  • .equals()方法比较对象的值。例如,"abc".equals("abc")返回 true。

在 Python 中,**原始值和对象值之间没有区别,您可以同时==用于这两个目的。**这可能会导致 Java 中的混乱和错误——对您尝试比较的类型使用错误的相等类型。

幸运的是,在原始类型上使用equals()很容易掌握。 5.equals(5)产生静态错误,因为 Java 不允许在原始类型上调用任何方法。

但是另一个错误,==用于比较对象值是否相等,要痛苦得多,因为==它在 Java 中被重载了。在对象类型上使用时,==测试两个表达式是否引用内存中的同一个对象。就我们一直在绘制的快照图而言,==如果它们的箭头指向同一个对象气泡,则有两个引用。在 Python 中,此运算符称为is.

所以如果程序的状态看起来像右图,那么:

image-20220912095733188

  • x.equals(y)返回 true 因为 x 和 y 具有相同的字符

  • x == y返回 true 因为 x 和 y 指向同一个对象

  • x.equals(z)返回 true 因为 x 和 z 具有相同的字符

  • x == z返回false因为 x 和 z 指向不同的对象

😎基于上面的对比,我们很熟悉equals()==的使用方法,基本类型可以用==,但是涉及到方法,对象值等等就需要equals()

作为基本规则:

  • 用于==比较原始值,如整数、字符和双精度值。
  • 用于equals()比较对象值,如列表、数组、字符串和其他对象。

请记住,在 Java 中:

  • char值是原语,只代表一个字符。char文字总是单引号,例如'a'.
  • String值是对象,表示零个或多个字符的字符串。文字是双String引号,例如"abc"and ""

Java 集合

列表、集合和映射

JavaList类似于Python列表。 一个List包含零个或多个对象的有序集合,其中同一个对象可能出现多次。我们可以在 中添加和删除项目List,这些项目将增长和缩小以适应其内容。

示例List操作:

java 描述 Python
int count = lst.size(); 计算元素的数量 count = len(lst)
lst.add(e); 在末尾追加一个元素 lst.append(e)
if (lst.isEmpty()) ... 测试列表是否为空 if not lst: ...
lst.contains(e) 测试一个元素是否在列表中 e in lst

在快照图中,我们将 a 表示List为一个对象,其索引绘制为字段:

此列表cities可能代表从波士顿到波哥大再到巴塞罗那的旅行。

一个Map类似于Python 字典。 在 Python 中,映射的必须是可散列的。Java 有一个类似的要求,我们将在遇到 Java 对象之间的相等性如何工作时讨论它。

示例Map操作:

爪哇 描述 Python
map.put(key, val) 添加映射键→val map[key] = val
map.get(key) 获取键的值 map[key]
map.containsKey(key) 测试map是否有key key in map
map.remove(key) 删除映射 del map[key]

在快照图中,我们将 a 表示Map为包含键/值对的对象:

turtles映射包含Turtle分配给String键的对象:Bob、Buckminster 和 Buster。

一个Set是零个或多个唯一对象的无序集合。 像数学Python 集一样——并且与 a 不同List——一个对象不能多次出现在一个集中。要么进,要么出。就像地图的键一样,Python 集中的对象必须是可散列的,Java 也有类似的要求。

示例Set操作:

爪哇 描述 Python
s1.contains(e) 测试集合是否包含元素 e in s1
s1.containsAll(s2) 测试是否s1 ⊇ s2 s1.issuperset(s2) s1 >= s2
s1.removeAll(s2) s1中删除s2 s1.difference_update(s2) s1 -= s2

在快照图中,我们将 a 表示Set为具有无名字段的对象:

这里我们有一组整数,没有特定的顺序:42、1024 和 -7。

字面量

Python 为创建列表提供了方便的语法:

lst = [ "a", "b", "c" ]

和地图:

map = { "apple": 5, "banana": 7 }

Java 没有。 它确实为数组提供了文字语法:

String[] arr = { "a", "b", "c" };

但这会创建一个数组,而不是一个List. 我们可以使用实用函数List.of来创建一个Listfrom 参数:

List.of("a", "b", "c");

Listcreated with有一个List.of重要的限制:它是不可变的!因此,一旦创建了列表,我们就无法添加、删除或替换元素。

Java 还提供Set.of了创建不可变集和Map.of创建不可变映射的功能:

Set.of("a", "b", "c");
Map.of("apple", 5, "banana", 7);

泛型:声明 List、Set 和 Map 变量

与 Python 集合类型不同,使用 Java 集合,我们可以限制集合中包含的对象的类型。当我们添加一个项目时,编译器可以执行静态检查以确保我们只添加适当类型的项目。然后,当我们拉出一个项目时,我们保证它的类型将是我们所期望的。

下面是声明一些变量来保存集合的语法:

List<String> cities;        // a List of Strings
Set<Integer> numbers;       // a Set of Integers
Map<String,Turtle> turtles; // a Map with String keys and Turtle values

由于泛型的工作方式,我们无法创建原始类型的集合。例如*,*Set<int>不起作用。然而,正如我们之前看到的,s 有一个我们可以使用的包装器(例如)。

int Integer Set<Integer> numbers

为了更容易使用这些包装器类型的集合,Java 进行了一些自动转换。如果我们声明List<Integer> sequence了 ,则此代码有效:

sequence.add(5);              // wrap 5 as an Integer object, and append it to the sequence
int second = sequence.get(1); // get the second Integer element, and unwrap it into an int

ArrayLists 和 LinkedLists:创建列表

正如我们很快就会看到的,Java 帮助我们区分类型的规范——它有什么作用?– 以及实现– 代码是什么?

List, Set, 和Map都是接口:它们定义了这些各自的类型如何工作,但它们不提供实现代码。有几个优点,但一个潜在的优点是我们这些类型的用户可以在不同的情况下选择不同的实现。

以下是创建一些实际Lists 的方法:

List<String> firstNames = new ArrayList<String>();
List<String> lastNames = new LinkedList<String>();

如果左右的泛型类型参数相同,Java 可以推断发生了什么,并为我们节省一些输入:

List<String> firstNames = new ArrayList<>();
List<String> lastNames = new LinkedList<>();

ArrayList并且LinkedList是 的两个实现List。两者都提供 的所有操作List,并且这些操作必须按照List. 在本例中,firstNameslastNames的行为方式相同;如果我们交换使用哪一个ArrayListvs. LinkedList,我们的代码不会中断。

不幸的是,这种选择能力也是一种负担:我们并不关心 Python 列表是如何工作的,我们为什么要关心我们的 Java 列表是ArrayLists还是LinkedLists?由于唯一的区别是性能>

如有疑问,请使用ArrayList.

如果你想初始化一个ArrayListor LinkedList,你可以给它另一个集合作为参数:

List<String> firstNames = new ArrayList<>(List.of("Huey", "Dewey", "Louie"));
List<String> lastNames = new ArrayList<>(Set.of("Duck"));

List.of和之间的一个关键区别ArrayList是可变性:List.of("Huey", "Dewey", "Louie")生成三个字符串的不可变列表,new ArrayList<>(List.of("Huey", "Dewey", "Louie"))生成一个用这些字符串初始化的可变列表。如果您需要一个初始化的可变列表,这比多次调用add().

HashSets 和 HashMaps:创建 Sets 和 Maps

HashSet是我们对 s 的默认选择Set

Set<Integer> numbers = new HashSet<>();

Java 还提供了带有实现的排序集TreeSet

对于Map默认选择是HashMap

Map<String,Turtle> turtles = new HashMap<>();

迭代

所以也许我们有:

List<String> cities        = new ArrayList<>();
Set<Integer> numbers       = new HashSet<>();
Map<String,Turtle> turtles = new HashMap<>();

一个非常常见的任务是遍历我们的城市/数字/海龟/等。

在 Python 中:

for city in cities:
    print(city)

for num in numbers:
    print(num)

for key in turtles:
    print("%s: %s" % (key, turtles[key]))

Java 提供了类似的语法来迭代Lists 和Sets 中的项目。

这是Java:

for (String city : cities) {
    System.out.println(city);
}

for (int num : numbers) { 
    System.out.println(num);
}

请注意,在上面的第二个示例中,我们声明int num而不是Integer num,即使numbers是 a List<Integer>。这是因为 Java 会自动在 和 之间进行转换int,并且尽可能Integer使用更简单的原始int类型优于对象包装器。Integer例如,int相等更简单,更不容易出错,如上面的== vs. equals中所讨论的。

我们不能Map以这种方式迭代 s 本身,但我们可以像在 Python 中那样迭代键:

for (String key : turtles.keySet()) {
    System.out.println(key + ": " + turtles.get(key));
}

这种for循环在底层使用了Iterator一种设计模式,我们将在后面的课程中看到。

警告:当你迭代它时,小心不要改变一个集合。添加、删除或替换元素会破坏迭代,甚至会导致程序崩溃。我们将在以后的课程中更详细地讨论原因。请注意,此警告也适用于 Python。下面的代码不符合您的预期:

numbers = [100,200,300]
for num in numbers:
    numbers.remove(num) # danger!!! mutates the list we're iterating over
print(numbers) # list should be empty here -- is it?

使用索引迭代

如果您愿意,Java 提供了不同的for循环,我们可以使用这些循环使用其索引来遍历列表:

for (int ii = 0; ii < cities.size(); ii++) {
    System.out.println(cities.get(ii));
}

除非我们真的需要索引值ii,否则这段代码很冗长并且有更多的地方可以隐藏错误(从 1 开始,而不是 0,使用<=而不是<,使用错误的变量名来出现ii,...)如果你能够。

阅读练习

收藏品

X 标记位置

枚举

有时,一个类型有一组小的、有限的不可变值,例如:

  • 一年中的月份:一月、二月、……、十一月、十二月
  • 一周中的几天:周一、周二、……、周六、周日
  • 罗盘点:北、南、东、西
  • 可用颜色:黑色,灰色,红色,...

当值集很小且有限时,将所有值定义为命名常量是有意义的,Java 将其称为枚举并用enum构造表示。

public enum Month { 
    JANUARY, FEBRUARY, MARCH, APRIL, 
    MAY, JUNE, JULY, AUGUST, 
    SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER;
}

public enum PenColor { 
    BLACK, GRAY, RED, PINK, ORANGE, 
    YELLOW, GREEN, CYAN, BLUE, MAGENTA;
}

您可以PenColor在变量或方法声明中使用枚举类型名称:

PenColor drawingColor;

引用枚举的值,就好像它们被命名为静态常量一样:

drawingColor = PenColor.RED;

请注意,枚举是一种独特的新类型。较旧的语言,如 Python 2 和 Java 的早期版本,倾向于使用数字常量或字符串文字来表示这样的有限值集。但是枚举更加类型安全,因为它可以捕获类型不匹配之类的错误:

int month = TUESDAY;  //  no error if integers are used
Month month = DayOfWeek.TUESDAY; // static error if enumerations are used 

或拼写错误:

String color = "REd";        // no error, misspelling isn't caught
PenColor drawingColor = PenColor.REd; // static error when enumeration value is misspelled

Python 3 有 enumerations,类似于 Java 的,虽然没有静态类型检查。

Java API 文档

本阅读材料的前面部分有许多指向作为Java 平台 API一部分的类的文档的链接。

API代表应用程序编程接口。如果你想编写一个与 Facebook 对话的应用程序,Facebook 会发布一个 API(实际上不止一个,用于不同的语言和框架),你可以针对它进行编程。Java API 是一组用于编程几乎任何东西的通用工具。

  • java.lang.String是 的全称String。我们可以String通过使用来创建类型的对象"double quotes"
  • java.lang.Integer和其他原始包装类。在大多数情况下,Java 会自动在原始类型和包装(或“装箱”)类型之间进行转换。
  • java.util.List就像 Python 列表,但在 Python 中,列表是语言的一部分。在 Java 中,List是用……Java 实现的!
  • java.util.Map就像一个 Python 字典。
  • java.io.File表示磁盘上的文件。看一下提供的方法File:我们可以测试文件是否可读,删除文件,查看上次修改的时间……
  • java.io.FileReader让我们阅读文本文件。
  • java.io.BufferedReader让我们高效地阅读文本,它还提供了一个非常有用的功能:一次阅读整行。

让我们仔细看看BufferedReader. 这里有很多与我们尚未讨论的 Java 特性相关的事情!保持头脑清醒,专注于下面粗体字

img

右上角是搜索框。您可以使用它跳转到类或接口,或直接跳转到特定方法。

Class BufferedReader标题下是类层次结构和已实现接口BufferedReader的列表。一个对象可以使用所有这些类型的所有方法(加上它自己的方法)。BufferedReader

接下来我们看到直接子类,对于接口,实现类。这可以帮助我们找到,例如,那HashMapMap.

接下来:类的描述。有时这些描述有点迟钝,但这是你应该首先去了解一个类的地方。

img

如果你想创建一个新BufferedReader构造函数摘要是第一个看的地方。构造函数并不是在 Java 中获取新对象的唯一方法,但它们是最常见的。

img

接下来:方法摘要列出了我们可以在一个BufferedReader对象上调用的所有方法。

下面是对每个方法和构造函数的详细描述。 单击构造函数或方法以查看详细说明。 这是您应该了解方法的作用的第一个地方。

每个详细描述包括:

  • 方法签名:我们看到返回类型、方法名称和参数。我们也看到了例外。目前,这些通常意味着该方法可能会遇到的错误。
  • 完整的描述
  • 参数:方法参数的描述。
  • 以及该方法返回的内容的描述。

规格

这些详细说明是规范。它们允许我们使用诸如StringMap或之类的工具,BufferedReader 而无需阅读或理解实现它们的代码。

阅读、写作、理解和分析规范将是我们在 6.031 中的首要任务之一,从几节课开始。

<iframe class="exercises-status" src="https://6031.mit.edu/handx/sp21-java/status.php" style="box-sizing: border-box; width: 232.164px; height: 260px; border: none; position: absolute; right: -232.164px;"></iframe

END 链接