-
javaSE中级篇3——集合体系(另外一种存储容器)——更新完毕
集合还是一种工具,所以它们的包都在java.util包下
1、集合的整个体系结构(是需要掌握的体系,完全体系不是这样)
-
对图中所说的 序和重复 这两词的说明:
- 序:指的是添加进去的元素和取出来的元素 的 顺序是一样的(不是指集合本身的顺序),如:放进去的时候这个元素在集合中是第一个,取的时候还是第一个——可以参照数组,数组里面的元素不就是有索引,有顺序吗
- 重复:集合中的两个元素一样,一样的道理:拿数组参照,数组中可以放两个a涩
- 虽然这个体系很大,但是学懂分支集合中的一种,分支下的其他集合就懂了,因为“面向接口”编程嘛,而它们都有同样的接口,所以这些集合自身的方法都不太会用,不然在另外层次里面无法适用了
collection集合接口中提供的方法:开发中要用方法一般也是用下面这些,不会轻易用每个子集合特有的方法(因:在另外子集合中不兼容,导致出问题)
返回值类型 方法名及作用
1、boolean add(E e)
向集合中添加元素(任何类型都可以)————有重载
2、boolean addAll( Collection<? extends E> c )
将指定collection中的所有元素都添加到此collection中————即:交集
3、void clear()
移除此collection中的所有元素————有重载
4、boolean contains( Object o )
查看给定元素( Object o )是否在集合中
5、boolean containsAll( Collection<?> c )
查看传进来的这个集合,是否包含了调用这个方法的集合的所有元素
6、boolean equals( Object o )
判断两个集合是否相等
7、int hashCode()
返回此 collection 的哈希码值
8、boolean isEmpty()
如果此 collection 不包含元素,则返回 true
9、Iterator<E> iterator()
给调用这个方法的集合,生成一个迭代器( 集合遍历需要这个 )
10、boolean remove( Object o)
从此 collection 中移除指定元素的单个实例,如果存在的话————有重载
11、boolean removeAll( Collection<?> c )
移除此 collection 中包含了指定 collection 的所有元素
12、boolean retainAll( Collection<?> c )
仅保留此 collection 中包含了指定 collection 的元素
13、int size()
返回此 collection 中的元素数
14、Object[] toArray()
返回包含此 collection 中所有元素的数组
-
ArrayList集合————适合遍历,不适合插入和删除
- 这个集合是Vector集合的后来版本,这二者的区别和StringBuffer、StringBuilder的区别一样
- 这个集合是在java.util包下
-
这个集合底层中是利用数组实现的,所以优点才是:适合遍历,缺点是:不适合插入和删除
- 另外:由于本质是数组,所以就有索引(即:下标)————也就是有序。同时数组中存放东西是可以有一样的————因此也就是可重复
- 继而:ArrayList集合的特点就是————有序可重复
-
这个集合如何创建对象?
-
无参构造
- ArrayList arrayList = new ArrayList()
-
自定义空间大小的有参构造
- ArrayList arrayList = new ArrayList( 20 )
-
直接把另外一个集合当做新集合的内容、空间大小
- ArrayList arrayList = new ArrayList( anotherCollecctionName )
-
当然以上的创建方式都太low了(这些都是针对于这个集合本身来创建对象的),并且不符合实际开发( 利用多态创建 )————重点掌握这种
-
父类类型引用 指向 子类类型
-
list arrayList = new ArrayList();
- 这种创建方式就符合规范了,因为集合接口的子集合虽然有自己特有的那些方法,但是在其他集合接口下会出问题,所以最好用集合接口中提供的方法即可,这样整懂一个集合下的子集合,那这个集合接口下的另外子集合也可以通用了【 因为都是实现自同一个接口,所以改new后面的名字即可。如:创建LinkedList集合,则:list linkedList = new LinkedList() 】,最后只需要考虑每种集合在什么场景下使用最适合即可
- 注:要看源码的时候,最好还是采用前面的那三种创建方式,因为使用多态创建的话,编译看左边,运行看右边,所以源码就是跳到左边的那个集合去了
-
list arrayList = new ArrayList();
-
父类类型引用 指向 子类类型
-
无参构造
-
集合是一个容器,所以常用方法就是容器的基本操作————增删改取查(有点儿特殊),Collection、List接口提供的方法ArrayList这个子集合都可以玩
- add()————向集合中添加元素————有重载,支持各种数据类型————也是开发中经常用的(甚至也会不用)——看实际需求
List arrayList = new ArrayList();
arrayList.add("邪公子");
arrayList.add("喜爱Java");
arrayList.add("不爱女人");
-
-
remove( Object obj )————删除集合中给定的内容( 即:Object obj ),删除的内容是什么类型都可以,因为是Object类型的————另:这个方法有重载
- remove( int index ) ————删除给定索引位置的元素
-
问题来了:Object类型兼容其他的任何类型,那么后面这个方法参数是int index,这不也兼容进去了吗?————所以怎么解决?
- 若是调用remove( Object obj ) ,则:传参时,通过new的方式传参
-
remove( Object obj )————删除集合中给定的内容( 即:Object obj ),删除的内容是什么类型都可以,因为是Object类型的————另:这个方法有重载
arrayList.remove("不爱女人");
arrayList.remove(1); // 这种传递参数效果其实和"1"是等效的
// 问题的解决
arrayList.remove("不爱女人");
arrayList.remove( new Integer( 1 ) ); // 这样就表明我调用的是integer类型参数的方法,而不是Object类型的了(注意:是new的包装类类型)
-
- removeAll()————差集
- retainAll()————交集
List arrayList2 = new ArrayList( Arrays.asList(1,35,6,8,3,9) );
List arrayList3 = new ArrayList( Arrays.asList(1,9,6) );
arrayList2.removeAll(arrayList3);
System.out.println( arrayList2 );
-
- size()————获取ArrayList集合的个数大小,和数组的length()一样的东西
-
- get( int index )————取集合中index位置的元素
List arrayList = new ArrayList();
arrayList.add("邪公子");
arrayList.add("喜爱Java");
arrayList.add("不爱女人");
System.out.println( arrayList.get(0) );
-
- set( int index , Object obj )————这是把index位置的元素 改成 obj(任意类型的内容都可以)
List arrayList = new ArrayList();
arrayList.add("邪公子");
arrayList.add("喜爱Java");
arrayList.add("不爱女人");
arrayList.add("是怎么回事?");
arrayList.set(2, "这是一种病吗?");
System.out.println( arrayList );
- ArrayList集合自身拓展的方法如下:
返回值类型 方法名及作用
1、void add(int index, E element)
在列表的指定位置插入指定元素(可选操作)
2、boolean addAll(int index, Collection<? extends E> c)
将指定 collection 中的所有元素都插入到列表中的指定位置
3、E get(int index)
返回列表中指定位置的元素
4、int indexOf(Object o)
返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1
5、int lastIndexOf(Object o)
返回此列表中最后出现的指定元素的索引;如果列表不包含此元素,则返回 -1
6、E remove(int index)
移除列表中指定位置的元素(可选操作)
7、E set(int index, E element)
用指定元素替换列表中指定位置的元素(可选操作)
8、List<E> subList(int fromIndex, int toIndex)
返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图
-
集合的查询有三种方式——System这个是打印出来了,但是不是查询(遍历)
- 通过普通for
List arrayList = new ArrayList( Arrays.asList(1, 5, 3, 7, 9, 2) );
for (int i = 0; i < arrayList.size(); i++) {
System.out.print( arrayList.get( i ) + " " );
}
-
- 通过增强for————JDK1.5之后才可以玩的
List arrayList = new ArrayList( Arrays.asList(1, 5, 3, 7, 9, 2) );
for ( Object element : arrayList ) { // Object方便吗?在看ArrayList源码分析时做说明
System.out.print( element + " ");
}
-
-
通过迭代器【 iterator ]来实现
-
先解释一下什么叫迭代器?
- 可以理解为:传送带传送东西————传送过来了,看有没有东西【这就是一个方法hasNext() 】,有就拿【 这也是一个方法next() 】,没有就等着后面传过来
-
先解释一下什么叫迭代器?
-
通过迭代器【 iterator ]来实现
-
- 怎么通过迭代器遍历集合?————建议用这种遍历集合( 因为增强for的原理就是这个 )
List arrayList = new ArrayList( Arrays.asList(1, 5, 3, 7, 9, 2) );
// 1、获取迭代器对象————即:利用iterator()方法
// 这一步可以粗暴理解为:告知系统,谁需要进行迭代遍历
// 产生的这个迭代器对象————就替代为了:想要进行遍历的那个集合(容器)
Iterator iterator = arrayList.iterator();
// 有了需要进行遍历的容器之后
// 2、就开始对这个容器进行遍历
while ( iterator.hasNext() ){ // 看这个容器有没有东西可以拿
// 3、有就拿出来
System.out.print( iterator.next() + " ");
}
- 再来看一下ArrayList集合的源码:
-
- 从中发现两个有意思的东西,即:< E > ,以及Object[],甚至后面代码的,Collection< ? extends E >,所以这里引申出另外一个知识:泛型
-
泛型
- 由于ArrrayList集合的底层中用了Object[],也就是说:什么类型都可以传进去,而这样的话,我们在取元素的时候就要把Object类型转为我们想要的类型( 即:多态 ),因此很麻烦
- 所以在JDK1.5之后,泛型就出来了
- 定义:用来规定数据类型的,即:定义的时候使用一个符号来代替不确定的数据类型(这个符号可以是随意的),而在使用的时候,我们用符号代替的那个数据类型就会随着我们传进去的数据类型变化而变化
举个例子:自己定义一个类,然后这个类中属性,方法这些的数据类型俺都不晓得
package cn.xieGongZi.test;
// 自定义的Person类
public class Person<A,B> { // 这里不确定类型,就传两个未知的A和B,
// 这样后面用到的A和B就来自于用这个类的时候,传进来的数据类型
A notName;
B tooNotName;
public Person() {
}
public Person(A notName, B tooNotName) {
this.notName = notName;
this.tooNotName = tooNotName;
}
public A getNotName() {
return notName;
}
public void setNotName(A notName) {
this.notName = notName;
}
public B getTooNotName() {
return tooNotName;
}
public void setTooNotName(B tooNotName) {
this.tooNotName = tooNotName;
}
@Override
public String toString() {
return "Person{" +
"notName=" + notName +
", tooNotName=" + tooNotName +
'}';
}
}
// 测试类
package cn.xieGongZi.test; public class Demo { public static void main(String[] args) { // 在这里用的时候,把具体的数据类型传进去,那么Person里面A就对应Integer,B就对应String类型了 Person<Integer, String> person = new Person<>(); } }
- 以上这种就称之为“泛型”
-
泛型可以用在哪些地方?
- 泛型类————就是前面示例中所展示的Person
-
- 泛型集合————这种直接可以声明:创建的集合存的是什么类型的value
package cn.xieGongZi.play;
import java.util.ArrayList;
import java.util.List;
public class Demo {
public static void main(String[] args) {
List<String> list = new ArrayList<>(); // 这表明:这个集合存储的全是String字符串类型的value
}
}
-
- 泛型接口————和泛型类的使用基本一致( 注:子类实现泛型接口的时候,必须使用泛型 ),如:
interface Test<X> { // 这是一个泛型接口
X value;
}
class Son<X> implements Test<X> { // 这个子类就必须使用泛型
}
-
-
泛型方法————方法可以传参数涩,所以这个参数就可以使用泛型了
- 注:泛型与类无关,带有泛型的方法可以不放在泛型类中
-
泛型方法————方法可以传参数涩,所以这个参数就可以使用泛型了
-
-
高级泛型————泛型边界(或者叫泛型限定)————通过extends、super来实现
- 简单理解:设计泛型方法的时候,对传递的参数 的 泛型类型做一些要求
-
高级泛型————泛型边界(或者叫泛型限定)————通过extends、super来实现
public static void m1(List<Integer> obj ){ // 接收一个 特定泛型的集合 参数
}
public static void m2( List<?> obj ){ // 接收一个 任意泛型的集合 参数 ?不限定
}
public static void m3( List< ? super Integer > obj ){ // 接收一个 特定泛型及其父类类型 的集合参数
// 这种的理解就是:只能接收Integer、及继承Integer这个类的类型
}
public static void m4( List< ? extends Number > obj ){ // 接收一个 特定泛型及其子类类型 的集合参数
// 用这个方法来理解就是:传递进来的类型只能是Number的子类 类型
/*
对于List< ? extends Number > 的详细解读
List集合是一种集合接口————有ArrayList、LinkedList、Vector....集合
——————>所以:传递进来的参数,必须是List接口下其中一个集合类型
< ? extends Number > 是指:看传递进来的这个参数的类型 是不是 Number类型的子类
*/
}
-
-
泛型擦除
- java中的泛型都是伪泛型,只是一种编译效果。一旦编译泛型结束之后,泛型信息就没有了,所以不同的泛型之间不可以构成重载
-
泛型擦除
public static void m1( List<Integer> obj ){
}
public static void m1( List<Double> obj ){ // 报错 不可重载,泛型编译结束之后,这个<Double>泛型信息就没了
}
//--------------------泛型擦除后------------------------
public static void m1( List obj ){ // 参数一样了
}
public static void m1( List obj ){ // 参数一样了
}
-
- 泛型 不支持 多态
public static void main(String[] args) {
List<Integer> brr = new ArrayList<>();
List<Number> arr ;
arr= brr; // 报错 不可直接赋值,不考虑多态问题,泛型类+具体类型完全是一个全新类型。
}
-
collection接口的另外两个子集合————玩法和ArrayList集合一样,只需要创建的时候改成相应的集合即可
-
Vector集合————就是ArrayList集合的早期版本(区别与 StringBuffer和StringBuilder一样)
- 但是:vector底层中的扩容方式和ArrayList不同————vector底层中是2倍扩容的
- LinkedList集合————底层中是通过:双向链表来实现的,如下图所示:
-
Vector集合————就是ArrayList集合的早期版本(区别与 StringBuffer和StringBuilder一样)
-
-
- 所以LinkedList集合的优点就来了:适合插入和删除(只要把上下链断开,然后把地址改成加入的链的上下地址即可)。缺点就是:不适合遍历(因为这不是连续存储的,而是这个数据的左右有一个地址存储空间,是通过这个地址去找寻的上下链存的数据,所以就只能从第一个开始找,才能找到想要找的那个数据)
-
-
Set接口————注:这个分支下的集合不可以单独取出某一个元素,需要先遍历,然后再取出想要的元素
-
hashSet集合————底层是用数组+链表实现的
- 这个集合也是Collection接口的一个子集合,但是这个集合没有拓展任何新的方法,即:它的方法就是Collection接口中的那些方法
- 这个集合是一个“无序不重复”集合
-
hashSet集合————底层是用数组+链表实现的
-
-
是集合,即:容器,则:还是离不开——增删查取————注:这个集合没有修改这个方法———因为:hashSet无序——当然:不是一定不能修改,只是没有提供这个方法而已,但是:可以遍历出来,然后判断是不是某一个值,然后利用值覆盖不就可以改了
- hashSet集合中的方法也就是下面这些,在ArrayList中已经玩过了,就不重复玩了,在ArrayLIst中怎么玩,这里就怎么玩
-
是集合,即:容器,则:还是离不开——增删查取————注:这个集合没有修改这个方法———因为:hashSet无序——当然:不是一定不能修改,只是没有提供这个方法而已,但是:可以遍历出来,然后判断是不是某一个值,然后利用值覆盖不就可以改了
-
-
hashSet集合的遍历
-
这个集合是无序不重复的,所以for循环就直接不考虑了
- 通过增强for
-
HashSet<String> hashSet = new HashSet<>(); hashSet.add("邪公子"); hashSet.add("小垃圾"); hashSet.add("太欠揍"); for ( String s : hashSet ) { System.out.println( s ); }
-
这个集合是无序不重复的,所以for循环就直接不考虑了
-
hashSet集合的遍历
-
-
-
- 使用迭代器
-
HashSet<String> hashSet = new HashSet<>(); hashSet.add("邪公子"); hashSet.add("小垃圾"); hashSet.add("太欠揍"); Iterator<String> iterator = hashSet.iterator(); while ( iterator.hasNext() ){ System.out.println( iterator.next() ); }
-
-
-
hashSet集合为什么可以无序不重复?
- 先来看一个效果
-
- 接着来看一下源码
-
-
通过这个源码发现一个有意思的东西:套娃,在创建hashSet集合对象的时候,居然去调了另外一个方法:hashMap,所以hashSet集合的底层就是一个hashMap()集合
-
因此hashSet集合为什么可以无序?————因为hashMap的底层不再是数组,是数组+链表+红黑树实现的,而hashSet只是取了hashMap的一部分功能,所以根本没有什么下标之说。至于具体怎么实现,后续在数据结构篇中会分析源码,然后做详细说明————当然:就这么简单了解,不深入理解也不影响操作
- 因此在这里简单理解就是:hashSet集合没有索引,所以根本无序
-
因此hashSet集合为什么可以无序?————因为hashMap的底层不再是数组,是数组+链表+红黑树实现的,而hashSet只是取了hashMap的一部分功能,所以根本没有什么下标之说。至于具体怎么实现,后续在数据结构篇中会分析源码,然后做详细说明————当然:就这么简单了解,不深入理解也不影响操作
-
通过这个源码发现一个有意思的东西:套娃,在创建hashSet集合对象的时候,居然去调了另外一个方法:hashMap,所以hashSet集合的底层就是一个hashMap()集合
-
-
再来了解一下hashSet去重的原理————因为:重写了hashCode() 和 equals()方法
-
先判断当前准备存储的元素的hashCode码 和 集合中已有的元素的hashCode码,看二者是否相同
- 如果不同,则说明这两个根本不是同一个对象嘛,所以直接把准备添加进来的元素保存到集合中
-
如果相同,则进行下一步判断
-
通过equals比较二者的内容,看是否相同
- 不同则直接保存
- 相同的话,就说明将要准备存储的元素 和 集合中某一个元素是一模一样的,因此直接拒绝让准备加入的元素进入到集合中————这就解决上面的问题:相同的value,集合中存的是第一次放进去的value还是第二次的——结果是:第一次的,因为第二次直接拒绝保存了
-
通过equals比较二者的内容,看是否相同
-
先判断当前准备存储的元素的hashCode码 和 集合中已有的元素的hashCode码,看二者是否相同
-
再来了解一下hashSet去重的原理————因为:重写了hashCode() 和 equals()方法
-
-
自己来模仿一下:
- 先来看一下不去重的效果
- 自己定义一个Book类(有名字、价格、作者这三个属性),想要这个Book类也实现无重复原则
-
自己来模仿一下:
-
-
-
-
-
package cn.xieGongZi.play; public class Book { private String name; private Double price; private String author; // 如下为一个类的基本配置 public Book() { } public Book(String name, Double price, String author) { this.name = name; this.price = price; this.author = author; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } @Override public String toString() { return "Book{" + "name='" + name + '\'' + ", price=" + price + ", author='" + author + '\'' + '}'; } }
-
-
-
-
-
-
-
注:这不是排好序了的,这只是一个巧合 ,证明一下:
- 回到正题:如何去重?
-
-
-
前面不是说了吗,是先比较hashCode,如果相同再比较equals涩,既然这样,那直接让它们的hashCode一样,然后再来自定义equals规则测试一下
- 即:在Book类中添加如下代码:
-
前面不是说了吗,是先比较hashCode,如果相同再比较equals涩,既然这样,那直接让它们的hashCode一样,然后再来自定义equals规则测试一下
-
@Override public int hashCode() { return 1; // 先让它们hashCode值一样,从而引导它去执行equals }
@Override
public boolean equals(Object obj) {
if ( this == obj ){ // 如果调用这个方法的对象(创建的这个类对象) 和 传进来的这个对象(集合 [这个集合就是我们自定义的Book] 中的某个对象)相同(即:hashCode值相同)
return true; // 则:说明将要存储的内容 和 集合中的某个对象的内容相同,这就告知这二者是同样的嘛,所以直接不保存了
}
// 不是同一个hashCode值(这个是指地址啊,别搞了这里,忘了Object中的hashCode是干嘛的),那看一下这个对象 是不是 自定义的Book类型
if ( obj instanceof Book ){
// 则:直接强制转换(不用担心会异常,因为上一步都确定是Book类型了
Book book = (Book) obj;
// 如果传进来的Book类的name、price、author 都和 这个自定义的Book集合中的某一个对象存的name、price、author一样
if (this.name.equals(book.name) && this.price.equals(book.price) && this.author.equals(book.author) ){
// 则:表明将要存进来的这个对象的内容 和 Book这个自定义集合中的某一个对象的内容是一样的
return true;
}
}
// 如果以上都不满足 则:说明不是同样的对象(hashCode值不同) 和 内容(equals不同)
return false;
}
- 去重效果如下:
-
当然:这是用来测试的,所以hashCode那里是定死了,实际开发中不需要这样,也不需要自己手写
- idea编辑器中:直接按alt+insert键,然后选择如下项,一直下一步下一步即可
-
-
set接口另一个子集合:treeSet集合
-
treeSet和hashSet都是Set接口下的子集合,而且都没新增任何方法
- 所以:它们的方法都一样,玩法也就一样了
-
treeSet和hashSet都是Set接口下的子集合,而且都没新增任何方法
-
set接口另一个子集合:treeSet集合
-
-
但是这个treeSet集合有两个地方需要注意:
- 1、这个集合可以自动排序——因为它是SortedSet接口的实现类
-
-
但:treeSet是怎么实现排序的?————因:treeSet重写了CompareTo()方法————重写这个方法有三种实现方式
- 第一种(自然排序):让类实现Comparable接口,重写CompareTo方法( IDEA编辑器中通过alt+回车,选择implements method,然后回车确认即可进行自己重写排序规则 )
- 还是用前面的Book类来举例
-
但:treeSet是怎么实现排序的?————因:treeSet重写了CompareTo()方法————重写这个方法有三种实现方式
-
-
-
从这个Book类中可以看出:利用price价格来比更合适(也可以选择其他的,只是price更合适而已)
- 在Book类中加入以下代码
-
从这个Book类中可以看出:利用price价格来比更合适(也可以选择其他的,只是price更合适而已)
-
@Override
public int compareTo(Object o) {
Book book = (Book) o;
if ( this.price > book.price ){
return 1;
}
if ( this.price < book.price ){
return -1;
}
// return 0; // 去重
return this.name.compareTo(book.name);
}
-
- 效果如下:
-
- 第二种方式(排序器排序):在创建treeSet的时候,传一个Comparable接口的实现类进去(利用匿名内部类)——在面向对象思想编程中讲的现写现用
TreeSet<Book> books = new TreeSet<>( new Comparator<Book>() {
@Override
public int compare(Book o1, Book o2) {
if ( o1.getPrice() > o2.getPrice() ){
return 1;
}
if ( o1.getPrice() < o2.getPrice() ){
return -1;
}
return 0;
}
});
-
- 效果图如下:
-
-
- 第三种方式:使用编辑器推荐的这种lambda(兰姆达)函数,也就是一个没有名字的函数,是基于数学中的“λ”这个东西演算而来的,目的是:为集合提供方法
-
-
-
-
-
lambda函数引申(简单理解——详细的有点复杂,用的时候记不得了直接面向百度编程):
- lambda函数的基本语法
-
-
即: ( 参数 ) -> 表达式。注:参数可以多个,也可以只有一个;表达式可以是一个常数,也可以是一个已经声明好了的变量,甚至是一个块(严格来理解这个块应该为一个集合体——然后这个集合体里面是一些判断流程,甚至其他都可以,具体看后面的实例分析),lambda函数带集合体的语法格式如下图所示:
-
-
lambda函数引申(简单理解——详细的有点复杂,用的时候记不得了直接面向百度编程):
-
-
-
-
-
-
-
lambda函数实例:
-
lambda函数实例:
-
-
-
-
-
-
-
-
- 实例分析:
-
-
-
-
-
-
-
-
-
-
- 参数类型可以显式声明(例 1、4),也可以隐式推断(例 2、5、6)。声明型和推断型参数不能混合在一个 Lambda 表达式中
- 主体可以是块(用括号括起来,例 6)或表达式(例 1 - 5)。块体可以返回一个值(例 6),也可以什么都不返回。在块体中使用或省略 return 关键字的规则与普通方法体的规则相同
- 如果主体是一个表达式,它可能返回一个值(例如 1、2、3、5)或什么也不返回(例如 4)
- 单个推断类型参数可以省略括号(例如 5、6)
- 例 6 的注释应该被理解为 Lambda 可以作用于一个集合。同样,根据它出现的上下文,它可以作用于其他类型的对象,这些对象具有方法大小和 clear,以及适当的参数和返回类型
-
-
-
-
-
以上便为集合的整个知识体系
接下来的Map严格来讲不是集合体系(不是实现了Iterable接口——它和iterable毫不相干),Map也不叫集合,更不叫地图,而是“映射”
把它放在这里是因为:
1、是因为Map也是用来存东西的,而且高频使用
2、是因为hashtable(哈希表)的儿子properties(属性)在后续会用来写配置信息
3、是因为hashMap是hashSet的原理,treeMap是treeSet的原理
-
Map接口————无序不重复原则(为什么会无序在数据结构篇中当做提升层面知识来说明——当然:其实在hashSet和treeSet已经提到了一部分)
- Map是一种映射,它保存的是key和value的对应关系,即:保存一组 / 多组对应关系
- 也在java.util包下
- Map体系结构如下(需要熟悉的体系,完全体不是这样):
-
- 前面的Collection体系都是单列存储,而这个Map是双列存储,如图所示:
-
- Map常用方法如下:
1、void clear()
从此映射中移除所有映射关系(有重载)
2、boolean containsKey(Object key)
如果此映射包含指定键的映射关系,则返回 true
3、boolean containsValue(Object value)
如果此映射将一个或多个键映射到指定值,则返回 true
4、Set<Map.Entry<K,V>> entrySet()
返回此映射中包含的映射关系的Set视图
5、boolean equals(Object o)
比较指定的对象与此映射是否相等
6、V get(Object key)
返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null7、int hashCode()
返回此映射的哈希码值
8、boolean isEmpty()
如果此映射未包含键-值映射关系,则返回 true
9、Set<K> keySet()
返回此映射中包含的键的Set视图
10、V put(K key, V value)
将指定的值与此映射中的指定键关联(有重载)
11、void putAll(Map<? extends K,? extends V> m)
从指定映射中将所有映射关系复制到此映射中(有重载)
12、V remove(Object key)
如果存在一个键的映射关系,则将其从此映射中移除(有重载)
13、int size()
返回此映射中的键-值映射关系数
14、Collection<V> values()
返回此映射中包含的值的 Collection视图
-
Map的实现类
-
怎么创建对象?————另:Map也支持泛型
-
对应它本身来说(看源码的时候会用如下方式创建对象)
- 无参构造 HashMap hashMap = new HashMap()
- 自定义空间大小的有参构造 HashMap hashMap = new HashMap( 20 ); // 底层中的默认空间大小为:16
-
也支持把相同映射Map 当做 新HashMap对象的参数
-
HashMap hashMap = new HashMap(); HashMap map = new HashMap( hashMap );
-
-
对应它本身来说(看源码的时候会用如下方式创建对象)
-
怎么创建对象?————另:Map也支持泛型
-
-
-
-
实际开发中的创建(多态):HashMap< Integer,String > hashMap = new HashMap<>();
- Integer和String泛型类型只是用来举例( 本例子的泛型表明:创建的这个Map对象的key为Integer类型,value为String类型 ),当然:具体的key-value是什么类型,得看实际开发中的需求
-
实际开发中的创建(多态):HashMap< Integer,String > hashMap = new HashMap<>();
-
-
-
-
需要掌握的方法———这也是一个容器,逃不开:增删改取查
-
size()————获取Map中的存储的元素个数
-
// 获取Map中的元素个数 System.out.println( hashMap.size() );
-
-
size()————获取Map中的存储的元素个数
-
需要掌握的方法———这也是一个容器,逃不开:增删改取查
-
-
put( Object key , Object value )————向Map容器中添加key-value键值对(即:映射项)
-
HashMap hashMap = new HashMap(); hashMap.put(1, "邪公子"); hashMap.put(2, "波多野结衣"); hashMap.put(3, "苍井空");
-
-
put( Object key , Object value )————向Map容器中添加key-value键值对(即:映射项)
-
-
-
-
- 但是:一般人不会这么干,傻子才会把Map容器中的value弄成一样,不是自找麻烦吗(存的值都是一样的有意义吗?),这里只是为了了解这个细节,不建议这么玩——最好是key和value都不重复
-
-
-
-
-
remove( Object key )————删除Map中给定key的value————有一个重载方法remove( Object key , Object value )————这个方法基本上不用,麻烦(还要提供key和value)
-
// 删除Map中的元素 hashMap.remove(4); System.out.println( hashMap );
-
-
put( Object key , Object value )————修改指定key位置的value————这个是知道value的key是什么,所以原理就是值覆盖,因此:也可以用同样原理的另外一个方法达到同样的效果
- replace( Object key , Object value )————这个方法就是把指定key位置的value 利用 一个新的value来替换(replace)旧的value
-
-
-
get( Object key )————取Map中的给定key位置的value
-
// 取Map中的元素 System.out.println( hashMap.get(2) );
-
-
get( Object key )————取Map中的给定key位置的value
-
-
-
Map的遍历(必须掌握)
-
因为Map是双列存储,所以遍历方式就有三种
-
1、利用key这一列来做手脚,从而进行遍历,即:调用keySet()方法————就是把key这一列单独生成为一个Set集合,然后在这个Set集合中就可以利用Set的方式来进行遍历了(增强for、迭代器)————注意:生成的Set,可以强制转换为Set下的子集合,如:HashSet....,但是不建议用
-
-
// Map的遍历 Set keySet = hashMap.keySet(); // 这个Set集合就是存储的hashMap中的key,想要找hashMap中对应的value,就利用get()方法即可 for (Object o : keySet) { System.out.println( hashMap.get( o )); } // 强制转换 // HashSet hashSet = (HashSet) hashMap.keySet();
-
-
-
2、利用values这一列来做手脚,从而进行遍历,也是将value这一列生成为另外的一个集合,从而对这个存储values的集合进行遍历操作(这个是生成的一个Collection集合(注:不建议修改为ArrayList...等之类的子集合)——另:虽然是生成的Collection集合,但是建议别使用for循环进行遍历,因为Collection里面也有list和Set(Set集合是无序不重复的,所以要是有前面说的那种key不重复,但是value重复的【即:前面存的时候:key值为1和4的value都是邪公子】,这种遍历就可能产生问题了,所以最好使用增强for、以及迭代器遍历)
-
// 正常遍历 for (Object value : values) { System.out.println( value ); }
-
-
3、利用映射项生成一个Set集合——即:把key-value弄为一行,把这所有行生成出来装入一个容器
-
// 3、利用每一项key-value生成一个Set集合 Set entrySet = hashMap.entrySet(); Iterator iterator = entrySet.iterator(); while ( iterator.hasNext() ){ System.out.println( iterator.next() ); }
-
-
1、利用key这一列来做手脚,从而进行遍历,即:调用keySet()方法————就是把key这一列单独生成为一个Set集合,然后在这个Set集合中就可以利用Set的方式来进行遍历了(增强for、迭代器)————注意:生成的Set,可以强制转换为Set下的子集合,如:HashSet....,但是不建议用
-
因为Map是双列存储,所以遍历方式就有三种
-
Map的遍历(必须掌握)
treeMap的玩法和这上面的hashMap一样
只需要注意:treeMap的去重原理和hashMap的原理不一样
可以采用:hashMap对照hashSet、treeMap对照treeSet就知道去重原理了
因为:hashSet的底层就是hashMap、treeSet的底层就是treeMap
但是:hashMap和treeMap上面还有东西套着的,底层是不断套娃,因此:这里提前知道一点即可————Set能够无序不重复,归根到底是Map导致的
-
-
hashtable的儿子———properties
- 首先来看properties这个单词是什么意思————指:属性
- 那为什么叫属性?————接着来看一个图————计算机右键有一个属性,打开是个什么效果?
-
hashtable的儿子———properties
-
- 点开之后发现一个鬼迷日眼的东西:有名字(系统、处理器......),然后还有对应的信息(64位操作系统........)
- 这就是属性?————那是不是可以理解为:properties这个属性也是key-value键值对的形式存储的
- 那怎么证明一下properties中的东西是key-value存储的————试一下嘛,建一个这个类型的文件来看一下
哟西~新建一个这样的文件好使,那说明这个properties就是一个文件(也就是一个容器嘛)
那由前面得出的结论:它是一个key-value存储机制,那再来试一下
以前面的结论保存好了这样格式的信息( 注意:这个key-value我没用什么 “ ” 之类括起来 ),但是这样保存之后怎么证明好使、以及有什么用呢?Java中又是怎么玩的?
首先来看一下:java中是怎么创建一个properties对象的?
就这样:通过 new 关键字就创建了一个properties对象了————那这个对象可以拿来干嘛?
前面不是在D盘中建了一个文件,然后保存了一些信息吗,拿来java里面玩一下
-
1、实现去读取指定位置中的文件(这里就读取刚刚新建的那个文件)
-
package cn.xieGongZi.testProperties; import java.io.FileReader; import java.io.IOException; import java.util.Properties; public class PlayProperties { public static void main(String[] args) throws IOException { Properties properties = new Properties(); properties.load( new FileReader("d:\\test.properties")); // 这是流操作,暂时不用理解都行,等到了高级篇中:玩儿了流操作技术就懂了 System.out.println( properties ); } }
诶嘿~写在D盘的文件在这里读取到了,并保存到properties容器中了,这说明什么?-
说明:可以通过java实现 去读取一个指定位置的properties文件(容器)类型的信息嘛,这作用可不就很大了啊
- 试问:要是把一些信息,如:配置信息、程序项目中会经常产生变化的信息,把它配置在这样一个properties文件(容器)中,然后需要改的时候,直接在这里面进行修改,那不就改动了整个程序项目需要的这些相关信息了吗,这就方便多了啊,再也不用去拼命找项目程序中的源代码,从而进行修改了
-
说明:可以通过java实现 去读取一个指定位置的properties文件(容器)类型的信息嘛,这作用可不就很大了啊
-
-
-
- 举个例子:java链接数据库的信息,每个人的数据库用户名、密码这些......等等都不一样涩,那需要链接一个人的数据库的时候,在这里面把这些信息改成对应那人的信息不就可以了 ^_^
- 当然好像感觉txt文件也可以保存信息啊,因为这里主要用了流操作嘛,但是:得注意,这个Properties是以key-value形式存储的信息,那这样取的时候不是更方便吗(虽然key-value存储还没证明完啊,但是马上就得出结论了 ^_^)
-
-
-
-
-
那如果只想要获取这个容器中的指定信息呢,怎么做?
- 通过getProperty( String key )方法————这个方法是:获得自己properties容器中的一个指定key的信息
-
那如果只想要获取这个容器中的指定信息呢,怎么做?
System.out.println( properties.getProperty("intro") );
这里获取到这个容器里面的信息了,这又表明:
- 我是把D盘中的信息读入到自己在java中创建的Properties容器中,但是获取到了,说明没有犯错嘛,那也就证明:Properties容器就是key-value形式存储的
结论:
- Properties也是一个容器,用来存储信息的
- java中通过new关键字创建Properties对象
- Properties是以key-value的形式存储信息的
- 通过getProperty( String key )获取Properties容器中的指定key信息
好了,这个容器就简单了解到这里即可
-
Map中各个容器的特点:
-
hashMap:
- 是最常用的实现类,底层是使用的hash表,这个hash表是一种数组+链表+红黑树的数据结构,不保证线程安全。HashSet底层就是使用的HashMap实现的功能,并且只使用了它的Key空间。HashMap的键是无序的,且允许包含空键和空值。
-
简单来看一下这个数据结构图(这里看不懂也没事,在数据结构篇中会做深度说明):
-
-
hashtable
- 这个是很老的一个容器了,JDK1.0的时候就有了,是处理线程安全的(和StringBuffer一批的,所以特点和StringBuffer一样),同时这个容器可以兼容前面说的所有容器(Collection、Set、Map————即:方法通用),底层的实现原理和hashMap一样的,但是:它有个点和hashMap不一样,即:它不可以出现空键和空值。现在已经不用这个了,被hashMap替代了,现在要用也只是会用它的其中一个儿子properties,可是:这个才算是真正意义上的hash表
-
hashMap:
-
-
treeMap
-
treeMap的底层是利用红黑树实现的,可以把key进行排序。
TreeSet可以把元素排序,就是因为它的底层是使用的TreeMap,并利用key空间来装元素。
TreeMap 虽然可以对key排序,但是对Key的类型有要求,要求Key类型实现Comparable 接口。 这就是为什么使用TreeSet时元素类型需要实现接口的原因。
-
-
treeMap
至此:Java的集合体系知识就全部学完了,至于深入了解原理,就需要知道相关的数据结构知识了
当然:不学习数据结构也可以,就学了前面这些也不影响操作
作者:紫邪情
出 处:https://www.cnblogs.com/xiegongzi/p/15106731.html