一、Set集合概述和特点
1. Set集合概述和特点
java.util.Set 接口和 java.util.List 接口一样,同样继承自 Collection 接口,它与 Collection 接口中的方法基本一致,并没有对 Collection 接口进行功能上的扩充,只是比 Collection 接口更加严格了。与List 接口不同的是,Set 接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。Set 集合有多个子类,这里我们介绍其中的 HashSet 、LinkedHashSet及TreeSet这三个集合。
二、集合 HashSet
1.HashSet类概述
java.util.HashSet 是 Set 接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致)。 java.util.HashSet底层的实现其实是一个java.util.HashMap 支持,由于我们暂时还未学习,先做了 解。
HashSet 是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于: hashCode 与 equals 方法。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。
6.2.2 HashSet使用-去重
1. 存储字符串并遍历
public class Test {
public static void main(String[] args) {
// Set是接口,创建对象new 的是 实现类HashSet 是其中一个实现类
Set<String> set = new HashSet<String>();
//给集合 添加元素
set.add("Hello1");
set.add("Hello1");
set.add("Hello2");
set.add("Hello3");
set.add("Hello4");
//增强for遍历
for (String string : set) {
//不含有重复元素 Hello1
//且存取顺序不一致
System.out.println(string);
}
}
}
运行结果:
2. 存储自定义对象并遍历
public class Test {
public static void main(String[] args) {
// Set是接口,创建对象new的是实现类,HashSet 是其中一个实现类
Set<Student> set = new HashSet<Student>();
//给集合 添加元素
set.add(new Student(15, "小李"));
set.add(new Student(15, "小王"));
set.add(new Student(15, "小王"));
set.add(new Student(15, "小王"));
set.add(new Student(15, "小王"));
//增强for遍历
for (Student s : set) {
System.out.println(s);
}
}
//自定义类
public static class Student {
int age;
String name;
public Student(int age, String name) {
super();
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Student [age=" + age + ", name=" + name + "]";
}
//使用的是HashSet实现类,由 hashCode 和 equals 保证唯一
// 如果是自定义类,作为元素要重写 hashCode 和 equals 方法
@Override
public int hashCode() {
//调用工具类,获取当前对象的hashcode
return Objects.hash(name, age);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
//对象地址值一样,则是同一个对象
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (age != other.age)
//年龄不相同,则不是同一个对象
return false;
if (name == null) {
if (other.name != null)
//比较的对象名字是null,而被比较的不是,则不是同一个对象
return false;
} else if (!name.equals(other.name))
//name不相同,不是同一个对象
return false;
return true;
}
}
}
运行结果:
三、集合 LinkedHashSet
6.3.1 LinkedHashSet类概述和特点
我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢? 在HashSet下面有一个子类 java.util.LinkedHashSet ,它是链表和哈希表组合的一个数据存储结构,由链表保证元素有序,由哈希表保证元素唯一。
1. 存储字符串并遍历
public class Test {
public static void main(String[] args) {
LinkedHashSet<String> set = new LinkedHashSet<String>();
//给集合 添加元素
set.add("Hello1");
set.add("Hello1");
set.add("Hello2");
set.add("Hello3");
set.add("Hello4");
//增强for 遍历
for (String string : set) {
/*
* Hello1
Hello2
Hello3
Hello4
去掉重复的
存取有序
*/
System.out.println(string);
}
}
}
运行效果:
2. 存储自定义对象并遍历
public class Test {
public static void main(String[] args) {
LinkedHashSet<Student> set = new LinkedHashSet<Student>();
//给集合 添加元素
set.add(new Student(15, "小李"));
set.add(new Student(15, "小王"));
set.add(new Student(16, "小王"));
set.add(new Student(15, "小王"));
set.add(new Student(15, "小王"));
//增强for遍历
for (Student s : set) {
System.out.println(s);
}
}
//自定义类
public static class Student {
int age;
String name;
public Student(int age, String name) {
super();
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Student [age=" + age + ", name=" + name + "]";
}
//使用的是HashSet实现类,由 hashCode 和 equals 保证唯一
//如果是自定义类,作为元素要重写 hashCode 和 equals 方法
@Override
public int hashCode() {
//调用工具类,获取当前对象的hashcode
return Objects.hash(name, age);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
//对象地址值一样,则是同一个对象
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (age != other.age)
//年龄不相同,则不是同一个对象
return false;
if (name == null) {
if (other.name != null)
//比较的对象名字是null,而被比较的不是,则不是同一个对象
return false;
} else if (!name.equals(other.name))
//name不相同,不是同一个对象
return false;
return true;
}
}
}
运行效果:
四、集合 TreeSet
1. TreeSet类概述和特点
TreeSet是一个有序的集合,它的作用是提供有序的Set集合。它继承了AbstractSet抽象类,实现了NavigableSet<E>,Cloneable,Serializable接口。TreeSet是基于TreeMap实现的,TreeSet的元素支持2种排序方式:自然排序或者根据提供的Comparator进行排序。
特点如下:
①元素唯一且有序;
②支持2种排序方式:自然排序或者根据提供的Comparator进行排序。
③底层数据结构是红黑树(红黑树是一种自平衡的二叉树)
2. TreeSet是如何保证元素的排序和唯一性的
TreeSet底层是通过TreeMap实现的。它把Map中的Key作为一类元素归结起来,而Map的Key是唯一的。Map我们后面会学习到。
3. TreeSet的自然排序和比较器排序
1. 自然排序
a.定义一个类(示例是Student)实现Comparable接口,不实现会报异常 java.lang.ClassCastException: com.xts.im.test.Test$Student cannot be cast to java.lang.Comparable
b.重写Comparable接口中的compareTo()方法;
c.在compareTo()中按指定属性进行排序(示例中先按年龄,如果年龄一样按姓名排序)
示例代码如下:
public class Test {
public static void main(String[] args) {
TreeSet<Student> set = new TreeSet<>();
//给集合 添加元素
set.add(new Student(100, "小张"));
set.add(new Student(15, "小王"));
set.add(new Student(15, "小李"));
set.add(new Student(15, "小王"));
set.add(new Student(16, "小王"));
//增强for遍历
for (Student s : set) {
System.out.println(s);
}
}
//自定义类,实现了Comparable接口
public static class Student implements Comparable{
int age;
String name;
public Student(int age, String name) {
super();
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student [age=" + age + ", name=" + name + "]";
}
@Override
public int compareTo(@NonNull Object obj) {
Student student = (Student) obj;
//1.优先以年龄排序
//2.年龄一样使用name排序
//先比较年龄
int a = this.age - student.getAge();
//System.out.println(this.age);
//System.out.println(student.getAge());
//System.out.println(a);
//次要排序条件name
//如果年龄一样就比较姓名
if(a==0){
a = this.name.compareTo(student.getName());
}
// 根据a是 负整数、零或正整数,此对象是小于、等于还是大于指定对象
//a是0:当前对象和obj一样,不存储
//a是正整数:当前对象比obj大,在二叉树中,obj排在当前对象的左边节点
//a是负整数:当前对象比obj小,在二叉树中,obj排在当前对象的右边节点
return a;
}
}
}
2. 比较器排序
a.创建一个比较器类,实现Comparator接口,重写compare()方法;
b.在compare()中按指定属性进行排序(示例中先按年龄,如果年龄一样按姓名排序)
c.创建TreeSet的时候,传入比较器对象;
示例代码如下:
public class Test {
public static void main(String[] args) {
//创建TreeSet的时候传入自定义的比较器
TreeSet<Student> set = new TreeSet<>(new MyComparator());
//给集合 添加元素
set.add(new Student(100, "小张"));
set.add(new Student(15, "小王"));
set.add(new Student(15, "小李"));
set.add(new Student(15, "小王"));
set.add(new Student(16, "小王"));
//增强for遍历
for (Student s : set) {
System.out.println(s);
}
}
//自定义的比较器类
public static class MyComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
//1.优先以年龄排序
//2.年龄一样使用name排序
//先比较年龄
int a = o1.age - o2.getAge();
//System.out.println(this.age);
//System.out.println(student.getAge());
//System.out.println(a);
//次要排序条件name
//如果年龄一样就比较姓名
if(a==0){
a = o1.name.compareTo(o2.getName());
}
// 根据a是 负整数、零或正整数,此对象是小于、等于还是大于指定对象
//a是0:o1和o2一样,不存储
//a是正整数:o1比o2大,在二叉树中,o2排在o1的左边节点
//a是负整数:o1比o2小,在二叉树中,o2排在o1的右边节点
return a;
}
}
//自定义类
public static class Student{
int age;
String name;
public Student(int age, String name) {
super();
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student [age=" + age + ", name=" + name + "]";
}
}
}
3. 两种方式的区别
a.TreeSet构造函数什么都不传, 默认按照类中Comparable的顺序(没有就报错ClassCastException)
b.TreeSet如果传入Comparator, 就优先按照Comparator
五、增强for概述及使用
1. 增强for概述,原因
增强for又叫foreach,是一种加强型的for循环操作,主要可以用于简化数组或者集合数据的输出操作。普通for循环需要控制索引,容易出现索引越界的问题(IndexOutOfBoundsException)。
2. 增强for的格式,好处,注意事项
1. 增强for循环格式如下:
for(数据类型 变量:数组|集合){
//每次循环都会自动将数组或者集合中的内容依次取出,设置给变量,可以避免索引问题
}
示例代码:
public class Test {
public static void main(String[] args) {
//数组
int[] arr = {1, 4, 62, 431, 2};
//增强for遍历输出
for (int value :arr) {
System.out.println(value);
}
}
}
2. foreach好处
a.简化数组或者集合
b.出现索引越界的问题
3. 使用增强for注意事项
a.在使用增强型for循环不支持遍历时删除元素(会报异常:ConcurrentModificationException)
b.使用增强型for循环时,对遍历的集合需要做null判断,不然可能引发空指针异常。
看如下案例:
public class Test {
public static void main(String[] args) {
//list集合
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
//增强for遍历输出
for (int value :list) {
System.out.println(value);
//移除元素
list.remove(value);
}
}
}
六、 了解静态导入概述及使用
1. 静态导入概述,注意事项
如果某个类中定义的方法全部都是static类型的,那么其他类要引用此类时必须先使用import导入所需要的包,再使用“类名称.方法()”进行调用
示例如下:
package com.xts.im.util;
public class MyMath {
//相加
public static int add(int a,int b){
return a+b;
}
//相除
public static int div(int a,int b){
return a/b;
}
}
package com.xts.im.test;
//导包
import com.xts.im.util.MyMath;
public class Test {
public static void main(String[] args) {
int a = 10;
int b = 2;
//类名称.方法()调用
System.out.println("a+b = "+ MyMath.add(a,b));
System.out.println("a/b = "+ MyMath.div(a,b));
}
}
当前程序MyMath两个方法都使用了static定义,而后在不同包中的类实现调用,如果在调用的时候不希望出现类名称,即直接在主方法中可以直接调用不同包中的static方法,那么就可以使用静态导入的操作完成,语法格式如下:
import static 包路径.类名称.*(*是通配符)
示例如下:
package com.xts.im.test;
//静态导入
import static com.xts.im.util.MyMath.*;
public class Test {
public static void main(String[] args) {
int a = 10;
int b = 2;
//使用的时候就不用再加 类名称. 了
System.out.println("a+b = "+ add(a,b));
System.out.println("a/b = "+div(a,b));
}
}
七、了解可变参数概述及使用
1. 可变参数概述,格式
在JDK1.5之后,如果我们定义一个方法需要接受多个参数,并且多个参数类型一致,我们可以对其简化成
如下格式:
修饰符 返回值类型 方法名(参数类型... 形参名){ }
其实这个书写完全等价于:
修饰符 返回值类型 方法名(参数类型[] 形参名){ }
只是后面这种定义,在调用时必须传递数组,而前者可以直接传递数据即可。 JDK1.5以后。出现了简化操作。... 用在参数上,称之为可变参数。出现了简化操作。... 用在参数上,称之为可变参数。
同样是代表数组,但是在调用这个带有可变参数的方法时,不用创建数组(这就是简单之处),直接将数组中的元素 作为实际参数进行传递,其实编译成的class文件,将这些元素先封装到一个数组中,在进行传递。这些动作都在编译.class文件时,自动完成了。
示例代码:
public class Test {
public static void main(String[] args) {
//数组的写法
int[] arr = {1, 4, 62, 431, 2};
int sum = getSum1(arr);
System.out.println("数组求和:"+sum);
//可变参数,不用再写数组了
int sum2 = getSum2(6, 7, 2, 12, 2121);
System.out.println("可变参数求和:"+sum2);
}
//数组写法,所有元素的求和
public static int getSum1(int[] arr) {
int sum = 0;
for (int a : arr) {
sum += a;
}
return sum;
}
//可变参数写法,所有元素的求和
public static int getSum2(int... arr) {
int sum = 0;
for (int a : arr) {
sum += a;
}
return sum;
}
}
*注意:如果在方法书写时,这个方法拥有多参数,参数中包含可变参数,可变参数一定要写在参数列表的末尾位置。
八、Arrays工具类的使用
Arrays类位于 java.util 包中,主要包含了操纵数组的各种方法。
1. public static void sort(byte[] a) :对数组按照升序排序
示例代码:
public class Test {
public static void main(String[] args) {
int[] nums = {2,5,0,4,6,-10};
//按照升序排序
Arrays.sort(nums);
for(int i :nums)
System.out.print(i+" ");
/* 之前:2 5 0 4 6 -10
* 结果:-10 0 2 4 5 6
*/
}
}
2. public static void fill(byte[] a, byte val) :可以为数组元素填充相同的值
示例代码:
public class Test {
public static void main(String[] args) {
int[] nums = {2,5,0,4,1,-10};
Arrays.fill(nums, 1);
for(int i :nums)
System.out.print(i+" ");
/* 之前:2 5 0 4 1 -10
* 结果:1 1 1 1 1 1
*/
}
}
3. public static String toString(byte[] a):返回数组的字符串形式
示例代码:
public class Test {
public static void main(String[] args) {
int[] nums = {2,5,0,4,1,-10};
System.out.println(Arrays.toString(nums));
/*
* 结果:[2, 5, 0, 4, 1, -10]
*/
}
}
4. public static int binarySearch(byte[] a, byte key):二分法查找给定数据在数组中的索引,要求数组有序
找到的情况下:
如果key在数组中,则返回搜索值的索引。
找不到的情况下:
a.搜索值在数组范围内,从1开始计数,得“ - 插入点位置”;
b.搜索值大于数组内元素,索引值为 – (length + 1);
c.搜索值小于数组内元素,索引值为 – 1。
示例代码:
public class Test {
public static void main(String[] args) {
int a[] = new int[] {1, 3, 4, 6, 8, 9};
//x1 = -4: 5不在数组中,应该插入在4和6中间,位置是从1开始,所以是4,前面加-
int x1 = Arrays.binarySearch(a, 5);
//x2 = 2,找到了,返回索引
int x2 = Arrays.binarySearch(a, 4);
//x3 = -1,没有找到,且比最小的1还小,返回-1
int x3 = Arrays.binarySearch(a, 0);
//x4 = -7, 10不在数组中,比最大的9还大,位置是从1开始,所以是7,前面加-
int x4 = Arrays.binarySearch(a, 10);
//x1 = -2: 2不在数组中,应该插入在1和3中间,位置是从1开始,所以是2,前面加-
int x5 = Arrays.binarySearch(a, 2);
System.out.println("x1="+x1);
System.out.println("x2="+x2);
System.out.println("x3="+x3);
System.out.println("x4="+x4);
System.out.println("x5="+x5);
}
}
5. 特殊方法asList方法使用
//将数组/可变参数转换为List
public static <T> List<T> asList(T... a)
示例代码:
public class Test {
public static void main(String[] args) {
Integer[] a = new Integer[] {1, 3, 4, 6, 8, 9};
List<Integer> list = Arrays.asList(a);
System.out.println(list);
String[] str = new String[] {"a","b","c"};
List<String> strList = Arrays.asList(str);
System.out.println(strList);
}
}
*注意事项
a.该方法适用于对象型数据的数组(String、Integer...)
b.该方法不建议使用于基本数据类型的数组(byte,short,int,long,float,double,boolean)
c.该方法将数组与List列表链接起来:当更新其一个时,另一个自动更新
d.不支持add()、remove()、clear()等方法