JavaSE 进阶 - 第22章 集合(3)
0、Map接口
1、Map和Collection没有关系!
2、Map集合以key和value的方式存储数据:键值对
key和value都是引用数据类型。
key和value都是存储对象的内存地址。
key起到主导的地位,value是key的一个附属品。
3、Map集合的key部分特点:无序不可重复。(Map集合的key部分 和 Set集合 存储元素特点相同)
1、掌握Map接口中常用方法
-
V put(K key, V value)———— 向Map集合中添加键值对
-
V get(Object key)———— 通过key获取value
-
int size()———— 获取Map集合中键值对的个数。
-
V remove(Object key)———— 通过key删除键值对
-
boolean containsKey(Object key)———— 判断Map中是否包含某个key
-
boolean containsValue(Object value)———— 判断Map中是否包含某个value
-
Collection values()———— 获取Map集合中所有的value,返回一个Collection
-
Set keySet()———— 获取Map集合所有的key(所有的键是一个set集合)
-
void clear()———— 清空Map集合
-
boolean isEmpty()———— 判断Map集合中元素个数是否为0
-
Set<Map.Entry<K,V>> entrySet()———— 将Map集合转换成Set集合
【对第11个方法的理解】:假设现在有一个Map集合,如下所示: map1集合对象 key value ---------------------------- 1 zhangsan 2 lisi 3 wangwu 4 zhaoliu Set set = map1.entrySet(); set集合对象: 1=zhangsan 【注意:Map集合通过entrySet()方法转换成的这个Set集合,Set集合中元素的类型是 Map.Entry<K,V>】 2=lisi 【Map.Entry和String一样,都是一种类型的名字,只不过:Map.Entry是静态内部类,Entry是Map中的静态内部类】 3=wangwu 4=zhaoliu ---> 这个东西是一个整体、是一个元素,这个东西的类型是:Map.Entry
【静态内部类在之前18章涉及过,这里可以参看MyClass.java】
import java.util.HashSet;
import java.util.Set;
public class MyClass {
// 声明一个静态内部类
private static class InnerClass {
// 静态方法
public static void m1(){
System.out.println("静态内部类的m1方法执行");
}
// 实例方法
public void m2(){
System.out.println("静态内部类中的实例方法执行!");
}
}
public static void main(String[] args) {
// 类名叫做:MyClass.InnerClass
MyClass.InnerClass.m1();
// 创建静态内部类对象
MyClass.InnerClass mi = new MyClass.InnerClass();
mi.m2();
// 给一个Set集合
// 该Set集合中存储的对象是:MyClass.InnerClass类型
Set<MyClass.InnerClass> set = new HashSet<>();
// 这个Set集合中存储的是字符串对象。
Set<String> set2 = new HashSet<>();
Set<MyMap.MyEntry<Integer, String>> set3 = new HashSet<>();
}
}
class MyMap {
public static class MyEntry<K,V> {
}
}
/*
静态内部类的m1方法执行
静态内部类中的实例方法执行!
*/
【对Map中常用11个方法的应用示例】:
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapTest01 {
public static void main(String[] args) {
// 创建Map集合对象
Map<Integer, String> map = new HashMap<>();
// 1、 向Map集合中添加键值对
map.put(1, "zhangsan"); // 1在这里进行了自动装箱。
map.put(2, "lisi");
map.put(3, "wangwu");
map.put(4, "zhaoliu");
// 2、 通过key获取value
String value = map.get(2);
System.out.println(value);
// 3、 获取键值对的数量
System.out.println("键值对的数量:" + map.size());
// 4、 通过key删除key-value
map.remove(2);
System.out.println("键值对的数量:" + map.size());
// 5、 判断是否包含某个key
// contains方法底层调用的都是equals进行比对的,所以自定义的类型需要重写equals方法。
System.out.println(map.containsKey(new Integer(4))); // true
// 6、 判断是否包含某个value
System.out.println(map.containsValue(new String("wangwu"))); // true
// 7、 获取所有的value
Collection<String> values = map.values();
// foreach
for(String s : values){
System.out.println(s);
}
// 8、获取Map集合所有的key(所有的键是一个set集合)
Set<Integer> key = map.keySet();
for (Integer k : key) {
System.out.println(k);
}
// 11、 将Map集合转换成Set集合
Set<Map.Entry<Integer,String>> set = map.entrySet();
for (Map.Entry<Integer,String> s : set) {
System.out.println(s);
}
// 9、 清空map集合
map.clear();
System.out.println("键值对的数量:" + map.size());
// 10、 判断是否为空
System.out.println(map.isEmpty()); // true
}
}
/*
lisi
键值对的数量:4
键值对的数量:3
true
true
zhangsan
wangwu
zhaoliu
1
3
4
1=zhangsan
3=wangwu
4=zhaoliu
键值对的数量:0
true
*/
2、遍历Map集合的两种方式
-
第一种:获取所有key,遍历每个key,通过key获取value.
-
第二种:使用这个方法Set<Map.Entry<K,V>> entrySet() ,
遍历Set集合中的node,调用node.getKey(), node.getValue()第二种方式效率比较高,因为获取key和value都是直接从node对象中获取的属性值。
比较适合于大数据量。
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/*
Map集合的遍历。【非常重要】
*/
public class MapTest02 {
public static void main(String[] args) {
// 第一种方式:获取所有的key,通过遍历key,来遍历value
Map<Integer, String> map = new HashMap<>();
map.put(1,"1号");
map.put(2,"2号");
map.put(3,"3号");
map.put(4,"4号");
// 遍历Map集合
// 获取所有的key,所有的key是一个Set集合
Set<Integer> keys = map.keySet();
// 遍历key,通过key获取value
/* // 迭代器可以
Iterator<Integer> it = keys.iterator();
while(it.hasNext()){
// 取出其中一个key
Integer key = it.next();
// 通过key获取value
String value = map.get(key);
System.out.println("key="+key+",value="+value);
}
*/
// foreach也可以
for(Integer key : keys){
System.out.println("key="+key+",value="+map.get(key));
}
// 第二种方式:Set<Map.Entry<K,V>> entrySet()
// 以上这个方法是把Map集合直接全部转换成Set集合。
// Set集合中元素的类型是:Map.Entry
Set<Map.Entry<Integer,String>> set = map.entrySet();
// 遍历Set集合,每一次取出一个Node
// 迭代器
/*Iterator<Map.Entry<Integer,String>> it2 = set.iterator();
while(it2.hasNext()){
Map.Entry<Integer,String> node = it2.next();
Integer key = node.getKey();
String value = node.getValue();
System.out.println(key + "=" + value);
}*/
// foreach
// 这种方式效率比较高,因为获取key和value都是直接从node对象中获取的属性值。
// 这种方式比较适合于大数据量。
for(Map.Entry<Integer,String> node : set){
System.out.println(node.getKey() + "--->" + node.getValue());
}
}
}
/*
key=1,value=1号
key=2,value=2号
key=3,value=3号
key=4,value=4号
1--->1号
2--->2号
3--->3号
4--->4号
*/
3、HashMap集合、哈希表数据结构
1、HashMap集合底层是哈希表/散列表的数据结构。
2、哈希表是一个怎样的数据结构呢?
哈希表是一个数组和单向链表的结合体。
数组:在查询方面效率很高,随机增删方面效率很低。
单向链表:在随机增删方面效率较高,在查询方面效率很低。
哈希表将以上的两种数据结构融合在一起,充分发挥它们各自的优点。
3、HashMap集合底层的源代码:
public class HashMap{
// HashMap底层实际上就是一个数组。(一维数组)
Node<K,V>[] table;
// 静态的内部类HashMap.Node
static class Node<K,V> {
final int hash; // 哈希值(哈希值是key的hashCode()方法的执行结果。hash值通过哈希函数/算法,可以转换存储成数组的下标。)
final K key; // 存储到Map集合中的那个key
V value; // 存储到Map集合中的那个value
Node<K,V> next; // 下一个节点的内存地址。
}
}
哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表。(数组和链表的结合体。)
4、最主要掌握的是:
map.put(k,v)——放
v = map.get(k)——取
以上这两个方法的实现原理,是必须掌握的。
5、HashMap集合的key部分特点:
无序,不可重复。
为什么无序?
因为不一定挂到哪个单向链表上。
不可重复是怎么保证的?
equals方法来保证HashMap集合的key不可重复。
如果key重复了,value会覆盖。
放在HashMap集合key部分的元素其实就是放到HashSet集合中了。
所以HashSet集合中的元素也需要同时重写hashCode()+equals()方法。
6、哈希表HashMap使用不当时无法发挥性能!
假设将所有的hashCode()方法返回值固定为某个值(所有下标相同),那么会导致底层哈希表变成了 纯单向链表。
这种情况我们成为:散列分布不均匀。
什么是散列分布均匀?
假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的,是散列分布均匀的。
假设将所有的hashCode()方法返回值都设定为不一样的值(所有下标不同),可以吗,有什么问题?
不行,因为这样的话导致底层哈希表就成为一维数组了,没有链表的概念了。也是散列分布不均匀。
散列分布均匀需要你重写hashCode()方法时有一定的技巧。
7、重点:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法。
8、HashMap集合的默认初始化容量是16,默认加载因子是0.75
这个默认加载因子是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容。
重点,记住:HashMap集合初始化容量必须是2的倍数,这也是官方推荐的,
这是因为达到散列均匀,为了提高HashMap集合的存取效率,所必须的。
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class HashMapTest01 {
public static void main(String[] args) {
// 测试HashMap集合key部分的元素特点:无序不可重复
// Integer是key,它的hashCode和equals都重写了。
Map<Integer,String> map = new HashMap<>();
map.put(1111, "zhangsan");
map.put(6666, "lisi");
map.put(7777, "wangwu");
map.put(2222, "zhaoliu");
map.put(2222, "king"); //key重复的时候value会自动覆盖。
System.out.println(map.size()); // 4
// 遍历Map集合
Set<Map.Entry<Integer,String>> set = map.entrySet();
for(Map.Entry<Integer,String> entry : set){
// 验证结果:HashMap集合key部分元素:无序不可重复。
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
}
/*
4
7777=wangwu
1111=zhangsan
6666=lisi
2222=king
*/
4、存放在HashMap集合key部分和HashSet集合中的元素需要同时重写hashCode和equals
-
4.0、为什么重写equals方法就得重写hashCode方法?
equals方法重写,为了来比较HashMap集合的key的内容(值),而不是内存地址 hashCode方法重写,是为了遵循原则: equals比较对象的内容(值)相同,那么hashCode一定相等 hashCode如果相等的情况下,对象的内容不一定相等。 如果不重写,可能导致因为hash值不同,把元素直接放进去集合了,但是内容相同,有违“无序不可重复” 1.重写hashCode() 用于获得元素的存储位置【数组下标】。 2.重写equals() 用于在两个元素的位置相同的时候 比较两个元素是否相等。
-
4.1、向Map集合中存,以及从Map集合中取,都是先调用key的hashCode方法,然后再调用equals方法!equals方法有可能调用,也有可能不调用。
拿put(k,v)举例,什么时候equals不会调用?
k.hashCode()方法返回哈希值,
哈希值经过哈希算法转换成数组下标。
数组下标位置上如果是null,equals不需要执行。
拿get(k)举例,什么时候equals不会调用?
k.hashCode()方法返回哈希值,
哈希值经过哈希算法转换成数组下标。
数组下标位置上如果是null,equals不需要执行。 -
4.2、注意:如果一个类的equals方法重写了,那么hashCode()方法必须重写。
并且equals方法返回如果是true,hashCode()方法返回的值必须一样。
equals方法返回true表示两个对象相同,在同一个单向链表上比较。
那么对于同一个单向链表上的节点来说,他们的哈希值都是相同的。
所以hashCode()方法的返回值也应该相同。 -
4.3、hashCode()方法和equals()方法不用研究了,直接使用IDEA工具生成,但是这两个方法需要同时生成。
-
4.4、终极结论:
放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法。 -
4.5、对于哈希表数据结构来说:
如果o1和o2的hash值相同,一定是放到同一个单向链表上。
当然如果o1和o2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。 -
4.6、HashMap集合key部分允许 null
但是注意:HashMap集合的key 是 null值只能有一个,因为key重复的话value是覆盖。
import java.util.Objects;
public class Student {
private String name;
public Student() {
}
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//hashcode
//equals
// public boolean equals(Object obj) {
// if(obj == null || !(obj instanceof Student)) return false;
// if(obj == this) return true;
// Student s = (Student)obj;
// if(this.name.equals(s.name))return true;
// return false;
// }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
import java.util.HashSet;
import java.util.Set;
public class HashMapTest02 {
public static void main(String[] args) {
Student s1 = new Student("zhangsan");
Student s2 = new Student("zhangsan");
// 重写equals方法之前是false
//System.out.println(s1.equals(s2)); // false
// 重写equals方法之后是true
System.out.println(s1.equals(s2)); //true (s1和s2表示相等)
System.out.println("s1的hashCode=" + s1.hashCode()); //-1432604525(重写hashCode前603742814)
System.out.println("s2的hashCode=" + s2.hashCode()); //-1432604525(重写hashCode前1826771953 )
// s1.equals(s2)结果已经是true了,表示s1和s2是一样的,相同的,那么往HashSet集合中放的话,
// 按说只能放进去1个。(HashSet集合特点:无序不可重复)
Set<Student> students = new HashSet<>();
students.add(s1);
students.add(s2);
//System.out.println(students.size()); // 2 这个结果按说应该是1. 但是结果是2.显然不符合HashSet集合存储特点。怎么办?重写hashCode方法
System.out.println(students.size());//1 重写hashCode之后,结果是1
}
}
/*
true
s1的hashCode=-1432604525
s2的hashCode=-1432604525
1
*/
5、HashMap和Hashtable的区别
- Hashtable是Map接口下面的另一个实现类。
- HashMap和Hashtable的区别:
HashMap | Hashtable |
---|---|
初始化容量16,扩容后是原容量的:2倍 | 初始化容量11,扩容后是原容量的:2倍+1 |
非线程安全 | 线程安全(方法都带有synchronized关键字) |
key和value可以为null | key和value都不能是null |
6、属性类Properties类
- 6.1、Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型。
- 6.2、Properties被称为属性类对象。
- 6.3、Properties是线程安全的。
- 6.4、常用两个方法,一个存,一个取。
setProperty
getProperty
import java.util.Properties;
public class PropertiesTest01 {
public static void main(String[] args) {
// 创建一个Properties对象
Properties pro = new Properties();
// 存
pro.setProperty("username","zhangsan");
pro.setProperty("password","123");
//取 通过key获取value
String user = pro.getProperty("username");
String pwd = pro.getProperty("password");
System.out.println(user);
System.out.println(pwd);
}
}
/*
zhangsan
123
*/
7、TreeSet集合(TreeMap集合的key部分)
-
7.1、TreeSet集合底层实际上是一个TreeMap
-
7.2、TreeMap集合底层是一个二叉树。
-
7.3、放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了。
-
7.4、TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序。 称为:可排序集合。
(1)无序:存储时顺序和取出的顺序不同,并且没有下标
(2)不可重复:元素不可重复
(3)可以按照元素的大小顺序自动排序:
————存进去的String类型的字符串,遍历时,输出的按照字典顺序,升序!
————存进去的Integer整数类型,遍历时,输出的按照大小,升序!
import java.util.TreeSet;
public class TreeSetTest02 {
public static void main(String[] args) {
// 创建一个TreeSet集合
TreeSet<String> ts = new TreeSet<>();
// 添加String
ts.add("zhangsan");
ts.add("lisi");
ts.add("wangwu");
ts.add("zhangsi");
ts.add("wangliu");
// 遍历
for(String s : ts){
// 按照字典顺序,升序!
System.out.println(s);
}
TreeSet<Integer> ts2 = new TreeSet<>();
ts2.add(100);
ts2.add(200);
ts2.add(900);
ts2.add(800);
ts2.add(600);
ts2.add(10);
for(Integer elt : ts2){
// 升序!
System.out.println(elt);
}
}
}
/*
lisi
wangliu
wangwu
zhangsan
zhangsi
10
100
200
600
800
900
*/
-
7.5、TreeSet集合到底有什么用处?
数据库中有很多数据: userid name birth ------------------------------------- 1 zs 1980-11-11 2 ls 1980-10-11 3 ww 1981-11-11 4 zl 1979-11-11 编写程序从数据库当中取出数据,在页面展示用户信息的时候按照生日升序或者降序。 这个时候可以使用TreeSet集合,因为TreeSet集合放进去,拿出来就是有顺序的。
-
7.6、TreeSet集合无法对自定义类型排序
以下程序中对于Person类型来说,无法排序。 因为没有指定Person对象之间的比较规则。谁大谁小并没有说明啊。 运行时出现了这个异常: java.lang.ClassCastException: class com.yuming.javase.collection.Person cannot be cast to class java.lang.Comparable 出现这个异常的原因是: Person类没有实现java.lang.Comparable接口。 而之前的String类和Integer类在创建时已经实现了Comparable接口,所以可以自动排序
package com.yuming.javase.collection;
import java.util.TreeSet;
public class TreeSetTest03 {
public static void main(String[] args) {
Person p1 = new Person(32);
//System.out.println(p1);
Person p2 = new Person(20);
Person p3 = new Person(30);
Person p4 = new Person(25);
// 创建TreeSet集合
TreeSet<Person> persons = new TreeSet<>();
// 添加元素
persons.add(p1);
persons.add(p2);
persons.add(p3);
persons.add(p4);
// 遍历
for (Person p : persons){
System.out.println(p);
}
}
}
class Person {
int age;
public Person(int age){
this.age = age;
}
// 重写toString()方法
public String toString(){
return "Person[age="+age+"]";
}
}
自定义类型想要排序,可以实现Comparable接口
【示例1】:
import java.util.TreeSet;
public class TreeSetTest04 {
public static void main(String[] args) {
Customer c1 = new Customer(32);
Customer c2 = new Customer(20);
Customer c3 = new Customer(30);
// 创建TreeSet集合
TreeSet<Customer> customers = new TreeSet<>();
// 添加元素
customers.add(c1);
customers.add(c2);
customers.add(c3);
// 遍历
for (Customer c : customers){
System.out.println(c);
}
}
}
// 放在TreeSet集合中的元素需要实现java.lang.Comparable接口
// 并且实现compareTo方法。equals可以不写。
class Customer implements Comparable<Customer>{
int age;
public Customer(int age){
this.age = age;
}
public String toString(){
return "Customer[age="+age+"]";
}
// 需要在这个方法中编写比较的逻辑,或者说比较的规则,按照什么进行比较!
// k.compareTo(t.key)
// 拿着参数k和集合中的每一个k进行比较,返回值可能是>0 <0 =0
// 比较规则最终还是由程序员指定的:例如按照年龄升序。或者按照年龄降序。
@Override
public int compareTo(Customer c) { // c1.compareTo(c2);
// this是c1,c是c2
// c1和c2比较的时候,就是this和c比较。
/*int age1 = this.age;
int age2 = c.age;
if(age1 == age2){
return 0;
} else if(age1 > age2) {
return 1;
} else {
return -1;
}*/
//以上代码可简化:
return this.age - c.age; // =0 >0 <0 升序
//return c.age - this.age; // 降序
}
}
/*
输出结果:
Customer[age=20]
Customer[age=30]
Customer[age=32]
*/
【示例2】:
import java.util.TreeSet;
/*
先按照年龄升序,如果年龄一样的再按照姓名升序。
*/
public class TreeSetTest05 {
public static void main(String[] args) {
TreeSet<Vip> vips = new TreeSet<>();
vips.add(new Vip("zhangsi", 20));
vips.add(new Vip("zhangsan", 20));
vips.add(new Vip("king", 18));
vips.add(new Vip("soft", 17));
for(Vip vip : vips){
System.out.println(vip);
}
}
}
class Vip implements Comparable<Vip>{
String name;
int age;
public Vip(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Vip{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
/*
compareTo方法的返回值很重要:
返回0表示相同,value会覆盖。
返回>0,会继续在右子树上找。【10 - 9 = 1 ,1 > 0的说明左边这个数字比较大。所以在右子树上找。】
返回<0,会继续在左子树上找。
*/
@Override
public int compareTo(Vip v) {
// 写排序规则,按照什么进行比较。
if(this.age == v.age){
// 年龄相同时按照名字排序。
// 姓名是String类型,可以直接比。调用compareTo来完成比较。
return this.name.compareTo(v.name);
} else {
// 年龄不一样
return this.age - v.age;
}
}
}
/*
Vip{name='soft', age=17}
Vip{name='king', age=18}
Vip{name='zhangsan', age=20}
Vip{name='zhangsi', age=20}
*/
compareTo方法的返回值很重要:
返回0表示相同,value会覆盖。
返回>0,会继续在右子树上找。【10 - 9 = 1 ,1 > 0的说明左边数字(要存放的元素)比较大。所以左边数字再到右子树上找、去比较】
返回<0,会继续在左子树上找。
8、自平衡二叉树 数据结构 (了解)
存储原则:左小右大
遍历方式:中序遍历(左根右)
9、TreeMap的key或者TreeSet集合中的元素要想排序,有两种实现方式
-
第一种:实现java.lang.Comparable接口(上面已讲)
-
第二种:实现java.util.Comparator(比较器)接口
方法一:单独编写一个比较器的类,比较器实现java.util.Comparator接口
方法二:使用匿名内部类的方式(这个类没有名字。直接new接口。)
【示例】:
import java.util.Comparator;
import java.util.TreeSet;
public class TreeSetTest06 {
public static void main(String[] args) {
// 创建TreeSet集合的时候,需要传一个比较器。
// TreeSet<WuGui> wuGuis = new TreeSet<>();//这样不行,没有通过构造方法传递一个比较器进去。
// 给构造方法传递一个比较器。【方法一代码片段】
//TreeSet<WuGui> wuGuis = new TreeSet<>(new WuGuiComparator());
// 大家也可以使用匿名内部类的方式(这个类没有名字。直接new接口。)【方法二代码片段】
TreeSet<WuGui> wuGuis = new TreeSet<>(new Comparator<WuGui>() {
@Override
public int compare(WuGui o1, WuGui o2) {
return o1.age - o2.age;
}
});
wuGuis.add(new WuGui(1000));
wuGuis.add(new WuGui(800));
wuGuis.add(new WuGui(810));
for(WuGui wuGui : wuGuis){
System.out.println(wuGui);
}
}
}
// 乌龟
class WuGui{
int age;
public WuGui(int age){
this.age = age;
}
@Override
public String toString() {
return "小乌龟[" +"age=" + age +']';
}
}
// 单独在这里编写一个比较器【方法一代码片段】
// 比较器实现java.util.Comparator接口。(Comparator是java.util包下的。而Comparable是java.lang包下的。)
/*
class WuGuiComparator implements Comparator<WuGui> {
@Override
public int compare(WuGui o1, WuGui o2) {
// 指定比较规则
// 按照年龄排序
return o1.age - o2.age;
}
}
*/
/*
小乌龟[age=800]
小乌龟[age=810]
小乌龟[age=1000]
*/
- Comparable和Comparator怎么选择呢?
(1)当比较规则不会发生改变的时候,或者说当比较规则只有1个的时候,建议实现Comparable接口
(2)如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口
Comparator接口的设计符合OCP原则(对扩展开放,对修改关闭。)。
另外:String类和Integer类在创建时已经实现了Comparable接口。
10、集合工具类Collections
-
注:java.util.Collections 是集合工具类,方便集合的操作
java.util.Collection 是集合接口,下面有List集合接口、Set集合接口 -
Collections的其中两个方法:
1、Collections.synchronizedList(list); // 将非线程安全的集合变成线程安全的
2、Collections.sort(list); // 将list集合中的元素排序(要求集合中元素已实现Comparable接口。) -
通过集合工具类Collections,我们不只是TreeMap的key或者TreeSet集合中的元素可以排序了,List集合和Set集合也可以排序了
(要求集合中元素已实现Comparable接口,另外Set集合需要先转换成List集合才能再排序)
import java.util.*;
public class CollectionsTest {
public static void main(String[] args) {
// ArrayList集合不是线程安全的。
List<String> list = new ArrayList<>();
// 第一个方法: 变成线程安全的
Collections.synchronizedList(list);
list.add("abf");
list.add("abx");
list.add("abc");
list.add("abe");
//第二个方法: 排序
Collections.sort(list);
for(String s : list){
System.out.println(s);
}
System.out.println("——————我是分割线——————");
List<WuGui2> wuGuis = new ArrayList<>();
wuGuis.add(new WuGui2(1000));
wuGuis.add(new WuGui2(8000));
wuGuis.add(new WuGui2(500));
// 注意:对List集合中元素排序,需要保证List集合中的元素实现了:Comparable接口。
Collections.sort(wuGuis);
for(WuGui2 wg : wuGuis){
System.out.println(wg);
}
System.out.println("——————我是分割线——————");
// 对Set集合怎么排序呢?
Set<String> set = new HashSet<>();
set.add("king");
set.add("kingsoft");
set.add("king2");
set.add("king1");
// 将Set集合转换成List集合
List<String> myList = new ArrayList<>(set);
Collections.sort(myList);
for(String s : myList) {
System.out.println(s);
}
// 这种方式也可以排序。
//Collections.sort(list集合, 比较器对象);
}
}
//自定义类型,排序,这里实现Comparable接口
class WuGui2 implements Comparable<WuGui2>{
int age;
public WuGui2(int age){
this.age = age;
}
//重写compareTo方法
@Override
public int compareTo(WuGui2 o) {
return this.age - o.age;
}
@Override
public String toString() {
return "WuGui2{" + "age=" + age +'}';
}
}
/*
abc
abe
abf
abx
——————我是分割线——————
WuGui2{age=500}
WuGui2{age=1000}
WuGui2{age=8000}
——————我是分割线——————
king
king1
king2
kingsoft
*/
【总结——集合】:集合这块最主要掌握什么内容?
1、每个集合对象的创建(new)
2、向集合中添加元素
3、从集合中取出某个元素
4、遍历集合
5、主要的集合类:
ArrayList
LinkedList
HashSet (同HashMap的key,存储在HashMap集合key的元素需要同时重写hashCode + equals)
TreeSet
HashMap (存储在HashMap集合key的元素需要同时重写hashCode + equals)
Properties
TreeMap
1、【ListTest.java】—ArrayList、LinkedList
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
/*
1、每个集合对象的创建(new)
2、向集合中添加元素
3、从集合中取出某个元素
4、遍历集合
*/
public class ListTest {
public static void main(String[] args) {
//1、每个集合对象的创建(new)
//ArrayList<String> list = new ArrayList<>(); // ArrayList集合
LinkedList<String> list = new LinkedList<>(); // LinkedList集合
//2、向集合中添加元素
list.add("张三");
list.add("lisi");
list.add("wangwu");
//3、从集合中取出某个元素 (List集合有下标)
String s1 = list.get(0);
System.out.println(s1); // 张三
System.out.println(list.get(1)); // lisi
//4、遍历集合
//遍历1(迭代器方式,是所有Collection通用的一种方式)
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
//遍历2(下标方式)(List集合有下标)
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
//遍历3(增强for循环)
for (String s : list) {
System.out.println(s);
}
// 遍历4: while循环修改为for循环
for(Iterator<String> it2 = list.iterator(); it2.hasNext(); ){
System.out.println("====>" + it2.next());
}
}
}
/*
张三
lisi
张三
lisi
wangwu
张三
lisi
wangwu
张三
lisi
wangwu
====>张三
====>lisi
====>wangwu
*/
2、【HashSetTest.java】—HashSet
import java.util.HashSet;
import java.util.Iterator;
import java.util.Objects;
/*
1、每个集合对象的创建(new)
2、向集合中添加元素
3、从集合中取出某个元素
4、遍历集合
5、测试HashSet集合的特点:无序不可重复。
6、HashSet ,实际上是在HashMap的key部分(存储在HashMap集合key的元素需要同时重写hashCode + equals)
*/
public class HashSetTest {
public static void main(String[] args) {
// 1、每个集合对象的创建(new)
HashSet<String> set = new HashSet<>();
// 2、向集合中添加元素
set.add("abc");
set.add("def");
set.add("king");
// 3、从集合中取出某个元素
// set集合中的元素不能通过下标取了。没有下标。取不出某个元素
// 4、遍历集合
//遍历1(迭代器方式,是所有Collection通用的一种方式)
Iterator<String> it = set.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
//遍历2(增强for循环)
for (String s : set) {
System.out.println(s);
}
//5、测试HashSet集合的特点:无序不可重复。
set.add("king");
set.add("king");
System.out.println(set.size()); //3 (不可重复--后面2个king都没有加进去。)
set.add("1");
set.add("10");
set.add("2");
for (String s : set) {
System.out.println("----->"+s);// (无序--遍历出来的和存进去的顺序不一样)
}
//6、HashSet ,实际上是在HashMap的key部分(存储在HashMap集合key的元素需要同时重写hashCode + equals)
HashSet<Student> students = new HashSet<>();
Student s1 = new Student(111,"zhangsan");
Student s2 = new Student(222,"lisi");
Student s3 = new Student(111,"zhangsan");
students.add(s1);
students.add(s2);
students.add(s3);
System.out.println(students.size()); // 2 不可重复,s3没有添加进去
// 遍历
for(Student stu : students){
System.out.println(stu);
}
}
}
class Student{
int no;
String name;
public Student() {
}
public Student(int no, String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return no == student.no &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(no, name);
}
}
/*
abc
def
king
abc
def
king
3
----->1
----->2
----->abc
----->def
----->king
----->10
2
Student{no=222, name='lisi'}
Student{no=111, name='zhangsan'}
*/
3、【TreeSetTest.java】—TreeSet、 TreeMap
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
/*
1、每个集合对象的创建(new)
2、向集合中添加元素
3、从集合中取出某个元素
4、遍历集合
5、测试TreeSet集合中的元素是可排序的。
6、测试TreeSet集合中存储的类型是自定义的。
7、第一种方式:测试实现Comparable接口的方式
8、第二种方式:测试实现Comparator接口的方式
8.1 方法一:单独编写一个比较器的类,比较器实现java.util.Comparator接口
8.2 方法二:使用匿名内部类的方式(这个类没有名字。直接new接口。)
*/
public class TreeSetTest {
public static void main(String[] args) {
//1、集合的创建(可以测试以下TreeSet集合中存储String、Integer的。这些类都是SUN写好的。)
//TreeSet<Integer> ts = new TreeSet<>();
// 5、可以编写比较器可以改变规则(让遍历结果降序)。重写比较器
TreeSet<Integer> ts = new TreeSet<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1; // 自动拆箱(把Integer拆成数字)
}
});
//2、向集合中添加元素
ts.add(1);
ts.add(100);
ts.add(10);
ts.add(10);
ts.add(10);
ts.add(10);
ts.add(0);
// 3、从集合中取出某个元素
// set集合中的元素不能通过下标取了。没有下标。取不出某个元素
// 4、遍历集合
//遍历1(迭代器方式,是所有Collection通用的一种方式)
Iterator<Integer> it = ts.iterator();
while (it.hasNext()){
System.out.println(it.next()); //5、结果是排序了的
}
//遍历2(增强for循环)
for(Integer x : ts){
System.out.println(x); //结果是排序了的
}
// 6、TreeSet集合中存储自定义类型
// 7、A类创建时已经在下面实现Comparable接口
TreeSet<A> atree = new TreeSet<>();
atree.add(new A(1000));
atree.add(new A(200));
atree.add(new A(500));
atree.add(new A(300));
atree.add(new A(400));
atree.add(new A(100));
// 遍历
for(A a : atree){
System.out.println(a);
}
// 8.1 给构造方法传递自己在下面写的比较器:BComparator
TreeSet<B> btree = new TreeSet<>(new BComparator());
// // 8.2 匿名内部类方式
// TreeSet<B> btree = new TreeSet<>(new Comparator<B>() {
// @Override
// public int compare(B o1, B o2) {
// return o1.i - o2.i;
// }
// });
btree.add(new B(500));
btree.add(new B(1000));
btree.add(new B(2000));
btree.add(new B(600));
btree.add(new B(300));
btree.add(new B(5));
for(B b : btree){
System.out.println(b);
}
}
}
// 7、第一种方式:实现Comparable接口
class A implements Comparable<A>{
int i;
public A() { }
public A(int i) { this.i = i; }
@Override
public String toString() { return "A{" + "i=" + i + '}'; }
@Override
public int compareTo(A o) {
//return this.i - o.i; //升序
return o.i - this.i; //降序
}
}
class B{
int i;
public B(int i) { this.i = i; }
@Override
public String toString() { return "B{" + "i=" + i + '}'; }
}
//8.1 单独编写一个比较器的类,比较器实现java.util.Comparator接口
class BComparator implements Comparator<B>{
@Override
public int compare(B o1, B o2) {
return o1.i - o2.i;
}
}
/*
100
10
1
0
100
10
1
0
A{i=1000}
A{i=500}
A{i=400}
A{i=300}
A{i=200}
A{i=100}
B{i=5}
B{i=300}
B{i=500}
B{i=600}
B{i=1000}
B{i=2000}
*/
4、【HashMapTest.java】—HashMap
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/*
1、每个集合对象的创建(new)
2、向集合中添加元素(所有的Map集合的key部分 同 Set集合特点:无序不可重复 )
3、从集合中取出某个元素
4、遍历集合
第一种:获取所有key,遍历每个key,通过key获取value.
第二种:使用这个方法Set<Map.Entry<K,V>> entrySet() ,把Map集合直接全部转换成Set集合
遍历Set集合中的node,调用node.getKey(), node.getValue()
*/
public class HashMapTest {
public static void main(String[] args) {
// 1、创建 HashMap 集合
// Integer是key,它的hashCode和equals都已经重写了。
Map<Integer, String> map = new HashMap<>();
// 2、添加元素
map.put(1, "zhangsan");
map.put(9, "lisi");
map.put(10, "wangwu");
map.put(2, "king");
map.put(2, "simth"); // key重复value会覆盖。
System.out.println(map.size()); // 4
// 3、取元素--取key是2的元素
System.out.println(map.get(2)); //simth
// 4、遍历Map集合很重要,几种方式都要会。
// 第一种方式:先获取所有的key,遍历key的时候,通过key获取value
Set<Integer> keys = map.keySet();
for(Integer key : keys){
System.out.println(key + "=" + map.get(key));
}
// 第二种:使用这个方法Set<Map.Entry<K,V>> entrySet() ,把Map集合直接全部转换成Set集合
// 遍历Set集合中的node,调用node.getKey(), node.getValue()
Set<Map.Entry<Integer,String>> nodes = map.entrySet();
for (Map.Entry<Integer,String> node: nodes) {
System.out.println(node.getKey()+"="+node.getValue());
}
}
}
/*
4
simth
1=zhangsan
2=simth
9=lisi
10=wangwu
1=zhangsan
2=simth
9=lisi
10=wangwu
*/
5、【PropertiesTest.java】—Properties
import java.util.Properties;
public class PropertiesTest {
public static void main(String[] args) {
//创建对象
Properties pro = new Properties();
//存
pro.setProperty("username","zhangsan");
pro.setProperty("password","123");
//取
String u= pro.getProperty("username");
String p= pro.getProperty("password");
System.out.println(u);
System.out.println(p);
}
}
/*
zhangsan
123
*/