泛型类有一个不幸的限制, 不能使用基本类型作为类型参数, 解决办法是使用这个包装类, 每一种基本类型都有对应的包装类
基本类型和它们对应的包装类型之间的转换是自动进行的。在调用 add 方法时,个值为 42 的 Integer 类对象在一种被称为自动装箱的过程中自动构造了。
代码片段的最后一行,get 调用返回一个 Integer 对象,在给 int 变量赋值之前, 该对象自动拆箱,内部产生一个 int 值。
添加元素的源代码
public class ArrayList<E> extends AbstractList<E> //集合的开头定义
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
警告: 基本类型和它们的包装类之间的转换对程序员完全是透明的,但是==和!=操作符例外。==和!=操作符比较对象引用,而不是对象的内容。条件测试 if (numbers.Get (i) == numbers.Get (j))不会测试索引 i 和索引 j 的数字是否相同。就像字符串,你需要记得调用包装类的 equals 方法。
具体介绍
[[包装类]] obsidian 内链
CSDN博客
Collection代表单列集合,每个元素(数据)只包含一个值。
Map 代表双列集合,每个元素包含两个值(键值对)。
注意这个接口是不能够直接进行创建对象的,可以用多态的方式创建对象
List<Integer> list1 = new ArrayList<>();
Collection集合特点
由于Collection是一个泛型,所以我们在定义的时候要用确定的类型来定义这个
[!info]
这个 collection 的集合类型很多, 但是我们常用的其实就是 hashset 和 Arraylist, 当你不知道选什么的时候就用这两个肯定没问题
Collection集合有哪两大常用的集合体系,各自有啥特点?
List系列集合:添加的元素是有序、可重复、有索引。
Set系列集合:添加的元素是无序、不重复、无索引。
根据日常使用的需求,我们来挑选不同的集合
举例
public class CommonFunction {
public static void main(String[] args) {
//简单确认一下collection集合 的特点
ArrayList<String> arr = new ArrayList<>();
arr.add("qiu");
arr.add("ke");
arr.add("hao");
System.out.println(arr); //[qiu, ke, hao]有序、可重复、有索引。
HashSet<String> hah = new HashSet<>();
hah.add("qiu");
hah.add("ke");
hah.add("hao");
hah.add("hao");
System.out.println(hah); //[hao, ke, qiu]无序、不重复、无索引;
}
}
这些方法是所有集合都能够进行使用的方法, 这个 collection 是父类, 子类可以继承这个父类的方法
方法名 | 说明 |
---|---|
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public void clear() | 清空集合中所有的元素 |
public boolean remove(E e) | 把给定的对象在当前集合中删除 |
public boolean contains(Object obj) | 判断当前集合中是否包含给定的对象 |
public boolean isEmpty() | 判断当前集合是否为空 |
public int size() | 返回集合中元素的个数。 |
public Object[] toArray() | 把集合中的元素,存储到数组中 |
package colletion.CommonFunction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
public class collectionFunction {
public static void main(String[] args) {
Collection<String> c = new ArrayList<>();
c.add("qiu");
c.add("ke");
c.add("hao");
c.add("qiu");
System.out.println(c);
System.out.println(c.isEmpty());
System.out.println(c.size());
System.out.println("------------------------");
c.remove("qiu");
c.remove("k");
System.out.println(c);
System.out.println(c.size());
System.out.println(c.contains("qiu"));
System.out.println(c.contains("h"));
System.out.println("------------------------");
//在某些情况下,这个里面的数据类型并不一定都是字符串类型
Object[] arr = c.toArray();
System.out.println(Arrays.toString(arr));
//如果我们就是想让其转化为字符数组呢
String[] str = c.toArray(new String[c.size()]);
System.out.println(Arrays.toString(str));
System.out.println("------------------------");
c.clear();
System.out.println(c.isEmpty());
System.out.println(c.size());
}
}
集合来调用这个迭代器的方法
然后我们再用这个 Iterator 类来获取这个返回对象
It.Next ()获取下一个值, 并后移
It.Hasnext ()判断其后面有没有值 根据名字也能判断出来
如果我们遍历的比较多的话, 我们很有可能会造成这个迭代器越界, 一定要注意
迭代器遍历的时候我们一定要注意这个迭代遍历的 next 函数不能在一段循环代码中, 执行几次, 否则的话, 容易出错
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
for(int i=0;i<10;i++){
list.add(i);
}
Collections.shuffle(list);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
int s = iterator.next();
System.out.print(s+",");
}
System.out.println();
System.out.println(list);
}
可以用来遍历这个集合和数组
for (元素的数据类型变量名 : 数组或者集合){}
//增强for
for(String s : c){
System.out.println(s);
}
IDEA 快捷 for 循环遍历, 如果我们想用这个索引遍历就是数组/集合名字. Fori
如果是增强 for 循环就是数组. 集合名字. For
源码
foreach方法
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
其实 foreach 本质还是调用这个增强 for 循环
//lambda表达式
c.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
c.forEach(s->System.out.println(s));
//方法引用
c.forEach(System.out::println); //快速遍历方式,IDEA里的是c.foreach(sout);快速构建
遍历电影, 考验我们对于对象的遍历的理解, 我们还可以通过重写这个 toString 方法, 来进行快速遍历
movies.forEach(System.out::println); //我们可以通过重写内部类的toString方法来实现这个
package colletion.CommonFunction.movies;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class moviesOutput {
public static void main(String[] args) {
Collection<movie> movies = new ArrayList<>();
movies.add(new movie("《肖生克的救赎》", 9.7 , "罗宾斯"));
movies.add(new movie("《霸王别姬》", 9.6 , "张国荣、张丰毅"));
movies.add(new movie("《阿甘正传》", 9.5 , "汤姆.汉克斯"));
for (movie mov : movies) {
System.out.println("电影名: "+mov.getName());
System.out.println("得分: "+mov.getScore() );
System.out.println("演员: "+mov.getActor());
System.out.println("--------------------");
}
Iterator<movie> iterator = movies.iterator();
while (iterator.hasNext()){
System.out.println("电影名: "+iterator.next().getName());
System.out.println("得分: "+iterator.next().getScore() );
System.out.println("演员: "+iterator.next().getActor());
System.out.println("--------------------");
}
movies.forEach(System.out::println); //我们可以通过重写内部类的toString方法来实现这个
}
}
List系列集合特点: 有序,可重复,有索引
ArrayList:有序,可重复,有索引。
LinkedList:有序,可重复,有索引。
List和Collection都是接口,我们并不能够直接创建对象,我们要通过接口的方式来创建对象
方法名称 | 说明 |
---|---|
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
for(int i=0;i<10;i++){
list.add(i);
}
Collections.shuffle(list);
list.add(0,100);
list.add(list.size(),100);
list.forEach(integer-> System.out.print(integer+", "));//100, 9, 2, 5, 1, 3, 6, 8, 4, 7, 0, 100,
list.remove(list.size()-1);
System.out.println();
list.forEach(integer-> System.out.print(integer+", "));//100, 9, 2, 5, 1, 3, 6, 8, 4, 7, 0, 移除了200
list.set(0,200);
System.out.println();
list.forEach(integer-> System.out.print(integer+", "));//200, 9, 2, 5, 1, 3, 6, 8, 4, 7, 0, 将第一个元素设置为200
System.out.println();
System.out.println(list.get(0)); //200获取第一个元素
}
collection 用来遍历的方式它都能遍历,初次之外它还能用 for 循环来进行遍历
具体可以参考这个 [[#Collection 集合]]
因为我们可以通过这个for循环索引遍历的方式
这个是因为这个 list 是有索引的,和这个 collection 不一样
Arraylist集合的底层原理
其是基于数组来实现的
特点是查询速度快,注意是根据索引查询速度快
构建原理
private static final int DEFAULT_CAPACITY = 10;
适合根据索引查询数据,比如根据随机索引取数据,或者数据量不是很大的时候
不适合,数据量大的同时,又要频繁的进行增删操作
基于双链表实现的。
链表中的结点是独立的对象,在内存中是不连续的,每个结点包含数据值和下一个结点的地址。
链表的特点1:查询慢,无论查询哪个数据都要从头开始找。
链表的特点2:链表增删相对快
单向链表
双向链表, 特点:查询慢,增删相对较快,但对首尾元素进行增删改查的速度是极快的。可以根据索引位置,选择从前,从后遍历
public static void main(String[] args) {
LinkedList<Integer> list = new LinkedList<>();
for(int i=0;i<10;i++){
list.add(i);
}
//仍然可以用这个打乱数组
System.out.println("打乱前");
list.forEach(s-> System.out.print(s+", "));
System.out.println();
System.out.println("打乱后");
Collections.shuffle(list);
list.forEach(s-> System.out.print(s+", "));
System.out.println();
System.out.println("向开头插入元素");
list.addFirst(100);
list.forEach(s-> System.out.print(s+", "));
System.out.println();
System.out.println("向结尾插入元素");
list.addLast(200);
list.forEach(s-> System.out.print(s+", "));
System.out.println();
System.out.println("开头元素");
Integer first = list.getFirst();
System.out.println(first);
System.out.println("结尾元素");
Integer last = list.getLast();
System.out.println(last);
System.out.println("移除开头元素");
list.removeFirst();
list.forEach(s-> System.out.print(s+", "));
System.out.println();
System.out.println("移除结尾元素");
list.removeLast();
list.forEach(s-> System.out.print(s+", "));
System.out.println();
}
结果
打乱前
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
打乱后
0, 2, 6, 3, 7, 1, 4, 9, 5, 8,
向开头插入元素
100, 0, 2, 6, 3, 7, 1, 4, 9, 5, 8,
向结尾插入元素
100, 0, 2, 6, 3, 7, 1, 4, 9, 5, 8, 200,
开头元素
100
结尾元素
200
移除开头元素
0, 2, 6, 3, 7, 1, 4, 9, 5, 8, 200,
移除结尾元素
0, 2, 6, 3, 7, 1, 4, 9, 5, 8,
Linkedlist 的应用场景之一,我们可以用来设计队列----先进先出
每次都添加到最后的位置,移除最开始的元素
队列是一种常见的数据结构系统,是一种经常用来操作首尾数据的一种系统,比如说排队系统
LinkedList<String> queue = new LinkedList<>();
queue.addLast("11");
queue.addLast("22");
queue.add("55"); //add本身也是在最后面添加元素
queue.addLast("33");
queue.add("44");
System.out.println(queue);
LinkedList的引用场景之一,可以用来设计栈
栈的特点是:后进先出,先进后出
每次都是往最后面添加元素,从最后面往外面拿元素
[!warning]
需要注意的是这个栈有的是将这个 linkedlist 的最后面的元素作为栈顶,有的是最开始的元素最为栈顶,我们一般认为这个最开始的元素作为栈顶
数据进入栈模型的过程称为:压/进栈(push)
数据离开栈模型的过程称为:弹/出栈(pop)
LinkedList<String> stack = new LinkedList<>();
stack.addFirst("11");
stack.addFirst("22");
stack.addFirst("33");
System.out.println(stack);
System.out.println(stack.removeFirst());
System.out.println(stack.removeFirst());
System.out.println(stack.removeFirst());
从JDK6以后,java就在linkedlist里面添加了push和pop的API,其实就是换了个马甲
public void push(E e) {
addFirst(e);
}
public E pop() {
return removeFirst();
}
无序:添加数据的顺序和获取出的数据顺序不一致; 不重复; 无索引;
set 的特点主要是这个不重复和无索引,至于有序还是无序,这个取决于具体的 set 类型
注意:
Set要用到的常用方法,基本上就是Collection提供的!!
自己几乎没有额外新增一些常用功能!
无序、不重复、无索引。
Set<Integer> set = new HashSet<>();
set.add(90);
set.add(80);
set.add(70);
set.add(70);
System.out.println(set);
//[80, 70, 90]
[!warning]
public static void main(String[] args) {
HashSet<Integer> hashSet = new HashSet<>();
for(int i=0;i<10;i++){
hashSet.add(i);
}
hashSet.add(-1);
hashSet.forEach(s-> System.out.print(s+", "));
System.out.println();
}
当我刚开始想验证这个无序性的时候, 我用了一个循环来添加元素, 但是我输出之后发现这个遍历出来的数据并不是无序的, 而是 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
我感到困惑, 后来发现其实是因为我不太明白这个底层原理, 这个 0-9 的 hashcode 值的大小顺序跟这个 0-9 的大小顺序是一样的, 所以会出现这个类似有序现象, 你可以向其中添加一个 -1
来验证这个无序性最后的结果是 0, -1, 1, 2, 3, 4, 5, 6, 7, 8, 9,
注意:在正式了解HashSet集合的底层原理前,我们需要先搞清楚一个前置知识:哈希值!
哈希值
就是一个int类型的数值,Java中每个对象都有一个哈希值。
Java中的所有对象,都可以调用Obejct类提供的hashCode方法,返回该对象自己的哈希值
对象哈希值的特点
同一个对象多次调用 hashCode()方法返回的哈希值是相同的。
hashcode 是继承的基类 object 的一个方法, 任何对象都能使用
不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞)。
比如说abc和acD
基于哈希表实现。
哈希表是一种增删改查数据,性能都较好的数据结构
哈希表是一种增删改查数据性能都较好的结构。
根据数据对应的hash值,我们可以快速定位,所以查很快
增删改由于这个虽然是数组,但是可以允许有空元素的存在再加上链表的特征,所以也很快
无序
这是有一个计算方法的,所以无序
不重复
因为重复的不存,不重复的挂链表
无索引
元素是无序的,自然就不支持索引,没法确定索引
如果数组快占满了,会出什么问题
链表会过长,导致查询性能降低
16 * 0.75 = 12 当数组占满了12的时候就会扩容 0.75是加载因子
哈希表
JDK8之前,哈希表 = 数组+链表
JDK8开始,哈希表 = 数组+链表+红黑树
JDK8开始,当链表长度超过8,且数组长度>=64时,自动将链表转成红黑树
HashSet集合默认不能对内容一样的两个不同对象去重复!
比如内容一样的两个学生对象存入到HashSet集合中去 , HashSet集合是不能去重复的!
如何让HashSet集合能够实现对内容一样的两个不同对象也能去重复???
结论:如果希望Set集合认为2个内容一样的对象是重复的,
必须重写对象的hashCode()和equals()方法
IDEA里有快速构造方法
public static void main(String[] args) {
HashSet<Student> stu = new HashSet<>();
stu.add(new Student("qiu",18));
stu.add(new Student("qiu",18));
/*
* 并没有去重复化
* Student{name='qiu', age=18}
* Student{name='qiu', age=18}
* */
stu.forEach(System.out::println);
//我们需要重写这个equals和hashcode方法
//此时只有一个结果 Student{name='qiu', age=18}
}
重写方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
[!tip]
把里面的 equal 和 hashcode 方法进行修改使得这个学生类只要名字相等就会认为相等
@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);
}
有序、不重复、无索引。
Set<Integer> set = new LinkedHashSet<>();
set.add(90);
set.add(80);
set.add(70);
set.add(70);
System.out.println(set);
[90, 80, 70]
这个有序是按照这个添加元素的顺序进行修改的
依然是基于哈希表(数组、链表、红黑树)实现的。
但是,它的每个元素都额外的多了一个双链表的机制记录它前后元素的位置。
其实也是一样的,存放的位置和hashset是一样的,但是每一个元素都有一个前置节点和后置节点,可以确定这个节点的下一个元素,和这个节点的上一个元素,这样我们就能够知道这个哈希表中的第一个元素和后一个元素.
里面记录的信息更多,这样也会导致它更占内存
排序、不重复、无索引。会对你上传的数据,会自动进行排序,升序排序
Set<Integer> set = new TreeSet<>();
set.add(90);
set.add(80);
set.add(70);
set.add(70);
System.out.println(set);
[80, 70, 90]
底层是基于红黑树实现的排序。
对于数值类型:Integer , Double,默认按照数值本身的大小进行升序排序。
对于字符串类型:默认按照首字符的编号升序排序。
对于自定义类型如 Student 对象,TreeSet 默认是无法直接排序的。
我们需要自己自定义比较器
第一种方式是在学生类中添加接口在你要进行比较的对象当中添加接口
public class Student implements Comparable
添加 Comparable 接口然后重写这个 compareTo 方法,
@Override
public int compareTo(Student o) {
//如果认为左边对象大于右边对象返回正整数
//如果认为左边对象小于右边对象返回负整数
//如果认为左边对象等于右边对象返回0 相等的话,就认为这个对象不存在,所以我们要在添加另一个规则
//左边对象就是调用对象
//按照年龄升序排序
int num =0;
num = this.age-o.age;
num = num==0?(int)(this.height-o.height):num;
num = num==0? this.name.compareTo(o.name):num;
return num;
}
}
第二种方式是用匿名内部类-我们用的最多的
Set<Student> s = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return 0;
}
});
注意点
接口是
Comparable
接口是
Comparator
1、如果希望记住元素的添加顺序,需要存储重复的元素,又要频繁的根据索引查询数据?
用ArrayList集合(有序、可重复、有索引),底层基于数组的。(常用)
2、如果希望记住元素的添加顺序,且增删首尾数据的情况较多?
用LinkedList集合(有序、可重复、有索引),底层基于双链表实现的。
3. 如果不在意元素顺序,也没有重复元素需要存储,只希望增删改查都快?
用HashSet集合(无序,不重复,无索引),底层基于哈希表实现的。 (常用)
4.如果希望记住元素的添加顺序,也没有重复元素需要存储,且希望增删改查都快?
用LinkedHashSet集合(有序,不重复,无索引), 底层基于哈希表和双链表。
5. 如果要对元素进行排序,也没有重复元素需要存储?且希望增删改查都快?
用TreeSet集合,基于红黑树实现。
使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误
Iterator<String> iter = list.iterator();
while (iter.hasNext()){
String name = iter.next();
if(name.contains("李")){
list.remove(name);
}
}
这是因为:删除的时候下标会继续向后所以会漏删
for (int i = 0; i < list.size(); i++) {
String name = list.get(i);
if(name.contains("李")){
list.remove(name);
}
}
System.out.println(list);
[王麻子, 小李子, 李爱花, 张全蛋, 晓李, 李玉刚]
[王麻子, 李爱花, 张全蛋, 李玉刚]
for (int i = 0; i < list.size(); i++) {
String name = list.get(i);
if(name.contains("李")){
list.remove(name);
i--;
}
}
System.out.println(list);
[王麻子, 小李子, 李爱花, 张全蛋, 晓李, 李玉刚]
[王麻子, 张全蛋]
由于增强for循环遍历集合就是迭代器遍历集合的简化写法,因此,使用增强for循环遍历集合,又在同时删除集合中的数据时,程序也会出现并发修改异常的错误
lamda foreach表达式本身其实也是利用增强for循环
怎么保证遍历集合同时删除数据时不出bug?
使用迭代器遍历集合,但用迭代器自己的删除方法删除数据即可。
迭代器自带的
iter.remove();
即利用遍历器进行删除
删除当前的元素
如果能用for循环遍历时:可以倒着遍历并删除;或者从前往后遍历,但删除元素后做i --操作。
Iterator<String> iter = list.iterator();
while (iter.hasNext()){
String name = iter.next();
if(name.contains("李")){
iter.remove();
}
}
System.out.println(list);
就是一种特殊形参,定义在方法、构造器的形参列表里,格式是:数据类型…参数名称;
传递不定数量的某种类型的参数
public static void test(int ...nums){
}
可变参数的特点和好处
特点:可以不传数据给它;可以传一个或者同时传多个数据给它;也可以传一个数组给它。
好处:常常用来灵活的接收数据。
其实就是一个数组
一个形参列表中,只能有一个可变参数,无论类型,都只能有一个
可变参数必须放在形参列表的最后面
public class test01 {
public static void main(String[] args) {
test("kehao",1,2,3,4,5,6);
}
public static void test(String name,int ... num){
System.out.println(num.length);
System.out.println(name);
System.out.println(Arrays.toString(num));
}
}
这个sort是只能对默认的整形规则来进行排序,但是对于我们自己自定义的规则,我们并不能
本身我的做法有很大问题,在面对这汇总类型固定,数量固定的牌组构建,我并没有想到直接利用数组来进行构建,而是用了多层循环来遍历,导致这个代码看起来臃肿不堪
public Room() {
//本身在构建对象的时候我们就可以自己产生一副牌
String[] color = new String[]{"红桃","方片","黑桃","梅花"};
List<Card> temp = new ArrayList<>();
//从3到10
for (int i=3;i<11;i++){
for (String s : color) {
Card card = new Card(String.valueOf(i),s,i-2);
temp.add(card);
}
}
//3 到 j
for (char c = 'J';c<='K';c++){
for (String s : color) {
Card card = new Card(String.valueOf(c),s,(int)(c)-65);
temp.add(card);
}
}
for (int i=1;i<=2;i++){
for (String s : color) {
Card card = new Card(String.valueOf(i),s,i+11);
temp.add(card);
}
}
Card joker = new Card("大王","红色",15);
temp.add(joker);
Card joker1 = new Card("小王","黑色",14);
temp.add(joker1);
this.cards.addAll(temp);
}
较为合适的代码
public Room() {
//本身在构建对象的时候我们就可以自己产生一副牌
String[] color = new String[]{"红桃","方片","黑桃","梅花"};
String[] points = new String[]{"3","4","5","6","7","8","9","10","J","Q","K","J","2"};
//从3到10
int size=0;
for (String point : points) {
size++;
for (String s : color) {
Card c= new Card(point,s,size);
cards.add(c);
}
}
Card joker = new Card("大王","红色",++size);
Card joker1 = new Card("小王","黑色",++size);
Collections.addAll(cards,joker,joker1);
}
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
for(int i=0;i<10;i++){
list.add(i);
}
System.out.println(list);
Collections.shuffle(list);
System.out.println(list);
Collections.reverse(list);
System.out.println(list);
}
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[3, 2, 9, 8, 5, 1, 6, 7, 0, 4]
[4, 0, 7, 6, 1, 5, 8, 9, 2, 3]
public class W1随机点名器 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "范闲", "范建", "范统", "杜子腾", "杜琦燕", "宋合泛", "侯笼藤", "朱益群", "朱穆朗玛峰", "袁明媛");
// 思路一:
Random r = new Random();
System.out.println("随机点到的是");
int n = r.nextInt(list.size());
System.out.println(list.get(n));
System.out.println("点名结束");
// 思路二 打乱集合,在获取第一个
Collections.shuffle(list);
System.out.println(list.get(0));
System.out.println("点名结束");
input.close();
}
}
对于这个Map对象如果我不指定这个对象那么它的键值是什么类型的
Map map = new HashMap();
如果没有指定泛型类型参数,则 Map 对象 map 的键和值的类型将默认为 Object 类型。
Map集合是双列集合
Map集合称为双列集合,格式:{key1=value1 , key2=value2 , key3=value3 , …}, 一次需要存一对数据做为一个元素.
Map集合的每个元素“key=value”称为一个键值对/键值对对象/一个Entry对象,Map集合也被叫做“键值对集合”
Map集合的所有键是不允许重复的,但值可以重复,键和值是一一对应的,每一个键只能找到自己对应的值
Map集合的键不能重复,Map集合的值可以重复
{商品1=2 , 商品2=3 , 商品3 = 2 , 商品4= 3}
需要存储一一对应的数据时,就可以考虑使用Map集合来做
注意:Map 系列集合的特点都是由键决定的,值只是一个附属品,值是不做要求的
注意下面的几种常见的 Map 的实现类和 set 简直一模一样, 这是因为 set 其实是利用 map 进行创建的
HashMap(由键决定特点): 无序、不重复、无索引; (用的最多)
LinkedHashMap (由键决定特点):由键决定的特点:有序、不重复、无索引。
TreeMap (由键决定特点):按照键大小默认升序排序、不重复、无索引。
经典代码
Map
Map<String,Integer> map = new HashMap<>();
map.put("黑丝",2);
map.put("白丝",1);
map.put("白丝",3); //后面重复的数据会覆盖前面的数据
map.put("渔网",3);
System.out.println(map);
返回值类型
因为这个键值是无序,无索引,不重复,所以自然要将这个放到这个set集合中0
因为值是可以重复的, 所以把这个值放到这个 collection 集合中
我们可以利用 ctrl+alt+v 快速的进行操作
Set<String> keys = map.keySet();
System.out.println(keys);
Collection<Integer> values = map.values();
System.out.println(values);
将一个集合的元素倒入到另一个集合中去
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
map.put("heisi","zhouyi");
map.put("baisi","zhouer");
System.out.println(map);
Map<String,String> map1 = new HashMap<>();
map1.putAll(map);
System.out.println(map1);
}
{baisi=zhouer, heisi=zhouyi}
{baisi=zhouer, heisi=zhouyi}
我们利用 keyset 方法来获得所有键的集合,然后根据这个 getkey 获得对应的值
[!tip ]
注意这个只能根据 ket 来获取值, 不能根据值来获取 key, 这是因为我们 key 是唯一的, 而值并不是唯一的
本质是利用这个增强for循环来进行遍历,但是对于增强for循环,我们要有其对那个的数据类型,为了方便遍历,java封装了一个entry对象,我们可以通过这个来进行遍历
[!info]
注意这个需要用到这个 entrySet 方法先获得这个 set 类型的集合, 只有集合和数组可以用这个增强 for 循环来进行遍历
Set< Map.Entry
entries = map.entrySet();
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry.getValue()+"的服装是"+entry.getKey());
}
查看源码就是这个foreach 使用这个匿名内部类,然后内部的实现方式是通过第二种的entry方式来进行实现的.
//foreach循环进行遍历
//lambda
map.forEach(new BiConsumer<String, String>() {
@Override
public void accept(String s, String s2) {
System.out.println(s2+"的服装是"+s);
}
});
System.out.println("========");
map.forEach((s, s2)-> System.out.println(s2+"的服装是"+s));
只要是存储意义对应的数据的时候,我们就可以考虑用map集合
HashMap集合的底层原理
HashMap跟HashSet的底层原理是一模一样的,都是基于哈希表实现的。
实际上:原来学的Set系列集合的底层就是基于Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。
public HashSet() {
map = new HashMap<>();
}
哈希表
HashMap 集合是一种增删改查数据,性能都较好的集合.但是它是无序,不能重复,没有索引支持的(由键决定特点
HashMap的键依赖hashCode方法和equals方法保证键的唯一
如果键存储的是自定义类型的对象,可以通过重写hashCode和equals方法,这样可以保证多个对象内容一样时,HashMap集合就能认为是重复的。
底层数据结构依然是基于哈希表实现的,只是每个键值对元素又额外的多了一个双链表的机制记录元素顺序(保证有序)。
实际上:原来学习的LinkedHashSet集合的底层原理就是LinkedHashMap。
特点:不重复、无索引、可排序(按照键的大小默认升序排序,只能对键排序)
原理:TreeMap跟TreeSet集合的底层原理是一样的,都是基于红黑树实现的排序
TreeMap集合同样也支持两种方式来指定排序规则
让类实现Comparable接口,重写比较规则。
TreeMap集合有一个有参数构造器,支持创建Comparator比较器对象,以便用来指定比较规则
.