第十八天 集合-泛型&list接口&set接口【面试+工作】

第十八天 集合-泛型&list接口&set接口【面试+工作】

第18天 集合

第1章 泛型

1.1 泛型概述

泛型用来灵活地将数据类型应用到不同的类、方法、接口当中。将数据类型作为参数传递。

泛型是数据类型的一部分,我们将类名与泛型合并一起看做数据类型。

泛型的定义:定义泛型可以在类中预支地使用未知的类型。

泛型的使用:一般在创建对象时,将未知的类型确定具体的类型。当没有指定泛型时,默认类型为Object类型。

1.2 泛型的定义与使用

我们在集合中会大量使用到泛型,这里来完整地学习泛型知识。

泛型,用来灵活地将数据类型应用到不同的类、方法、接口当中。将数据类型作为参数进行传递。

1.3 泛型代码实现

1.3.1 含有泛型的类

定义格式:修饰符 class 类名<代表泛型的变量> { }

  • 例如,API中的ArrayList集合:

class ArrayList<E>{

public boolean add(E e){ }

public E get(int index){ }

}

使用格式:创建对象时,确定泛型的类型

  • 例如,ArrayList<String> list = new ArrayList<String>();

此时,变量E的值就是String类型

class ArrayList<String>{

public boolean add(String e){ }

public String get(int index){ }

}

  • 例如,ArrayList<Integer> list = new ArrayList<Integer>();

此时,变量E的值就是Integer类型

class ArrayList<Integer>{

public boolean add(Integer e){ }

public Integer get(int index){ }

}

1.3.2 含有泛型的方法

定义格式:修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }

  • 例如,API中的ArrayList集合中的方法:

public <T> T[] toArray(T[] a){ }

//该方法,用来把集合元素存储到指定数据类型的数组中,返回已存储集合元素的数组

使用格式:调用方法时,确定泛型的类型

  • 例如

ArrayList<String> list = new ArrayList<String>();

String[] arr = new String[100];

String[] result = list.toArray(arr);

此时,变量T的值就是String类型。变量T,可以与定义集合的泛型不同

public <String> String[] toArray(String[] a){ }

  • 例如

ArrayList<String> list = new ArrayList<String>();

Integer[] arr = new Integer[100];

Integer [] result = list.toArray(arr);

此时,变量T的值就是Integer类型。变量T,可以与定义集合的泛型不同

public <Integer> Integer[] toArray(Integer[] a){ }

1.3.3 含有泛型的接口

定义格式:修饰符 interface接口名<代表泛型的变量> { }

  • 例如,API中的Iterator迭代器接口

public interface Iterator<E> {

public abstract E next();

}

使用格式:

1、定义类时确定泛型的类型

  • 例如

public final class Scanner implements Iterator<String> {

public String next(){ }

}

此时,变量E的值就是String类型。

2、始终不确定泛型的类型,直到创建对象时,确定泛型的类型

  • 例如

Collection<String> list = new ArrayList<String>();

Iterator<String> it = list.iterator();

此时,变量E的值就是String类型。

public interface Iterator<String> {

public abstract String next();

}

}

1.4 泛型通配符

在JDK1.5出现前,使用Object代表任意类型,但在使用时,涉及到了强转的麻烦。泛型替代了Object来代表任意类型。

泛型在编译时会擦除:泛型仅用来在编译期限制、方便程序员的操作,实际上真正编译后的.class中是没有泛型的,其中仍然使用的为Obejct类,通过类似多态的方式完成任意某个类型的指定。

当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。

定义:(查看ArrayList的构造方法)无法在类中使用

使用:调用方法时可以给予任意类型。参照Arraylist的构造方法

? extends E代表只要是E类型的子类即可

? super E代表只要是E类型的父类即可

/*

* 泛型通配符?,代表任意的数据类型

*

* 定义:(查看ArrayList的构造方法)无法在类中使用

*

* 使用:调用方法时可以给予任意类型。参照Arraylist的构造方法

* public ArrayList(Collection<? extends E> c)

* 为了便于?的理解,我们将以上方法重写为public ArrayList(ArrayList<? extends E> c)

*

* 该方法的意思:创建集合对象A时,给于另外一个集合对象B作为参数,则创建好的集合A中包含了集合B中的元素

*

* ? extends E代表只要是E类型的子类即可

* ? super E代表只要是E类型的父类即可

*/

public class Demo01 {

public static void main(String[] args) {

//定义集合b,包含3个元素

ArrayList<String> listB = new ArrayList<String>();

listB.add("Jack");

listB.add("Rose");

listB.add("Trump");

//使用集合b创建集合a

ArrayList<Object> listA = new ArrayList<Object>(listB);

listA.add("Obama");

//观察集合A

System.out.println(listA);

}

1.5 泛型优点与常见应用

泛型的好处:

1.提高了程序的安全性

2.将运行期遇到的问题转移到了编译期

3.省去了类型强转的麻烦

泛型的常见应用:

1.泛型类

2.泛型方法

3.泛型接口

第2章 List接口

2.1 接口特点及主要子类

  • 单列集合
  • 可存放重复元素
  • 元素有序
  • 主要子类

ArrayList:底层数据结构是数组结构。线程不安全的。所以ArrayList的出现替代了Vector。增删慢,查找快。

LinkedList:底层是链表数据结构。线程不安全的,同时对元素的增删快,查找慢。

Vector:底层数据结构是数组结构。jdk1.0版本。线程安全的。无论增删还是查询都非常慢,已被ArrayList替代。

2.2 List集合存储数据的结构

List接口下有很多个集合,它们存储元素所采用的结构方式是不同的,这样就导致了这些集合有它们各自的特点,供给我们在不同的环境下进行使用。数据存储的常用结构有:堆栈、队列、数组、链表。我们分别来了解一下:

  • 堆栈,采用该结构的集合,对元素的存取有如下的特点:
    • 先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。例如,子弹压进弹夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的子弹。
    • 栈的入口、出口的都是栈的顶端位置
    • 压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。
    • 弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。
  • 队列,采用该结构的集合,对元素的存取有如下的特点:
    • 先进先出(即,存进去的元素,要在后它前面的元素依次取出后,才能取出该元素)。例如,安检。排成一列,每个人依次检查,只有前面的人全部检查完毕后,才能排到当前的人进行检查。
    • 队列的入口、出口各占一侧。例如,下图中的左侧为入口,右侧为出口。
  • 数组,采用该结构的集合,对元素的存取有如下的特点:
    • 查找元素快:通过索引,可以快速访问指定位置的元素
    • 增删元素慢:
      • 指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。如下图
      • 指定索引位置删除元素:需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中。如下图
  • 链表,采用该结构的集合,对元素的存取有如下的特点:
    • 多个节点之间,通过地址进行连接。例如,多个人手拉手,每个人使用自己的右手拉住下个人的左手,依次类推,这样多个人就连在一起了。
    • 查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素
    • 增删元素快:
      • 增加元素:操作如左图,只需要修改连接下个元素的地址即可。
      • 删除元素:操作如右图,只需要修改连接下个元素的地址即可。

2.3 List接口常用方法

void add(int index, E element) //指定索引添加元素

E remove(int index) //移除指定索引处元素

E get(int index) //获取指定索引元素

E set(int index, E element) //修改指定索引元素

List<E> subList(int fromIndex, int toIndex) //截取指定索引子集

int indexOf(Object o) //返回指定元素索引位置

import java.util.ArrayList;

import java.util.List;

/*

* List 方法

*

* void add(int index, E element) //指定索引添加元素

*/

public class Demo {

public static void main(String[] args) {

//fun3();

//fun2();

//fun();

}

private static void fun3() {

List <String > al = new ArrayList<>();

al.add("111");// 添加 Collection中 继承来的

al.add("222");

al.add("333");

al.add("444");

System.out.println(al);

al.set(2, "6666");

System.out.println(al);

List<String> subList = al.subList(1, 4);

System.out.println(subList);

int indexOf = subList.indexOf("444");

System.out.println(indexOf);

}

private static void fun2() {

List <Integer > al = new ArrayList<>();

al.add(1);

al.add(2);

al.add(3);

al.add(4);

al.remove(2);

System.out.println(al);

}

private static void fun() {

List <String > al = new ArrayList<>();

al.add("111");// 添加 Collection中 继承来的

al.add("222");

al.add("333");

al.add("444");

al.add(2, "666");

al.add(5, "999");

al.add(6, "000999");

System.out.println(al);

al.remove(6);

System.out.println(al);

}

}

ListIterator<E> listIterator() 注意:用于应对并发修改异常的返回迭代器方法与迭代器

import java.util.ArrayList;

import java.util.Collection;

import java.util.Iterator;

import java.util.List;

import java.util.ListIterator;

/*

* ListIterator : Iterator 子接口.

*

* Collection 是得不到ListIterator , 因为 List 专属的.

*

* add添加方法 比较常用.

*/

public class Demo2 {

public static void main(String[] args) {

//fun();

List c = new ArrayList<>();

c.add("孙悟空");

c.add("白骨精");

c.add("唐三藏");

c.add("八戒");

ListIterator li = c.listIterator();

while (li.hasNext()) {

Object object = (Object) li.next();

if (object.equals("白骨精")) {

li.add("白龙马");

}

}

System.out.println(c);

ListIterator listIterator = c.listIterator();

listIterator.next();

System.out.println(listIterator.hasPrevious());

}

private static void fun() {

Collection c = new ArrayList<>();

c.add("孙悟空");

c.add("白骨精");

c.add("唐三藏");

c.add("八戒");

//遍历 集合, 如果 包含 白骨精 ,你就添加一个 白龙马.

Iterator iterator = c.iterator();

while (iterator.hasNext()) {

Object object = (Object) iterator.next();

if (object.equals("白骨精")) {

c.add("白龙马"); //ConcurrentModificationException

}

}

System.out.println(c);

}

}

2.4 具体子类介绍

2.4.1 ArrayList

ArrayList底层数据结构是数组结构。线程不安全的,所以运行速度快,ArrayList的出现替代了Vector。增删慢,查找快,由于日常开发中使用最多的功能为查询数据,遍历数据,所以ArrayList是最常用的集合。目前市面上许多程序员开发时并不严谨,非常随意地使用ArrayList完成任何需求,这种用法是不提倡的。

2.4.2 LinkedList

LinkedList集合数据存储的结构是链表结构。方便元素添加、删除的集合。实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。如下图

LinkedList是List的子类,List中的方法LinkedList都是可以使用,这里就不做详细介绍,我们只需要了解LinkedList的特有方法即可。在开发时,LinkedList集合也可以作为堆栈,队列的结构使用。

方法演示:

LinkedList<String> link = new LinkedList<String>();

//添加元素

link.addFirst("abc1");

link.addFirst("abc2");

link.addFirst("abc3");

//获取元素

System.out.println(link.getFirst());

System.out.println(link.getLast());

//删除元素

System.out.println(link.removeFirst());

System.out.println(link.removeLast());

while(!link.isEmpty()){ //判断集合是否为空

System.out.println(link.pop()); //弹出集合中的栈顶元素

}

2.4.3 Vector

Vector:我们可以将其理解为版本旧的、安全的、效率低的ArrayList,Vector中提供了一个独特的取出方式,就是枚举Enumeration。此接口Enumeration的功能与 Iterator 接口的功能是类似的。

有兴趣的同学可以自己了解:

public E elementAt(int index) / get(index )

public E firstElement()

public E lastElement()

public void setElementAt(E obj, int index) set(index,obj)

public void removeElementAt(int index)及其他删除 remove(index)

public Enumeration<E> elements()

import java.util.Enumeration;

import java.util.Vector;

/*

* Vector : 数组结构,单线程的 ArrayList.

*/

public class Demo2 {

public static void main(String[] args) {

Vector<String > vector = new Vector<>();

vector.addElement("111");

vector.addElement("222");

vector.addElement("444");

vector.addElement("333333");

// 获取 枚举.

Enumeration<String> elements = vector.elements();

while (elements.hasMoreElements()) {

String nextElement = elements.nextElement();

System.out.println(nextElement);

}

}

}

第3章 Set接口

查阅Set集合的API介绍,通过元素的equals方法,来判断是否为重复元素,它是个不包含重复元素的集合。Set集合取出元素的方式可以采用:迭代器、增强for。

Set集合有多个子类,这里我们介绍其中的HashSet、LinkedHashSet这两个集合。

3.1 HashSet集合介绍

查阅HashSet集合的API介绍:此类实现Set接口,由哈希表支持(实际上是一个 HashMap集合)。HashSet集合不能保证的迭代顺序与元素存储顺序相同。

HashSet集合,采用哈希表结构存储数据,保证元素唯一性的方式依赖于:hashCode()与equals()方法。

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

什么是哈希表呢?

哈希表底层使用的也是数组机制,数组中也存放对象,而这些对象往数组中存放时的位置比较特殊,当需要把这些对象给数组中存放时,那么会根据这些对象的特有数据结合相应的算法,计算出这个对象在数组中的位置,然后把这个对象存放在数组中。而这样的数组就称为哈希数组,即就是哈希表。

当向哈希表中存放元素时,需要根据元素的特有数据结合相应的算法,这个算法其实就是Object类中的hashCode方法。由于任何对象都是Object类的子类,所以任何对象有拥有这个方法。即就是在给哈希表中存放对象时,会调用对象的hashCode方法,算出对象在表中的存放位置,这里需要注意,如果两个对象hashCode方法算出结果一样,这样现象称为哈希冲突,这时会调用对象的equals方法,比较这两个对象是不是同一个对象,如果equals方法返回的是true,那么就不会把第二个对象存放在哈希表中,如果返回的是false,就会把这个值存放在哈希表中。

总结:保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。

3.3 HashSet存储JavaAPI中的类型元素

给HashSet中存储JavaAPI中提供的类型元素时,不需要重写元素的hashCode和equals方法,因为这两个方法,在JavaAPI的每个类中已经重写完毕,如String类、Integer类等。

  • 创建HashSet集合,存储String对象。

public class HashSetDemo {

public static void main(String[] args) {

//创建HashSet对象

HashSet<String> hs = new HashSet<String>();

//给集合中添加自定义对象

hs.add("zhangsan");

hs.add("lisi");

hs.add("wangwu");

hs.add("zhangsan");

//取出集合中的每个元素

Iterator<String> it = hs.iterator();

while(it.hasNext()){

String s = it.next();

System.out.println(s);

}

}

}

输出结果如下,说明集合中不能存储重复元素:

wangwu

lisi

zhangsan

3.4 HashSet存储自定义类型元素

给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一

  • 创建自定义对象Student

public class Student {

private String name;

private int age;

public Student(String name, int age) {

super();

this.name = name;

this.age = age;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public int getAge() {

return age;

}

public void setAge(int age) {

this.age = age;

}

@Override

public String toString() {

return "Student [name=" + name + ", age=" + age + "]";

}

@Override

public int hashCode() {

final int prime = 31;

int result = 1;

result = prime * result + age;

result = prime * result + ((name == null) ? 0 : name.hashCode());

return result;

}

@Override

public boolean equals(Object obj) {

if (this == obj)

return true;

if(!(obj instanceof Student)){

System.out.println("类型错误");

return false;

}

Student other = (Student) obj;

return this.age == other.age && this.name.equals(other.name);

}

}

  • 创建HashSet集合,存储Student对象。

public class HashSetDemo {

public static void main(String[] args) {

//创建HashSet对象

HashSet hs = new HashSet();

//给集合中添加自定义对象

hs.add(new Student("zhangsan",21));

hs.add(new Student("lisi",22));

hs.add(new Student("wangwu",23));

hs.add(new Student("zhangsan",21));

//取出集合中的每个元素

Iterator it = hs.iterator();

while(it.hasNext()){

Student s = (Student)it.next();

System.out.println(s);

}

}

}

输出结果如下,说明集合中不能存储重复元素:

Student [name=lisi, age=22]

Student [name=zhangsan, age=21]

Student [name=wangwu, age=23]

3.5 判断元素唯一原理

3.5.1 ArrayList的contains方法判断元素是否重复原理

ArrayList的contains方法会使用调用方法时,传入的元素的equals方法依次与集合中的旧元素所比较,从而根据返回的布尔值判断是否有重复元素。此时,当ArrayList存放自定义类型时,由于自定义类型在未重写equals方法前,判断是否重复的依据是地址值,所以如果想根据内容判断是否为重复元素,需要重写元素的equals方法。

3.5.2 HashSet的add/contains等方法判断元素是否重复原理

Set集合不能存放重复元素,其添加方法在添加时会判断是否有重复元素,有重复不添加,没重复则添加。

HashSet集合由于是无序的,其判断唯一的依据是元素类型的hashCode与equals方法的返回结果。规则如下:

先判断新元素与集合内已经有的旧元素的HashCode值

  • 如果不同,说明是不同元素,添加到集合。
  • 如果相同,再判断equals比较结果。返回true则相同元素;返回false则不同元素,添加到集合。

所以,使用HashSet存储自定义类型,如果没有重写该类的hashCode与equals方法,则判断重复时,使用的是地址值,如果想通过内容比较元素是否相同,需要重写该元素类的hashcode与equals方法。

hashCode方法重写规则:将该对象的各个属性值hashCode相加即是整个对象的HashCode值。如果是基本类型,类似int,则直接返回int值就是该属性的hash值,如果是引用类型,类似String,就调用该成员变量的hashCode方法返回该成员变量hash值。这样可以根据对象的内容返回hashCode值,从而可以根据hashCode判断元素是否唯一。

但是由于在一些”碰巧的”情况下,可能出现内容不同但hashCode相同的情况,为了避免这些情况,我们加入一些干扰系数。

可是加入干扰系数后,仍会出现一些”碰巧”的情况,所以我们还要进行equals的二次判断。

public class Student {

String name ;

int age;

public Student() {

super();

// TODO Auto-generated constructor stub

}

public Student(String name, int age) {

super();

this.name = name;

this.age = age;

}

@Override

public String toString() {

return "Student [name=" + name + ", age=" + age + "]";

}

// 想要保证 对象的唯一性, 必须重写 equals 和 hashCode();

// (老王, 60 ) , (金莲 ,28 )

// hashCode 而是通过 属性的值计算出来的hashCode

@Override

public int hashCode() {

final int prime = 31;

int result = 1;

result = prime * result + age; // 91 59

result = prime * result + ((name == null) ? 0 : name.hashCode());

return result;

}

@Override

public boolean equals(Object obj) {

if (this == obj) // 当前对象与 传参对象 地址 相同 .返回true .

return true;

if (obj == null) //传参对象 为null, 返回 false .

return false;

//当前对象与 传参对象 不一样, 返回false .

if (this.getClass() != obj.getClass()) // getClass() 获取类型

return false;

// 强制转换 .

Student other = (Student) obj;

if (age != other.age) //比较年龄

return false;

if (name == null) { // 比较名字, 我没名,你有名, 返回false

if (other.name != null)

return false;

} else if (!name.equals(other.name)) // 姓名不一样,返回false .

return false;

return true;

}

}

import java.util.HashSet;

/*

* Set : 不包含重复元素的集合.无序 常用 其子类 HashSet .

*

* 你想要使用HashSet保存自定义类型的元素,必须通过重写hashCode和equals方法来保证对象的唯一性.

* 记住: 重写就好使,不重写就挂了. alt + shift + s --> h

*/

public class Demo {

public static void main(String[] args) {

// fun();

HashSet<Student> hashSet = new HashSet<>();

Student student = new Student("金莲", 28);

Student student2 = new Student("小明", 15);

Student student3 = new Student("韩梅梅", 16);

Student student4 = new Student("李雷", 18);

Student student5 = new Student("李雷", 18);

Student student6 = new Student("李雷", 18);

Student student7 = new Student("李雷", 18); // 属性值 全一样, 说明 是同一个对象.

hashSet.add(student);

hashSet.add(student2);

hashSet.add(student3);

hashSet.add(student4);

hashSet.add(student5);

hashSet.add(student6);

hashSet.add(student7);

// 打印 出几个同学?

System.out.println(hashSet);

}

private static void fun() {

HashSet<String> hashSet = new HashSet<>();

// 添加元素

hashSet.add("武大郎");

hashSet.add("武大郎");

hashSet.add("武大郎");

hashSet.add("奥巴马");

hashSet.add("金三胖");

System.out.println(hashSet);

}

}

3.6 Hashset练习

3.6.1 编写一个程序,获取10个1至20的随机数,要求随机数不能重复

import java.util.HashSet;

import java.util.Iterator;

import java.util.Random;

/*

* 需求:编写一个程序,获取10个1至20的随机数,要求随机数不能重复。

* 并把最终的随机数输出到控制台。

*/

public class Test {

public static void main(String[] args) {

// 创建Random 对象

Random random = new Random();

//创建集合

HashSet<Integer> hashSet = new HashSet<>();

int count = 0;

// 循环获取,循环添加 , 集合的个数做为条件.

while (hashSet.size() < 10) {

count ++; // 计数

// 调用Random 获取随机数

int i = random.nextInt(20) + 1;

// 添加到集合 , HashSet , 无序,唯一的.

hashSet.add(i);

}

System.out.println(count);

System.out.println("添加完毕,遍历集合");

// 遍历 hashSet

Iterator<Integer> iterator = hashSet.iterator();

while (iterator.hasNext()) {

Integer integer = (Integer) iterator.next();

System.out.print(integer +" ");

}

}

}

3.6.2 将集合中的重复元素去掉(自定义类 )

public class Student {

String name;

int age;

public Student(String name, int age) {

super();

this.name = name;

this.age = age;

}

@Override

public String toString() {

return " Student 姓名=" + name + ", 年龄=" + age ;

}

@Override

public int hashCode() {

final int prime = 31;

int result = 1;

result = prime * result + age;

result = prime * result + ((name == null) ? 0 : name.hashCode());

return result;

}

@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)

return false;

} else if (!name.equals(other.name))

return false;

return true;

}

// 重写hashCode 和equals

}

import java.util.ArrayList;

import java.util.HashSet;

/*

* 将集合中的重复元素去掉(自定义类 )

ArrayList<Student> list = new ArrayList<>();

Student student = new Student("金莲", 28);

Student student2 = new Student("小明", 15);

Student student3 = new Student("韩梅梅", 16);

Student student4 = new Student("李雷", 18);

Student student5 = new Student("李雷", 18);

Student student6 = new Student("李雷", 18);

list.add(student);

list.add(student2);

list.add(student3);

list.add(student4);

list.add(student5);

list.add(student6);

*/

public class Test2 {

public static void main(String[] args) {

ArrayList<Student> list = new ArrayList<>();

Student student = new Student("金莲", 28);

Student student2 = new Student("小明", 15);

Student student3 = new Student("韩梅梅", 16);

Student student4 = new Student("李雷", 18);

Student student5 = new Student("李雷", 18);

Student student6 = new Student("李雷", 18);

list.add(student);

list.add(student2);

list.add(student3);

list.add(student4);

list.add(student5);

list.add(student6);

System.out.println(list);

//创建hashSet

HashSet<Student> hashSet = new HashSet<>();

for (Student s : list) {

hashSet.add(s);

}

System.out.println(hashSet);

}

}

第4章 集合综合案例

4.1 案例介绍

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

具体规则:

使用54张牌打乱顺序

三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌。

4.2 案例需求分析

  • 准备牌:

牌可以设计为一个ArrayList<String>,每个字符串为一张牌。

每张牌由花色数字两部分组成,我们可以使用花色集合与数字集合嵌套迭代完成每张牌的组装。

牌由Collections类的shuffle方法进行随机排序。

  • 发牌:

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

  • 看牌:

直接打印每个集合。

4.3 实现代码步骤

修改文件编码由GBK修改为UTF-8,因为GBK没有我们要的梅花、方片、黑桃、红桃(♠♥♦♣)等字符。

public class Poker {

public static void main(String[] args) {

//♠♥♦♣

//准备牌

ArrayList<String> poker = new ArrayList<String>();

//花色

ArrayList<String> color = new ArrayList<String>();

color.add("♠");

color.add("♥");

color.add("♦");

color.add("♣");

//数字

ArrayList<String> number = new ArrayList<String>();

for (int i = 2; i <= 10; i++) {

number.add(i+"");

}

number.add("J");

number.add("Q");

number.add("K");

number.add("A");

//完成新牌

for (String thisColor : color) {

for (String thisNumber : number) {

String thisCard = thisColor + thisNumber;

poker.add(thisCard);

}

}

poker.add("小☺");

poker.add("大☻");

//洗牌

Collections.shuffle(poker);

//发牌

//玩家1

ArrayList<String> player1 = new ArrayList<String>();

//玩家2

ArrayList<String> player2 = new ArrayList<String>();

//玩家3

ArrayList<String> player3 = new ArrayList<String>();

//底牌

ArrayList<String> secretCards = new ArrayList<String>();

for (int i = 0; i < poker.size(); i++) {

if(i>=51) {

//最后三张发给底牌

secretCards.add(poker.get(i));

}else {

//剩余牌通过对3取模依次摸牌

if(i%3==0) {

player1.add(poker.get(i));

}else if(i%3==1) {

player2.add(poker.get(i));

}else {

player3.add(poker.get(i));

}

}

}

//看牌

System.out.println(player1);

System.out.println(player2);

System.out.println(player3);

System.out.println(secretCards);

}

}

  • 最后发到三个人手中的牌是无序的,在明天学习完Map集合后,我们提供一个排序的解决方案。

第5章 Collection知识点总结

  • Collection:

|- List 可以存储重复元素,有序的(元素存取顺序)

|- ArrayList

|- LinkedList

|- Set 不能存储重复元素,无序的(元素存取顺序)

|- HashSet

|- LinkedHashSet

  • Collection方法:
    • boolean add(Object e) 把给定的对象添加到当前集合中
    • void clear() 清空集合中所有的元素
    • boolean remove(Object o) 把给定的对象在当前集合中删除
    • boolean contains(Object o) 判断当前集合中是否包含给定的对象
    • boolean isEmpty() 判断当前集合是否为空
    • Iterator iterator() 迭代器,用来遍历集合中的元素的
    • int size() 返回集合中元素的个数
    • Object[] toArray() 把集合中的元素,存储到数组中
  • Iterator : 迭代器
    • Object next()返回迭代的下一个元素
    • boolean hasNext()如果仍有元素可以迭代,则返回 true。
  • List与Set集合的区别?

List:

它是一个有序的集合(元素存与取的顺序相同)

它可以存储重复的元素

Set:

它是一个无序的集合(元素存与取的顺序可能不同)

它不能存储重复的元素

  • List集合中的特有方法
    • void add(int index, Object element) 将指定的元素,添加到该集合中的指定位置上
    • Object get(int index)返回集合中指定位置的元素。
    • Object remove(int index) 移除列表中指定位置的元素, 返回的是被移除的元素
    • Object set(int index, Object element)用指定元素替换集合中指定位置的元素,返回值的更新前的元素
  • ArrayList:

底层数据结构是数组,查询快,增删慢

线程不安全,效率高

  • LinkedList:

底层数据结构是链表,查询慢,增删快

线程不安全,效率高

  • 泛型: 用来约束数据的数据类型
    • 泛型的格式:

<数据类型>

泛型可以使用在 类,接口,方法,变量上

  • 泛型的好处

A:提高了程序的安全性

B:将运行期遇到的问题转移到了编译期

C:省去了类型强转的麻烦

  • 增强for

简化数组和Collection集合的遍历

格式:

for(元素数据类型 变量 : 数组或者Collection集合) {

使用变量即可,该变量就是元素

}

好处:简化遍历

  • HashSet:

元素唯一不能重复

底层结构是 哈希表结构

元素的存与取的顺序不能保证一致

如何保证元素的唯一的?

重写hashCode() 与 equals()方法

  • LinkedHashSet:

元素唯一不能重复

底层结构是 哈希表结构 + 链表结构

元素的存与取的顺序一致

第6章 本日自习作业:

6.1 知识点相关题

1. 知识点:自定义类 迭代器 泛型 增强for

要求:会使用迭代器

1) 按以下步骤编写代码:

a) 定义类:Cat,包含以下成员:

成员属性(私有):

名称:

颜色:

年龄:

构造方法:

无参

全参

成员方法:

1).get/set方法;

2).重写toString()方法;内部打印所有属性的值;

b) 在main()方法中,按以下步骤编写代码:

  • 向集合中添加以下cat对象:

“波斯猫”,“白色”,2

“折耳猫”,“灰色”,1

“加菲猫”,“红色”,3

“机器猫”,“蓝色”,5

遍历集合

1) 使用普通for循环

2) 迭代器(需要指定泛型)

3) 增强for三种方式

6.1.1 泛型的好处是什么?泛型用于什么上?

泛型的好处:

1.提高了程序的安全性

2.将运行期遇到的问题转移到了编译期

3.省去了类型强转的麻烦

泛型的常见应用:

1.泛型类

2.泛型方法

3.泛型接口

6.1.2 请简述List<? extends T>和List<? super T>之间有什么区别?

答案:

List<? extends T> :向下限制

List<? super T> :向上限制

? extends T : 代表接收的泛型类型为T类型或T子类类型

? super T :代表接收的泛型类型为T类型或T父类类型

6.1.3 请编写程序,将3个学生的信息存储到数组中,并遍历数组,获取得到每一个学生信息

6.1.4 请编写程序,存储3个手机对象到ArrayList集合中

a) 使用迭代器进行遍历,要有泛型

b) 打印出三个手机对象的信息,比如颜色,品牌。

6.1.5 描述Collection Frameword的体系,可以简易画图

6.1.6 描述每种接口或类的存储特点

6.1.7 熟练使用迭代器与foreach循环完成Collection/List/Set任意集合迭代

6.1.8 试完成以下需求,使用集合嵌套完成

某企业集合:包含各分校元素

每个分校均有班级元素

班级为一个类,类中包括:

所有同学属性

班级班号属性

班级所在楼层属性

同学为一个类,类中包括:

姓名

学号

年龄

6.1.9 熟练泛型的使用方法,完成简单泛型类定义,并使用。

说出 ? extends E的含义。使用ArrayList的构造方法:public ArrayList(Collection<? extends E> c) 创建集合,添加元素,迭代集合。

6.1.10 简述ArrayList的contains方法判断自定类型对象是否相同时的判断过程

6.1.11 简述HashSet的add方法判断自定类型对象是否相同时的判断过程

6.1.12 阅读Eclipse自动生成的hashCode方法与equals方法。

6.1.13 创建一个LinkedList集合,里面存储的数据类型为Integer类型,将1,2,3,4,5这5个数依次使用push方法,添加到集合中,使得打印集合时显示的效果是[5, 4, 3, 2, 1]

package day08_Test基础练习题;

import java.util.LinkedList;

/*

 * 考察LinkedList的push方法

 */

public class Test001 {

public static void main(String[] args) {

//创建一个LinkedList集合

LinkedList<Integer> stack = new LinkedList<Integer>();

//使用push方法

stack.push(1);

stack.push(2);

stack.push(3);

stack.push(4);

stack.push(5);

//打印此时的集合

System.out.println(stack);

}

}

6.1.14 创建LinkedList集合,里面存储的数据类型是String类型,分别将字符串"我","爱","Java","但是","我","更","爱","LOL"添加到集合中,使得打印这个集合最终显示为:[我, 爱, Java, 但是, 我, 更, 爱, LOL]

package day08_Test基础练习题;

import java.util.LinkedList;

/*

 * 考察LinkedList的add方法

 */

public class Test002 {

public static void main(String[] args) {

//创建LinkedList集合,里面存储的数据类型是String类型,分别将字符串"我","爱","Java","但是","我","更","爱","LOL"添加到集合中,

//使得打印这个集合最终显示为:[我, 爱, Java, 但是, 我, 更, 爱, LOL]

LinkedList<String> list = new LinkedList<String>();

list.add("我");

list.add("爱");

list.add("Java");

list.add("但是");

list.add("我");

list.add("更");

list.add("爱");

list.add("LOL");

//打印这个集合

System.out.println(list);

}

}

6.1.15 创建一个LinkedList集合,里面存储的数据类型是String类型;创建一个String类型的数组,里面的元素为{"我","爱","LOL","但是","我","更","爱","MONEY"};将String类型的数组里面的元素依次添加到创建的LinkedList集合中,最后打印这个LinkedList集合,显示为:[我, 爱, LOL, 但是, 我, 更, 爱, MONEY]

package day08_Test基础练习题;

import java.util.LinkedList;

/*

 * 考察LinkedList和数组之间的灵活运用

 */

public class Test003 {

/*创建一个LinkedList集合,里面存储的数据类型是String类型;

创建一个String类型的数组,里面的元素为{"我","爱","LOL","但是","我","更","爱","MONEY"};

将String类型的数组里面的元素依次添加到创建的LinkedList集合中

最后打印这个LinkedList集合,显示为:[我, 爱, LOL, 但是, 我, 更, 爱, MONEY]*/

public static void main(String[] args) {

//创建一个LinkedList集合

LinkedList<String> list = new LinkedList<String>();

//创建一个字符串数组

String[] str = {"我","爱","LOL","但是","我","更","爱","MONEY"};

//方法一:使用普通的方式

/*for (int i = 0; i < str.length; i++) {

list.add(str[i]);

}

System.out.println(list);*/

System.out.println("---------------------------------------");

//方法二:使用增强for循环的方式

for (String string : str) {

list.add(string);

}

System.out.println(list);

}

}

6.1.16 在括号中选择对应结构的解释,什么是数据结构(),什么是数组结构(),什么是链表结构(),什么是队列结构(),什么是栈结构()

A:数据存储和组织方式

B:容器先进后出规则

C:容器先进先出的规则

D:每个元素指向下一个元素

E:一块连续的存储区域

答案说明:数组结构:一块连续的存储区域,查询速度快,添加速度慢链表结构:每个元素指向下一个元素,添加快,查询慢队列结构:容器先进先出的规则栈结构:容器先进后出规则

6.1.17 给定一个字符串数组,数组内容为:{"我","爱","编程","但是","我","更","爱","IPHONE"}创建一个集合,这个集合存储上面字符串数组里面的元素,并且不存储重复的,此时的集合打印效果为:[更, 爱, IPHONE, 我, 编程, 但是],遍历集合,将集合中元素是IPHONE的元素删除,过后添加一个新的元素,元素为MJ,最终的集合的打印效果为:[更, 爱, 我, 编程, MJ, 但是]

package day08_Test基础练习题;

import java.util.HashSet;

public class Test005 {

public static void main(String[] args) {

HashSet<String> list = new HashSet<>();

String[] str = {"我","爱","编程","但是","我","更","爱","IPHONE"};

for (int i = 0; i < str.length; i++) {

list.add(str[i]);

}

System.out.println(list);

for (String string : str) {

if(string.equals("IPHONE")){

list.remove(string);

//添加元素

list.add("MJ");

}

}

System.out.println("操作过后的元素为:");

System.out.println(list);

}

}

6.1.18 创建Person类并测试

1.编写Person类,有age属性(数据类型是int)和name属性(数据类型是String),且都被private修饰,提供get/set方法,不重写equals方法和hashCode方法

2.编写Test类,在Test类的main方法中定义一个ArrayList集合,集合内存放的元素的数据类型是Person类

  • 分别创建三个对象,对象p1的年龄为10,名字为李四;对象p2的年龄为20,名字为王五;对象p3的年龄为30,名字为小强
  • 将三个对象依次添加到定义的集合中
  • 创建一个对象p4,此对象的年龄为30,名字为小强
  • 调用集合的contains方法,将p4作为参数传递进去,查看打印的是true还是false
  • 如果打印为false,那么在Person类中重写hashCode和equals方法
  • 查看打印的结果是否为true

思考:是否还能够将重复的对象添加成功?

Person类:

package day08_Test基础练习题;

public class Person {

private int age;

private String 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;

}

public Person(int age, String name) {

super();

this.age = age;

this.name = name;

}

public Person() {

super();

// TODO Auto-generated constructor stub

}

@Override

public int hashCode() {

final int prime = 31;

int result = 1;

result = prime * result + age;

result = prime * result + ((name == null) ? 0 : name.hashCode());

return result;

}

@Override

public boolean equals(Object obj) {

if (this == obj)

return true;

if (obj == null)

return false;

if (getClass() != obj.getClass())

return false;

Person other = (Person) obj;

if (age != other.age)

return false;

if (name == null) {

if (other.name != null)

return false;

} else if (!name.equals(other.name))

return false;

return true;

}

@Override

public String toString() {

return "Person [age=" + age + ", name=" + name + "]";

}

}
测试类:

package day08_Test基础练习题;

import java.util.ArrayList;

public class Test006 {

public static void main(String[] args) {

//定义一个ArrayList集合,集合中存储的数据类型是一个类,类的名字叫Person

ArrayList<Person> list = new ArrayList<>();

//使用匿名对象的方式分别将三个Person对象添加到集合中

list.add(new Person(10,"李四"));

list.add(new Person(20,"王五"));

list.add(new Person(30,"小强"));

//创建一个新的对象

Person p4 = new Person(30, "小强");

//打印结果,在Person类中有equals方法和hashCode方法时为true,反之没有时为false

System.out.println(list.contains(p4));

System.out.println(list);

System.out.println("------------------是否还能够将重复的对象添加成功?-------------------");

list.add(new Person(30, "小强"));

System.out.println(list);

}

}

 

6.2 代码题

6.2.1 点菜系统

/*

* 菜品类. 名字 ,价格, id. corsh 顶配的类.

*/

public class Cai {

private String name; // 菜名

private double price ;//价格

private String id ; // 菜id . 001

public Cai() {

super();

}

public Cai(String name, double price, String id) {

super();

this.name = name;

this.price = price;

this.id = id;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public double getPrice() {

return price;

}

public void setPrice(double price) {

this.price = price;

}

public String getId() {

return id;

}

public void setId(String id) {

this.id = id;

}

@Override

public String toString() {

return id + "--[" + name +":"+ price+"]" ;

}

@Override

public int hashCode() {

final int prime = 31;

int result = 1;

result = prime * result + ((id == null) ? 0 : id.hashCode());

result = prime * result + ((name == null) ? 0 : name.hashCode());

long temp;

temp = Double.doubleToLongBits(price);

result = prime * result + (int) (temp ^ (temp >>> 32));

return result;

}

@Override

public boolean equals(Object obj) {

if (this == obj)

return true;

if (obj == null)

return false;

if (getClass() != obj.getClass())

return false;

Cai other = (Cai) obj;

if (id == null) {

if (other.id != null)

return false;

} else if (!id.equals(other.id))

return false;

if (name == null) {

if (other.name != null)

return false;

} else if (!name.equals(other.name))

return false;

if (Double.doubleToLongBits(price) != Double.doubleToLongBits(other.price))

return false;

return true;

}

}

/*

* 点菜程序.

* 1. 创建一个集合,保存所有的菜品.

* 2. 录入菜品的编号, 遍历集合,选择菜品 ,保存到新的集合.

* 3. exit . 不点了, 遍历 新的集合,即是你点过菜.

*

* =====================

* 根据价格去选择!

* 16块钱.

* 18块钱.

*

* ========================

*

* 带肉的 . 简单实现 . 菜名有肉就行. 菇

* 复杂实现 . 根据 菜的原料 ,去判断.

*

* 实现:

*

* 1. 定义类 : 菜品类. 名字 ,价格, id.

* 2. 创建菜品对象,保存到集合.

* a. 通过 id ,点菜 .

* b. 价格 选菜 .

* c. 菜名字 ,选菜.

*

* 3.选择的菜品 ,添加到点菜的集合中.

* 4. 遍历集合.

*/

public class Test {

public static void main(String[] args) {

Cai cai = new Cai("锅包肉", 28.8, "001");

System.out.println(cai);

}

}

6.2.2 LinkedList集合题

步骤一:创建LinkedList集合,里面存储的元素的数据类型是Integer

步骤二:将1,2,3,4,5依次添加到集合中

步骤三:调用集合的push方法将6,7,8,9,10依次添加到集合中

步骤四:最后打印集合的效果为:[10, 9, 8, 7, 6, 1, 2, 3, 4, 5]

package day08_Test拓展三道编程题;

import java.util.LinkedList;

/*

 * LinkedList的弹栈和压站

 * push

 * add

 */

public class Test001 {

public static void main(String[] args) {

//创建LinkedList集合,里面存储的元素的数据类型是Integer

LinkedList<Integer> stack = new LinkedList<Integer>();

//将1,2,3,4,5添加到集合中

stack.add(1);

stack.add(2);

stack.add(3);

stack.add(4);

stack.add(5);

System.out.println(stack);

System.out.println("------------------------------");

//调用集合的push方法将6,7,8,9,10添加到集合中

stack.push(6);

stack.push(7);

stack.push(8);

stack.push(9);

stack.push(10);

//最后打印集合的效果为:[10, 9, 8, 7, 6, 1, 2, 3, 4, 5]

System.out.println(stack);

}

}

6.2.3 set集合题

1.定义一个员工类(Employee),要求具有以下属性:

a)empName(姓名)

b)sex(性别)

c)age(年龄)

2.定义一个List集合,存储以下”Employee”信息:

a)孙红雷 男 20

b)葛优 男 30

c)黄渤 男 25

d)孙俪 女 18

3.要求:

a)在”葛优”前面添加一个学员:王骏迪 女 17

b)定义一个Set集合存储Employee信息,要求不能存储相同的:姓名,性别,年龄的员工信息.并测试;

Employee类:

package day08_Test拓展三道编程题;

public class Employee {

private String name;

private char sex;

private int age;

public Employee(String name, char sex, int age) {

super();

this.name = name;

this.sex = sex;

this.age = age;

}

public Employee() {

super();

// TODO Auto-generated constructor stub

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public char getSex() {

return sex;

}

public void setSex(char sex) {

this.sex = sex;

}

public int getAge() {

return age;

}

public void setAge(int age) {

this.age = age;

}

@Override

public String toString() {

return "Employee [name=" + name + ", sex=" + sex + ", age=" + age + "]";

}

@Override

public int hashCode() {

final int prime = 31;

int result = 1;

result = prime * result + age;

result = prime * result + ((name == null) ? 0 : name.hashCode());

result = prime * result + sex;

return result;

}

@Override

public boolean equals(Object obj) {

if (this == obj)

return true;

if (obj == null)

return false;

if (getClass() != obj.getClass())

return false;

Employee other = (Employee) obj;

if (age != other.age)

return false;

if (name == null) {

if (other.name != null)

return false;

} else if (!name.equals(other.name))

return false;

if (sex != other.sex)

return false;

return true;

}

}
测试类:

package day08_Test拓展三道编程题;

import java.util.ArrayList;

import java.util.HashSet;

import java.util.List;

import java.util.Set;

public class Test002 {

public static void main(String[] args) {

List<Employee> empList = new ArrayList<>();

empList.add(new Employee("孙红雷",'男',20));

empList.add(new Employee("葛优",'男',30));

empList.add(new Employee("孙俪",'女',18));

//在”葛优”前面添加一个学员:王骏迪 女 17

empList.add(1,new Employee("王骏迪",'女',17));

//使用Set存储

Set<Employee> empSet = new HashSet<>();

empSet.add(new Employee("孙红雷",'男',20));

empSet.add(new Employee("葛优",'男',30));

empSet.add(new Employee("葛优",'男',30));//存储相同元素--失败

System.out.println(empSet);

}

}

6.2.4 定义集合并去除重复元素

1.定义一个存储数字的集合,并随意存储一些数据(要求这些数据中包含一些重复数字);

2.使用一种很方便的方式将这个集合中重复的数字去掉.

package day08_Test拓展三道编程题;

import java.util.ArrayList;

import java.util.HashSet;

import java.util.List;

import java.util.Set;

public class Test003 {

/*

 * 1.定义一个存储数字的集合,并随意存储一些数据(要求这些数据中包含一些重复数字);

   2.使用一种很方便的方式将这个集合中重复的数字去掉.

 */

public static void main(String[] args) {

List<Integer> intList = new ArrayList<>();

intList.add(10);

intList.add(20);

intList.add(20);

intList.add(30);

intList.add(30);

Set<Integer> intSet = new HashSet<>();

intSet.addAll(intList);

System.out.println(intSet);

}

}

原文发布于微信公众号 - Java帮帮(javahelp)

原文发表时间:2018-06-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

扫码关注云+社区