【Java⑨】P234~267(Collection集合,Iterator迭代器,泛型,集合实现斗地主发牌,List,Set,可变参数,哈希值,Collections,比较器和自然排序,Map)

目录

cheack

  • 会使用集合存储数据
  • 会遍历集合,把数据取出来
  • 掌握每种集合的特性
  • 能够说出集合与数组的区别
  • Collection集合的常用功能,7个
  • 能够使用迭代器对集合进行取元素
  • 能够说出集合的使用细节
  • 能够使用集合存储自定义类型
  • 能够使用foreach循环遍历集合
  • 能够使用泛型定义集合对象
  • 能够理解泛型上下限
  • 能够阐述泛型通配符的作用
  • 说出List集合特点
  • 能够说出常见的数据结构
  • 能够说出数组结构特点
  • 能够说出栈结构特点
  • 能够说出队列结构特点
  • 能够说出单向链表结构特点
  • 能够说出Set集合的特点
  • 能够说出哈希表的特点
  • 使用HashSet集合存储自定义元素
  • 能够说出可变参数的格式
  • 能够使用集合工具类
  • 能够使用Comparator比较器进行排序
  • 能够说出Map集合特点
  • 使用Map集合添加方法保存数据
  • 使用”键找值”的方式遍历Map集合
  • 使用”键值对”的方式遍历Map集合
  • 能够使用HashMap存储自定义键值对的数据
  • 能够使用HashMap编写斗地主洗牌发牌案例

主要内容

Collection
迭代器
增强for
泛型
数据结构基础概念
List集合
Set集合
Collections

1 Collection 集合

1.1 集合概述

集合存储的都是对象,而且对象的类型可以不一致。Collection的对象直接输出不是地址,说明接口的toString方法有重写

1.2 集合框架

集合类型,接口子类继承关系和简单特性。
TreeSet和HashSet都无序,那个子类有序
在这里插入图片描述

1.3 Collection接口下对象的7个常用功能

public boolean add(E e): 把给定的对象添加到当前集合中。方法的返回值普遍为true,无需接收没意义
public void clear(): 清空集合中所有的元素。清空无返回
public boolean remove(E e):把给定的对象在当前集合中删除。删除元素成功,返回true。没有这个元素删除失败返回false
public boolean contains(E e): 判断当前集合是否包含给定的对象。包含返回true,不包含返回false
public boolean isEmpty(): 判断当前集合是否为空。集合为空返回true,不为空返回false
public int size():返回集合中元素的个数
public Object[] toArray(): 把集合中的元素,存储到数组中。

Collection coll = new ArrayList();
coll = new HashSet();

一般是多态创建,Collection接口创建的对象从ArrayList改为HashSet也是正确的,都是Collection接口下共用的方法

常用方法的返回值:
add的返回值一般都是true,可以不用接收
contains包含返回true,不包含返回false
isEmpty集合为空返回true,不为空范围false
size返回集合个数

2 Iterator迭代器

因为Collection接口中没有带索引的方法,不能像数组用for通过索引访问,所以使用迭代器

2.1 Iterator接口

取之前判断有没有这个元素,有就取出,再判断还有再取。直到取出集合所有元素,叫迭代
java.util.Iterator 迭代器(对集合进行遍历)
两个常用方法 boolean hasNext() 如果仍有元素可以迭代,则返回true。判断集合中还有没有下一个元素,有就true,没有false
E next()返回迭代的下一个元素

迭代器Iterator是抽象类不可以直接new,Collection接口中有一个方法iterator(),返回迭代器的实现类对象。

❗迭代器和使用步骤
1.先获取对象,用Iterator接口接收(多态写法)
2. 使用接口方法hasNext判断有没有下一个
3.使用next取出下一个

❕Iterator接口也是有泛型的,迭代器的泛型跟所接收的集合保持一致。
collArrayList<String>集合
Interator<String> it = coll.iterator();没有元素hasNext() 返回 false,如果没元素硬取,抛出NoSuchElementException没有元素异常

2.2 迭代器的实现原理

获取迭代器的实现类对象后,把索引指向集合的-1索引,判断下个位置0,有下个元素取出来,指针向后移动一位。一次循环,直到下一个没有元素为止。
next方法执行一次做了两件事,1. 取出下一个元素 2. 会把指针向后移动一位

2.3 增强for

也叫for each循环,JDK1.5后的,专门用来遍历数组和集合,内部原理是Iterator迭代器,所以遍历过程中,不能对集合中的元素进行增删操作
Collection extends Iterable: 所有的单列集合都可以使用增强for
public interface Iterable: 所有的单列集合都可以使用增强 for
public interface Iterable:实现这个接口的话,允许对象成为“for each” 语句的目标。

增强for循环: 用来遍历集合和数组
格式:

for (集合/数组的数据类型变量名: 集合名/数组名){
	sout(变量名);
}

❕tips:新for循环必须有被遍历的目标。目标只能是Collection或者是数组。新式for仅仅作为遍历操作出现

3 泛型

3.1 泛型概述

E: 未知的数据类型
E e : Element元素
T t :Type类型
ArrayList 集合 在定义时,不知道集合中会存储什么类型的数据,所以类型使用泛型

创建集合对象的时候,会确定泛型的数据类型。ArrayList<String> list = new ArrayList<String>();把数据类型作为参数传递,把String赋值给泛型E

3.2 使用泛型的好处

创建集合对象,不使用泛型
好处: 默认是Object类型,可以存任意类型数据。坏处:不安全,会引发异常

例如:存入的数据中有String类型,用他的length方法(必须向下转型,因为此时是多态不能用String特有的方法)此时会引发异常,因为并不是所有元素都是String,可以统一向下转型。所以不安全

使用泛型的好处:
1.避免了类型转换的麻烦,存储的是什么类型,取出的就是什么类型
2. 把运行异常(代码运行之后会抛出的异常),提升到了编译期(写代码的时候会报错)
坏处: 泛型是什么类型,只能存储什么类型的数据。存其他类型的报错

3.3 泛型的定义与使用

在这里插入图片描述

泛型可以接收任意的对象,不写泛型默认Object类型。
❓思考,Iterator,ArrayList等的泛型可以不写默认吗?是什么情况
💯Iterator都是根据对应的Collection对象决定的泛型,一般不手动定义。ArrayList主要看左侧引用是否定义泛型(包括多态写法,只定义右侧泛型无意义)。成员方法是编译看左,运行看右
发现: //变量集合,左右侧定义泛型的问题
如果左侧引用没有规定泛型,右侧定义的泛型无效

   ArrayList list = new ArrayList<String>();
    list.add(12);
    list.add("re@##214");
    for (Object o : list) {
        System.out.println(o + " ");
    }

定义和使用含有泛型的类

修饰符 class 类名<代表泛型的变量> {  }
public class Demo04_Generics<XX> {
    /*有泛型的类*/
    private XX num;
    public void setNum(XX num){
        this.num = num;
        System.out.println(num);
    }
    public XX getNum(){//静态方法中使用泛型为什么不能直接用类定义的?
        return num;
    }
    //✔ 静态方法不能访问类上定义的泛型
//    public static XX getNum2(){
//        return num;
//    }
}

静态方法不可以访问类上定义的泛型
如果静态方法操作的应用数据类型不确定,可以将泛型定义在方法上。

含有泛型的方法

❤定义含有泛型的方法:泛型定义在方法的修饰符和返回值类型之间
格式:

修饰符 <泛型> 返回值类型 方法名(参数列表(使用泛型)){
	方法体;
}

使用格式:调用方法时,确定泛型的类型
传递什么泛型参数,就按什么用。静态方法同理,泛型写什么大写几个大写字母都可以。

public class Demo04_Generics<XX> {
	public <T> T method01(T num){
       return num;
    }

    //如果是使用泛型的方法
    public <E> E method02(){
//        E num;所以一般方法要返回泛型,要么返回成员变量,要么返回接收到的参数,
//        无参数,自己无法初始化泛型对象并返回
//        return num;未初始化的引用不可以使用,比如打印,输出之类的
        return null;
    }

    public static <QQ> void method03(QQ num){// ● 静态方法中使用泛型为什么不能直接用类定义的?
        System.out.println("QQ类型" + num.getClass());
    }
}

含有泛型的接口

public interface GenericInterface<I>{
	public abstract void method(I i);
}

一共两种使用方式

❤ 定义一个使用泛型的接口,模拟两种实现接口的方式
第一种:定义接口的实现类,实现接口,指定接口的泛型。
第二种:接口使用什么泛型,实现类就使用什么泛型,类跟着接口走。就相当于定义了一个含有泛型的类
格式:

public class GenericInterfaceImpl1 implements GenericInterface<String>{
	@Override
	public void method(String i){
		System.out.println(i);
	}
}
public class GenericInterfaceImpl2<I> implements GenericInterface<I>{
	@Override
	public void method(I i){
		System.out.println(i);
	}
}

调用时,实现类1定义了泛型为String。所以只能传字符串
实现类2在创建实现类对象时才确定泛型
在这里插入图片描述

3.4 泛型通配符

泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。

泛型中?:代表任意的数据类型(?和E等等大写字母的泛型类型的区别是?
💯 ?做参数对象的泛型,可以接受多种数据,在一个地方类似一次性使用。E只能接受当前不确定但是同一种类型的对象,同类中或同方法中多个E代表的是同一个类型。

泛型中?和Object的区别在于,Object是接收不同的未知对象,?是对象类型已知,对象的泛型未知。相当于Object - Integer - ArrayList- Double……
Collection<?> - Collection - Collection - Collection<ArrayList>)
因为泛型没有继承关系,相当于泛型中的?类似对象的Object,都可以接收其他任何对象类型

此时只能接受数据,不能往集合中存储数据(就是作为参数接收)
例如:

Collection<Integer> list1 = new ArrayList<Integer>();
getElement(list1);
Collection<String> list2 = new ArrayList<String>();
getElement(list2);

public static void getElement(Collection<?> coll){
	//这里参数就不能写Object,因为泛型没有继承概念。如果要接收两种不同元素类型的集合,只能在集合对象泛型处写?
}

不能创建对象时使用泛型通配符,只能作为方法的参数使用
注意:泛型是没有继承概念的,方法内参数 不能写Object 只能写?定义的时候不能用?,参数传递时可以用

通配符基本使用

通配符高级使用—受限泛型

<?extends E > 使用的泛型只能是E类型的子类/本身
<? super E > E 的父类/本身
看代码能看懂就行

4 集合综合案例

4.1 斗地主案例介绍

❤54张牌,三个人摸牌,一人17,留3张做底牌。只实现存牌,洗牌,然后发牌的过程
特殊牌:大小王。其他由大到小2,A,K,……3。花色4种,组合两组形成52张牌

4.2 案例分析

①牌:54张牌存储到一个List集合中
②洗牌:使用工具类Collections的方法static void shuffle(List<?> list)使用指定的随机源对指定列表进行置换。 会随机打乱集合中元素的顺序
③发牌:一人一张轮流发:集合的索引%3,有0,1,2三种可能刚好给3个玩家发牌。索引>=51,给底牌发
定义4个List集合,存储3个玩家的牌和底牌。
④看牌:直接打印集合,遍历存储玩家和底牌的集合

代码实现见练习11

数组名.for 快捷生成for each循环
Collections.suffle(List集合对象),会打乱集合内元素顺序

见练习

5 数据结构简述

5.1 数据结构作用

更快地查询或更改数据

5.2 常见的数据结构及概念


  • 先进后出
    在这里插入图片描述

  • 队列
    先进先出
    在这里插入图片描述

  • 数组
    查询快:地址连续,通过数组首地址找到数组,通过索引快速查找
    增删慢:长度固定,想增删一个元素,必须创建一个新数组,把原数组的数据复制过来
    在这里插入图片描述

  • 链表
    在这里插入图片描述

查询慢:链表中地址不连续,每次查询元素,都必须从头开始查询
增删快: 链表结构,增删一个元素,对链表整体结构没有影响,所以增删快

单向链表:链表中只有一条链子,不能保证元素的顺序(存储元素和取出元素的顺序有可能不一致)
双向链表:链表中有两条链子,有一条链子是专门记录元素的顺序,是一个有序的集合
奇怪,为啥单链表无序,双链表有序。这里无序就是指存储和当时添加的顺序不一致。(应该是直接使用Java本身包装好的链表内部的添加规律是这样的,我下意识想的是在C++里自己构造的那种链表)自己写的链表和增删方法就符合顺序有序
在这里插入图片描述

  • 红黑树
    查询的速度非常快

在这里插入图片描述
在这里插入图片描述

6 List集合

6.1 List接口介绍

List接口的特点:

  1. 有序的集合,存储元素和取出元素的顺序是一致的(存123,取123)
  2. 有索引,包含了一些带索引的方法
  3. 允许存储重复的元素

6.2 List接口中4种常用方法

public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。如果该索引已经有元素,插入进去,原元素及后续元素后移。可以添加到size位置做新的尾部元素,插入索引 <= size ,因为中间索引未赋值没有元素
public E get(int index): 返回集合中指定位置的元素
public E remove(int index): 移除列表中指定位置的元素,返回的是被移除的元素。注意区分:Collection中
public E set(int index, E element): 用指定元素替换集合中指定位置的元素,返回值的更新前的元素。索引只能设置已经存在的元素,不可以新添加,索引必须 <= size - 1

注意:操作索引的时候,一定要防止索引越界异常
常见三种索引越界异常

  1. List的是索引越界异常 对象.get(index);
  2. 数组
  3. 字符串中有访问索引的方法new String().charAt(index);

7 List的子类

List接口大小可变数组的实现,此实现不是同步的(就是多线程)

7.1 ArrayList集合

增删效率低,因为底层是复制数组。多用于查找遍历
Vector<E> 是同步的,单线程速度慢,1.2后被ArrayList取代

7.2 LinkedList集合

在这里插入图片描述

是双向链表结构pop==removeFirst push==addFirst

  1. 底层是一个链表结构:查询慢,增删快
  2. 里边包涵力大量操作首尾元素的方法
    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。

其中remove()如果无参调用,也是删除首位
注意:使用LinkedList集合特有的方法,不能使用多态

8 Set接口

一个不包含重复元素的Collection,没有带索引的方法

hashCode 哈希码 简述

方法 public native int hashCode();
native: 代表该方法调用的是本地操作系统的方法
直接输出对象

review.java9.Demo31_Person@17bad
97197

在这里插入图片描述

例如:自定义类Person,输出方法hashCode,不同对象是不同的哈希码值
String对象也有自己的哈希码值,只要字符串内容相同,哈希码就相同
其中“重地”和“通话”比较特别,哈希码值是一样的

复习: 字符串对象相同的情况
在常量池的字符串内容相同的情况,地址也相同,是同一个对象。不在常量池的字符串内容相同,地址也不同,不是同一个对象,== 结果为false
① 直接用引号"“创建的字符串,而不是构造器创建的字符串在常量池
② 字符串常量(即"xxx” 直接创建引号内的匿名字符串)用 “+” 连接的结果在常量池
其他各种方法:构造器构造的字符串,字符串引用参与“+”得到的字符串,concat,substring,replace,split等方法得到的字符串都不在常量池

特殊的哈希码值:

  • 同一个对象哈希码是相同的
    特殊情况:包装类中的Integer,Byte,Short,Character这4种包装类只要值相同,就是同一个对象
    特点:都可以跟Ascii码值互换
    并且其中Integer的哈希码跟自己的数值相同
  • String只要内容相同,不是同一个对象哈希码也相同,并且内容相同在Set下对象中算作重复。
    因为HashSet中按equals比较,所以字符串内容只要相同哪怕不是同一个对象在Set中也算重复元素,不可重复存储
 		Integer int1 = new Integer("123");
        Double dou1 = 23.53;
        System.out.println(int1);//123,直接输出值而不是对象地址,说明类重写了toString方法
        System.out.println(int1.hashCode());//123
        Integer int2 = 144;
        System.out.println(int2.hashCode());//144 ● Integer的哈希码居然跟自己的值一样
        System.out.println(dou1);//23.50
        System.out.println(dou1.hashCode());//1414358758
 		// ● 两个new String对象参数相同,返回的hashCode也相同
        String str1 = new String("abc");
        char[] ch1 = new char[]{97, 98, 99};
        String str2 = new String(ch1);
        String str3 = "a".concat("bc");
        String str4 = "abd".replace("d", "c");

        System.out.println("str1哈希码:" + str1.hashCode());//96354
        System.out.println("str2哈希码:" + str2.hashCode());//96354
        System.out.println("str3哈希码:" + str3.hashCode());//96354

        System.out.println("str1 == str2:" + (str1 == str2) + "\n" +
                "str2 == str3:" + (str2 == str3)); //false, false
        System.out.println("str1.eqals(str2):" + str1.equals(str2) + "\n"
        + "str2.equals(str3):" + str2.equals(str3));//true true 
        /*说明对于String,只要内容字符串相同,即使不是同一个对象(实际地址相同)。哈希码也是相同的 */

8.1 HashSet集合介绍

哈希表结构,没有顺序,底层是一个哈希表结构(查询的速度非常的快)

在这里插入图片描述

8.2 HashSet集合存储数据的结构(哈希表)

哈希值: 是一个十进制的整数,由系统随机给出(就是对象的地址值,是一个逻辑地址,是模拟出来得到地址,不是数据实际存储的物理地址)
Object类方法public native int hashCode()返回该对象的哈希码值
native:代表该方法调用的是本地操作系统的方法
在这里插入图片描述

8.3 HashSet不可存储重复元素+如何存储自定义类型元素

1. HashSet存数原理以及不可以存储重复元素的原理
① Set集合在调用add方法的时候,add方法会调用元素的hashCode方法和equals方法,判断元素是否重复。
② add方法会调用要存储元素的hashCode方法,计算对象的哈希值,没在集合中找有没有这个哈希值的元素,如果没有就存储该元素。
③ 如果有(类似“重地”和“通话”的情况)再用equals判断哈希冲突的这两个元素是否相同,如果不同就存储该元素。如果相同,说明元素重复,不存储。

2 . HashSet存储自定义元素

  • 给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一
  • 自定义的对象类型,equals都是比较对象的地址,hashcode也不同。
    重写的hashCode和equals方法,都是根据自定义类的属性是否相同作为判断标准的

在这里插入图片描述
在这里插入图片描述
Alt + Insert 后选Override
快捷重写生成hashCode() equals()
在这里插入图片描述

LinkedHashSet

特点:底层是一个哈希表(数组+链表/红黑树)+链表:多了一条链表(记录元素的存储顺序),保证元素有序
同样是不可重复,但有序。(类似List接口的实现类对象,按照添加删除的元素排列。其余Set接口的实现类都是随机排列)

记Set接口下特点:1.元素不可重复 2.无索引

8.4 可变参数

底层是数组,个数不确定,数据类型确定。

  • 使用前提:
    当方法的参数列表数据类型已经确定,但是参数的个数不确定,就可以使用可变参数。
  • 使用格式:定义方法时使用
修饰符 返回值类型 方法名(数据类型…… 变量名){}
  • 可变参数的原理:
    可变参数底层就是一个数组,根据传递参数个数不同,会创建不同长度的数组,来存储这些参数
    传递的参数个数,可以是0个(不传递),1,2,……多个

public static void method(int…a,String… b)
public static void method(String b, double c, int d,int…a)
可变参数终级写法:public static void method(Object…obj) 就可以传递各种不同类型(相当于多态)的对象,并且个数也不限制

  • 可变参数底层就是数组,以下两种写法都可以接收数组。只是可变参数既可以接收数组,也可以接收数据(例如:1,2,3这种数列)。注意,下图两方法不可同时存在,不属于重载,是重复的冲突方法
    在这里插入图片描述
    注意: 如果方法有多个参数,参数中包含可变参数,可变参数一定要在参数列表的末尾,且多参数中只允许存在一个可变参数

9 Collections

9.1常用功能

java.utils.Collections 是集合工具类,用来对集合进行操作。部分方法如下:

  • public static <T> boolean addAll(Collection<T> c, T... elements) :往集合中添加一些元素。数组或数列
	List<String> list = new ArrayList<String>();
	Collections.addAll(list, "a", "b", "c");
	String [] array = new String[] {"a", "b", "c"};
	List<String> list = new ArrayList<String>();
	Collections.addAll(list, array);
  • public static void shuffle(List<?> list) 打乱顺序 :打乱集合顺序。
  • public static <T> void sort(List<T> list) :将集合中元素按照默认规则排序。
    这个方法完成的排序,实际上要求了被排序的类型,使用前提,被排序的集合里边存储的元素,必须实现Comparable,重写接口中的方法compareTo定义排序的规则(例如,所有包装类和String类的对象)
    在String类型上如下:

public final class String implements java.io.Serializable, Comparable, CharSequence {

因为String已经确定了比较的规则,就是从头开始比较两个字符串的每一个字符,直到结束或字符不同,例如 “abc” 规定小于 “abcc”,“aba” 小于 "abb"并且升序排序,如果想要按字符串第一个字符降序排序,但不可能改变源码,所以用以下方法

  • public static <T> void sort(List<T> list,Comparator<? super T> ) 将集合中元素按照指定规则排序。功能使用见下

9.2 Comparator比较器

中的方法 public int compare(String o1, String o2) :比较其两个参数的顺序。
其中o1是内部已经排序完成的元素,o2指的是准备添加的元素

两个对象比较的结果有三种:大于,等于,小于。
如果要按照升序排序, 则o1 < o2时 返回(负数),相等返回0,o1大于o2返回(正数)
如果要按照降序排序 则o1 < o2时 返回(正数),相等返回0,01大于02返回(负数)

总结:记方法规则认定,返回正数时,o2(o)新添加的元素在前o1(this)已排序的元素在后。返回负数时则相反,根据升序降序需求选择如何设置返回值

复习:只有需要添加到Set集合时,才重写equals和hashCode方法,唯一作用就是防止元素重复

Collections.sort(list01, new Comparetor<Integer>(){
     @Override
      public int compare(Integer o1, Integer o2){
         return o1 - o2;//升序
		//return o2 - o1;降序
     }
});//匿名内部类重写

更好记的Comparator的排序规则:
已有的o1(this)- 新o2(o):升序
o2(o) - o1(this):降序

Comparable接口

重写compareTo方法,记 return this(自己)-参数 就是升序

Comparable接口的排序规则:自己(this)- o 参数: 升序 this - o。分析:因为方法内部compareTo(o) return 一个值,内部规定当this > o 时 return 正数 或1,反过来一样,当返回1或正数时,说明this > o

9.3 简述Comparable和Comparator两个接口的区别

【集合-定制排序】详解总结自然排序compareTo+比较器排序compare+两种定制排序的区别,用法+TreeSet,TreeMap存储定制排序的类对象
Comparable:只有两种使用方式
Comparator:相当于找一个第三方的裁判,比较两个

  • Comparable:强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo方法被称为它的自然比较方法。只能在类中实现compareTo()重写一次,不能经常修改类的代码实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。
  • Comparator强行对某个对象进行整体排序。可以将Comparator 传递给sort方法(如Collections.sort或Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。

10 Map

key不可重复,value可重复
Collection 中的集合,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储。
Map 中的集合,元素是成对存在的。每个元素由键与值两部分组成,通过键可以找对所对应的值。
Collection 中的集合称为单列集合, Map 中的集合称为双列集合。
需要注意的是, Map 中的集合不能包含重复的键,值可以重复;每个键只能对应一个值。

Map子类

  • HashMap:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
  • LinkedHashMap:HashMap下有个子类LinkedHashMap,存储数据采用的哈希表结构+链表结构。通过链表结构可以保证元素的存取顺序一致;通过哈希表结构可以保证的键的唯一、不重复,需要重写键的 hashCode()方法、equals()方法。

tips:Map接口中的集合都有两个泛型变量,在使用时,要为两个泛型变量赋予数据类型。两个泛型变量的数据类型可以相同,也可以不同。

Map 常用方法

public V put(K key, V value) : 把指定的键与指定的值添加到Map集合中。

tips:
使用put方法时,若指定的键(key)在集合中没有,则没有这个键对应的值,返回null,并把指定的键值添加到集合中;
若指定的键(key)在集合中存在,则返回值为集合中键对应的值(该值为替换前的值),并把指定键所对应的值,替换成指定的新值。

public V remove(Object key) : 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的
值。
public V get(Object key) :根据指定的键,在Map集合中获取对应的值。
public Set<K> keySet() : 获取Map集合中所有的键,存储到Set集合中。
public Set<Map.Entry<K,V>> entrySet() : 获取到Map集合中所有的键值对对象的集合(Set集合)。

Entry键值对对象

Map中存放的是两种对象,一种key(键),一种是value(值),它们在Map中是一对对应关系,这一对称做Map中一个Entry(项)。
Entry将键值对的对应关系封装成了对象,就是键值对对象。在遍历Map集合时,就可以从每一个键值对(Entry)对象中获取对应的键与对应的值。

  • Entry方法
    public K getKey(); 获取Entry对象中的键
    public V getValue(); 获取Entry对象中的值

● 练习

Collection是所有单列集合(List和Set)的父接口

1. Collection增删查改,使用常用的7种方法,并分析返回值含义

A:-----------------------------------------------------------------------------------------------------------

private static void demo01() {
        Collection coll = new ArrayList();
        coll.add(12);
        coll.add(true);
        System.out.println("coll.add(12.123) " + coll.add(12.123));//①add返回值一般为true,指添加成功


        System.out.println("coll " + coll);//输出集合内容而非地址,说明这个类重写了toString方法
        System.out.println("coll.remove(12) " + coll.remove(12));//② 删除成功返回true
        System.out.println("coll.remove(111) " + coll.remove(111));//如果没有的还要删除相当于无法删除,返回false
        System.out.println("size " + coll.size());//③ 返回集合长度,即元素个数
        System.out.println("是否存在true " + coll.contains(true));//④存在返回true
        System.out.println("是否空 " + coll.isEmpty());//⑤集合为空,即size为0返回true

        Object[] objArr = coll.toArray();//⑥返回值是一个Object数组,把集合转成数组
        System.out.println(objArr);
        printArray(objArr);

        coll.clear();//⑦无返回值,就清空
        System.out.println("coll" + coll);

    }

2. 练习用while循环,for循环(可以只写前两项)和foreach 执行迭代器,取出集合元素并输出。

A:-----------------------------------------------------------------------------------------------------------
要遍历的数组or集合名.fori -> 快捷生成for循环
.for -> 快捷生成foreach循环

 private static void demo02() {
        Collection<Integer> coll1 = new ArrayList<>();
        coll1.add(1);
        coll1.add(2);
        coll1.add(3);
        coll1.add(3);
        Iterator it1 = coll1.iterator();
        while(it1.hasNext()){
            System.out.print(it1.next() + ",");
        }
        System.out.println();

        Collection<Double> coll2 = new HashSet<>();
        coll2.add(12.23);
        coll2.add(34.234);
        for(Iterator it2 = coll2.iterator(); it2.hasNext() == true;){
            System.out.print(it2.next() + ",");
        }
        System.out.println();
    }
    for(Double d: coll2){
    }

注意: foreach是迭代器Iterator的简化版,所以只能取到元素打印,不可以更改集合或数组内容

3. 分析迭代器实现原理

A:-----------------------------------------------------------------------------------------------------------

在这里插入图片描述

4. ①分析一个泛型类中有泛型做参数和返回值,在调用类及方法时泛型变化的过程。②举例说明泛型类和泛型方法的注意事项和常用写法

A:-----------------------------------------------------------------------------------------------------------

①创建对象的时候确定泛型的数据类型,创建时候确定的是啥类型就才传入代替泛型

在这里插入图片描述
② 常用

  • 定义类的泛型或方法的泛型
  • 类的泛型 类中非静态成员和非静态方法都可以直接使用,例如方法中的返回值和参数使用类泛型,或方法内可以使用类泛型定义的变量,也可以直接调用泛型类的成员变量。
  • 注意:静态方法不可以使用类定义的泛型(可以理解为,因为类的泛型在new类对象时才确定,直接使用类方法没有new对象,类泛型无法确定就无法使用)
    静态变量也不能使用类定义的泛型
public class Demo04_Generics<XX> {
    /*有泛型的类*/
    private XX num;
    public void setNum(XX num){
        this.num = num;
        System.out.println(num);
    }
    public XX getNum(){//静态方法中使用泛型为什么不能直接用类定义的?
        return num;
    }
    //✔ 静态方法不能访问类上定义的泛型
//    public static XX getNum2(){
//        return num;
//    }


    /*泛型方法*/
    public <T> void method01(T num){
        System.out.println(num);
    }

    //如果是使用泛型的方法
    public <E> E method02(){
//        E num;所以一般方法要返回泛型,要么返回成员变量,要么返回接收到的参数,
//        无参数,自己无法初始化泛型对象并返回
//        return num;未初始化的引用不可以使用,比如打印,输出之类的
        return null;
    }

    public static <QQ> void method03(QQ num){// ● 静态方法中使用泛型为什么不能直接用类定义的?
        System.out.println("QQ类型" + num);
    }
}
public static void main(String[] args){
 	private static <T> void demo04(Collection<T> list) {
        /*Demo04_Generics 类中说明*/
        Demo04_Generics<Character> obj1 = new Demo04_Generics<>();
        obj1.setNum('!');
        System.out.println(obj1.getNum());
        // ●方法的泛型可以在方法调用名前<>定义或者直接传参定义●
        // 但一般无参的泛型方法,定义了也没啥意义,用不上
        obj1.<Integer>method01(123);
        obj1.method01("!@$#");
        obj1.<Double>method02();//可以定义该方法的泛型,但不好使用
        //✘ Demo04_Generics<Integer>.<Boolean>method03(true);
        // ●调用静态方法,不可定义类的泛型● why? 因为没类对象,定义了泛型也没处用
        //直接定义静态方法的泛型
        Demo04_Generics.<Boolean>method03(true);


        //在定义泛型类时确认泛型类型,所以先统一打印
        System.out.print("♥");
        int size = list.size();
        for (T t : list) {
            System.out.print(t);
            if(size != 1) {
                System.out.print("-");
            }
            size--;
        }
        System.out.println("♥");
    }
}

5. 如果集合和迭代器都没有泛型,缺点是?

A:-----------------------------------------------------------------------------------------------------------
比如集合保存了String和Integer类型的元素,在打印的时候想打印String的长度对象.length()。
但是!因为没有泛型,所以元素默认都是Object类型的,但是父类不可以使用子类的方法 复习:多态写法中,使用属性是编译看左,运行也看左。方法是编译看左,运行看右)
所以如果Object obj = new String(str);使用String对象的length()方法的话,是编译错误,因为Object中没有length方法,需要向下转型才能正确使用。 所以开始使用泛型会更方便

使用泛型的缺点:

  1. 只能存储一种类型的元素。

优点:

  1. 把可能出错的多态写法中父类不能使用子类的成员错误提到了编译期,而不是运行时才发现错误
  2. 避免了类型转换的麻烦

6. 定义一个含有泛型的类,模拟ArrayList集合。其中成员变量,方法参数和返回值都用到泛型。

A:-----------------------------------------------------------------------------------------------------------

public class Demo06_Generics<MVP> {
    private MVP num;
    public MVP getNum(){
        return this.num;
    }
    public void setNum(MVP num){
        this.num = num;
    }
}
 private static void demo06() {
        Demo06_Generics<Character> class01 = new Demo06_Generics<>();
        class01.setNum('%');//注意:这里也是自动装箱,即包装类引用接收对应基本数据类型自动装
        System.out.println(class01.getNum());

    }

7. ★ ①定义一个使用泛型的接口,模拟两种实现接口的方式,并调用这两种实现类对象的方法② 复习 接口中可写内容及作用,接口中使用泛型的注意事项,实现类重写接口方法的规则

A:-----------------------------------------------------------------------------------------------------------

① 一种是实现类实现接口时,不确定泛型;第二种是实现类直接确定接口的泛型。

  • 注意接口中的常量必须直接赋值
    因为接口中常量都是final静态的,接口中没有变量只有常量,final常量只有两种赋值方式,1.构造器赋值,2.创建时赋值。
    很明显接口没有构造器,所以必须创建时直接赋值
  • ♥并且接口中常量不可使用接口的泛型♥
    why? 分析接口如没有实现类,因为接口中常量是静态的所以没写实现类也存在,
    但没有实现类,接口泛型无法确定,常量存在但类型不确定就报错。
    简而言之就是接口中一切静态的都不可以使用类泛型 原理:泛型只有在创建对象时才能确定泛型类型,静态的不需要对象也存在,如果使用

方法的泛型可以在接口中随意使用,因为无论是否静态方法调用时必确定方法的泛型

  • 接口可以写五种成员,复习写并且分析作用
public interface Demo07_Generics<QQ> {
    /*接口中可以写5种成员
    * 1. 常量
    * 2. 抽象方法
    * 3. 默认方法
    * 4. 静态方法
    * 5. 私有方法*/
   
    //错QQ num = null;必须直接赋值
   //QQ num;
   int num = 12;
   Object setNum(QQ num);
   QQ getNum();
   //含有泛型的方法不限制方法类型
   <E> E abMethod03();
   //以上三个方法均为抽象方法,实现类必须重写

//接口中其他内容的作用-----------------------------------------------------------------------------------------------------------
   //默认方法,让实现类直接继承,不强制重写的
   default <T> void deMethod01(T num){
      
       System.out.println("default <T> void deMethod01(T num)方法输出num" + num);
   }
   default void deMethod02(){
      System.out.println("接口定义的默认方法2");
   }
   /*同样注意:♥接口中的静态方法也不可以使用接口定义的泛型QQ♥
   * 类似普通类的静态方法也不可以使用类定义的泛型,因为静态方法不用对象,没有new对象泛型没确定。
   * 接口则是没实现类,就没法确定接口的泛型*/
   static <T> void staMethod01(T num){
       //静态方法,只能接口名调用,实现类不可以调用
       //定义在方法上的泛型可以用在静态方法上
   }
   private static void staMethod(){
       //解决静态方法之间重复代码的问题
   }
   private void method02(QQ num){
       //解决默认方法之间重复代码的问题
   }
}
  • 实现类直接确定接口的泛型为Double,类内部使用到类的泛型的部分都替换为Double
public class Demo07_GenericsImpl1 implements Demo07_Generics<Double>{

    Double num = 11.11;
    @Override
    public Double setNum(Double num){//超类
        this.num = num;
        return num + 1;//自动拆箱+自动装箱,Double进入运算转double,
        // 加完按范围最大的为结果类型double,再自动装箱到Double符合返回值类型
    }

    @Override
    public Double getNum() {
        return this.num;
    }

    @Override
    public<EM> EM abMethod03(){
        //没有传入泛型参数,只能返回null,因为不确定泛型具体什么类型
        System.out.println("第一种实现类(确定接口泛型为Double的)重写抽象泛型方法");
        /* ● 重写接口的抽象泛型方法时,可以改变泛型符号,但没确定类型还是不好写返回什么具体数据or对象*/
        return null;
    }
//接口中其他方法的继承重写-----------------------------------------------------------------------------------------------------------
    // ● 注意:默认方法只能存在在接口中,实现类不可重写
    //因为default 是专门接口中默认方法用的,实现类方法没有这个修饰符

//    ✘ default <Double> void deMethod01(Double num){
//
//    }
    @Override
    public <Double> void deMethod01(Double num){
        //接口中默认方法默认是公有类型的,
        //因为[public] default 返回值 方法名(参数){}
        //复习继承规则:子继承方法时修饰符只能 >= ,返回值 <= 这里的大小指继承关系,子类更小
    }
    //@Override
     /static <GG> void staMethod01(GG num){
       // ● 复习:静态方法不可以被重写
       
   }
}
  • 实现类继承接口的泛型,并不确定泛型类型。保持类泛型名和继承的接口的泛型名相同,可以改名把QQ换成EE,也是统一改,类中用到类定义的泛型的部分全改名保持一致即可。
/*同样的,泛型重写时把QQ改成EE根本没影响*/
public class Demo07_GenericsImpl2<EE> implements Demo07_Generics<EE>{
    EE num = null;
    @Override
    public Object setNum(EE num) {
        this.num = num;
        return "实现类二num:" + num;
    }

    @Override
    public EE getNum() {
        return this.num;
    }

    @Override
    public <MM>  MM abMethod03() {
        //这里永远都只能返回null,因为方法重写时是不可以确定泛型的,只能确定引用类型统一可以用null代表
        // 只有调用时才可以确定泛型,但是调用时又不能重写方法
        System.out.println("第二种实现类重写抽象泛型方法");
        return null;
    }
}
  • ☃奇思:用匿名内部类实现接口
    当使用匿名实现类实现接口时,因为直接new了对象
    ① 所以接口泛型会直接确定,不写泛型就默认接口的泛型是Object类型
    ② 内部实现时也不可以用Object的子类代替,只能使用Object类型。
    (内部类实现接口抽象方法时,内部使用到的接口泛型和匿名实现类定义的接口泛型的类型保持一致即可。方法的泛型未使用所以保持符号统一,类型不确定)
 private static void demo07() {
        /*正常方法内使用包装类无异,但泛型接口实现类的泛型抽象方法中使用异常?
        * ↓ 错在不可以用包装类名定义方法中泛型的名字,与包装类名重复按泛型名算,不算包装类名
        * 即不论如何继承重写方法,可以替换泛型名称,但只有调用定义使用时,才能确定泛型
        * 继承重写过程无法确定泛型名称,只能改名*/
//        Character ch = Character.valueOf('&');
//        Double d = Double.valueOf(23.12);
//        //return Character.valueOf('一');
//        Integer num = Integer.valueOf(12);
//        Character ch3 = '&';
//        Boolean b1 = true;
//        Float f1 = 12.23F;

        //分别是已经确定泛型的实现类,继承泛型的实现类和使用内部类实现接口的匿名实现类
        //注意:匿名内部类匿名的是接口实现类的名字,不是对象的名字。对象名匿名叫匿名对象
        Demo07_Generics obj1 = new Demo07_GenericsImpl1();
        Demo07_Generics<Integer> obj2 = new Demo07_GenericsImpl2<>();
        
         /*  ● ↓ 特殊写法,写匿名实现类实现接口。当场重写接口的所有抽象方法,
         ①类泛型没定义默认Object类型,内部重写时保持所有类泛型的部分是Object即可
         ②因为重写的同时直接创建了实现类对象,所以类定义的泛型已经被确定,内部使用到类定义泛型与
         对象定义的泛型类型Character同步即可,泛型方法的泛型未确定调用时再确定*/
        Demo07_Generics obj4 = new Demo07_Generics() {
            @Override
            public Object setNum(Object num) {
                return null;
            }

            @Override
            public Object getNum() {
                return null;
            }

            @Override
            public Object abMethod03() {
                return null;
            }
        };
        Demo07_Generics<Character> obj3 = new Demo07_Generics<>() {
            Character num = '三';
            @Override
            //把父接口的泛型从Object改为了Double
            public Double setNum(Character num) {//复习:重写方法时,修饰符必须>=,返回值必须一致或是子类
                this.num = num;
                return 56.2;
            }

            @Override
            public Character getNum(){
                return this.num;
            }

            //此处重写泛型方法
            @Override
            public <W> W abMethod03() {
                System.out.println("匿名实现类讲抽象方法<E> E abMethof03();重写为" +
                        "public <W> W abMethod03(),泛型未确定");
                return null;
            }
            @Override
            public <T> void deMethod01(T num){
                System.out.println("匿名实现类将默认方法重写,public <T> void deMethod01(T num)" +
                        "泛型未确定");
            }
            // ● 内部类不可以有静态成员:原因内部类本来就是只重写一次接口的实现类,这个类名没有,是匿名内部类。类方法即使存在也无法调用,因为没有类名
            //public static <QQ> void method03(QQ num);

        };  
        obj1.abMethod03();
        obj2.abMethod03();
        obj3.abMethod03();
        obj4.abMethod03();
    }
    

复习:内部类不可以有静态成员,原因内部类本来就是只重写一次接口的实现类,这个类名没有,是匿名内部类。类方法即使存在也无法调用,因为没有类名


- 复习接口中5种成员
[]内指接口中不写也会自动默认有的修饰符
{}内指可有可无的

  1. 常量[public] [static] [final] 数据类型 常量名称 = 数据值
  2. 抽象方法 [public] [abstract] 返回值类型 方法名称(参数列表);
  3. 默认方法 [public] default 返回值类型方法名称(参数列表){
    //方法体,用在接口升级的情况,如接口新添加了方法
    //如果是抽象方法,那该接口所有的实现类都要重写一下
    //默认方法就不用实现类必须重写,直接继承即可
    }
  4. 静态方法 [public] static 返回值类型 方法名称(参数列表){
    //方法体,只能用接口名调用,实现类不可以调用
    }
  5. 私有方法 private {static} 返回值类型 方法名称(参数){
    //方法体,解决接口中两个默认方法之间重复代码的问题
    }
  • 泛型接口中使用泛型注意事项
    接口中常量不可使用接口的泛型,QQ num;QQ num = null;都不行,因为都是静态的
    接口中的静态方法也不可以使用接口定义的泛型
    接口中定义含有泛型的方法不限制方法类型(注意泛型名不可与关键字重复)
    在这里插入图片描述
    在这里插入图片描述
  • 实现类(或子类)实现(或重写)接口(超类)的方法时,修饰符范围只能更大(由大到小:public > protected 多一个子类> (default)多一个同包> private 类内部 )
  • 实现类(子类)返回值类型 <= 接口

★ 重大错误混淆:泛型方法中使用包装类的valueOf方法失败?★

在这里插入图片描述
在这里插入图片描述
原因:类继承重写多少次方法都只是是改变泛型名称,只是起名,可以再次重写时统一改符号,但无法确定泛型类型。
在方法调用时才确定泛型类型,此处起名相当于和包装类名重复,但定义在泛型里Integer,Character和MVP,QQ等任意无意义字母代表的含义相同,都只是指代一个同类引用名,具体什么类型未能确定。与包装类同名,在这个泛型方法里相当于覆盖了重名的包装类名,导致原本的包装类名无法使用

//泛型方法继承时候,改不改泛型名无所谓,但一定要写,不然泛型返回值或者泛型参数没法写
public interface Demo07_Generics<QQ> {
	<E> E abMethod02();
}
public class Demo07_GenericsImpl implements Demo07_Generics<Double>{
	@Override
    public<EM> EM abMethod02(){
        //没有传入泛型参数,只能返回null,因为不确定泛型具体什么类型
        return null;
    }
}

8. 使用泛型通配符“ ?”写一个方法,参数是ArrayList集合,不确定集合元素的泛型。并用迭代器打印集合内容

A:-----------------------------------------------------------------------------------------------------------

		Collection<ArrayList> coll2 = new ArrayList<>();
        ArrayList<Integer> list1 = new ArrayList<>();
        list1.add(1);
        list1.add(2);
        list1.add(3);
        ArrayList<String> list2 = new ArrayList<>();
        list2.add("!!!");
        list2.add("@@@");
        list2.add("###");
        coll2.add(list1);
        coll2.add(list2);
        coll2.add(list1);

        Collection<String> coll3 = new HashSet<>();
        coll3.add("$$");
        coll3.add("%%");
        Collection<Integer> coll4 = new LinkedHashSet();
        coll4.add(10);
        coll4.add(20);

        getElement(coll2);
        getElement(coll3);
        getElement(coll4);


    }
    private static void getElement(Collection<?> coll){
        for (Object o : coll) {
            System.out.print(o + " ");
        }
        System.out.println();
    }

9. 复习分析 泛型通配符 ?,泛型符号 E和类Object理解使用上的区别

A:-----------------------------------------------------------------------------------------------------------

? 和E等泛型符号的区别
例如E在一个方法或者一个类中定义为泛型,以形式定义在类名后,或者方法返回值前
就可以作为返回值类型,参数类型或者定义变量使用,如E num;但一般不做定义类型,因为无法确定类型就无法赋值
//除非参数也是E 类型,用来接收参数可以定义E num

    //?只能用在<?>内,不能像E单独使用,一般是作为集合泛型不确定的情况使用,如ArrayList<?>参数可以接收
    //ArrayList<Integer>,ArrayList<Double>……引用类型

    //通配符 ?其实类似Object,可以接收Integer,Double…… 只是泛型没有继承的概念所以有?通配符

10. 分析泛型通配符报错行

A:-----------------------------------------------------------------------------------------------------------
Number是数据类型包装类的父类,即整数4种和浮点数的2种

 	public static void getElement1(Collection<? extends Number> coll){
 	}
    public static void getElement2(Collection<? super Integer> coll) {
    }
    private static void demo10() {
            Collection<Integer> list1 = new ArrayList<Integer>();
            Collection<String> list2 = new ArrayList<String>();
            Collection<Number> list3 = new ArrayList<Number>();
            Collection<Object> list4 = new ArrayList<Object>();

            getElement1(list1);
            getElement1(list2);//12 行
            getElement1(list3);
            getElement1(list4);//14行

            getElement2(list1);
            getElement2(list2);//17 行
            getElement2(list3);
            getElement2(list4);
    }

12,14行错,因为String和Object不是Number的子类
17行错,String不是Integer的子类

<? extends Number> 只能接收Number及其子类
<? super Integer> 只能接收Integer及其父类

11. 斗地主1.0 发牌模拟实现, 54张牌,创建牌并洗牌发给3个玩家,完成发牌

A:-----------------------------------------------------------------------------------------------------------
思路分析
54张牌,13*4=52+大王小王共54张牌
1.创建牌:先用两个数组分别创建花色和牌号,再创建List接收组好的54张牌
2.洗牌:然后洗牌,用Collections.flush(集合)打乱集合中元素的顺序
3.发牌:创建三个玩家和底牌都是ArrayList用按顺序取出牌,可以用remove的返回值取?或者get(i)
4.看牌:打印每个玩家和底牌的集合元素

 	private static void lookCard(List<Integer> list, Map<Integer, String> map){
        int size = list.size();
        int count = 0;
        for (Integer integer : list) {
            System.out.print(map.get(integer));
            if(count++ < size - 1){
                System.out.print(",");
            }else{
                System.out.println();
            }
        }
    }
	private static void demo11(){       
        //①
        String[] arr1 = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "1", "2"};
        String[] arr2 = {"♠", "♣", "♦", "♥"};

        ArrayList<String> pork = new ArrayList<>();
        for (String s : arr1) {
            for(String s2 : arr2){
                pork.add(s2 + s);
            }
        }
        pork.add("大王");
        pork.add("小王");
        System.out.print("创建好的扑克牌为:");
        lookCard(pork);
        System.out.println("长度:" + pork.size());
        //②
        Collections.shuffle(pork);
        System.out.print("洗后的扑克牌为:");
        lookCard(pork);
        //③
        List<String> p1 = new ArrayList<>();
        List<String> p2 = new ArrayList<>();
        List<String> p3 = new ArrayList<>();
        List<String> bottom = new ArrayList<>();
        for (int i = 0; i < pork.size(); i++) {
            if(i > 50){
                bottom.add(pork.get(i));
            } else if(0 == i % 3){
                //p1.add(pork.remove(i));统一使用remove的话,有一个问题,删掉0位,1位会成为新的0位。
                //i就不能后移,始终remove首位直到取完
                p1.add(pork.get(i));
            }else if(1 == i % 3){
                p2.add(pork.get(i));
            }else if(2 == i % 3){
                p3.add(pork.get(i));
            }
        }
        //------------------------------------------------
        // 齐思:发牌方法二,不用get用remove
//        int flag = 1;
//        while (pork.size() != 0) {
//            if(pork.size() <= 3){
//                bottom.add(pork.remove(0));
//            }else if(1 == flag){//不可以把++写在外面,因为每判断一次都会进行一次++
//                flag++;
//                p1.add(pork.remove(0));
//            }else if(2 == flag){
//                flag++;
//                p2.add(pork.remove(0));
//            }else if(3 == flag){
//                flag = 1;
//                p3.add(pork.remove(0));
//            }
//        }

        //------------------------------------------------
        //④
        System.out.println("看牌");
        lookCard(p1);
        lookCard(p2);
        lookCard(p3);
        lookCard(bottom);

    }

12. 简述栈,队列,数组,链表 特性

A:-----------------------------------------------------------------------------------------------------------

: 先进后出
队列:先进先出
数组:
查询快:地址连续,通过数组首地址找到数组,通过索引快速查找
增删慢:长度固定,想增删一个元素,必须创建一个新数组,把原数组的数据复制过来
在这里插入图片描述
链表:查询慢:链表中地址不连续,每次查询元素,都必须从头开始查询
增删快:增删一个元素,对链表整体结构没有影响,所以增删快
单向链表:无序(取出和存储顺序不一致)
双向链表:有序(链表中有两条链子,有一条链子是专门记录元素的顺序)
在这里插入图片描述

13 简述 二叉树,排序树,平衡树,红黑树的特点

A:----------------------------------------------------------------------------------------------------------- 二叉树:分支不能超过两个
可以是空集,

排序树:在二叉树上的基础上,元素是有大小顺序的。左子树值 < 根值 < 右子树大
在这里插入图片描述

平衡二叉树:任何一个节点的左右子树的高度差最多为1。,且它的左子树和右子树都是一颗平衡二叉树。
在这里插入图片描述

红黑树: 自平衡的二叉查找树
特点:趋近于平衡树,查询叶子节点最大次数和最小次数不能超过2倍
约束:

  1. 节点可以是红色的或者黑色的
  2. 根节点是黑色的
  3. 叶子节点(空节点)是黑色的
  4. 每个红色结点的子结点都是黑色
  5. 任何一个结点到其每一个叶子节点的所有路径上黑色节点数相同
    在这里插入图片描述

14. List接口3大特点

A:--------------------------------------------------------------------------------------------------------

  1. 有序的集合,存储元素和取出元素的顺序是一致的(存123,取123)
  2. 有索引,包含了一些带索引的方法
  3. 允许存储重复的元素

15. List常用方法5种

其中add两种,get,remove,set

	private static void demo15() {
        System.out.println("demo15---------------------------------------");
        //这些都是Collection的方法
        List<Integer> list = new ArrayList<>();
        list.add(10);
        list.add(20);
        list.add(30);
        list.add(40);
        System.out.println("list add 10,20,30,40");
        System.out.println("list:" + list);
        System.out.println("list 是否有20:" + list.contains(20));
        //✘Integer[] arr = list.toArray();//虽然有规定泛型,但是数组接收还是Object才可以
        Object[] arr = list.toArray();
        System.out.print("list转数组:");
        for (Object o : arr) {
            System.out.print(o + "♥");
        }
        System.out.println();
        System.out.println("list 清理后输出");
        list.clear();
        System.out.println("list:" + list);

        /* ● 复习:List接口独有的4种*/
        //1.add(index,e)
        //list.add(0);
        System.out.println("list:" + list);
        list.add(0,11);
        System.out.println("list:" + list);
        list.add(1, 55);//测试跨到超出size的范围添加
        System.out.println("list:" + list);
        //不可 list.add(3,33);//给还不存在的索引添加
        list.add(1,22);
        System.out.println("list:" + list);

        //2. 区分Collection中remove是根据元素内容删除,List中只能根据索引删除
        System.out.println("list remove索引1返回:" + list.remove(1));
        //注意remove的是根据索引,而不是根据值。remove返回的也是删除的数字
        //System.out.println("list remove元素" + list.remove(30));
        System.out.println("list:" + list);

        //3.set(index, e)
        list.set(0, 23);
        System.out.println("list:" + list);
        list.set(0, 24);
        System.out.println("list:" + list);
        //list.set(1, 34);
        //list.set(4,44);
        //4.get(index)
        //System.out.println(list.get(4));
        System.out.println(list.get(0));
        System.out.println("-----------------------------------------------------------------------------------------------------------");

    }

16. 用3种方式遍历list, for循环,迭代器,foreach

A:-----------------------------------------------------------------------------------------------------------

private static void demo16() {
        /*因为List有索引,可以直接访问。但Collection类型的*/
        ArrayList<Double> list = new ArrayList<>();
        list.add(11.1);
        list.add(22.2);
        list.add(33.3);
        list.add(44.4);
        list.add(55.5);
        int size = list.size();
        int count = 0;
        //1.迭代器
        for(Iterator it1 = list.iterator(); it1.hasNext();){
            System.out.print(it1.next());
            if(count++ < size - 1){
                System.out.print(".");
            }else{
                System.out.println();
            }
        }
        //2.foreach
        count = 0;
        for (Double aDouble : list) {
            System.out.print(aDouble);
            if(count++ < size - 1){
                System.out.print("-");
            }else{
                System.out.println();
            }
        }
        //3.for循环
        count = 0;
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i));
            if(count++ < size - 1){
                System.out.print("♡");
            }else{
                System.out.println();
            }
        }
    }

17. 用增强for遍历数组, 用增强for遍历集合ArrayList

A:-----------------------------------------------------------------------------------------------------------
就是增强for,也是迭代器的简化版

private static void demo17() {
        int[] arr = {1, 2, 3, 4, 5};
        int length = arr.length;
        ArrayList<Double> list = new ArrayList<>();
        list.add(1.2);
        list.add(2.3);
        list.add(3.4);
        int size = list.size();
        int count = 0;
        for(int i : arr){
            System.out.print(i);
            if(count++ < length - 1){
                System.out.print(",");
            }else{
                System.out.println();
            }
        }
        count = 0;
        for(Double d : list){
            System.out.print(d);
            if(count++ < size - 1){
                System.out.print("-");
            }else{
                System.out.println();
            }
        }

    }

18. ① LinkedList使用4种添加方法 add,addFirst,push,addLast;

②获取并返回第一个元素和最后一个元素,清空集合中的元素。注意若无元素,获取报错,判断是否为空
③ 使用删除一个元素但不指定索引,移除并返回此列表的第一个和最后一个元素,从此列表的堆栈处弹出一个元素。共4种方法
④复习Collection接口的7种常用方法
A:----------------------------------------------------------------------------------------------------------
注意点:
list.push(40);//等同于addFirst
list.add(50);//等同于addLast
list.remove();//删除首
list.pop();//删除首

	private static void demo18() {
        //① LinkedList使用4种添加方法 add,addFirst,push,addLast
        LinkedList<Integer> list = new LinkedList<>();
        list.add(10);
        list.addFirst(20);
        list.addLast(30);
        list.push(40);//等同于addFirst
        list.add(50);//等同于addLast
        list.push(60);
        list.push(70);
        list.add(80);
        System.out.println("添加完的LinkedList" + list);
        
        // ②获取并返回第一个元素和最后一个元素,清空集合中的元素。注意若无元素,获取报错,判断是否为空
        System.out.println("获取首" + list.getFirst());
        System.out.println("获取尾" + list.getLast());
        System.out.println("获取索引3:" + list.get(3));
       // list.clear();
        //System.out.println(list.getFirst());
        
        // ③ 移除并返回此列表的第一个和最后一个元素,从此列表的堆栈处弹出一个元素。共4种方法
        list.remove(2);
        System.out.println("删除索引2后" + list);
        list.remove();//删除首
        System.out.println("删除无参" + list);
        list.removeFirst();
        System.out.println("删除首" + list);
        list.removeLast();
        System.out.println("删除尾" + list);
        list.pop();//删除首
        System.out.println("删除pop" + list);

        // ④复习Collection接口的7种常用方法,对比List的4种方法
        //List的5个,1.add(e)添加元素e,2.remove(index)根据索引位置删除,并返回删除的元素
        //3.contains(e)是否存在 4.toArray()返回数组集合转数组5.clear()清空集合
        System.out.println("Collection-----------------------------------------------------------------------------------------------------------");
        Collection<Character> coll = new ArrayList<>();
        coll.add('&');
        coll.add('#');
        coll.add('*');
        System.out.println("coll:" + coll);
        //coll.remove(0);不可根据索引删除,因为Collection接口下不是所有集合都有索引
        //System.out.println("删除索引0后coll:" + coll);删除失败,集合内容不变
        coll.remove('*');
        System.out.println("删除元素'*'后coll:" + coll);
        System.out.println("coll存在'&'?:" + coll.contains('&'));
        System.out.println("coll空?:" + coll.isEmpty());
        System.out.println("coll.size:" + coll.size());
        Object[] arr = coll.toArray();
        System.out.println("coll转数组:");
        printArray(arr);
        coll.clear();
        System.out.println("coll清空后:" + coll);

        /*↑ 注意点:
        list.push(40);//等同于addFirst
        list.add(50);//等同于addLast
        list.remove();//删除首
        list.pop();//删除首*/
    }

19. 复习String的常用方法9个;String3+1种创建方式;4种获取方式;1种截取;3种转换

A:-----------------------------------------------------------------------------------------------------------

private static void demo19() {
        /*String的3+1种 创建方式*/
        String str1 = "!!!";
        String str2 = new String("@@@");
        byte[] by1 = {97, 48, 65, 98};
        String str3 = new String(by1);
        char[] ch1 = {'!', '2', '3', '我','t'};
        String str4 = new String(ch1);

        String str5 = "23$%23#45$%%$^&&$$%";
        String str6 = "WErewwwq#$!@@421";
        String str7 = "@312Sdcad$3142#@14";

        /*4种获取方法*/
        System.out.println("str1长度:" + str1.length());
        System.out.println("str2与¥%……拼接:" + str2.concat("¥%……"));
        System.out.println("获取str3索引3处的字符:" + str3.charAt(3));
        System.out.println("字符串str4中$%第一次出现的位置索引:" + str4.indexOf("$%"));
        System.out.println("字符串str5中$%第一次出现的位置索引:" + str5.indexOf("$%"));
        
        /*1种截取,两种参数*/
        System.out.println("str5:" + str5 );
        System.out.println("截取str5[6,size-1]:" + str5.substring(6));
        System.out.println("截取str5[4,10]: " + str5.substring(4,11));
        
        /*3种转换*/
        char[] ch2 = str6.toCharArray();
        System.out.print("str6:" + str6 + " 转字符数组:");
        printArray(ch2);
        byte[] by2 = str7.getBytes();
        System.out.print("str7:" +str7 + "转字节数组:");
        printArray(by2);
        System.out.println("str5:" + str5);
        String str8 = str5.replace("$%", "♥");
        System.out.println("str5中 $%转 ♥:" + str8);
         
         /*1种分割*/
        String[] strArr1 = str8.split("♥");
        System.out.print("str8:" + str8 + "以♥分割得到的数组strArr:");
        printArray(strArr1);

//        int[] arrInt = {1,2,4,2,4,1,5,6};
//        printArray(arrInt);
    }

20. 【哈希表 * 5】①new两个对象,查看输出的hashCode,hashcode方法是输出哈希码10进制(DEC),直接输出对象是包名.类名.哈希码值 末尾是16进制(HEX)的哈希码

在这里插入图片描述

② 重写自定义对象Person的hashCode方法,始终返回-1,发现p1 == p2为false,因为实际地址不同。hashCode只是模拟的地址
A:-----------------------------------------------------------------------------------------------------------
注意: 快捷重写equals方法alt + insert 然后连续next即可

public class Demo20_Person {
    String name;

//    @Override
//    public boolean equals(Object o) {
//        if (this == o) return true;
//        if (o == null || getClass() != o.getClass()) return false;
//        Demo20_Person that = (Demo20_Person) o;
//        return Objects.equals(name, that.name);
//    }

    @Override
    public int hashCode() {
        return -1;
    }
}
// 因为实际地址不同。hashCode只是模拟的地址
        //      回忆equals和==区别:
        //      == 基本类型比较值,引用类型比较地址,
        //      equals 只能比较引用对象,也是比较地址,
        //      但是String,File,Date和包装类是比较内容。因为这些类重写了equals方法
        Demo20_Person p1 = new Demo20_Person();
        Demo20_Person p2 = new Demo20_Person();
        System.out.println("p1 == p2? " + p1.equals(p2));
        HashSet<Demo20_Person> set1 = new HashSet<>();
        set1.add(p1);
        set1.add(p2);
        System.out.println("Person类型的set:" + set1);//Person类型的set:[review.java9.Demo20_Person@ffffffff, review.java9.Demo20_Person@ffffffff]
        /*↑ 可以看出,哈希码相同。但是在Set中不属于重复元素,可以一起存*/

③ 验证两个new String对象只要参数相同,返回的hashCode也相同
A:----------------------------------------------------------------------------------------------------------

		String str1 = new String("abc");
        char[] ch1 = new char[]{97, 98, 99};
        String str2 = new String(ch1);
        String str3 = "a".concat("bc");

        System.out.println("str1哈希码:" + str1.hashCode());//96354
        System.out.println("str2哈希码:" + str2.hashCode());//96354
        System.out.println("str3哈希码:" + str3.hashCode());//96354

        System.out.println("str1 == str2:" + (str1 == str2) + "\n" +
                "str2 == str3:" + (str2 == str3)); //false, false
        System.out.println("str1.eqals(str2):" + str1.equals(str2) + "\n"
        + "str2.equals(str3):" + str2.equals(str3));//true true
        /*说明对于String,只要内容字符串相同,即使不是同一个对象(实际地址相同)。哈希码也是相同的
        储,因为实际地址不同不重复*/
 /*? 疑问已知Integer内容相同哈希码就相同,求问内容相同的Integer是同一个对象吗?我觉得不是但以防万一测试一下*/
        Integer in1 = 123;
        Integer in2 = 123;
        Double d1 = 12.23;
        Double d2 = 12.23;
        Boolean bo1 = true;
        Boolean bo2 = true;
        Character c1 = '$';
        Character c2 = '$';
        Float f1 = 1.23F;
        Float f2 = 1.23F;
        Long l1 = 9223372036854775807L;
        Long l2 = 9223372036854775807L;
        Byte b1 = 127;
        Byte b2 = 127;
        Short s1 = 32767;
        Short s2 = 32767;


        System.out.println(in1 == in2);//true
        //System.out.println(in1.equals(in2));//这个绝对true因为File,Date,String和包装类重写了equals方法
        System.out.println("Double" + (d1 == d2));//Doublefalse
        System.out.println("Boolean" + (bo1 == bo2));//Booleantrue
        System.out.println(c1 == c2);//true
        System.out.println(f1 == f2);//false
        System.out.println(l1 == l2);//false
        System.out.println(b1 == b2);//true
        System.out.println(s1 == s2);//true

特殊情况:
1)同一个对象哈希值必然相同
2)Integer,Character,Byte,Short的哈希码和自己的值相同,并且这4种包装类只要值相同就是同一个对象。(共同点:都是整数类型的,都可以跟Ascii码互转)Boolean也符合值相同就是同一个对象。其他包装类都不符合
3)String对象的字符串内容相同,哈希码就相同
4)包装类只要同类且值相同,哈希值就相同

④ 哈希表=数组+红黑树,画哈希表存储图
存储数据到集合中,先计算元素的哈希值
"重地"和"通话"哈希值相同,两个元素不同,但是哈希值相同,哈希冲突
A:-----------------------------------------------------------------------------------------------------------
见图,哈希值不同的存在数组中,哈希值相同的存在数组中,就是用链表。链表长度超过8就转换成红黑树
在这里插入图片描述
为什么HashSet不能存储重复元素?
A:-----------------------------------------------------------------------------------------------------------

  • 存数原理:Set集合在调用add方法的时候,add方法会调用元素的hashCode方法判断新添加的元素和哈希表中已有的元素是否哈希冲突,
    如果冲突调再用equals方法,判断元素是否相同,为true就不存储。
  • 例如 “重地” 和 “通话” hashCode一样,但值不一样equals为false也可以保存成功。

21. 使用迭代器和增强for遍历HashSet

A:-----------------------------------------------------------------------------------------------------------

	private static void demo21() {
        HashSet set1 = new HashSet();
        set1.add(12);
        set1.add('#');
        set1.add(12.2134);

        Iterator it1 = set1.iterator();
        while(it1.hasNext()){
            Object obj = it1.next();
            System.out.println(obj);
        };
        for (Object o : set1) {
            System.out.println(o + ",");
        }
    }

22. 自定义Person类,默认属性年龄和姓名相同就是同一个人,重写equals和hashcode,保证存储HashSet中的该对象元素不重复

A:-----------------------------------------------------------------------------------------------------------

public class Demo22_Person {
    public Demo22_Person(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public Demo22_Person() {
    }

    private int age;
    private String name;
    //只要姓名和年龄都相同就默认是同一个人,是重复对象
    //重写equals和hashCode都保持这两个属性冲突


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Demo22_Person that = (Demo22_Person) o;
        return age == that.age &&
                Objects.equals(name, that.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(age, 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;
    }
}
	private static void demo22() {
        Demo22_Person p1 = new Demo22_Person(18,"babie");
        Demo22_Person p2 = new Demo22_Person(18,"babie");
        Demo22_Person p3 = new Demo22_Person(19,"babie");
        HashSet<Demo22_Person> personSet = new HashSet<>();
        personSet.add(p1);
        personSet.add(p2);
        personSet.add(p3);
        System.out.println(p1);
        System.out.println(p2);
        System.out.println(p3);
        System.out.println(personSet);
    }

23. HashSet添加哈希冲突但不重复的两个元素,模拟分析过程

A:-----------------------------------------------------------------------------------------------------------

  • 哈希冲突并存入数据的过程
    当存入新数据时,会比较新元素的哈希码和已有元素的哈希码,如果相同,说明哈希冲突,再次用equals判断是否是同一个对象,如果true说明元素重复,不可存入
    Set集合特点:无序,不可存储重复元素
 哈希表特点,就是HashSet,底层是一个哈希表结构:数组+链表(红黑树)
   存数原理和不重复原因:先hashCode判断再equals都相同则默认重复,不可添加
 	private static void demo23() {
        HashSet set1 = new HashSet();
        String str1 = "@#$";
        String str2 = "@".concat("#$");
        System.out.println("重地".hashCode() + "," + "通话".hashCode());
        System.out.println(str1.hashCode() + "," + str2.hashCode());

        set1.add("重地");
        set1.add("通话");
        set1.add(str1);
        set1.add(str2);
        //虽然String不是同一个对象,但是equals比较的是内容,所以在Set中算重复,只存储成功一个
        System.out.println(set1);
       
    }

24. LinkedHashSet集合特点:

底层是一个哈希表(数组+链表/红黑树)+链表:多了一条链表(记录元素的存储顺序),保证元素有序
复习:
List特点3: 1.有序 2.有索引 3.可重复
Set特点2: 1. 无序 2. 不可重复元素
LinkedHashSet: 底层为双链表,所以有序。多了一条链表记录元素顺序,
有序指的是按照添加顺序存储元素

25. 使用一个HashSet,一个LinkedHashSet对比存储结果

A:-----------------------------------------------------------------------------------------------------------

 	private static void demo25() {
        LinkedHashSet<Integer> lSet1 = new LinkedHashSet<>();
        lSet1.add(4);
        lSet1.add(3);
        lSet1.add(2);
        lSet1.add(1);
        HashSet<Integer> set1 = new HashSet<>();
        set1.add(4);
        set1.add(3);
        set1.add(2);
        set1.add(1);

        System.out.println("LinkedHashSet:" + lSet1);//[4, 3, 2, 1]
        System.out.println("HashSet:" + set1);//[1, 2, 3, 4]
        /* ↑ HashSet是按自己的排序,LinkedHashSet按添加的先后顺序*/
    }

26. 写一个方法,返回 0 ~ n的数据和,但不确定会传几个参数,数据类型int。一次传入数组,一次传入数据们

A:-----------------------------------------------------------------------------------------------------------
可变参数比数组做参数多一个可以接收数列,如 1,2,3这种参数传递

	public static void main(String[] args){
		// 一次传入数组,一次传入数据们
        int[] arr = {5, 4, 3, 2, 1};
        demo26(arr);
        demo26(8, 7, 6, 5, 4);
    }
	private static void demo26(int... num) {
        
        int[] arr = num;
        int sum = 0;
        for (int i = 0; i < num.length; i++) {
            sum += arr[i];
        }
        System.out.println("sum:" + sum);
    }

27. 判断可变参数写法是否正确

 public final static void method01(String... arr){✔}
 public void method02(int... num1, String... num2){✘ 可变参数最多只有有一个}
public void method03(int  num1, String...num2){✔ }
public void method04(int... num1, String num2){✘ 可变参数必须在末尾 }

28. 使用Collections类中方法,给集合排序(那些是可以排的,List

A:-----------------------------------------------------------------------------------------------------------
sort两种用法
1)static <T extends Comparable<? super T>> void sort(List list)
根据其元素的natural ordering对指定的列表进行排序。
2)static void sort(List list, Comparator<? super T> c)
根据指定的比较器引起的顺序对指定的列表进行排序。
即被排序的list中元素必须继承实现了自然排序,或者传参一个比较器

rivate static void demo28() {
        List<Integer> list = new ArrayList<>();
        list.add(40);
        list.add(15);
        list.add(14);
        list.add(25);

        System.out.println(list);//[40, 15, 14, 25]
        Collections.sort(list);
        System.out.println(list);//[14, 15, 25, 40]

        List<String> list1 = new ArrayList<>();
        Collections.addAll(list1, "ab", "", "baa", "aa", "ca", "");
        System.out.println("list1:" + list1);//list1:[ab, , baa, aa, ca, ]
        Collections.sort(list1);
        System.out.println("排序后list1:" + list1);//排序后list1:[, , aa, ab, baa, ca]
        // ● List可以存null,但有null的排序会异常,空字符串可以排序

    }

29. 打乱集合顺序shuffle,给集合一次添加多个元素addAll

A:-----------------------------------------------------------------------------------------------------------

 	    private static void demo29() {
        /*Collections.addAll(集合名,数列)添加数列或者数组都可以*/
        List<Integer> list1 = new ArrayList<>();
        Collections.addAll(list1, 40, 8, 3, 1);

        String[] strArr = new String[]{"!", "@#", "12#@"};
        List<String> list2 = new LinkedList<>();
        Collections.addAll(list2, strArr);
        System.out.println("list1:" + list1);//list1:[40, 8, 3, 1]
        System.out.println("list2:" + list2);//list2:[!, @#, 12#@]
        
        /*Collections.shuffle(集合名)*/
        Collections.shuffle(list2);
        System.out.println("list2打乱后:" + list2);//list2打乱后:[!, @#, 12#@]
        Collections.shuffle(list2);
        System.out.println("list2打乱后:" + list2);//list2打乱后:[12#@, @#, !]
        Collections.shuffle(list2);
        System.out.println("list2打乱后:" + list2);//list2打乱后:[12#@, !, @#]        
    }

★ 30. 用Comparator实现按字符串的首字母降序排序。

A:-----------------------------------------------------------------------------------------------------------

  • 举例分析设置返回值控制升降序:
    改变字符串的比较方式,按首位字母降序排序,只根据首位字符排序,首位相同就保持稳定性
    稳定性:例如 ab,ba,aa 排序后应该是 ab,aa,ba,因为只根据首位排序,
    ab和aa相对位置不变,ab依然在aa前就叫稳定性
 private static void demo30() {
       List<String> list1 = new ArrayList<>();
        Collections.addAll(list1, "ab", "", "baa", "aa", "ca", "");
        System.out.println("list1:" + list1);
        //因为ArrayList中可以存储null,但null不可参与排序,见demo28
        //然后空字符串默认最小,因为降序所以排在最后
        
        Collections.sort(list1, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                if(null == o1 || null == o2){
                    //注意,为了不处理异常,只是这里抛出一次,所以只能写运行时异常
                    throw new NullPointerException("null可以作为元素存储在List下集合中,但不可以参与排序");
                }else if(0 == o1.length() && 0 == o2.length()){
                    return 0;
                }
                else if(0 == o1.length()){//空字符串最小,降序排得在最后面。因为返回正数数时,新添加的o2排在前
                    return 1;
                }else if(0 == o2.length()){//因为返回负数时,新添加的o2排在后
                    return -1;
                }
                char ch1 = o1.charAt(0);
                char ch2 = o2.charAt(0);

                if(ch1 == ch2){
                    return 0;
                }
                return ch2 - ch1;
            }
        });
        System.out.println(list1);

分析ch2 - ch1 如何实现降序
● 记一句即可:返回正数时,方法会把新添加的o2(o)排在前,已存在的o1(this)在后。返回负数时反之。返回0时相等,按原相对顺序排一起 ●
所有首字母降序排序的需求下

  • 情况1:ch1 < ch2
    代表 o1 < o2,而ch2-ch1 返回正数的情况,新o2(o)在前。ch2在前,符合降序
  • 情况2:ch2 < ch1
    此处ch2代表o2,ch2-ch1返回负数时,旧o1(this)在前。ch1在前,符合降序

简记:旧this(o1) - 新o(o2) -> 升序,反之降序
对于自然排序
如果 this > obj 返回正数
this < obj 返回负数
this == obj 返回0

31. ① 用Comparable给Person类对象排序,姓名降序,姓名相同时年龄升序 ② 再用Comparator给Person用年龄降序,相同时姓名升序构造集合③使用比较器传参排序List

A:-----------------------------------------------------------------------------------------------------------
不同的三种集合存储规则。

  • TreeSet或TreeMap判断元素或key是否重复是根据对象的排序规则,如果排序规则认为这两个无大小先后是相同的大小则重复,不可重复存入。并且直接按照排序规则存储的
  • HashSet或HashMap则是根据hashCode方法和equals方法的返回值,如果都为true,说明这两元素或key相同,不存入重复元素。并且无法排序,所以排序方法此时无效
  • ArrayList 是按照添加顺序存储,再通过Collections.sort(list)排序,或者Collections.sort(list, 传入对应元素类的比较器);或创建时构造器内传比较器排序List集合

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

A:-----------------------------------------------------------------------------------------------------------

public class Demo31_Person implements Comparable<Demo31_Person>{
    String name;
    int age;
    int id;

    public Demo31_Person(String name, int age, int id) {
        this.name = name;
        this.age = age;
        this.id = id;
    }

//    @Override
//    public int compareTo(Demo31_Person o) {
//        return this.id - o.id;
//    }

    /** 姓名降序,姓名相同时年龄升序排序
     */
    @Override
    public int compareTo(Demo31_Person o) {

        //类似return o.name - this.name;
        if(o.name.compareTo(this.name) == 0){//姓名相同,年龄升序
            return this.age - o.age;//年龄升序
        }
        return o.name.compareTo(this.name);//姓名降序,类似o.name - this.name;
        //分析: 因为comparaTo中 如果this > obj 返回正数。
        // 此时o.name.compareTo(this.name)方法中的this是o.name,obj是this.name
        // 如果o.name更大,方法返回正数
        // 此方法return这个正数,默认新添加的元素o(比较器中新的是o2)排在前,
        // 则是o更大排在前,完成降序
    }

    //只跟HashSet或HashMap中存储有关,和排序这类无关

//    @Override
//    public boolean equals(Object o) {
//        if (this == o) return true;
//        if (o == null || getClass() != o.getClass()) return false;
//        Demo31_Person that = (Demo31_Person) o;
//        return age == that.age &&
//                Objects.equals(name, that.name);
//    }
//
//    @Override
//    public int hashCode() {
//        return Objects.hash(name, age);
//    }

    @Override
    public String toString() {
        return "{name='" + name + '\'' +
                ", age=" + age + '\'' +
                ", id=" + id +
                '}';
//        return "{name='" + name + '\'' +
//                ", age=" + age +
//                '}';
    }
}

 private static void demo31() {
        Demo31_Person p1 = new Demo31_Person("aa", 12, 3);
        Demo31_Person p2 = new Demo31_Person("aa", 30, 3);
        Demo31_Person p3 = new Demo31_Person("ab", 18, 3);
        Demo31_Person p4 = new Demo31_Person("ba", 2, 2);
        Demo31_Person p5 = new Demo31_Person("bb", 2, 1);
        Demo31_Person p6 = new Demo31_Person("bb", 2, 1);
        Demo31_Person p7 = new Demo31_Person("bb", 2, 4);

        Set<Demo31_Person> set1 = new TreeSet<>(new Comparator<Demo31_Person>() {
            @Override
            public int compare(Demo31_Person o1, Demo31_Person o2) {
                if(o1.age == o2.age){//年龄相同,姓名升序
                    return o1.name.compareTo(o2.name);//姓名升序
                }
                return o2.age - o1.age;//年龄降序

            }
        });//因为TreeSet,是直接按照排序规则存储。覆盖了元素本身的排序规则
        Set<Demo31_Person> set2 = new TreeSet<>();//按照元素类排序规则
        List<Demo31_Person> list1 = new ArrayList<>();//按照添加顺序存储
        Collections.addAll(set1, p1, p2, p3, p4, p5, p6, p7);
        Collections.addAll(list1, p1, p2, p3, p4, p5, p6, p7);
        Collections.addAll(set2, p1, p2, p3, p4, p5, p6, p7);
        System.out.println("年龄降序,姓名升序TreeSet1: " + set1);
        System.out.println("姓名降序,年龄升序TreeSet2:" + set2);
        System.out.println("ArrayList:" + list1);


       Collections.sort(list1, new Comparator<Demo31_Person>() {
           @Override
           public int compare(Demo31_Person o1, Demo31_Person o2) {
               if(o1.name.equals(o2.name)){//姓名相同
                   return o2.age - o1.age;//年龄降序

               }
               return o1.name.compareTo(o2.name);//姓名升序
           }
       });//排序一下,参数只接收List接口下对象
        System.out.println("排序后姓名升序,年龄降序ArrayList:" + list1);

        //TreeSet只能存入有排序规则的类对象,因为Set接口下对象不存重复数据
        //在类中重写hashCode和equals即可
    }

结果:
set1和set2中p5,p6,p7两属性相同,只存储一个,所以size==5,List.size() == 7

年龄降序,姓名升序TreeSet1: [{name='aa', age=30', id=3}, {name='ab', age=18', id=3}, {name='aa', age=12', id=3}, {name='ba', age=2', id=2}, {name='bb', age=2', id=1}]
姓名降序,年龄升序TreeSet2:[{name='bb', age=2', id=1}, {name='ba', age=2', id=2}, {name='ab', age=18', id=3}, {name='aa', age=12', id=3}, {name='aa', age=30', id=3}]
ArrayList:[{name='aa', age=12', id=3}, {name='aa', age=30', id=3}, {name='ab', age=18', id=3}, {name='ba', age=2', id=2}, {name='bb', age=2', id=1}, {name='bb', age=2', id=1}, {name='bb', age=2', id=4}]
排序后姓名升序,年龄降序ArrayList:[{name='aa', age=30', id=3}, {name='aa', age=12', id=3}, {name='ab', age=18', id=3}, {name='ba', age=2', id=2}, {name='bb', age=2', id=1}, {name='bb', age=2', id=1}, {name='bb', age=2', id=4}]

32. 1)使用Comparable的两种用法,Comparator的三种用法,2)简述Comparable和Comparator两个接口的区别

A:-----------------------------------------------------------------------------------------------------------

1)

  • compareTo自然排序
    ①直接用于Number对象和String对象
    ②自定义类中继承Comparable接口重写comparaTo方法
  • compare比较器排序
    ①自定义类中继承Comparator<类名>重写compare‘’方法,集合参数new自定义对象(和自然排序2)类似)例: 上题set2‘’
    ②特有:普通类,new集合时,提供参数传入匿名比较器,当场重写排序方法。(用于TreeSet,TreeMap所存元素必须有排序规则的的集合)例:上题 set1
    ③特有:在Collections类的方法public static void sort(List list, Comparator<? superT>)中传入比较器做第二个参数,重写排序规则(用于List下对象集合)例:上题list

2)

  • 对于Number类和String默认有自然排序且不可更改排序规则,但比较器可以修改这几类的排序规则
  • 自然排序对于自定义类或普通类只有在类中重写规则这一种使用方式;而比较器有方法二:new Tree类型需要元素自带排序规则的集合时,传入比较器用内部类重写比较方法;方法三 Collections.sort(List下对象,比较器)也是内部类重写比较器传入即可
  • 自然排序旧元素this,新o;比较器旧o1,新o2

33. Map集合特点

A:-----------------------------------------------------------------------------------------------------------
Map是双列集合,集合中元素是成对存在的。每个元素由键和值两部分组成,通过键可以找到对应的值
Map中不可以包含重复的键,值可以重复,键不可以

34.使用Map存储,替换,删除数据

A:-----------------------------------------------------------------------------------------------------------

	public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>();
        System.out.println(map.put(1, "one"));//返回 null
        System.out.println(map.put(2, "one"));//null
        System.out.println(map.put(2, "two"));//one
        System.out.println(map.put(2, "three"));//two
        System.out.println(map.put(3, "three"));//null

        System.out.println(map);
        map.remove(2);
        System.out.println(map);
    }
null
null
one
two
null
{1=one, 2=three, 3=three}
{1=one, 3=three}

35. 用键找值的方式遍历Map集合

A:-----------------------------------------------------------------------------------------------------------
键找值方式:即通过元素中的键,获取键所对应的值
分析步骤:

  1. 获取Map中所有的键,由于键是唯一的,所以返回一个Set集合存储所有的键。方法: keyset()
  2. 遍历键的Set集合,得到每一个键。
  3. 根据键,获取键所对应的值。get(K key)
 Set<Integer> keySet1 = map.keySet();
        System.out.print("[");
        int size = 0;
        for(Integer i: keySet1){
            System.out.print(i + "->" + map.get(i));
            size++;
            //根据keySet和map分别取键和值
            if(size != map.size()){
                System.out.print(",");
            }
        }
        System.out.println("]");//[1->one,3->three]

36.用键值对的方式遍历Map集合

A:-----------------------------------------------------------------------------------------------------------
在Map集合中也提供了获取所有Entry对象的方法:
public Set<Map.Entry<K,V>> entrySet() : 获取到Map集合中所有的键值对对象的集合(Set集合)。

	public static void main(String[] args) {
		map.put(4, "four");
        map.put(5, "five");
        Set<Map.Entry<Integer, String>> set2 = map.entrySet();
        System.out.print("[");
        size = 0;
        for(Map.Entry<Integer,String> entry: set2){
            System.out.print(entry);
            size++;
            if(size != set2.size()){
                System.out.print(",");
            }
        }
        System.out.println("]");//[1=one,3=three,4=four,5=five]
    }

37.用HashMap存储自定义键值对数据

A:-----------------------------------------------------------------------------------------------------------

private static void demo37() {
        /*用HashMap存储自定义键值对数据*/
        Master m1 = new Master("Barbie", 99999999);
        Master m2 = new Master("Ken", 900);
        Pet p1 = new Pet("缅因猫", 3);
        Pet p2 = new Pet("蒙古獒", 8);
        Pet p3 = new Pet("布偶猫", 4);
        Pet p4 = new Pet("边牧", 3);
        Pet p5 = new Pet("萨摩耶", 3);
        Pet p6 = new Pet("西高地", 3);


        //一个主人可以养很多狗
        Map<Pet, Master> map1 = new HashMap<>();
        map1.put(p1, m1);
        map1.put(p2, m1);
        map1.put(p3, m1);
        map1.put(p4, m1);
        map1.put(p5, m1);
        map1.put(p6, m2);
        System.out.println(map1);

    }
{Pet{variety='蒙古獒'}=Master{name='Barbie'}, Pet{variety='边牧'}=Master{name='Barbie'}, Pet{variety='西高地'}=Master{name='Ken'}, Pet{variety='布偶猫'}=Master{name='Barbie'}, Pet{variety='缅因猫'}=Master{name='Barbie'}, Pet{variety='萨摩耶'}=Master{name='Barbie'}}

38. 用HashMap优化斗地主洗牌发牌案例

A:-----------------------------------------------------------------------------------------------------------
回忆之前用ArrayList实现是,是用List对象.suffle()洗牌后直接发。使用Map让每个人拿到的牌都是有序的
思路:

  1. 为了能根据牌面排序,给牌设置序号,作为key,牌名作为value。
  2. 内部发牌和洗牌都是根据牌的序号,用Collections.sort(List对象(含排序规则))排序
  3. 只有看牌的时候通过key序号找对应value牌名字
 private static void lookCard(Map map, List list){
        //根据list的序号在map中找值打印
        System.out.print("[");
        for (int i = 0; i < list.size(); i++) {
            System.out.print(map.get(list.get(i)));
            if(i < list.size() - 1){
                System.out.print(",");
            }
        }
        System.out.println("]");


    }
    private static void demo38() {
        //用HashMap优化斗地主洗牌发牌案例
        /*1. 创建牌和对应的序号*/
        String[] num1 = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};
        String[] color = {"♥", "♦", "♣", "♠"};
        List<String> poker = new ArrayList<>();
        for(int i = 0; i < num1.length; i++){
            for(int j = 0; j < color.length; j++){
                poker.add(color[j] + num1[i]);
            }
        }
        poker.add("小王");
        poker.add("大王");
        System.out.println("创建好的牌:" + poker);
        HashMap<Integer, String> map1 = new HashMap<>();
        List<Integer> num = new ArrayList<>();
        for (int i = 0; i < poker.size(); i++) {
            num.add(i + 1);
            map1.put(i + 1,poker.get(i));
        }
        System.out.println("牌和序号对应关系:" + map1);

        /*2.打乱洗序号*/
        Collections.shuffle(num);
        System.out.println("打乱后序号:" + num);
        /*3.发序号*/
        //三个人和底牌都用ArrayList代表,存储牌的序号即可
        List<Integer> p1 = new ArrayList<>();
        List<Integer> p2 = new ArrayList<>();
        List<Integer> p3 = new ArrayList<>();
        List<Integer> bottom = new ArrayList<>();

        for (int i = 0; i < num.size(); i++) {
            if(i >= 52){
                bottom.add(num.get(i));
            }
            if(0 == i%3){
                p1.add(num.get(i));
            }else if(1 == i%3){
                p2.add(num.get(i));
            }else if(2 == i%3){
                p3.add(num.get(i));
            }
        }

        /*4.发完的序号再排序,并看牌*/
        Collections.sort(p1);
        Collections.sort(p2);
        Collections.sort(p3);
        Collections.sort(bottom);

        //打印发完牌排完序的牌的序号顺序
        System.out.println(p1);
        System.out.println(p2);
        System.out.println(p3);
        System.out.println(bottom);
        //每个人手里牌的顺序
        lookCard(map1, p1);
        lookCard(map1, p2);
        lookCard(map1, p3);
        lookCard(map1, bottom);

    }
创建好的牌:[♥3, ♦3, ♣3, ♠3, ♥4, ♦4, ♣4, ♠4, ♥5, ♦5, ♣5, ♠5, ♥6, ♦6, ♣6, ♠6, ♥7, ♦7, ♣7, ♠7, ♥8, ♦8, ♣8, ♠8, ♥9, ♦9, ♣9, ♠9, ♥10, ♦10, ♣10, ♠10, ♥J, ♦J, ♣J, ♠J, ♥Q, ♦Q, ♣Q, ♠Q, ♥K, ♦K, ♣K, ♠K, ♥A, ♦A, ♣A, ♠A, ♥2, ♦2, ♣2, ♠2, 小王, 大王]
牌和序号对应关系:{1=♥3, 2=♦3, 3=♣3, 4=♠3, 5=♥4, 6=♦4, 7=♣4, 8=♠4, 9=♥5, 10=♦5, 11=♣5, 12=♠5, 13=♥6, 14=♦6, 15=♣6, 16=♠6, 17=♥7, 18=♦7, 19=♣7, 20=♠7, 21=♥8, 22=♦8, 23=♣8, 24=♠8, 25=♥9, 26=♦9, 27=♣9, 28=♠9, 29=♥10, 30=♦10, 31=♣10, 32=♠10, 33=♥J, 34=♦J, 35=♣J, 36=♠J, 37=♥Q, 38=♦Q, 39=♣Q, 40=♠Q, 41=♥K, 42=♦K, 43=♣K, 44=♠K, 45=♥A, 46=♦A, 47=♣A, 48=♠A, 49=♥2, 50=♦2, 51=♣2, 52=♠2, 53=小王, 54=大王}
打乱后序号:[36, 51, 4, 5, 39, 42, 13, 35, 37, 8, 43, 23, 52, 22, 3, 54, 28, 40, 20, 30, 53, 47, 29, 2, 1, 10, 24, 14, 49, 15, 25, 44, 6, 45, 27, 12, 11, 50, 26, 9, 33, 34, 16, 41, 38, 21, 19, 7, 31, 17, 32, 18, 46, 48]
[1, 5, 8, 9, 11, 13, 14, 16, 18, 20, 21, 25, 31, 36, 45, 47, 52, 54]
[10, 17, 19, 22, 27, 28, 29, 30, 33, 35, 39, 41, 43, 44, 46, 49, 50, 51]
[2, 3, 4, 6, 7, 12, 15, 23, 24, 26, 32, 34, 37, 38, 40, 42, 48, 53]
[46, 48]
[♥3,♥4,♠4,♥5,♣5,♥6,♦6,♠6,♦7,♠7,♥8,♥9,♣10,♠J,♥A,♣A,♠2,大王]
[♦5,♥7,♣7,♦8,♣9,♠9,♥10,♦10,♥J,♣J,♣Q,♥K,♣K,♠K,♦A,♥2,♦2,♣2]
[♦3,♣3,♠3,♦4,♣4,♠5,♣6,♣8,♠8,♦9,♠10,♦J,♥Q,♦Q,♠Q,♦K,♠A,小王]
[♦A,♠A]

❤tips:多个修饰符定义时先后顺序

public final static <QQ> void method03(QQ num){
        System.out.println("QQ类型-> " + num);
}
private final static int num = 12;
public static double num2 = 13.45;
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值