❤️💕💕包含软件工程、算法与架构:设计模式、软件架构、协同开发、质量保障。更多关注我的博客:Myblog:http://nsddd.top
[TOC]
- 学习基本的 Java 语法和语义
- 从写 Python 到写 Java 的转变
阅读**从 Python 到 Java**的前六节(27 页):
- 程序结构和执行
- 数据类型和表达式
- 简单语句
- 终端输入和输出
- 控制语句
- 对象和接口
在 Eclipse 中的 Java Tutor 中,完成前两个类别:Basic Java 和 Numbers & Strings。
完成这些类别后,Eclipse 中的 Java Tutor 窗格应显示已完全选中的类别,并带有绿色复选标记:
假设我们在 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
.
每个原始类型都有对应的对象包装类型:Float
for float
、Double
fordouble
等。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");
快照图语法旨在灵活,不必一直显示所有细节,以便我们可以绘制简单的图表,专注于我们想要讨论的程序状态的特定方面。
例如,右边的图表都是在快照图中显示字符串变量的合理方式。
String s = "hello";
我们可能会在不同的上下文中使用不同的图表。有时我们关心 的特定值s
,有时我们不关心。有时我们想强调 aString
是一个对象值(与原始值相反),有时这并不相关。
当我们想要显示有关对象值的更多详细信息时,我们将在对象圆圈内写入字段名称,并用箭头指向它们的值。
Point pt = new Point(5, -3);
更详细地说,变量可以包括它们声明的类型。有些人喜欢写x:int
而不是写int x
,但两者都很好。
快照图为我们提供了一种可视化更改变量和更改值之间区别的方法:
- 当您分配给变量或字段时,您正在更改变量的箭头指向的位置。您可以将其指向不同的值。
- 当您更改可变对象(例如数组或列表)的内容时,您正在更改该值内的引用。
例如,如果我们有一个String
变量,我们可以将它的值从s
重新赋值。"a"
"ab"
String s = "a";
s = s + "b";
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";
双线有助于强调快照图部分的不可更改性。但是当可重新分配或可变性很明显,或者与讨论无关时,我们将保持图表简单,只使用单线箭头和对象边框。
Java 有两种不同的方法来测试值的相等性,具体取决于值是基元还是对象:
- 运算符比较基元的
==
值。例如,5 == 5
返回 true,如'a' == 'a'
(这个是char
类型但是不是String
类型) - 该
.equals()
方法比较对象的值。例如,"abc".equals("abc")
返回 true。
在 Python 中,**原始值和对象值之间没有区别,您可以同时==
用于这两个目的。**这可能会导致 Java 中的混乱和错误——对您尝试比较的类型使用错误的相等类型。
幸运的是,在原始类型上使用equals()
很容易掌握。 5.equals(5)
产生静态错误,因为 Java 不允许在原始类型上调用任何方法。
但是另一个错误,==
用于比较对象值是否相等,要痛苦得多,因为==
它在 Java 中被重载了。在对象类型上使用时,==
测试两个表达式是否引用内存中的同一个对象。就我们一直在绘制的快照图而言,==
如果它们的箭头指向同一个对象气泡,则有两个引用。在 Python 中,此运算符称为is
.
所以如果程序的状态看起来像右图,那么:
-
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""
。
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
来创建一个List
from 参数:
List.of("a", "b", "c");
List
created with有一个List.of
重要的限制:它是不可变的!因此,一旦创建了列表,我们就无法添加、删除或替换元素。
Java 还提供Set.of
了创建不可变集和Map.of
创建不可变映射的功能:
Set.of("a", "b", "c");
Map.of("apple", 5, "banana", 7);
与 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
正如我们很快就会看到的,Java 帮助我们区分类型的规范——它有什么作用?– 以及实现– 代码是什么?
List
, Set
, 和Map
都是接口:它们定义了这些各自的类型如何工作,但它们不提供实现代码。有几个优点,但一个潜在的优点是我们这些类型的用户可以在不同的情况下选择不同的实现。
以下是创建一些实际List
s 的方法:
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
. 在本例中,firstNames
和lastNames
的行为方式相同;如果我们交换使用哪一个ArrayList
vs. LinkedList
,我们的代码不会中断。
不幸的是,这种选择能力也是一种负担:我们并不关心 Python 列表是如何工作的,我们为什么要关心我们的 Java 列表是ArrayLists
还是LinkedLists
?由于唯一的区别是性能>
如有疑问,请使用ArrayList
.
如果你想初始化一个ArrayList
or 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()
.
HashSet
是我们对 s 的默认选择Set
:
Set<Integer> numbers = new HashSet<>();
对于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 提供了类似的语法来迭代List
s 和Set
s 中的项目。
这是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一部分的类的文档的链接。
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 特性相关的事情!保持头脑清醒,专注于下面粗体字。
右上角是搜索框。您可以使用它跳转到类或接口,或直接跳转到特定方法。
Class BufferedReader标题下是类层次结构和已实现接口BufferedReader
的列表。一个对象可以使用所有这些类型的所有方法(加上它自己的方法)。BufferedReader
接下来我们看到直接子类,对于接口,实现类。这可以帮助我们找到,例如,那HashMap
是Map
.
接下来:类的描述。有时这些描述有点迟钝,但这是你应该首先去了解一个类的地方。
如果你想创建一个新BufferedReader
的构造函数摘要是第一个看的地方。构造函数并不是在 Java 中获取新对象的唯一方法,但它们是最常见的。
接下来:方法摘要列出了我们可以在一个BufferedReader
对象上调用的所有方法。
下面是对每个方法和构造函数的详细描述。 单击构造函数或方法以查看详细说明。 这是您应该了解方法的作用的第一个地方。
每个详细描述包括:
- 方法签名:我们看到返回类型、方法名称和参数。我们也看到了例外。目前,这些通常意味着该方法可能会遇到的错误。
- 完整的描述。
- 参数:方法参数的描述。
- 以及该方法返回的内容的描述。
这些详细说明是规范。它们允许我们使用诸如String
、Map
或之类的工具,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