集合:集合是java中提供的一种容器,可以用来存储多个数据。
集合和数组既然都是容器,区别为:
数组的长度是固定的。集合的长度是可变的。
数组中存储的是同一类型的元素,可以存储基本数据类型值。集合存储的都是对象。而且对象的类型可以不一致。在开发中一般当对象多的时候,使用集合进行存储。
Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是 java.util.List 和java.util.Set 。其中, List 的特点是元素有序、元素可重复。Set 的特点是元素无序,而且不可重复。List 接口的主要实现类有java.util.ArrayList 和java.util.LinkedList , Set 接口的主要实现类有 java.util.HashSet 和 java.util.TreeSet 。
Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。方法如下:
public boolean add(E e) : 把给定的对象添加到当前集合中 。
public void clear() :清空集合中所有的元素。
public boolean remove(E e) : 把给定的对象在当前集合中删除。
public boolean contains(E e) : 判断当前集合中是否包含给定的对象。
public boolean isEmpty() : 判断当前集合是否为空。
public int size() : 返回集合中元素的个数。
public Object[] toArray() : 把集合中的元素,存储到数组中。
在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口 java.util.Iterator 。Iterator 接口也是Java集合中的一员,但它与Collection 、Map 接口有所不同,Collection 接口与Map 接口主要用于存储元素,而Iterator 主要用于迭代访问(即遍历) Collection 中的元素,因此Iterator 对象也被称为迭代器。
想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作:
public Iterator iterator();//获取集合对应的迭代器,用来遍历集合中的元素的。
迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
Iterator接口的常用方法如下:
public E next() :返回迭代的下一个元素。
public boolean hasNext() ://如果仍有元素可以迭代,则返回 true。
1 使用集合中的方法iterator获取迭代器的实现类对象,然后使用Iterator接口接收(多态)
注意: Iterator接口也有泛型,类型取决于集合的泛型类型
2 使用Iterator接口中的方法hasNext判断还有没有下一个元素
3 使用Iterator接口中的方法next取出集合中的下一个元素
public class IteratorDemo {
public static void main(String[] args) {
// 使用多态方式 创建对象
Collection<String> coll = new ArrayList<String>(); // 等号右侧类型可省略不写,即<>
// 添加元素到集合
coll.add("串串星人");
coll.add("吐槽星人");
coll.add("汪星人");
//遍历
//使用迭代器 遍历 每个集合对象都有自己的迭代器
Iterator<String> it = coll.iterator();
// 泛型指的是 迭代出 元素的数据类型
while(it.hasNext()){ //判断是否有迭代元素
String s = it.next();//获取迭代出的元素
System.out.println(s);
}
}
}
注意:因为迭代器取出集合中元素是一个重复过程,所以可以使用循环,又由于未知集合元素个数,所以选择while循环,循环结束的条件为hasNext方法返回False。
建议使用while循环,但是不代表不能使用for循环,可用以下方法使用for循环:
for(Iterator<String> it2 = coll.iterator() ; it2.hasNext() ; ){
//第三个语句省略不写,因为语句中有取出操作,每取出一个就少一个元素
String e = it2.next(); //取出元素
System.out.println(e); //打印
}
tips::在进行集合元素取出时,如果集合中已经没有元素了,还继续使用迭代器的next方法,将会发生 java.util.NoSuchElementException 没有集合元素的异常。
1、当创建完成指向某个集合或者容器的Iterator对象时,这时的指针其实指向的是第一个元素的上方,即指向一个 空
2、当调用hasNext方法的时候,只是判断下一个元素的有无,并不移动指针
3、当调用next方法的时候,向下移动指针,并且返回指针指向的元素,如果指针指向的内存中没有元素,会报异 常。
4、remove方法删除的元素是指针指向的元素。如果当前指针指向的内存中没有元素,那么会抛出异常。
更正:实际上调用next方法前,指针指向元素的前方,但是并不是其索引值-1的位置,而是这两个位置中间,在执行next方法后,该指针经过第一个元素指向第一和第二两个元素之间,并将第一个元素取出。
增强for循环(也称for each循环)是JDK1.5以后出来的一个高级for循环,专门用来遍历数组和集合的。它的内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作。
格式:
for(元素的数据类型 变量 : Collection集合or数组){
//写操作代码
}
它用于遍历Collection和数组。通常只进行遍历元素,不要在遍历的过程中对集合元素进行增删操作。
补充:Collection extends Iterable: 即Collection 继承了Iterable接口。意味着所有单列集合都可以使用增强for
其中该接口描述为:
public interface Iterable :实现此接口的对象允许成为“foreach”的目标。
例1:
public class NBForDemo1 {
public static void main(String[] args) {
int[] arr = {3,5,6,87};
//使用增强for遍历数组
for(int a : arr){//a代表数组中的每个元素
System.out.println(a);
}
}
}
例2:
public class NBFor {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<String>();
coll.add("小河神");
coll.add("老河神");
coll.add("神婆");
//使用增强for遍历
for(String s :coll){//接收变量s代表 代表被遍历到的集合元素
System.out.println(s);
}
}
}
1、不使用泛型的好处是,默认类型为Object类,可存储任意类型的数据。弊端是不安全,会引发异常。
2、使用泛型的好处:
(1)避免了类型转换的麻烦,存储的是什么类型,取出的就是什么类型
(2)把运行期异常(代码运行之后会抛出的异常)提升到了编译期(写代码时会报错)
定义一个含有泛型的类的定义格式:
修饰符 class 类名<代表泛型的变量> { }
如:
public class GenericClass<E>{
private E name;
public E getName() {
return name;
}
public void setName(E name) {
this.name = name;
}
}
注意,在使用这个类时,若不写<数据类型>,则默认为Object类型。即每次使用这个类创建对象时都可以指定这个对象的类型。
定义一个含有泛型的方法的格式:(泛型定义在方法的修饰符和返回类型之间)
格式:
修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ } //泛型的符号可随意定义
例1:
public class GenericMethod {
//定义一个含有泛型的方法
public <M> void method01(M m) {
System.out.println(m);
}
//定义一个含有泛型的静态方法
public static <S> void method02(S s) {
System.out.println(s);
}
}
定义一个含有泛型的接口的格式:
修饰符 interface接口名<代表泛型的变量> { }
例1:
public interface GenericInterface<I> {
public abstract void method(I i);
}
//第一种实现方式:定义接口实现类,实现接口,指定接口的类型
//定义接口实现类
public class GenericInterfaceImpl1 implements GenericInterface<String> {
@Override
public void method(String s) {
System.out.println(s);
}
}
//第二种实现方式:接口使用什么泛型,实现类就使用什么泛型,类跟着接口走。即相当于定义了一个含有泛型的类,创建对象的时候指定泛型的类型
//定义接口实现类
public class GenericInterfaceImpl2 implements GenericInterface<I> {
@Override
public void method(I i) {
System.out.println(i);
}
}
3.6 泛型通配符
当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。
?:代表任意的数据类型。使用方式:不能创建对象使用,只能作为方法的参数使用
public static void main(String[] args) {
Collection<Intger> list1 = new ArrayList<Integer>();
getElement(list1);
Collection<String> list2 = new ArrayList<String>();
getElement(list2);
}
public static void getElement(Collection<?> coll) { }
//?代表可以接收任意类型
例1:
package com.itheima.demo03.Generic;
import java.util.ArrayList;
import java.util.Iterator;
/*
泛型的通配符:
?:代表任意的数据类型
使用方式:
不能创建对象使用
只能作为方法的参数使用
*/
public class Demo05Generic {
public static void main(String[] args) {
ArrayList<Integer> list01 = new ArrayList<>();
list01.add(1);
list01.add(2);
ArrayList<String> list02 = new ArrayList<>();
list02.add("a");
list02.add("b");
printArray(list01);
printArray(list02);
//ArrayList> list03 = new ArrayList>();
}
/*
定义一个方法,能遍历所有类型的ArrayList集合
这时候我们不知道ArrayList集合使用什么数据类型,可以泛型的通配符?来接收数据类型
注意:
泛型没有继承概念的
*/
public static void printArray(ArrayList<?> list){
//使用迭代器遍历集合
Iterator<?> it = list.iterator();
while(it.hasNext()){
//it.next()方法,取出的元素是Object,可以接收任意的数据类型
Object o = it.next();
System.out.println(o);
}
}
}
//运行结果为 1 2 a b
我的疑问1:在最后的静态方法中,去掉>描述代码运行结果与上述代码一致,即
public static void printArray(ArrayList list){
Iterator it = list.iterator();
while(it.hasNext()){
Object o = it.next();
System.out.println(o);
}
}
//运行结果为 1 2 a b
这个案例中,如果用代替将会发生编译错误,理由是泛型并没有继承的概念。但是如果干脆不使用泛型,在每一个集合都确定了各自的类型(数组储存的元素都是相同类型)的情况下,与使用泛型的代码运行结果一致,那在这个案例中使用泛型有什么优势吗,还是说根本没必要使用泛型?
之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在JAVA的泛型中可以指定一个泛型的上限和下限。
泛型的上限:
格式: 类型名称 extends 类 > 对象名称
意义: 只能接收该类型及其子类
泛型的下限:
格式: 类型名称 super 类 > 对象名称
意义: 只能接收该类型及其父类型
例子:
public static void main(String[] args) {
Collection<Integer> list1 = new ArrayList<Integer>();
Collection<String> list2 = new ArrayList<String>();
Collection<Number> list3 = new ArrayList<Number>();
Collection<Object> list4 = new ArrayList<Object>();
getElement(list1);
getElement(list2);//报错
getElement(list3);
getElement(list4);//报错
getElement2(list1);//报错
getElement2(list2);//报错
getElement2(list3);
getElement2(list4);
}
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){}
栈:stack,又称堆栈,它是运算受限的线性表,其限制是仅允许在标的一端进行插入和删除操作,不允许在其他任何位置进行添加、查找、删除等操作。
栈的特点:先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。
压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。
弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。
队列:queue,简称队,它同堆栈一样,也是一种运算受限的线性表,其限制是仅允许在表的一端进行插入,而在表的另一端进行删除。
队列的特点:先进先出(即,存进去的元素,要在后它前面的元素依次取出后,才能取出该元素)
队列的入口、出口各占一侧。
数组:Array,是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。
数组的特点:查找元素快:通过索引,可以快速访问指定位置的元素。增删元素慢:指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置
链表:linked list,由一系列结点node(链表中每一个元素称为结点)组成,结点可以在运行时i动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。我们常说的链表结构有单向链表与双向链表。
链表的特点:多个结点之间,通过地址进行连接。
查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素
增删元素快:增加元素:只需要修改连接下个元素的地址即可。删除元素:只需要修改连接下个元素的地址即可。
关于链表概述
关于链表的有序性
(虽然看了很多文章,还是不懂为什么单链是无序的,咋看都是有序的,一堆人,后一个人搭钱一个人肩膀站着,少一个人多一个人,难道大家所有人还要打乱顺序站吗……)
二叉树:binary tree ,是每个结点不超过2的有序树(tree) 。
二叉树是每个节点最多有两个子树的树结构。顶上的叫根结点,两边被称作“左子树”和“右子树”。
红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。
红黑树的约束:
1. 节点可以是红色的或者黑色的
2. 根节点是黑色的
3. 叶子节点(特指空节点)是黑色的
4. 每个红色节点的子节点都是黑色的
5. 任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同
红黑树的特点: 速度特别快,趋近平衡树,查找叶子元素最少和最多次数不多于二倍
java.util.List 接口继承自Collection 接口,是单列集合的一个重要分支,习惯性地会将实现了List 接口的对象称为List集合。在List集合中允许出现重复的元素,所有的元素是以一种线性方式进行存储的,在程序中可以通过索引来访问集合中的指定元素。另外,List集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致。
List接口特点:
1. 它是一个元素存取有序的集合。例如,存元素的顺序是11、22、33。那么集合中,元素的存储就是按照11、22、33的顺序完成的)。
2. 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
3. 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。
List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法,如下(增删改查):
public void add(int index, E element);//将指定的元素,添加到该集合中的指定位置上。
public E get(int index);//返回集合中指定位置的元素。
public E remove(int index);//移除列表中指定位置的元素, 返回的是被移除的元素。(有点剪切的意思)
public E set(int index, E element);//用指定元素替换集合中指定位置的元素,返回值的更新前的元素。
java.util.ArrayList 集合数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList 是最常用的集合。
注意:此实现不是同步的(多线程)
适合需要经常进行查询操作但不经常进行增删操作的需求解决。增删效率低下的一大原因就是在操作的最底层进行了数组创建、复制等操作。
java.util.LinkedList 集合数据存储的结构是链表结构。方便元素添加、删除的集合。
实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。这些方法作为了解即可:
public void addFirst(E e) :将指定元素插入此列表的开头。
public void addLast(E e) :将指定元素添加到此列表的结尾。
public E getFirst() :返回此列表的第一个元素。
public E getLast() :返回此列表的最后一个元素。
public E removeFirst() :移除并返回此列表的第一个元素。
public E removeLast() :移除并返回此列表的最后一个元素。
public E pop() :从此列表所表示的堆栈处弹出一个元素。
public void push(E e) :将元素推入此列表所表示的堆栈。
public boolean isEmpty() :如果列表不包含元素,则返回true。
LinkedList是List的子类,List中的方法LinkedList都是可以使用,只需要再了解LinkedList的特有方法即可。在开发时,LinkedList集合也可以作为堆栈,队列的结构使用。(了解即可)
注意:此实现是不同步的(多线程)
上述提到的集合均为JAVA1.2版本之后的集合,而在JDK1.0版本中,提供的单列集合为Vector,是其他单列集合的祖宗。底层是Array,但是是单线程的,所以在1.2版本中被ArrayList取代。此实现是同步的。
java.util.Set 接口和java.util.List 接口一样,同样继承自Collection 接口,它与Collection 接口中的方法基本一致,并没有对Collection 接口进行功能上的扩充,只是比Collection 接口更加严格了。与List 接口不同的是, Set 接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。
特点:不允许存储重复的元素。
没有索引,没有带索引的方法,也不能使用普通的for循环遍历。
java.util.HashSet 是Set 接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致)。java.util.HashSet 底层的实现其实是一个java.util.HashMap 支持。
HashSet 是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于: hashCode 与equals 方法。(底层是一个哈希表结构,其特点是查询速度特别快)
哈希值是一个十进制的整数,由系统随机给出(就是对象的地址值,是一个逻辑地址,是模拟出来的地址,不是数据实际存储的物理地址,在Object类中有一个方法可以获取对象的哈希值)
int hashCode() : 返回该对象的哈希码值
hashCode的方法源码: public native int hashCode(); // 其中native代表该方法调用的是本地操作系统的方法。
注意:对于哈希值,不能错误的认为不同对象的哈希值就是不同的,hashCode方法是可以被重写的,比如重写hashCode使其返回数字1,则所有对象的hashCode都会是1,此时用“==”将两个对象进行比较,返回的是False。再比如String类,就重写了hashCode方法,举个例子,“重地”.hashCode()的值与“通话”.hashCode()的值相等,都为1179395。但两个对象明显不可能存储在同一个物理地址中。
在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
JDK1.8引入红黑树大程度优化了HashMap的性能,那么对于我们来讲保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。
给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一。
例:
import java.util.Objects;
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
import java.util.HashSet;
/**
HashSet存储自定义类型元素
set集合报错元素唯一:
存储的元素(String,Integer,...Student,Person...),必须重写hashCode方法和equals方法
要求:
同名同年龄的人,视为同一个人,只能存储一次
*/
public class Demo03HashSetSavePerson {
public static void main(String[] args) {
//创建HashSet集合存储Person
HashSet<Person> set = new HashSet<>();
Person p1 = new Person("欣欣",18);
Person p2 = new Person("欣欣",18);
Person p3 = new Person("欣欣",19);
System.out.println(p1.hashCode());//1967205423
System.out.println(p2.hashCode());//42121758
System.out.println(p1==p2);//false
System.out.println(p1.equals(p2));//false
set.add(p1);
set.add(p2);
set.add(p3);
System.out.println(set);
}
}
HashSet保证元素唯一,可是元素存放进去是没有顺序的。在HashSet下面有一个子类java.util.LinkedHashSet ,它是链表和哈希表组合的一个数据存储结构。
LinkedHashSet是HashSet的子类,根据API文档:具有可预知迭代顺序的Set接口的哈希表和链表列表实现。此实现与HashSet的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链表定义了迭代顺序,即按照将元素插入到set中的顺序(插入顺序)进行迭代。注意,插入顺序不受在set中重新插入的元素的影响。(如果在调用s.contains(e)返回true后立即调用s.add(e),则元素e会被重新插入到set s中)此实现可以让客户免遭未指定的、由HashSet提供的通常杂乱无章的排序工作,而又不致引起与TreeSet关联的成本增加。使用它可以生成一个与原来顺序相同的set副本,并且与原set的实现无关。
在JDK1.5之后,如果我们定义一个方法需要接受多个数据,并且多个参数类型一致,我们可以对其简化成如下格式:
修饰符 返回值类型 方法名(参数类型… 形参名){ }
其实这个书写完全等价与
修饰符 返回值类型 方法名(参数类型[] 形参名){ }
只是后面这种定义,在调用时必须传递数组,而前者可以直接传递数据即可。JDK1.5以后。出现了简化操作。… 用在参数上,称之为可变参数。
同样是代表数组,但是在调用这个带有可变参数的方法时,不用创建数组(这就是简单之处),直接将数组中的元素作为实际参数进行传递,其实编译成的class文件,将这些元素先封装到一个数组中,在进行传递。这些动作都在编译.class文件时,自动完成了。
注意事项:
1、一个方法的参数列表,只能有一个可变参数
2、如果方法的参数有多个数据类型,那么可变参数必须写在参数列表的末尾
可变参数终极形态:
//使用obj接收任意类型的参数
private static void method(Object...obj) {
}
【待补充】
java.utils.Collections 是集合工具类,用来对集合进行操作。部分方法如下:
public static <T> boolean addAll(Collection<T> c, T... elements);//往集合中添加一些元素。
public static void shuffle(List<?> list);//打乱顺序:打乱集合顺序。
public static <T> void sort(List<T> list);//将集合中元素按照默认规则排序。
public static <T> void sort(List<T> list,Comparator<? super T> );//将集合中元素按照指定规则排序。
注意:public static void sort(List list) 这个方法完成的排序,实际上要求了被排序的类型需要实现Comparable接口完成比较的功能
自定义的类需要实现该接口,才能正常排序,其中的compareTo方法return的是正数,则调用该对象的元素放在被比较元素后方,否则在前方,假设是对数字进行比较,则return this.a - other,按升序排列(this.a大的时候,返回的为正数,把a排在了other后面),当return other - this.a时,为降序排列。
假设在数字相同时,各元素还有字符串属性,且字符串内容不同,则不能只根据数字进行比较,否则数字相同时,return 0,将认为这两个元素是相同元素。(Set集合元素唯一,将不会把这个元素添加进去)由于字符串String类本身就重写了compareTo方法,所以可以直接调用。
例:
public class Student implements Comparable<Student> {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(Student s) {
// return 0;
// return 1;
// return -1;
//按照年龄从小到大排序
int num = this.age - s.age;
// int num = s.age - this.age;
//年龄相同时,按照姓名的字母顺序排序
int num2 = num==0?this.name.compareTo(s.name):num;
return num2;
}
}
public class TreeSetDemo02 {
public static void main(String[] args) {
//创建集合对象
TreeSet<Student> ts = new TreeSet<Student>();
//创建学生对象
Student s1 = new Student("xishi", 29);
Student s2 = new Student("wangzhaojun", 28);
Student s3 = new Student("diaochan", 30);
Student s4 = new Student("yangyuhuan", 33);
Student s5 = new Student("linqingxia",33);
Student s6 = new Student("linqingxia",33);
//把学生添加到集合
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
//遍历集合
for (Student s : ts) {
System.out.println(s.getName() + "," + s.getAge());
}
}
}
Comparable:强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo方法被称为它的自然比较方法。只能在类中实现compareTo()一次,不能经常修改类的代码实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。
自己(this)和别人(参数)比较,自己需要实现Comparable接口,重写比较的规则
Comparator:强行对某个对象进行整体排序。可以将Comparator 传递给sort方法(如Collections.sort或Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。
相当于找一个第三方的裁判来比较两个元素。
Comparator排序规则 o1-o2(升序)(反之降序)
例:
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Demo03Sort {
public static void main(String[] args) {
ArrayList<Integer> list01 = new ArrayList<>();
list01.add(1);
list01.add(3);
list01.add(2);
System.out.println(list01);//[1, 3, 2]
Collections.sort(list01, new Comparator<Integer>() {
//重写比较的规则
@Override
public int compare(Integer o1, Integer o2) {
//return o1-o2;//升序
return o2-o1;//降序
}
});
System.out.println(list01);
ArrayList<Student> list02 = new ArrayList<>();
list02.add(new Student("a迪丽热巴",18));
list02.add(new Student("古力娜扎",20));
list02.add(new Student("杨幂",17));
list02.add(new Student("b杨幂",18));
System.out.println(list02);
/*Collections.sort(list02, new Comparator() {
@Override
public int compare(Student o1, Student o2) {
//按照年龄升序排序
return o1.getAge()-o2.getAge();
}
});*/
//扩展:了解
Collections.sort(list02, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
//按照年龄升序排序
int result = o1.getAge()-o2.getAge();
//如果两个人年龄相同,再使用姓名的第一个字比较
if(result==0){
result = o1.getName().charAt(0)-o2.getName().charAt(0);
}
return result;
}
});
System.out.println(list02);
}
}
//其中对姓名排序可以用字符串重写的compareTo方法
现实生活中,我们常会看到这样的一种集合:IP地址与主机名,身份证号与个人,系统用户名与系统用户对象等,这种一一对应的关系,就叫做映射。Java提供了专门的集合类用来存放这种对象关系的对象,即java.util.Map 接口。
我们通过查看Map 接口描述,发现Map 接口下的集合与Collection 接口下的集合,它们存储数据的形式不同,如下图。
java.util.Map
1.Map集合是一个双列集合,一个元素包含两个值(一个key一个value)
2.Map集合中的元素,key和value的数据类型可以相同也可以不同
3.Map集合中的元素,key是不允许重复的,value可以重复
4.Map集合中的元素,key和value一 一对应
java,util.HashMap
1.HashMap集合底层是哈希表:查询的速度特别快,JDK1.8之前为数组+单向链表,JDK1.8之后为数组+单向链表/红黑树(链表长度超过八)提高查询速度
2.HashMap是一个无序集合,存储元素和取出元素的顺序有可能不一致。
java.util.LinkedHashMap
1.LinkedHashMap集合底层为哈希表+链表(保证迭代顺序)
2.LinkedHashMap集合是一个有序集合,存储元素和取出元素的顺序是一致的
Map接口中定义了很多方法,常用的如下:
public V put(K key, V value);//把指定的键与指定的值添加到Map集合中。(若key已存在,则将新元素替换旧元素并返回旧元素,否则返回null)
public V remove(Object key);//把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。若键值不存在返回null。
public V get(Object key);//根据指定的键,在Map集合中获取对应的值。若键值不存在返回null。
public Set<K> keySet();//获取Map集合中所有的键,存储到Set集合中。
public Set<Map.Entry<K,V>> entrySet();//获取到Map集合中所有的键值对对象的集合(Set集合)。
public boolean containsKey(Object key);//如果此映射包含指定键的映射关系,则返回true。
public boolean containsValue(Object value);//如果此映射将一个或多个键映射到指定值,则返回true。
步骤:
1.使用Map集合中的方法keySet(),把Map集合所有的key取出来,存储到一个Set集合中
2.遍历set集合,获取Map集合中的每一个key
3.通过Map集合中的方法get(key),通过key找到value
例:
public class MapDemo01 {
public static void main(String[] args) {
//创建Map集合对象
HashMap<String, String> map = new HashMap<String,String>();
//添加元素到集合
map.put("胡歌", "霍建华");
map.put("郭德纲", "于谦");
map.put("薛之谦", "大张伟");
//获取所有的键 获取键集
Set<String> keys = map.keySet();
// 遍历键集 得到 每一个键
for (String key : keys) {
//key 就是键
//获取对应值
String value = map.get(key);
System.out.println(key+"的CP是:"+value);
}
}
}
Map 中存放的是两种对象,一种称为key(键),一种称为value(值),它们在在Map 中是一一对应关系,这一对对象又称做Map 中的一个Entry(项) 。Entry 将键值对的对应关系封装成了对象。即键值对对象,在遍历Map 集合时,就可以从每一个键值对( Entry )对象中获取对应的键与对应的值。
Entry表示了一对键和值,提供了获取对应键和对应值得方法:
public K getKey() ;//获取Entry对象中的键。
public V getValue() ;//获取Entry对象中的值。
在Map集合中提供了获取所有Entry对象的方法:
public Set
例:
public class MapDemo02 {
public static void main(String[] args) {
// 创建Map集合对象
HashMap<String, String> map = new HashMap<String,String>();
// 添加元素到集合
map.put("胡歌", "霍建华");
map.put("郭德纲", "于谦");
map.put("薛之谦", "大张伟");
// 获取 所有的 entry对象 entrySet
Set<Entry<String,String>> entrySet = map.entrySet();
// 遍历得到每一个entry对象
for (Entry<String, String> entry : entrySet) {
// 解析
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key+"的CP是:"+value);
}
}
}
tips:Map集合不能直接使用迭代器或者foreach进行遍历。但是转成Set之后就可以使用了。
当给HashMap中存放自定义对象时,如果自定义对象作为key存在,这时要保证对象唯一,必须复写对象的hashCode和equals方法。如果要保证map中存放的key和取出的顺序一致,可以使用java.util.LinkedHashMap 集合来存放。(参考HasSet)
Map接口的哈希表和链表实现,具有可预知的迭代顺序。是一个有序的集合。底层原理:哈希表+链表(记录元素顺序)
此类也继承了Map接口,实现一个哈希表。(但是不允许存储null),是最早期的双列集合,且是同步的(单线程),是线程安全的集合,速度慢。Hashtable和Vector集合一样,在JDK1.2版本之后被更先进的集合(HashMap,ArrayList)所取代。
Hashtable的子类Properties依然活跃在历史舞台,是唯一的和IO流相结合的集合。
Java 9,添加了几种集合工厂方法,更方便创建少量元素的集合、map实例。新的List、Set、Map的静态工厂方法可以更方便地创建集合的不可变实例。
List,Set,Map接口,增加了一个静态方法of,可以给集合一次性添加多个元素。
注意:
1:of()方法只是Map,List,Set这三个接口的静态方法,其父类接口和子类实现并没有这类方法,比如HashSet,ArrayList等;
2:返回的集合是不可变的;
3:Map和Set接口不能调用of方法存储重复元素,否则抛出异常。
IDEA
F8:逐行执行程序
F7:进入到方法中
shift+F8:跳出方法
F9:跳到下一个断点,如果没有下一个断点,那么就结束程序
Ctrl+F2:退出debug模式,停止程序
Console:切换到控制台