Java集合(二)

ops/2024/9/25 9:28:42/

目录

Java集合(二)

Collections集合工具类

泛型

泛型介绍

定义泛型

类型限定符

泛型通配符

泛型通配符与类型限定符结合

斗地主案例

Set接口

Set接口介绍

HashSet类

LinkedHashSet类

哈希值介绍

Java中计算哈希值的方法

HashSet去重的方式


Java集合(二)

Collections集合工具类

不同于CollectionCollections是一个工具类,所以其构造方法为私有方法,成员方法为静态

常用方法:

  1. static <T> boolean addAll(Collection<? super T> c, T... elements):批量向指定集合添加数据,第二个参数为一个可变参数列表,表示可以一次性添加多个元素
  2. static void shuffle(List<?> list):随机打乱单列集合中的元素(每一次运行结果都不一样)
  3. static <T> void sort(List<T> list):使用单列集合中的泛型实现的Comparable接口中的方法对集合中的数据进行排序。如果泛型对应的类没有实现Comparable接口,则编译报错
  4. static <T> void sort(List<T> list, Comparator<? super T> c):使用自定义实现Comparator接口中的方法对指定集合中的数据进行排序

基本使用如下:

java">public class Test {public static void main(String[] args) {ArrayList<String> strings = new ArrayList<>();// 1. static <T> boolean addAll(Collection<? super T> c, T... elements)Collections.addAll(strings,"abc","sac","bsd");for (String string : strings) {System.out.println(string);}System.out.println();// 2. static void shuffle(List<?> list)Collections.shuffle(strings);for (String string : strings) {System.out.println(string);}System.out.println();// 3. static <T> void sort(List<T> list)Collections.sort(strings); // String 实现了 Comparable接口for (String string : strings) {System.out.println(string);}System.out.println();// 4. static <T> void sort(List<T> list, Comparator<? super T> c)ArrayList<Person> people = new ArrayList<>();people.add(new Person(23,"张三"));people.add(new Person(12,"李四"));people.add(new Person(45,"王五"));Collections.sort(people, new Comparator<Person>() {@Overridepublic int compare(Person o1, Person o2) {return o1.getAge() - o2.getAge();}});for (Person person : people) {System.out.println(person);}}
}

泛型

泛型介绍

泛型:不明确具体类型,直到接收到具体类型再进行推导

使用泛型有两个原因:

  1. 统一数据类型,防止使用时出现的数据类型转换异常
  2. 定义带泛型的类,方法等,使用的时候给泛型确定什么类型,泛型就会自动推导为对应类型,使代码更灵活

定义泛型

定义泛型一共有三个位置:

  1. 定义泛型的类:在类名后面添加<T>,其中<>表示泛型,T为泛型名,类似于变量名,对于类泛型来说,当该类实例化出对象时,泛型就会被替代为指定类型,格式如下:
java">public class 类名 <T> {// 成员
}

基本使用如下:

java">public class MyArrayList <E> {//定义一个数组,充当ArrayList底层的数组,长度直接规定为10Object[] obj = new Object[10];//定义size,代表集合元素个数int size;/*** 定义一个add方法,参数类型需要和泛型类型保持一致** 数据类型为E  变量名随便取*/public boolean add(E e) {obj[size] = e;size++;return true;}/*** 定义一个get方法,根据索引获取元素*/public E get(int index) {return (E) obj[index];}@Overridepublic String toString() {return Arrays.toString(obj);}
}// 测试
public class Test {public static void main(String[] args) {MyArrayList<String> list1 = new MyArrayList<>();list1.add("aaa");list1.add("bbb");System.out.println(list1); //直接输出对象名,默认调用toStringSystem.out.println("===========");MyArrayList<Integer> list2 = new MyArrayList<>();list2.add(1);list2.add(2);Integer element = list2.get(0);System.out.println(element);System.out.println(list2);}
}

需要注意,之所以定义元素Object类型的数组是为了便于做强制类型转换,因为泛型不是具体类型,对于get方法来说,当泛型作为一个方法的返回值时,返回值需要进行强制转换,此时因为Object是所有类的父类,所以此时可以强制转换,例如源码迭代器中的next方法返回值

java">public E next() {// ...return (E) elementData[lastRet = i];
}
  1. 定义泛型方法:泛型方法在方法被调用时泛型被推导为具体类型。基本格式如下:
java">权限修饰符 其他修饰符 <T> 方法返回值 方法名(泛型形参列表) {// 方法体
}

基本使用如下:

java">public class ListUtils {//定义一个静态方法addAll,添加多个集合的元素public static <E> void addAll(ArrayList<E> list,E...e){for (E element : e) {list.add(element);}}}// 测试
public class Test01 {public static void main(String[] args) {ArrayList<String> list1 = new ArrayList<>();ListUtils.addAll(list1,"a","b","c");// 也可以写成如下形式// ListUtils.<String>addAll(list1,"a","b","c");System.out.println(list1);System.out.println("================");ArrayList<Integer> list2 = new ArrayList<>();ListUtils.addAll(list2,1,2,3,4,5);System.out.println(list2);}
}
  1. 定义泛型接口:泛型接口在实现类实例化对象时或者实现类本身指定了具体类型,泛型才会被推导为具体类型。基本格式如下,与定义泛型类基本一致:
java">public interface 接口名 <T> {// 成员
}

基本使用如下:

  • 实现类实例化对象时确定类型
java">// 泛型接口
public interface MyList <E> {public boolean add(E e);
}// 接口实现类
public class MyArrayList1<E> implements MyList<E> {//定义一个数组,充当ArrayList底层的数组,长度直接规定为10Object[] obj = new Object[10];//定义size,代表集合元素个数int size;/*** 定义一个add方法,参数类型需要和泛型类型保持一致** 数据类型为E  变量名随便取*/public boolean add(E e) {obj[size] = e;size++;return true;}/*** 定义一个get方法,根据索引获取元素*/public E get(int index) {return (E) obj[index];}@Overridepublic String toString() {return Arrays.toString(obj);}
}// 测试
public class Test02 {public static void main(String[] args) {MyArrayList1<String> list1 = new MyArrayList1<>();list1.add("张三");list1.add("李四");System.out.println(list1.get(0));}
}
  • 实现类已经指定了具体的类型
java">// 接口
public interface MyIterator <E> {E next();
}// 实现类指定了具体类型为String
public class MyScanner implements MyIterator<String> {@Overridepublic String next() {return "实现类指定具体类型时推导泛型";}
}// 测试
public class Test03 {public static void main(String[] args) {MyScanner myScanner = new MyScanner();String result = myScanner.next();System.out.println("result = " + result);}
}

类型限定符

在Java中,可以通过两个关键字限制方法或类时传递给泛型的类型

  1. extends:限制传递给模版的具体类型为extends后的本类或者子类类型,基本语法如下:
java">模版参数类型 extends 具体类型// 例如
// 类
public class Test04 <T extends String>{
}// 方法
public <T extends String> void test(T t) {System.out.println(t);
}
  1. super:限制传递给模版的具体类型为super后的本类或者父类类型
java">模版参数类型 super 具体类型// 使用方式同extends

泛型通配符

如果需要确保传递给泛型的具体类型为任意引用数据类型,可以在<>中使用?占位,表示泛型通配符,例如:

java">public class Test05 {public static void main(String[] args) {ArrayList<String> list1 = new ArrayList<>();list1.add("张三");list1.add("李四");ArrayList<Integer> list2 = new ArrayList<>();list2.add(1);list2.add(2);method(list1);method(list2);}public static void method(ArrayList<?> list){for (Object o : list) {System.out.println(o);}}}

泛型通配符与类型限定符结合

例如下面的代码:

java">public class Test06 {public static void main(String[] args) {ArrayList<Integer> list1 = new ArrayList<>();ArrayList<String> list2 = new ArrayList<>();ArrayList<Number> list3 = new ArrayList<>();ArrayList<Object> list4 = new ArrayList<>();get1(list1);//get1(list2);错误get1(list3);//get1(list4);错误System.out.println("=================");//get2(list1);错误//get2(list2);错误get2(list3);get2(list4);}//上限  ?只能接收extends后面的本类类型以及子类类型public static void get1(Collection<? extends Number> collection){}//下限  ?只能接收super后面的本类类型以及父类类型public static void get2(Collection<? super Number> collection){}
}

斗地主案例

案例介绍:

按照斗地主的规则,完成洗牌发牌的动作。 具体规则:

使用54张牌打乱顺序,三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌

案例分析:

  • 准备牌:

牌可以设计为一个ArrayList<String>,每个字符串为一张牌。 每张牌由花色数字两部分组成,我们可以使用花色集合与数字集合嵌套迭代完成每张牌的组装。 牌由Collections类的shuffle方法进行随机排序。

  • 发牌

将每个人以及底牌设计为ArrayList<String>,将最后3张牌直接存放于底牌,剩余牌通过对3取模依次发牌。

  • 看牌

直接打印每个集合

java">public class Test_Poker {public static void main(String[] args) {// 创建花色ArrayList<String> color = new ArrayList<>();Collections.addAll(color, "黑桃", "红心", "梅花", "方块");// 创建号牌ArrayList<String> number = new ArrayList<>();Collections.addAll(number, "2", "A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3");// 创建牌盒ArrayList<String> pokerBox = new ArrayList<>();// 大小王pokerBox.add("大王");pokerBox.add("小王");// 发牌for (String s : color) {for (String string : number) {pokerBox.add(s + string);}}// 打乱牌盒Collections.shuffle(pokerBox);// 创建玩家ArrayList<String> player1 = new ArrayList<>();ArrayList<String> player2 = new ArrayList<>();ArrayList<String> player3 = new ArrayList<>();ArrayList<String> last = new ArrayList<>();System.out.println(pokerBox.size());// 发牌,留三张底牌for (int i = 0; i < pokerBox.size(); i++) {if (i >= 51) {last.add(pokerBox.get(i));} else if (i % 3 == 0) {player1.add(pokerBox.get(i));} else if (i % 3 == 1) {player2.add(pokerBox.get(i));} else {player3.add(pokerBox.get(i));}}// 查看System.out.println("player1: " + player1);System.out.println("player2: " + player2);System.out.println("player3: " + player3);System.out.println("last: " + last);}
}

也可以使用字符分割方法对创建花色和创建号牌进行优化

java">public class Test_Poker01 {public static void main(String[] args) {// 创建花色String[] color = "黑桃-红心-梅花-方块".split("-");// 创建号牌String[] number = "2-3-4-5-6-7-8-9-10-J-Q-K-A".split("-");// 创建牌盒ArrayList<String> pokerBox = new ArrayList<>();// 添加大小王pokerBox.add("大王");pokerBox.add("小王");// 组合其他牌for (String s : color) {for (String n : number) {pokerBox.add(s+n);}}// 创建玩家并发牌ArrayList<String> player1 = new ArrayList<>();ArrayList<String> player2 = new ArrayList<>();ArrayList<String> player3 = new ArrayList<>();// 底牌ArrayList<String> last = new ArrayList<>();for (int i = 0; i < pokerBox.size(); i++) {if(i >= 51) {last.add(pokerBox.get(i));} else if(i % 3 == 0) {player1.add(pokerBox.get(i));} else if(i % 3 == 1) {player2.add(pokerBox.get(i));} else {player3.add(pokerBox.get(i));}}// 查看System.out.println("player1: " + player1);System.out.println("player2: " + player2);System.out.println("player3: " + player3);System.out.println("last: " + last);}
}

Set接口

Set接口介绍

Set接口实际上并没有对Collection接口进行功能上的扩充,而且所有的Set集合底层都是依靠Map实现,部分内容将在Map中具体介绍

SetMap密切相关的

Map的遍历需要先变成单列集合,只能变成set集合

HashSet

HashSetSet接口的实现类,下面是HashSet的特点:

  1. 元素不允许重复
  2. 元素插入顺序与存储顺序不一定相同
  3. 没有索引的方式操作元素
  4. 线程不安全
  5. 底层数据结构:
    1. JDK8之前:哈希表(数组+链表实现)
    2. JDK8之后:哈希表(数组+链表+红黑树实现)
  6. 方法:与Collection接口中的方法基本一致,但是因为HashSet是实现类,所以HashSet存在具体的方法体
  7. 遍历方式:
    1. 增强for
    2. 迭代器

基本使用实例:

java">public class Test {public static void main(String[] args) {HashSet<String> set = new HashSet<>();set.add("张三");set.add("李四");set.add("王五");set.add("赵六");set.add("田七");set.add("张三");System.out.println(set);//迭代器Iterator<String> iterator = set.iterator();while(iterator.hasNext()){System.out.println(iterator.next());}System.out.println();//增强forfor (String s : set) {System.out.println(s);}}
}
LinkedHashSet

LinkedHashSetSet接口的实现类,下面是LinkedHashSet的特点:

  1. 元素唯一
  2. 元素插入顺序与存储顺序相同
  3. 没有索引的方式操作元素
  4. 线程不安全
  5. 底层数据结构:哈希表+双向链表
  6. 方法:与HashSet基本一致

基本使用如下:

java">public class Test01 {public static void main(String[] args) {LinkedHashSet<String> set = new LinkedHashSet<>();set.add("张三");set.add("李四");set.add("王五");set.add("赵六");set.add("田七");set.add("张三");System.out.println(set);//迭代器Iterator<String> iterator = set.iterator();while(iterator.hasNext()){System.out.println(iterator.next());}System.out.println();//增强forfor (String s : set) {System.out.println(s);}}
}

哈希值介绍

在Java中,哈希值是由计算机算出来的一个十进制数,可以看做是对象的地址值

需要获取对象的哈希值可以通过Object类中的本地方法:public native int hashCode()

例如查看Person类的两个对象的hashCode

java">public class Test {public static void main(String[] args) {Person person1 = new Person();Person person2 = new Person();System.out.println(person1.hashCode());System.out.println(person2.hashCode());}
}输出结果:
1163157884
1956725890

实际上,在Person类中没有重写toString方法时打印的数据后面数字对应的就是对象的hashCode,只是显示的是对应的16进制,例如下面的代码

java">public class Test {public static void main(String[] args) {Person person1 = new Person();Person person2 = new Person();System.out.println(person1);System.out.println(person2);}
}输出结果:
com.epsda.advanced.test_HashCode.Person@4554617c
com.epsda.advanced.test_HashCode.Person@74a14482

如果将前面直接打印的hashCode值以16进制形式打印,则此时会发现对应的数值与默认打印对象名的数据的hashCode一致,例如下面的代码:

java">public class Test {public static void main(String[] args) {Person person1 = new Person();Person person2 = new Person();System.out.println(Integer.toHexString(person1.hashCode()));System.out.println(Integer.toHexString(person2.hashCode()));System.out.println(person1);System.out.println(person2);}
}输出结果:
4554617c
74a14482
com.epsda.advanced.test_HashCode.Person@4554617c
com.epsda.advanced.test_HashCode.Person@74a14482

所以,当对象重写了toString方法,此时就不会打印对象的地址,实际上就是不打印对应的hashCode

如果在类中重写hashcode,此时打印对象的hashCode值就会根据内容进行计算,例如下面的代码:

java">// Person类
public class Person {// ...// 重写hashCode@Overridepublic int hashCode() {return Objects.hash(name, age);}
}public class Test {public static void main(String[] args) {Person person1 = new Person("张三", 23);Person person2 = new Person("张三", 23);System.out.println(person1.hashCode());System.out.println(person2.hashCode());}
}输出结果:
24022543
24022543

在上面的代码中,因为Person类的两个对象内容一致,并且因为Person类重写了hashCode方法,方法中是根据成员内容进行hashCode计算的,所以打印的hashcode是相同的

但是,有些特殊情况,内容不同时,hashCode可能相同,这个现象被称为哈希冲突或哈希碰撞,例如下面的代码:

java">public class Test {public static void main(String[] args) {String s1 = "通话";String s2 = "重地";System.out.println(s1.hashCode());System.out.println(s2.hashCode());}
}输出结果:
1179395
1179395

总结:

当内容相同时,hashCode一定相同;但是内容不同时,hashCode不一定相同

Java中计算哈希值的方法

以下面的代码为例:

java">public class Test01 {public static void main(String[] args) {String s = "abc";System.out.println(s.hashCode());}
}

对应的hashCode源码如下:

java">public int hashCode() {int h = hash;if (h == 0 && value.length > 0) {char val[] = value;for (int i = 0; i < value.length; i++) {h = 31 * h + val[i];}hash = h;}return h;
}

上面的代码中,valueString底层的byte类型数组,使用char类型的val数组接受,根据val数组中的字符依次进行h = h * 31 + val[i]进行计算直到循环结束,此时h即为对应的hash

计算 hashCode时,之所以使用31可以简单理解为31是一个质数,31这个数通过大量的计算和统计,认为用31,可以尽量降低内容不一样但是哈希值一样的情况

HashSet去重的方式

本部分简单介绍,在 Map部分会进行详细介绍
  1. 先计算元素的哈希值(重写hashCode方法),再比较内容(重写equals方法)
  2. 先比较哈希值,如果哈希值不一样,存入集合中
  3. 如果哈希值一样,再比较内容
    1. 如果哈希值一样,内容不一样,直接存入集合
    2. 如果哈希值一样,内容也一样,去重复内容,留一个存入集合

所以前面之所以重写了equals方法的同时还需要重写hashCode就是为了尽可能保证内容比较和去重的可靠性

总结:

对于自定义类型来说,如果不需要打印对象的地址而是打印对象的内容就重写toString方法,而需要比较对象是否相同除了内容比较还需要进行hashCode比较,所以需要重写equalshashCode方法


http://www.ppmy.cn/ops/112666.html

相关文章

CenterPoint-KITTI:环境配置、模型训练、效果展示;KITTI 3D 目标检测数据集下载

目录 前言 Python虚拟环境创建以及使用 KITTI3D目标检测数据集 CenterPoint-KITTI编译遇到问题合集 ImportError: cannot import name VoxelGenerator from spconv.utils 失败案例 最终解决方案 对于可选参数&#xff0c;road plane的处理 E: Unable to locate packag…

DSC+DW自动安装工具

DSCDW自动安装工具 本次进行DSCDW的自动安装工具的使用&#xff0c;这里先安装一份两节点的DSC集群。 1.前期准备 1.1环境准备 数据库安装 两个节点上都得安装好DM数据库&#xff0c;暂时不用初始化实例&#xff0c;版本、安装路径都要一致 操作系统 两个都是使用的CentOS7…

【深海王国】初中生也能画的电路板?目录合集

Hi٩(๑ ^ o ^ ๑)۶, 各位深海王国的同志们&#xff0c;早上下午晚上凌晨好呀~辛勤工作的你今天也辛苦啦 (o゜▽゜)o☆ 今天大都督为大家带来系列文章《初中生也能画的电路板》&#xff0c;帮你一周内快速入门PCB设计&#xff0c;手把手教你从元器件库添加、电路原理图绘制、…

集成学习详细介绍

以下内容整理于&#xff1a; 斯图尔特.罗素, 人工智能.现代方法 第四版(张博雅等译)机器学习_温州大学_中国大学MOOC(慕课)XGBoost原理介绍------个人理解版_xgboost原理介绍 个人理解-CSDN博客 集成学习(ensemble)&#xff1a;选择一个由一系列假设h1, h2, …, hn构成的集合…

使用c#制作一个小型桌面程序

封装dll 首先使用visual stdio 创建Dll新项目,然后属性管理器导入自己的工程属性表&#xff08;如果没有可以参考visual stdio 如何配置opencv等其他环境&#xff09; 创建完成后 系统会自动生成一些文件&#xff0c;其中 pch.cpp 先不要修改&#xff0c;pch.h中先导入自己需…

无人机之处理器篇

无人机的处理器是无人机系统的核心部件之一&#xff0c;它负责控制无人机的飞行、数据处理、任务执行等多个关键功能。以下是对无人机处理器的详细解析&#xff1a; 一、处理器类型 无人机中使用的处理器主要包括以下几种类型&#xff1a; CPU处理器&#xff1a;CPU是无人机的…

数据湖-方案对比

数据湖架构结合了数据湖和数据仓库。虽然它不仅仅是两者之间的简单集成&#xff0c;但其理念是充分发挥两种架构的优势&#xff1a;数据仓库的可靠交易以及数据湖的可扩展性和低成本。 Lakehouse 架构支持管理各种数据类型&#xff0c;例如结构化、半结构化和非结构化数据&…

通信工程学习:什么是EPON以太网无源光网络

EPON&#xff1a;以太网无源光网络 EPON&#xff08;Ethernet Passive Optical Network&#xff0c;以太网无源光网络&#xff09;是一种结合了以太网技术和无源光网络&#xff08;PON&#xff09;优势的高速、大容量宽带接入技术。以下是关于EPON的详细解释&#xff1a; 一、…