集合
集合存储多个数据
–>区别:数组是定长,集合长度是任意的
数组存储的数据是同一种数据类型,集合是可以存储不同的数据类型
数组可以存储基本和引用数据类型,集合只能存储引用数据类型
集合体系
泛型
泛型<>
泛型: Generics
安全校验机制
可以在类或者方法中预支的使用未知的类型.在JDK1.5之后引入的新特性
让你在设计API时可以指定类或者方法支持泛型,这样我们使用API的时候也变得更为简洁,
并且得到了在程序编译时期的语法安全检查.
将运行时期的ClassCastException,转移到编译时期编程了编译异常
避免了类型强转的麻烦.
<>—>简化程序,减少类型转换
使用:
在集合中会大量使用到泛型.
在开发中,泛型,用来灵活的将数据类型应用到不同的类,方法,接口当中.
将数据类型作为参数进行传递.
Collection.add(E e) 泛型通配符 不是* 而是<?>
泛型的概述:
泛型:可以在类或者方法当中预支的使用未知的数据类型.
备注:一般在创建对象的时候,将未知的数据类型确定为具体的数据类型,当没有指定泛型时,默认类型为Object类型
使用泛型的好处
- 避免了类型转换的麻烦,存储的是什么样的数据类型,取出的就是什么样的类型
- 把运行期异常(代码运行之后会抛出的异常)提升到编译器阶段(写代码的时候就会报错)
*备注:泛型它其实也是一种数据类型的一部分,一般我们将类名和泛型合并一起看做泛型类型。
泛型的定义与使用
泛型,用来灵活的将数据类型应用到不同的类、方法、接口当中。将数据类型作为参数进行传递
因此我们的集合框架体系中,大量的使用了泛型。
定义和使用含有泛型的类
定义格式:
修饰符 class 类名<代表泛型的变量>{
}
eg.
public class ArrayList<E>{
public boolean add(E e){
public E get(int index){
//...
}
}
}
*备注:定义的时候使用未知的范型变量,使用的时候(创建对象)确定泛型的具体类型。
定义并使用含有泛型的方法
定义格式:
修饰符 <代表泛型的变量> 返回值类型 方法名(泛型参数){}
eg.
public class GenericMethod{
//定义带有泛型的方法
public <VIP> void show(VIP vip){
System.out.println(vip);
}
//定义一个含有泛型的返回值
public <VIP> VIP show02(VIP vip){
/....
return vip;
}
}
//定义测试类
public class TestGenericmethod{
public static void main(String[] args){
//创建对象
GenericMethod gm = new GenericMethod();
//调用带有泛型的方法
gm.show("abc");//VIP vip 参数 ----> 形参
}
}
定义并使用接口
定义格式:
修饰符 interface 接口名<代表泛型的变量> {}
eg.
public interface Collection<E>{
public void add(E e){}
public Iterator<E> iterator();
}
//自定义一个泛型的接口
public interface MyGenericInterface<T>{
public abstract void add(E e);
public abstract E get();
//...
}
使用格式:
1.定义实现类时可以确定泛型的类型
public class MyInterfaceImpl implements MyGenericInterface<String>{
@Override
public void add(String e){
//...
}
@Override
public String get(){
//...
}
}
备注:此时泛型【T】的值就是String类型
泛型的通配符
当使用泛型类或者泛型接口,传递的数据中,泛型的类型不确定,可以通过通配符<?>表示.一旦程序当中使用泛型通配符后,只能使用Object中的共性的方法,集合中元素自身 方法无法使用.
通配符的基本使用
泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用?来表示,?代表位置的通配符.
此时只能接收数据,不能往该集合当中存数据
代码示例:
public static void main(String[] args){
//可以存储正数的集合
Collection<Integer> list1 = new ArrayList<Integer>();
//此时list1可以存储正数数据
//展示list1集合当中的数据
getElement(list1);
//可以存储String字符串的集合
Collection<String> list2 = new ArrayList<String>();
}
//
public static void getElement(Collection<?> coll){
//只能接收Integer类型数据
}
*备注:泛型不存在继承关系,Collection<Object> list = new ArrayList<String>;
通配符的高级用法----受限泛型
之前设置泛型的时候,实际上是可以任意设置的.只要是类就可以的,但是Java的泛型当中还可以指定一个的泛型的上限和下限
泛型的上限
- 格式:类型名称<?extends 类名>对象名称
- 意义:只能接收该类型及其子类.
比如说:
比如说:已知的顶级父类Object, String类, Number类, Integer类 ,其中Number类 是Integer的父类
示例代码:
public static void main(String[] args) {
//初始化三个集合
Collection<Integer> list01 = new ArrayList<Integer>();
增强for循环
在JDK1.5之后出现了一个新的循环结构,for each循环,一般也称为增强for循环,专门用来遍历数组和集合的。它的内部原理其实是有个迭代器Iterator,在迭代过程中,不能对集合当中的元素进行增删操作
格式:
for(元素的数据类型 变量名 :collection集合或者数组){
//操作代码
//。。。
}
主要用于遍历Collection集合或者数组。在遍历的过程中,一定不要进行增删操作。
public static void main(String[] args) {
/*int[] arr = {3,5,7,9,12};
for(int a : arr){
System.out.println(a);
}*/
Collection<String> list = new ArrayList<>();
list.add("麓铭大岳丸");
list.add("待宵姑获鸟");
list.add("赤影妖刀姬");
list.add("苍风一目连");
list.add("少羽大天狗");
for (String name:
list) {
System.out.print(name+" ");
}
}
*备注:目标只能是Collection集合或者是数组,增强for循环仅仅是作为遍历操作出现的。简化迭代器的操作
Collection
Collection是单列集合层次的根接口
定义了集合操作的方法,并没有提供直接实现类,而是提供了直接子接口
一些集合存储的数据有序,一些集合存储的数据无序
一些集合存储的数据可以重复,一些集合存储的数据不能重复
—>至于如何做到上述效果,要根据不同实现类来决定
因为Collection是接口,要演示其方法,必须创建实现类对象,才可以执行方法.
我们以ArrayList 演示
package com.zhiyou100.collection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
public class TestCollection {
public static void main(String[] args) {
/*
* 遍历集合
* 方法1 : 将集合转成数组,再遍历数组即可
* 方法2 : 利用迭代器
* 方法3 : foreach - 增强for循环
*/
Collection<String> c1 = new ArrayList<>();
c1.add("D");
c1.add("C");
c1.add("B");
c1.add("A");
Iterator<String> i = c1.iterator();
// // 判断集合有无下一个元素
// boolean r1 = i.hasNext();
// // 如果有,就取出下一个元素
// if(r1) {
// String n1 = i.next();
// System.out.println(n1);
// }
while(i.hasNext()) {
System.out.println(i.next());
}
System.out.println("---------------");
/*
* foreach语法
* for(遍历得到结果的数据类型 变量名 : 要遍历的对象) {
* 遍历过程中的操作...
* }
*/
for(String r:c1) {
System.out.println(r);
}
int[] arr = {3,2,4,1};
for(int a:arr) {
System.out.println(a);
}
}
private static void test3() {
Collection<String> c1 = new ArrayList<>();
c1.add("D");
c1.add("C");
c1.add("B");
c1.add("A");
String[] str = c1.toArray(new String[c1.size()]);
System.out.println(Arrays.toString(str));
}
private static void test2() {
/*
* 虽然集合可以存储不同数据类型,但是大多数时间
* 集合存储的是同一种数据类型
* 为了限制集合存储同一种数据类型,就使用泛型来约束
*/
Collection<String> c1 = new ArrayList<>();
// 添加元素
boolean r1 = c1.add("A");
boolean r2 = c1.add("A");
// coll.add(new Date());
// coll.add(1); // 自动装箱 Integer i = 1
boolean r3 = c1.add("C");
System.out.println(c1);
Collection<String> c2 = new ArrayList<>();
c2.add("D");
c2.add("E");
c2.add("F");
// 添加整个集合中的元素到另一个集合中
c1.addAll(c2);
System.out.println(c1);
//System.out.println("清空前集合元素个数 : "+c1.size());
//c1.clear();
//System.out.println("清空后集合是否为空 : "+c1.isEmpty());
//System.out.println("清空后集合元素个数 : "+c1.size());
//System.out.println(c1);
System.out.println(c1.contains("A"));
Collection<String> c3 = new ArrayList<>();
c3.add("E");
c3.add("F");
c3.add("I");
System.out.println(c1.containsAll(c3));
// 移除匹配的单个元素
//System.out.println("移除A元素 是否成功 ? "+c1.remove("J"));
//System.out.println(c1);
// 移除此 collection 中那些也包含在指定 collection 中的所有元素
//c1.removeAll(c3);
//System.out.println(c1);
//c1.retainAll(c3);
//System.out.println(c1);
Object[] obj = c1.toArray();
System.out.println(obj);
String[] str = new String[obj.length];
for (int i = 0; i < obj.length; i++) {
str[i] = (String) obj[i];
}
System.out.println(str);
}
private static void test1() {
Collection coll = new ArrayList();
// 添加元素
coll.add("A");
coll.add("A");
coll.add(new Date());
coll.add(1); // 自动装箱 Integer i = 1
coll.add("C");
System.out.println(coll);
}
}
List
List集合
我们主要介绍java.util.List集合和java.util.Set集合
List接口介绍
java.util. List接口继承自Colletion接口,是单列集合的一个重要分支, 在List集合 当中允许出现重复的元素,所有的元素都是以一种线性方式进行存储的,在List集合 当中基本上我们可以通过索引来访问集合当中的元素。另外List集 合还有一个特点就是元素是有序的,指的是存取元素顺序相同。
List接口当中的常用的API方法:
出了继承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) :在指定索引位置上替换成给定的元素,并且返回更新前的元素。
ArrayList
实现自List,所以也是有序,允许重复
ArrayList的底层是数组
可以存储null元素
ArrayList是不同步的–>不保证线程安全
面试题:ArrayList的扩容机制.
演示方法:
大部分方法与Collection一致,不在演示
演示特有的与下标有关系的方法
public static void main(String[] args) {
ArrayList<String> al = new ArrayList<>();
al.add("A");
al.add("B");
al.add("C");
// 在指定下标处插入
al.add(1,"D");
System.out.println(al);
// 根据下标获得值
System.out.println(al.get(1));
// 根据元素找到第一个匹配的下标
System.out.println(al.indexOf("E"));
// 根据下标移除元素
//al.remove(1);
//System.out.println(al);
// 根据下标替换元素
al.set(1, "d");
System.out.println(al);
}
LinkedList
java.util.LinkedList集合数据存储的结构采用的是链表结构,方便元素的添加和删除**[LinkedList是一个双向链表]**
我们在开发中对一个链表集合中的元素大量的都是采用首尾节点操作(添加和删除):常用的API方法如下:
- public void addFirst(E e):将指定的元素添加此列表的开头。
- public void addLast(E e):将指定的元素添加此列表的末尾。
- public E getFirst():获取此列表的首节点元索
- public E getL ast():获取此列表的尾节点元素
- public E removeFist():删除此列表的首节点元素
- public E removel ast():删除此列表的尾节点元素
- public E pop():从此列表所表示的堆栈中弹出一个元素(首元索)
- public void push(E e):将元素推入此列表所表示的堆栈中(首元素)
- public boolean isEmpty():如果此列表不包含任何元素,返回true
实现List接口,所以也是 有序,允许重复
底层基于链表实现
可以存储null值
LinkedList是不同步 --> 不保证线程安全
LinkedList提供了与ArrayList大部分相同的方法,但是有些不同的
不同的在于,**提供了一下专门操作开头和结尾元素的方法 **
为什么?
因为ArrayList是数组,空间中是连续的结构,下标紧凑,可以快速定位元素
但是LinkedList是链表实现的,在LinkedList集合当中,封装了大量关于首节点和尾节点元素操作的方法,空间中不连续, 虽然也提供了根据下标找元素的方法,但是效率极低.
面试题 :
ArrayList 根据下标查询和更新效率高,ArrayList 插入和删除效率低
LinkedList 根据下标查询和更新效率低, 插入删除效率高
Set
java.util.Set接口和java.util.List接口是一样的,都是集成Collection接口,它与Collection接口中的方法基本一样,没有对Collection接口进行功能上的扩展,只是比Collection接口更加严格.与List接口不同的是,Set接口中的元素是无序的,并且都会以某种规则保证存入的元素不重复.
Set集合不能重复元素
允许存储null值
Set集合取出元素的方式可以采用:迭代器、增强for循环。
Set接口下定义的方法与Collection接口定义的方法一模一样.
HashSet
java.util.HashSet是Set接口的实现类,不允许重复元素,不保证迭代顺序–>无序
HashSet
是根据对象的哈希值来确定元素在集合当中的存储位置,因此它具有良好的存取和查找性能,保证元素唯一性
HashSet底层是HashMap,在创建HashSet时,同时创建了HashMap,且在向HashSet中存储数据时,其实是存储在HashMap的键上
HashSet集合存储数据的结构(哈希表)
什么是哈希表?
在JDK1.8之前,哈希表的底层采用的是数组+链表实现,即使用链表处理哈希冲突,同一哈希值的链表都存储在一个链表里,但是当位于一个链中的元素较多时,即hash值相等的元素较多时,通过key值依次查找的效率很低下。在JDK1.8中,哈希表存储结构采用数组+链表/红黑树实现的,当链表的长度超过阈值(8)时,将链表转换成红黑树结构,这样的好处是大大减少了查找的时间
如图:
总而言之,JDK1.8之后引入红黑树结构大大优化了HashMap的性能,那么对于我们来讲保证HashSet元素唯一不重复,其实是根据对象的hashCode和equals方法来决定的。如果我们往集合当中存储的是自定义的对象,需要保证对象的唯一性,就必须重写HashCode和equals方法,来自定义当前对象的比较方式。
HashSet保证元素唯一的原理介绍
HashSet存储自定义类型的元素
一般需要重写对象当中的hashCode和equals方法,建立自己的比较方式。才能保证HashSet集合中元素的唯一性。
代码示例:
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (this == o) {
return true;
}
// 向下转型 类型判断
if (o instanceof Student) {
Student student = (Student)o;
// 同名同年龄的人为同一个人 true
return student.getName().equals(name) && student.getAge() == age;
}
return false;
}
@Override
public int hashCode(){
// 使用Objects类中的hash方法
return Objects.hash(name,age);
}
允许存储null值
HashSet不同步 --> 不保证线程安全
方法演示
略
HashSet去重原理(面试):
在向HashSet中存储元素时,(执行 add()方法),会调用元素的hashcode() 和 equals()方法
1 在向HasHSet中存储元素时,首先先在 内存 中寻找有无 与当前元素的hashcode值一样的元素
2 如果没有一样的,认为在内存中没有这么一个元素,那就将其存入
3 如果有一个一样hashcode值的元素,那么此时就会调用当前元素的equals方法,用于和之前元素的内容比较,如果内容也一样,那么就认为重复,那就不存储 如果equals比较不一样,那就存储.
LinkedHashSet
我们知道HashSet保证元素的唯一,可是存进去的元素是没有顺序的,那么如何保证存进去的元素是有序的?
在java.util.HahSet
类的下面还有一个子类java.util.LinkedHashSet
,它是链表和哈希表的组合的一个数据存储结构。
代码示例:
// 构建一个LinkedHashSet集合对象
LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("www");
linkedHashSet.add("zhiyou100");
linkedHashSet.add("com");
linkedHashSet.add("abc");
linkedHashSet.add("abc");
linkedHashSet.add("java");
linkedHashSet.add("python");
// [www, zhiyou100, com, abc, java, python]
System.out.println(linkedHashSet);// [www, zhiyou100, com, abc] 有序的,不重复的
是哈希表和链接列表实现,即有Set集合 元素不重复的特点,也有链表结构元素有顺序特点
–>不重复,能保证迭代顺序
方法与HashSet的方法签名一样
可变参数
在JDK.15之后,如果我们定义一个方法需要接收多个参数,并且多个参数,数据类型一致,那么我们可以简化成如下格式:
修饰符 返回值类型 方法名(参数类型...形参名){
//...
}
其实上面的格式完全等价于:
修饰符 返回值类型 方法名(参数类型[] 参数名){
//...
}
只是后面的写法,在方法调用时,必须传递一个数组类型,而前者可以直接传递数据.
JDK1.5之后,出现的这种简化操作."…"用在参数上,我们称之为可变参数.
同样是代表数组,但是在方法调用这个带有可变参数时,不用创建数组,而是直接将数组当中的元素作为实际参数进行传递,其实编译生成的class文件,本质是将这些元素封装到了一个数组当中,在进行数据传递,这些动作都在变异生成class文件的时候,自动完成了.
代码示例:
public static void add(int... arr) {
//System.out.println(arr);// [I@1b6d3586 底层就是一个数组
//System.out.println(arr.length);//0
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
}
System.out.println(sum); // 30
}
备注:
可变参数的注意事项:
1. 一个方法的参数列表,只能有一个可变参数
2. 如果方法的参数有多个,类型不止一种,那么可变参数必须写在参数列表的末尾位置
TreeSet
TreeSet底层是TreeMap
TreeSet会将存入的元素进行自然排序 --> 有序
自然顺序 是指 : 实现了Comparable接口的任意一个类,都可以做到自然顺序
TreeSet是不保证线程安全
TreeSet集合不能存储重复元素
TreeSet方法演示
Collections集合工具类
常用功能
java.util.Collections
是集合工具类,用来操作集合对象中的元素,方法如下:
public static <T> boolean addAll(Collection<? super T> c,T... elements)
:往集合中一次性添加多个元素。public static <T> void shuffle(List<?> list)
:打乱集合中的元素顺序。public static <T> void sort(List<T> list)
:将集合中的元素按照默认规则排序。public static <T> void sort(List<T> list,Comparator<? super T> c)
:将集合中的元素按照指定的规则进行排序。
代码演示:
public static void main(String[] args) {
ArrayList<String> strs = new ArrayList<>();
// 往集合当中存储元素
/* strs.add("abc");
strs.add("小孙");
strs.add("小刘");
strs.add("小赵");*/
// 使用一下Collections集合工具类中的addAll()
// 所有通用的 Collection 实现类(通常通过它的一个子接口间接实现 Collection)
Collections.addAll(strs, "abc","小孙","小刘","小赵","a",new String());
System.out.println(strs);// [abc, 小孙, 小刘, 小赵]
System.out.println("====================");
// 打乱顺序 public static <T> void shuffle(List<?> list)
Collections.shuffle(strs);
System.out.println(strs);// [小刘, 123, 小赵, a, 小孙, abc]
}
结果:
[abc, 小孙, 小刘, 小赵, a, ]
====================
[, abc, 小孙, 小刘, a, 小赵]
//学生类实现Comparable接口
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 String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
// 重写排序规则
@Override
public int compareTo(Student o) {
// return 0:代表的是两个元素相同
// 自定义排序规则:按照年龄进行排序
// this参数,o参数
// this.getAge() - o.getAge()// 升序排序
// o.getAge() - this.getAge() 降序排序
System.out.println("========");
System.out.println(o.getAge()-this.getAge());
return -1;// 升序排序
}
}
Comparator
Collections工具集中的sort()
public static <T> void sort(List<T> list)
public static <T> void sort(List<T> list,Comparator<? super T> c)
Comparator接口在java.util包下面,排序是Comparator需要实现的功能之一,该接口代表的是是一个比较器,比较器具有可比性,可以做排序的,本质其实比较两个对象谁排在前边谁排在后边。那么比较的方法是:
public int compare(Object o1,Object o2):比较两个参数的的顺序
两个对象比较的结果有三种:大于,等于,小于
如果要按照升序排序:则o1小于o2返回(负数),相等返回0,o1大于o2返回(正数)
如果要按照降序排序:则o1小于o2返回(正数),相等返回0,o1大于o2返回(负数)
简化操作:
升序排序: o1-o2
降序排序: o2-o1
public static void main(String[] args) {
//public static <T> void sort(List<T> list)
ArrayList<String> list = new ArrayList<>();
list.add("abc");
list.add("cba");
list.add("bca");
list.add("sba");
list.add("nba");
// 排序规则,按照字符串的第一个字母降序排序
// 使用接口中的方法:
// public static <T> void sort(List<T> list,Comparator<? super T> c)
// public int compare(Object o1,Object o2):比较两个参数的的顺序
Collections.sort(list,new Comparator(){
@Override
public int compare(String o1,Strng o2) {
/*if(o2.charAt(0) - o1.charAt(0) > 0){
return 1;
} else if ( o2.charAt(0) - o1.charAt(0) == 0){
return 0;
} else {
return -1;
}*/
return o2.charAt(0) - o1.charAt(0);
}
});
System.out.println(list);
}
// 控制台结果:
[sba,nba,cba,bca,abc]
简述Comparable和Comparator两个接口的区别
Comparable:强行对实现它的每个类对象进行整体排序。这种排序我们一般把它称之为自然排序,类的compareTo方法被称之为它的自然比较方法。只能在类中实现compareTo方法一次,不能经常修改类的代码实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collections.sort()(和Arrays.sort())进行自动排序,对象可以用作有序映射中的键或者有序集合中的元素,无需指定比较器。
Comparator:强行对某个对象进行整体排序。可以将Comparator传递给sort()方法(如Collections.sort()后者Arrays.sort()),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(有序的set或者是有序的映射)的顺序,或者为那些没有自然顺序的对象Collection提供排序。
public class Student{
private String name;
private int age;
// get和set方法
// toString方法
// 无参构造和全参构造
}
public class MainClass {
public static void main(String[] args) {
// 假如学生都是老外 名字都是字母
ArrayList<Student> list = new ArrayList<>();
list.add(new Student("tom",20));
list.add(new Student("lily",18));
list.add(new Student("jack",26));
list.add(new Student("rose",40));
list.add(new Studeng("smith",26));
// 按照年龄升序排序
// 如果年龄相同的话,然后按照姓名排序,按照姓名的首字母降序排序
Collections.sort(list,new Comparator(){
@Override
public int compare(Student o1,Student o2) {
int num = o1.getAge() - o2.getAge();
if( num == 0) {
num = o2.getName().charAt(0) - o1.getName().charAt(0);
}
return num;
}
});
System.out.println(list);
}
}
// 控制台输出的结果:
[Student("lily",18),Student("tom",20),Student("jack",26),Student("rose",40)]
// 二次控制台输出结果:
[Student("lily",18),Student("tom",20),Studeng("smith",26),Student("jack",26),Student("rose",40)]