文章目录
Java基础(Basic Java)
开始学习Java(Getting started with Java)
语法基础(Language basics)
这部分要求先掌握Java的基本语法知识,可以用Eclipse中的Java Tutor做练习,可以通过Java API文档来学习,也可以看看这个东西语法基础。然后有一个小练习。
int a = 5; // (1)
if (a > 10) { // (2)
int b = 2; // (3)
} else { // (4)
int b = 4; // (5)
} // (6)
b *= 3; // (7)
假设我们在Java中编写了上述代码,那么哪一行会导致编译错误呢?答案是第7行。该代码声明了两个变量名字都是b
,其中过一个在if
分支中,另一个在else
分支中,当我们编译到第7行时,对b
的引用会模糊不清,从而引发编译错误。
要对上述代码修改正确,修改最少的一种方式,是这样的:
int a = 5; // (1)
int b; // new
if (a > 10) { // (2)
b = 2; // (3) int b = 2;
} else { // (4)
b = 4; // (5) int b = 4;
} // (6)
b *= 3; // (7)
继续看修改之后的代码,如果这时把else
分支中的b = 4;
注释掉,会发生什么呢?在程序运行之前,Java编译器就会报错了。Java编译器规定,在我们访问一个变量值之前,必须保证这个变量是被声明过的,不仅如此,还需要保证这个变量一定已经赋过值了,显然,在上述更改之后的代码中,如果把else
中代码注释掉,那么在a<=10的情况下,运行到第8行(原第7行)对b的引用时,b仍没有一个确定的值,Java编译器是会报错的。
数字和字符串(Numbers and strings)
这部分可以先阅读一下数字和字符串,然后课程要求我们做一下Java Tutor的练习。之后又有几个小练习。
fahrenheit = 212.0
celsius = (fahrenheit - 32) * 5/9
上述代码是一段Python代码,用于将华氏度转化为摄氏度。请问这段代码能否输出正确的转化结果?答案是能。在Python中,首先fahrenheit
变量被赋以浮点值212.0,在celsius
的赋值语句中的右侧算术式是从左往右算,从fahrenheit
开始,之后的运算都是浮点型,最后也就能得出正确答案。
先对上述Python代码改成Java形式,就要对变量有类型声明,在不改变代码语句的前提下,正确的类型声明应该是将fahrenheit
和celsius
都声明为double
类型。Java中的浮点类型都是double
而不是float
,每个基础类型都有一个对应的对象类型。有的时候必须用对象类型,比如说double
对应Double
,但通常我们不会用对象类型来声明普通的变量。至于上述代码的第二行,练习题给的答案是这样的:celsius = (fahrenheit - 32) * (5. / 9);
这也不难理解,需要注意的是5是浮点数,这样5./9才是浮点运算,而不是整数运算得0.不过,这样的写法也说得过去celsius = (fahrenheit - 32) * 5 / 9;
毕竟运算是从左向右的,不过前一种写法看起来更舒服也更明确。
要想把最终结果输出出来,一种正确的方式是System.out.println(fahrenheit + " -> " + celsius);
,打印变量的值,用+作连接,字符串常量要用""。还有一种比较冗长的写法是System.out.println(Double.toString(fahrenheit) + " -> " + Double.toString(celsius));
这种写法是把浮点数转化成了字符串输出,虽然结果也对,但这种转换是多余的。
使用对象(Using objects)
关于使用对象的知识就读这个吧对象。
Hello, world!
现在可以写一个HelloWorldApp.java
小程序了,按照教程Hello World!编译运行可以在控制台输出Hello World!
了。
快照图(Snapshot diagrams)
快照图是运行时的关系图,来理解一些微妙的问题。快照图表示运行时刻程序的内部状态,包括栈中的方法和局部变量,以及堆中的当前对象等。在该课中,快照图是一种辅助教学工具,便于理解。
基本变量(Primitive values)
来向的箭头表示变量值或对象域。
对象变量(Object values)
用一个标有对象类型的圈来表示一个对象变量值。也可以在圈里写上对象域的名字,用去向的箭头指出它们的值。x:int
和int x
的写法都是常见的。
赋值(Mutating values vs. reassigning variables,就姑且翻译成赋值吧。。)
这里有两个名词,我也不会翻译,不过解释是这样的。可变值变量(mutable values)主要指数组或列表,这种变量的内容是可以改变的,就是改变数组或列表中元素的值。还有不可变值变量(immutable values)主要指基本类型变量,比如int或者String也算这一类,它们没有什么内容可以改变,如果要改变它们的值,就是指向另一个值。
不可变值变量和赋值(Reassignment and immutable values)
比如说要吧一个String
类型变量s
由原来的"a"
赋值为"ab"
。
String s = "a";
s = s + "b";
快照图是这样的
可变值变量(Mutable values)
和String
类似的一种表示字符串的对象StringBuilder
是可变值变量,把这个类型的变量sb
从"a"
改为"ab"
是这样的:
StringBuilder sb = new StringBuilder("a");
sb.append("b");
不可变引用(Immutable references)
这里主要指关键字final
,它可以让一个引用不可变,也就是不能再指向其它值了,也就是不可赋值了(只能赋值一次,即初始化)。
如果Java编译器认为一个final
变量在运行时没有只赋值一次,那么就会报编译错误。
在快照图中,final
变量的表示方法是双线箭头指向它的值。下面id
就是不可再变的,age
就是可变的
Java集合(Java Collections)
先前我们提到了arrays,也就是一些对象或基本类型的定长数组。Java对于对象集合的组织有一套功能强大灵活的工具:Java集合工作框架(Java Collections Framework)。
列表,集合,和映射(Lists, Sets, and Maps)
Java的List
概念和Python的list差不多。List
是一个包含了若干个对象的集合,这里面同一个对象可能出现多次。可以向List
中添加或删除表项(item),同时List
自己会变长或变短来适应它的内容变化。
以下是几个List
的操作:
Java | 描述 | Python |
---|---|---|
int count = lst.size(); | 获取列表元素个数 | count = len(lst) |
lst.add(e); | 向列表末尾添加一个元素 | lst.append(e) |
if(lst.isEmpty()) ... | 判断列表是否为空 | if not lst: ... |
在快照图里,用一个对象来表示List
,里面用索引数来替代对象域,比如cities
是一个列表,而它的实际含义可能表示从波士顿到波哥大再到巴塞罗那的旅程。
Map
和Python的dictionary(字典)概念相似,意思是映射。Python中映射的关键字必须有哈希性(可散列性)。Java也有类似的要求,下面会介绍。
Map
的几个例子:
Java | 描述 | Python |
---|---|---|
map.put(key, val) | 添加映射key→val | map[key] = val |
map.get(key) | 获取关键字的映射值 | map[key] |
map.containsKey(key) | 判断映射集合中是否有关键字key | key in map |
map.remove(key) | 删除一个映射 | del map[key] |
在快照图中,用包含关键字和映射值的序对的对象来表示Map
:
Set
是若干个唯一的对象的无序集合。像数学上的集合定义,和Python中的set也类似,和Java中List
不同,在Set
中,一个对象绝不能出现多次。和Python一样,Java对此中的对象也有哈希性要求。
Set
的几个操作例子:
Java | 描述 | Python |
---|---|---|
s1.contains(e) | 判断集合中是否包含元素 | e in s1 |
s1.containsAll(s2) | 判断集合s2是否属于集合s1 | s1.issuperset(s2) s1 >= s2 |
s1.removeAll(s2) | 把集合s1中的s2集合除去 | s1.difference_update(s2) s1 -= s2 |
在快照图中,表示Set
是用对象表示的,这种对象的对象域没有名字。
写法(Literals)
Python的lists语法是这样的:
lst = [ "a", "b", "c" ]
Python的maps语法是这样的:
map = { "apple": 5, "banana": 7 }
Java的数组语法是这样的:
String[] arr = { "a", "b", "c" };
但这是在构造一个数组,而不是List
,可以用Arrays.asList
函数来把一个数组改造成一个List
:
Arrays.asList(new String[] { "a", "b", "c" })
或者直接这样:
Arrays.asList("a", "b", "c")
用Arrays.asList
函数构造的List
有一个缺点,就是它的长度也是固定的。
泛型:列表,集合和映射变量的声明(Generics: declaring List, Set, and Map variables)
和Python的集合类型不一样,Java集合可以确保加入集合中的对象一定属于这个集合的数据类型,这点是由Java编译器的静态检查保证的。这样,当我们从集合中拿出一个元素时,也能确保这个元素的类型是我们所预期的。
下面是声明集合类型变量的语法(查了百度和谷歌我也没明白Java里Turtle是什么鬼):
List<String> cities; // 字符串列表
Set<Integer> numbers; // 整数集合
Map<String,Turtle> turtles; // 字符串到Turtle类型的映射
由于泛型的原因,我们不能创建一个基本类型的集合,比如说Set<int>
就不行。不过,int
类型有Integer
作为封装,我们可以用这个封装对象来创建集合Set<Integer> numbers
。
虽然对于基本类型的集合我们是用它们的封装对象来创建的,不过在实际使用过程中,Java会做一些自动转换。比如说如果我们已经声明了List<Integer> squence
,那么下面的代码是完全成立的:
sequence.add(5); // 把5加入到集合中
int second = sequence.get(1); // 获取集合中第二个元素
用ArrayLists和LinkedLists创建列表(ArrayLists and LinkedLists: creating Lists)
List
,Set
和Map
都是接口(interfaces)。它们定义了各自的类型,却没有提供实例化代码。这么做有几个好处,其中之一就是我们可以根据实际情况来选择不同的实例化类型。
比如说,我们要创建两个List
类型变量:
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
的行为就是一样的,我们要是把这两个给换一下,lastNames
用ArrayList
而firstNames
用LinkedList
,代码也没问题。
可是选哪个实例化也是一个问题:在Python里面我们不关心lists,为什么到Java里就要区分ArrayLists
和LinkedList
了呢?事实上这两者的区别只有表现形式,深入理解可以知道,这两者的方法功能相同,但实现方法不太相同。在该课程中对这二者不作区分,不知道用哪个的话,就用ArrayList
吧。
用HashSets和HashMaps来创建集合和映射(HashSets and HashMaps: creating Sets and Maps)
HashSet
是Set
的默认实例化选择:
Set<Integer> numbers = new HashSet<>();
Java还提供了有序集合,用TreeSet
实例化来实现。
至于Map
默认的实例化选择是HashMap
:
Map<String,Turtle> turtles = new HashMap<>();
迭代(Iteration)
假设我们现在有:
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
和Set
有相似的语法来遍历:
for (String city : cities) {
System.out.println(city);
}
for (int num : numbers) {
System.out.println(num);
}
但我们不能在Java里对Map
用这种便利方式,我们倒是可以用这种方式来遍历集合中的关键字或值:
for (String key : turtles.keySet()) {
System.out.println(key + ": " + turtles.get(key));
}
这种for
循环模式涉及到了迭代器(Iterator
) 的概念,课程后续会讲到。
用索引迭代(Iterating with indices)
Java里还可以用for
循环来根据索引进行遍历:
for (int ii = 0; ii < cities.size(); ii++) {
System.out.println(cities.get(ii));
}
上面这个代码尽量避免,这个代码冗长且有很多潜在bug,除非我们真的需要索引ii
来帮我们遍历,否则就尽量避免这种模式。
下面两个小练习:
用List
重写下面这些数组变量的声明,只声明变量而不初始化值:
String[] names; // Answer is: List\<String\> names;
int[] numbers; // Answer is: List<Integer> numbers;
char[][] grid; //List<List<Character>> grid;
Java的Map
类似Python的字典。在我们运行下面的代码之后:
Map<String, Double> treasures = new HashMap<>();
String x = "palm";
treasures.put("beach", 25.);
treasures.put("palm", 50.);
treasures.put("cove", 75.);
treasures.put("x", 100.);
treasures.put("palm", treasures.get("palm") + treasures.size());
treasures.remove("beach");
double found = 0;
for (double treasure : treasures.values()) {
found += treasure;
}
下面表达式的结果是:
treasures.get(x) \\ Answer is 54.
treasures.get("x") \\ Answer is 100.
found \\ Answer is 229.
x指的是"palm",且"palm"的映射值被改为了原映射值+当时集合的大小,也就是50.+4=54.。
第二个,直接找"x"的映射值,就是100.了。
第三个,found
是对最后映射集中所有映射值的累加和,最后的映射集改变了"palm"值,并移除了"beach",所以结果是54.+75.+100.=229.。
Java API 文档(Java API documentation)
这段是教大家怎么看Java API的,Java API 文档真的是学习Java语法的官方教材。不多墨迹了,简单翻译一下原课程内容。
- java.lang.String是
String
的全名。创建String
对象类型只需要用双引号就好了""。 - java.lang.Integer还有其它基本类型的封装类,在多数情况下,Java可以在基本类型和封装类之间自由转换。
- java.util.List类似与Python的list。
- java.util.Map类似Python的字典(dictionary)。
- java.io.File表示磁盘上的文件。用
File
提供的方法可以判断文件是否可读,删除文件,获知文件最近一次被修改的时间等。 - java.io.FileReader是用来读文本文件的。
- java.io.BufferedReader可以高效地读文本,而且提供了一个非常有用的特性就是一次读一整行。
接下来是对Java API文档中BufferedReader的一次学习引导,教大家如何看文档。
文档最上面是BufferedReader
的类级展示,还有一些类的接口。一个BufferedReader
对象有除自己以外所有类型的所有方法(这句应该这么翻译吗?因为原文是A BufferedReader
object has all of the methods of all those types (plus its own methods) available to use.)。
接下来是直接子类,还展示了一个接口用来实例化类。
在下面是类的说明。要弄懂一个类,应该看这里吧。
如果想知道如何构建一个BufferedReader
的话就要看构造总结了。Java里不是非得靠构造器来实例化一个对象,不过这是最常见的方法了。
然后是能调用的所有方法总结清单。
总结下面是每个方法和构造器的详细描述。点击构造器和方法可以看详细描述。要理解一个方法的实现和功能,也应该看这里!
每个详细描述都包括:
- 方法特征:返回值类型,方法名称还有参数。还可以看异常(exceptions),目前异常就是指方法运行时可能遇到的错误。
- 全部描述。
- 参数:方法参数的描述。
- 还有方法返回值的描述。
后面还有两个练习,是为了训练阅读API能力的,就不放出来了。
关于Java API,method summary和method detail比较重要!!!