JAVA语法笔记(十)集合
Java标准库自带的java.util
包提供了集合类:Collection
,它是除Map
外所有其他集合类的根接口。Java的java.util
包主要提供了以下三种类型的集合:
List
:一种有序列表的集合,例如,按索引排列的Student
的List
;Set
:一种保证没有重复元素的集合,例如,所有无重复名称的Student
的Set
;Map
:一种通过键值(key-value)查找的映射表集合,例如,根据Student
的name
查找对应Student
的Map
。
Java集合的设计有几个特点:一是实现了接口和实现类相分离,例如,有序表的接口是List
,具体的实现类有ArrayList
,LinkedList
等,二是支持泛型,我们可以限制在一个集合中只能放入同一种数据类型的元素,例如:
List<String> list = new ArrayList<>(); // 只能放入String类型
[!TIP] Java访问集合总是通过统一的方式——迭代器(Iterator)来实现,它最明显的好处在于无需知道集合内部元素是按什么方式存储的。
由于Java的集合设计非常久远,中间经历过大规模改进,我们要注意到有一小部分集合类是遗留类,不应该继续使用:
Hashtable
:一种线程安全的Map
实现;Vector
:一种线程安全的List
实现;Stack
:基于Vector
实现的LIFO
的栈。
还有一小部分接口是遗留接口,也不应该继续使用:
Enumeration<E>
:已被Iterator<E>
取代。
List
创建List
除了使用ArrayList
和LinkedList
,还可以通过List
接口提供的of()
方法,根据给定元素快速创建List
:
List<Integer> list = List.of(1, 2, 5);
遍历List
和数组类型类似,我们要遍历一个List
,完全可以用for
循环根据索引配合get(int)
方法遍历:
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> list = List.of("apple", "pear", "banana");
for (int i=0; i<list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
}
}
但这种方式并不推荐,一是代码复杂,二是因为get(int)
方法只有ArrayList
的实现是高效的,换成LinkedList
后,索引越大,访问速度越慢。
所以我们要始终坚持使用迭代器Iterator
来访问List
。Iterator
本身也是一个对象,但它是由List
的实例调用iterator()
方法的时候创建的。Iterator
对象知道如何遍历一个List
,并且不同的List
类型,返回的Iterator
对象实现也是不同的,但总是具有最高的访问效率。
Iterator
对象有两个方法:boolean hasNext()
判断是否有下一个元素,E next()
返回下一个元素。因此,使用Iterator
遍历List
代码如下:
import java.util.Iterator;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> list = List.of("apple", "pear", "banana");
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
String s = it.next();
System.out.println(s);
}
}
}
通过Iterator
遍历List
永远是最高效的方式。并且,由于Iterator
遍历是如此常用,所以,Java的for each
循环本身就可以帮我们使用Iterator
遍历。把上面的代码再改写如下:
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> list = List.of("apple", "pear", "banana");
for (String s : list) {
System.out.println(s);
}
}
}
实际上,只要实现了Iterable
接口的集合类都可以直接用for each
循环来遍历,Java编译器本身并不知道如何遍历集合对象,但它会自动把for each
循环变成Iterator
的调用,原因就在于Iterable
接口定义了一个Iterator<E> iterator()
方法,强迫集合类必须返回一个Iterator
实例。
Properties
在编写应用程序的时候,经常需要读写配置文件。例如,用户的设置:
# 上次最后打开的文件:
last_open_file=/data/hello.txt
# 自动保存文件的时间间隔:
auto_save_interval=60
配置文件的特点是,它的Key-Value
一般都是String-String
类型的,因此我们完全可以用Map<String, String>
来表示它。
因为配置文件非常常用,所以Java集合库提供了一个Properties
来表示一组“配置”。由于历史遗留原因,Properties
内部本质上是一个Hashtable
,但我们只需要用到Properties
自身关于读写配置的接口。
用Properties
读取配置文件非常简单。Java默认配置文件以.properties
为扩展名,每行以key=value
表示,以#
开头的是注释。以下是一个典型的配置文件:
# setting.properties
last_open_file=/data/hello.txt
auto_save_interval=60
可以从文件系统读取这个.properties
文件:
String f = "setting.properties";
Properties props = new Properties();
props.load(new java.io.FileInputStream(f));
String filepath = props.getProperty("last_open_file");
String interval = props.getProperty("auto_save_interval", "120");
可见,用Properties
读取配置文件,一共有三步:
- 创建
Properties
实例; - 调用
load()
读取文件; - 调用
getProperty()
获取配置。
调用getProperty()
获取配置时,如果key
不存在,将返回null
。我们还可以提供一个默认值,这样当key
不存在的时候,就返回默认值。
也可以从classpath
读取.properties
文件,因为load(InputStream)
方法接收一个InputStream
实例,表示一个字节流,它不一定是文件流,也可以是从jar
包中读取的资源流:
Properties props = new Properties();
props.load(getClass().getResourceAsStream("/common/setting.properties"));
试试从内存读取一个字节流:
// properties
import java.io.*;
import java.util.Properties;
public class Main {
public static void main(String[] args) throws IOException {
String settings = "# test" + "\n" + "course=Java" + "\n" + "last_open_date=2019-08-07T12:35:01";
ByteArrayInputStream input = new ByteArrayInputStream(settings.getBytes("UTF-8"));
Properties props = new Properties();
props.load(input);
System.out.println("course: " + props.getProperty("course"));
System.out.println("last_open_date: " + props.getProperty("last_open_date"));
System.out.println("last_open_file: " + props.getProperty("last_open_file"));
System.out.println("auto_save: " + props.getProperty("auto_save", "60"));
}
}
如果有多个.properties
文件,可以反复调用load()
读取,后读取的key-value
会覆盖已读取的key-value
:
Properties props = new Properties();
props.load(getClass().getResourceAsStream("/common/setting.properties"));
props.load(new FileInputStream("C:\\conf\\setting.properties"));
上面的代码演示了Properties
的一个常用用法:可以把默认配置文件放到classpath
中,然后,根据机器的环境编写另一个配置文件,覆盖某些默认的配置。
Properties
设计的目的是存储String
类型的key-value
,但Properties
实际上是从Hashtable
派生的,它的设计实际上是有问题的,但是为了保持兼容性,现在已经没法修改了。除了getProperty()
和setProperty()
方法外,还有从Hashtable
继承下来的get()
和put()
方法,这些方法的参数签名是Object
,我们在使用Properties
的时候,不要去调用这些从Hashtable
继承下来的方法。
Set
我们知道,Map
用于存储key-value
的映射,对于充当key
的对象,是不能重复的,并且,不但需要正确覆写equals()
方法,还要正确覆写hashCode()
方法。
如果我们只需要存储不重复的key
,并不需要存储映射的value
,那么就可以使用Set
。
Q1: 什么是集合?
A1: 集合是Java标准库提供的数据结构,用于存储一组元素。集合可以分为三种类型:List
、Set
和Map
。
Q2: 什么是List?
A2: List
是一种有序列表的集合,元素可以重复。List
可以通过索引访问元素。
Q3: 什么是Set?
A3: Set
是一种保证没有重复元素的集合,元素不能重复。Set
不支持索引访问。
Q4: 什么是Map?
A4: Map
是一种通过键值(key-value)查找的映射表集合,元素可以重复。Map
支持通过键值查找元素。
Q5: 如何创建集合?
A5: 可以通过List.of()
、Set.of()
和Map.of()
方法创建集合,也可以通过ArrayList
、HashSet
和HashMap
类创建集合。
Q6: 如何遍历集合?
A6: 可以通过Iterator
对象遍历集合,也可以通过for each
循环遍历集合。
Q7: 什么是Iterator?
A7: Iterator
是集合类提供的用于遍历集合的对象。Iterator
对象知道如何遍历集合,提供了hasNext()
和next()
方法。
Q8: 什么是Properties?
A8: Properties
是Java标准库提供的用于存储键值对的集合。Properties
内部本质上是一个Hashtable
。
Q9: 如何读取Properties文件?
A9: 可以通过Properties
类的load()
方法读取Properties文件。
Q10: 什么是Set?
A10: Set
是一种保证没有重复元素的集合,元素不能重复。Set
不支持索引访问。
Q11: 如何创建Set?
A11: 可以通过Set.of()
方法创建Set,也可以通过HashSet
类创建Set。
Q12: 如何遍历Set?
A12: 可以通过Iterator
对象遍历Set,也可以通过for each
循环遍历Set。
Q13: 什么是Map?
A13: Map
是一种通过键值(key-value)查找的映射表集合,元素可以重复。Map
支持通过键值查找元素。
Q14: 如何创建Map?
A14: 可以通过Map.of()
方法创建Map,也可以通过HashMap
类创建Map。
Q15: 如何遍历Map?
A15: 可以通过Iterator
对象遍历Map,也可以通过for each
循环遍历Map。
Q16: 什么是Collection?
A16: Collection
是Java标准库提供的用于存储一组元素的接口。Collection
提供了add()
、remove()
和contains()
方法。
Q17: 如何使用Collection?
A17: 可以通过实现Collection
接口的类使用Collection,也可以通过ArrayList
、HashSet
和HashMap
类使用Collection。
Q18: 什么是Iterator?
A18: Iterator
是集合类提供的用于遍历集合的对象。Iterator
对象知道如何遍历集合,提供了hasNext()
和next()
方法。
Q19: 如何使用Iterator?
A19: 可以通过Iterator
对象遍历集合,也可以通过for each
循环遍历集合。
Q20: 什么是Properties?
A20: Properties
是Java标准库提供的用于存储键值对的集合。Properties
内部本质上是一个Hashtable
。
Q21: 如何读取Properties文件?
A21: 可以通过Properties
类的load()
方法读取Properties文件。
Q22: 什么是Set?
A22: Set
是一种保证没有重复元素的集合,元素不能重复。Set
不支持索引访问。
Q23: 如何创建Set?
A23: 可以通过Set.of()
方法创建Set,也可以通过HashSet
类创建Set。
Q24: 如何遍历Set?
A24: 可以通过Iterator
对象遍历Set,也可以通过for each
循环遍历Set。
Q25: 什么是Map?
A25: Map
是一种通过键值(key-value)查找的映射表集合,元素可以重复。Map
支持通过键值查找元素。
Q26: 如何创建Map?
A26: 可以通过Map.of()
方法创建Map,也可以通过HashMap
类创建Map。
Q27: 如何遍历Map?
A27: 可以通过Iterator
对象遍历Map,也可以通过for each
循环遍历Map。
Q28: 什么是Collection?
A28: Collection
是Java标准库提供的用于存储一组元素的接口。Collection
提供了add()
、remove()
和contains()
方法。
Q29: 如何使用Collection?
A29: 可以通过实现Collection
接口的类使用Collection,也可以通过ArrayList
、HashSet
和HashMap
类使用Collection。
Q30: 什么是Iterator?
A30: Iterator
是集合类提供的用于遍历集合的对象。Iterator
对象知道如何遍历集合,提供了hasNext()
和next()
方法。