文章目录
引入
假设存在学生类 Student ,现在需要对学生进行分班操作,根据之前的学习内容,需要先使用学生信息创建学生对象,然后存储在数组中(目前,作为容器的 JAVA 对象只有两类:数组和字符串缓冲区)。
// 入学时,软件A1班级,有5名同学
Student s1 = new Student("201501","Apple");
Student s2 = new Student("201502","Merry");
Student s3 = new Student("201503","Davoid");
Student s4 = new Student("201504","Max");
Student s5 = new Student("201505","Joian");
// 由需求创建一个 Student 数组,长度为5
Student[] classA1 = new Strudent[] { s1, s2, s3, s4, s5 };
// 第二学期,因Merry学业成绩不及格,被留级
// 将Merry所在的数组中位置使用 null 取代
classA1[1] = null;
// 第三学期,因低年级的 Yzk 学业成绩优秀,跳级到A1班级
// 很简单嘛,Yzk 占了 Merry 的位置就好了呀
String s6 = new Student("201301","Yzk");
classA1[1] = s6;
// 但是,高年级的 Dou 也被留级到A1班,怎么办呢?
/**
* 假设开学的时候,班级一共100人,中间成绩不好的留级了25人,
* 建立的100人的数组,是不是太过于浪费内存呢? */
/**
* 假设开学的时候,班级一共100人,高年级留级、低年级跳级的人一共有35人,
* 空间不够又怎么办呢? */
上面的例子中,追根究底,一切源于数组的不可变性,数组在创建时,或显式或隐式的确定了数组长度,并且不可修改。数组长度的不可变性,不能适应变化的需求,就像是 String 的不可变性,给频繁的拼接操作带来了额外的内存消耗,Java提供了 StringBuilder、StringBuffer这一对 内容可变的、适应于单线程高效和多线程安全的类,解决了 String 的不可变性问题。那么,Java如何解决的数组的不可变性问题呢?
Java是纯面向对象的语言,而面向对象语言对事物的描述是通过对象体现的,为了方便的对多个对象进行操作,我们就必须对多个对象进行存储,而欲存储对象则需一容器\集合,目前所学中,仅有 数组Array[] 和 字符串缓冲区为此类,然字符串缓冲区输出为字符串,故不可用。再者 数组Array[] 需用对象数组,而其却不适应变化的需求,故弃之不用。鉴于此,Java提供了集合类解决这一问题。
示例:验证集合中只可以保存引用数据类型
/** 原始代码 */
import java.util.ArrayList;
import java.util.Collection;
public class Demo4
{
public static void main(String[] args)
{
Collection c = new ArrayList();
c.add(1);
c.add(1.0);
c.add(true);
c.add('1');
}
}
/** 反编译代码 */
import java.util.ArrayList;
import java.util.Collection;
public class Demo4
{
public static void main(String[] args)
{
Collection c = new ArrayList();
c.add(Integer.valueOf(1));
c.add(Double.valueOf(1.0D));
c.add(Boolean.valueOf(true));
c.add(Character.valueOf('1'));
}
}
集合是存储多个元的,但存储多元,也有不同的需求,譬如:多个元不可重复、多个元有序的。针对不同的要求,Java提供了不同的集合类,多个集合类的数据结构不同,并且还能使用的存储的东西,例如:判断、获取
集合之间有共性的内容,将共性的内容向上抽取,最后形成了现在继承体系。
Collection 接口
Collection 接口是集合类层次结构的根接口。
1、集合可以理解为动态的对象数组,不同的是集合中的对象内容可以任意扩充。
2、集合的特点:高性能、容易扩展和修改。没有直接实现的具体类,有实现集合的接口,子接口有具体实现类。
3、Collection 常用的子接口:List、Set、Queue、Deque
4、Collection 的功能概述
-
a、添加功能
boolean add(E o)
如果此 collection 由于调用而发生更改,则返回 true。如果此 collection 不允许有重复元素,并且已经包含了指定的元素,则返回 falseboolean addAll(Collection<? extends E> c)
如果此 collection 由于调用而发生更改,则返回 true
-
b、判断功能(相等、为空、包含)
boolean contains(Object obj)
当且仅当此 collection 至少包含一个满足(obj==null ? e==null : obj.equals(e))
的元素 e 时,返回 trueboolean containsAll(Collection<?> c)
如果此 collection 包含指定 collection 中的所有元素,则返回 trueboolean isEmpty()
如果此 collection 不包含元素,则返回trueboolean boolean equals(Object o)
比较此 collection 与指定对象是否相等。
-
c、删除功能(交集、差集等)
void clear()
移除此 collection 中的所有元素(可选操作)。此方法返回后,除非抛出一个异常。boolean remove(Object o)
如果此 collection 包含一个或多个满足(o==null ? e==null : o.equals(e))
的元素 e,则移除这样的元素。如果此调用将移除一个元素,则返回 trueboolean removeAll(Collection<?> c)
移除此 collection 中那些也包含在指定 collection 中的所有元素(可选操作)。此调用返回后,collection 中将不包含任何与指定 collection 相同的元素。 如果此 collection 由于调用而发生更改,则返回 trueboolean retainAll(Collection<?> c)
仅保留此 collection 中那些也包含在指定 collection 的元素(可选操作)。换句话说,移除此 collection 中未包含在指定 collection 中的所有元素。 如果此 collection 由于调用而发生更改,则返回 true
注意:最终结果存入当前的Collection,c不变
-
d、获取功能(遍历和长度获取)
Iterator<E> iterator()
返回在此 collection 的元素上进行迭代的迭代器。关于元素返回的顺序没有任何保证(除非此 collection 是某个能提供保证顺序的类实例)。int size()
返回此collection中的元素个数
-
e、转换功能(集合转换为数组)
Object[] toArray()
返回包含此 collection 中所有元素的数组<T> T[] toArray(T[] a)
如果此 collection 对其迭代器返回的元素顺序做出了某些保证,那么此方法必须以相同的顺序返回这些元素。
像 toArray() 方法一样,此方法充当基于数组的 API 与基于 collection 的 API 之间的桥梁。更进一步说,此方法允许对输出数组的运行时类型进行精确控制,并且在某些情况下,可以用来节省分配开销。
假定 x 是只包含字符串的一个已知 collection。以下代码用来将 collection 转储到一个新分配的 String 数组:String[] y = x.toArray(new String[0]);
注意:toArray(new Object[0])
和toArray()
在功能上是相同的。
示例1:基本功能测试
示例2:高级功能(All)测试
// 最简单的代码,没有之一! 自己写 !!!!!!
集合遍历
1、使用方法 Object[] toArray()
转换为数组,再进行便利
2、使用方法 Iterator<E> iterator()
集合专用的迭代器遍历
首先,来看一下,迭代器相关得信息。
迭代器 (Iterator)
public interface Iterator<E>
集合专用遍历方式
对 Collection 进行迭代的迭代器。迭代器取代了Java Collections Framework 中的 Enumeration 。迭代器与枚举有两点不同:
- 迭代器允许调用者利用定义好的语法在迭代期间从迭代器所指向的 Collection 中移除元素
- 方法名称得到了改进
成员方法【Method Summary】
boolean hasNext()
如果仍有元素可以迭代,则返回 true。E next()
返回迭代的下一个元素。void remove()
从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。
示例1:遍历字符串的集合
package com.rupeng.collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Demo2
{
public static void main(String[] args)
{
Collection<String> c = new ArrayList<String>();
c.add("I");
c.add("Love");
c.add("Java");
c.add("2015");
c.add("07");
c.add("05");
c.add("RuPeng.com");
// 1、遍历方式1 toArray()
Object[] objs = c.toArray();
for (int i = 0; i < objs.length; i++)
{
System.out.print(objs[i] + " ");
}
System.out.println();
System.out.println("---------------------------------");
// 2、遍历方式2 iterator()
// Iterator<String> it = c.iterator();
// while (it.hasNext())
// {
// System.out.print(it.next() + " ");
// }
for (Iterator<String> it = c.iterator(); it.hasNext();)
{
String str = it.next();
System.out.print(str + " ");
}
}
}
示例2:遍历自定义对象的集合
package com.rupeng.collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Demo3
{
public static void main(String[] args)
{
Student s1 = new Student("Merry", 12);
Student s2 = new Student("IAn", 18);
Student s3 = new Student("Mix", 15);
Collection<Student> c = new ArrayList<Student>();
c.add(s1);
c.add(s2);
c.add(s3);
Object[] objs = c.toArray();
for (int i = 0; i < objs.length; i++)
{
Student s = (Student) objs[i];
System.out.println(s);
}
System.out.println("------------------------------------");
// Iterator<Student> it = c.iterator();
// while (it.hasNext())
// {
// Student s = (Student) it.next();
// System.out.println(s);
// }
for (Iterator<Student> it = c.iterator(); it.hasNext();)
{
Student stu = (Student) it.next();
System.out.println(stu);
}
}
}
// 获取迭代器实例
Iterator it = c.iterator();
// 判断 it.hasnext()
// 指针 it.next()
源码
public interface Iterable<T>
{
Iterator<T> iterator(); // 仅有方法
// 默认修饰符:public abstract
}
public interface Iterator<E>
{
/** 所有方法 */
boolean hasNext();
E next();
void remove();
// 默认修饰符:public abstract
}
public interface Collection<E> implements Iterable<E>
{
Iterator<E> iterator(); // 一个方法,只是继承没有给出实现
}
public interface List<E> extends Collection<E>
{
Iterator<E> iterator(); // 一个方法,只是继承没有给出实现
}
public class ArrayList<E> implements List<E>
{
public Iterator<E> iterator()
{
return new Itr();
}
/** 私有成员内部类(安全) */
private class Itr implements Iterator<E>
{
public boolean hasNext() { // 功能代码快 }
public E next() { // 功能代码快 }
public void remove() { // 功能代码快 }
}
}
List 接口
public interface List<E> extends Collection<E>
有序的集合,也称为序列、列表。可以对列表中的每个元素的插入位置进行精确控制,可以根据元素的索引访问、搜索元素。
与set不同,列表通常允许重复的元素。更确切地讲,列表通常允许存在满足 e1.equals(e2)
的元素对e1、e2,允许多个null元素 。
List 接口提供了 4 种对列表元素进行定位(索引)的访问方法。列表(像 Java 数组一样)是基于 0 的。注意:这些操作可能在执行时间上与某些实现类的索引值成比(例如:LinkedList),因此在不知道具体实现类时,在列表上使用迭代通常优于用索引遍历列表。
List 接口为其具体实现类提供了针对于列表的迭代器 ListIterator,除了继承 Iterator 的正常操作外,还允许元素的插入和替换,以及双向访问。还提供了重写的方法实现从列表中指定位置开始遍历的列表迭代器。
List 接口提供了两种搜索指定对象的方法。从性能上来看,很多的实现中,是一种高开销的线性搜索。
List 接口提供了两种在列表的任意位置高效插入和移除多个元素的方法。
1:功能概述
除了继承了所有 Collection 接口的方法外,还提供了列表关于索引的一系列操作。
-
a、添加功能
boolean add(E o)
boolean add(int index,E o)
(list 定义)
在指定索引位置 index 插入元素。如果此 collection 由于调用而发生更改,则返回 true。如果此 collection 不允许有重复元素,并且已经包含了指定的元素,则返回 falseboolean addAll(Collection<? extends E> c)
boolean addAll(int index,Collection<? extends E> c)
(list 定义)
在指定索引位置,插入集合c中的所有元素。的如果此 collection 由于调用而发生更改,则返回 true
-
b、判断功能(相等、为空、包含)
boolean contains(E o)
当且仅当此 collection 至少包含一个满足(obj==null ? e==null : obj.equals(e))
的元素 e 时,返回 trueboolean containsAll(Collection<?> c)
如果此 collection 包含指定 collection 中的所有元素,则返回 trueboolean isEmpty()
如果此 collection 不包含元素,则返回trueboolean equals(Object o)
比较此 collection 与指定对象是否相等。
-
c、删除功能(交集、差集等)
E remove(int index)
(list 定义)
删除指定索引处的元素。将所有的后续元素向左移动(将其索引减 1)。返回从列表中移除的元素。boolean remove(Object o)
如果此 collection 包含一个或多个满足(o==null ? e==null : o.equals(e))
的元素 e,则移除这样的元素。如果此调用将移除一个元素,则返回 trueboolean removeAll(Collection<?> c)
移除此 collection 中那些也包含在指定 collection 中的所有元素(可选操作)。此调用返回后,collection 中将不包含任何与指定 collection 相同的元素。 如果此 collection 由于调用而发生更改,则返回 trueboolean retainAll(Collection<?> c)
仅保留此 collection 中那些也包含在指定 collection 的元素(可选操作)。换句话说,移除此 collection 中未包含在指定 collection 中的所有元素。 如果此 collection 由于调用而发生更改,则返回 true
注意:最终结果存入当前的Collection,c不变
void clear()
移除此 collection 中的所有元素(可选操作)。此方法返回后,除非抛出一个异常。
-
d、获取功能(遍历和长度获取)
int indexOf(Object obj)
(list 定义)
返回元素第一次出现的索引位置。返回满足(o==null ? get(i)==null : o.equals(get(i)))
的最低索引 i;如果没有这样的索引,则返回 -1。int lastIndexOf(Object obj)
(list 定义)
返回元素最后一次出现的索引位置。返回满足(o==null ? get(i)==null : o.equals(get(i)))
的高索引 i;如果没有这样的索引,则返回 -1。
List 接口提供了两种搜索指定对象的方法。从性能上来看,很多的实现中,是一种高开销的线性搜索。
-
d、获取功能(遍历和长度获取)
-
ListIterator<E> listIterator()
(list 定义)
返回此列表元素的列表迭代器(按适当顺序)。 -
ListIterator<E> listIterator(int index)
(list 定义)
返回此列表元素的列表迭代器(按适当顺序)。从列表的指定位置开始。指定的索引表示 next 的初始调用所返回的第一个元素。 previous 方法的初始调用将返回的索引比指定索引少 1 的元素。 -
List<E> subList(int fromIndex, int toIndex)
(list 定义)
1、 返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图
。(如果 fromIndex 和 toIndex 相等,则返回的子列表为空,但是针对子列表的操作,会在原列表的 fromIndex 体现)。返回的子列表由原列表支持,因此子列表中的非结构性更改将反映在原列表中,反之亦然。返回的子列表支持原列表支持的所有可选列表操作。
2、此方法省去了显式范围操作(此操作通常针对数组存在)。通过传递 subList 视图而非整个列表,期望列表的任何操作可用作范围操作。例如,下面的语句从列表中移除了指定范围的元素:list.subList(from, to).clear();
可以对 indexOf 和 lastIndexOf 构造类似的语句,而且 Collections 类中的所有算法都可以应用于 subList。 -
Iterator<E> iterator()
返回在此 collection 的元素上进行迭代的迭代器。关于元素返回的顺序没有任何保证(除非此 collection 是某个能提供保证顺序的类实例)。 -
int size()
返回此collection中的元素个数
-
-
e、转换功能(集合转换为数组)
Object[] toArray()
返回包含此 collection 中所有元素的数组<T> T[] toArray(T[] a)
如果此 collection 对其迭代器返回的元素顺序做出了某些保证,那么此方法必须以相同的顺序返回这些元素。
像 toArray() 方法一样,此方法充当基于数组的 API 与基于 collection 的 API 之间的桥梁。更进一步说,此方法允许对输出数组的运行时类型进行精确控制,并且在某些情况下,可以用来节省分配开销。
假定 x 是只包含字符串的一个已知 collection。以下代码用来将 collection 转储到一个新分配的 String 数组:String[] y = x.toArray(new String[0]);
注意:toArray(new Object[0])
和toArray()
在功能上是相同的。
-
f、列表的读写器(Setter \ Getter)
(List 定义)
-
E get(int index)
返回列表中指定位置的元素。 -
E set(int index, E element)
用指定元素替换列表中指定位置的元素。 返回的是替换之前的值。
-
2、列表四种遍历方式
1、转换为对象数组 + for 循环
Object[] arrs = list.toArray();
for (int i = 0; i < arrs.length; i++)
{
String str = (String) arrs[i];
System.out.print(str + " ");
}
2、集合的遍历 iterator + for 、while
for (Iterator<String> it = list.iterator(); it.hasNext();)
{
String str = it.next();
System.out.print(str + " ");
}
/**---------------------------------*/
Iterator<String> it = list.iterator();
while (it.hasNext())
{
String str = it.next();
System.out.print(str + " ");
}
3、列表的遍历 listIterator + for、while
for (Iterator<String> iterator = list.listIterator(); iterator
.hasNext();)
{
String str = iterator.next();
System.out.print(str + " ");
}
/**---------------------------------*/
Iterator<String> iterator = list.listIterator();
while (iterator.hasNext())
{
String str = iterator.next();
System.out.print(str + " ");
}
4、Setter \ Getter + for 循环
for (int i = 0; i < list.size(); i++)
{
String str = list.get(i);
System.out.print(str + " ");
}
3、迭代器 (ListIterator)
public interface ListIterator<E> extends Iterator<E>
列表迭代器,允许程序员按任一方向遍历列表、迭代期间修改列表,并获得迭代器在列表中的当前位置。ListIterator 没有当前元素;它的光标位置 始终位于调用 previous() 所返回的元素和调用 next() 所返回的元素之间。
注意
:remove() 和 set(Object) 方法不是 根据光标位置定义的;它们是根据对调用 next() 或 previous() 所返回的最后一个元素的操作定义的。
逆向遍历:要实现逆向遍历,必须先正向遍历,将指针索引移动,故此,逆向遍历无意义
。
主要方法如下:
-
继承自 Iterator 的三个主要方法:向后遍历 + 移除
- 判断 :
boolean hasNext()
是否存在下一个元素 - 获取 :
E next()
指针后的元素 - 移除 :
void remove()
移除 next\previous 获取的元素
- 判断 :
-
ListIterator 特有的功能:逆向遍历
- 判断 :
boolean hasPrevious()
是否存在前一个元素 - 获取 :
E previous()
指针前的元素 - 索引 :
int previousIndex()
指针前的元素索引 - 索引 :
int nextIndex()
指针后的元素的索引 ,值等于previousIndex()+1
- 判断 :
-
ListIterator 特有的功能:修改值
- 添加 :
void add(E e)
指针位置之后 插入 元素 e - 设置 :
void set(E e)
设置 获取的元素
- 添加 :
示例:遍历字符串列表,如果存在元素“world”的话,就插入“JAVASE”
package com.rupeng.collection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ListDemo1
{
public static void main(String[] args)
{
// 创建并初始化 字符串列表
List<String> list = new ArrayList<String>();
list.add("hello");
list.add("world");
list.add("java");
// 使用 for 亦可
Iterator<String> it = list.listIterator();
while (it.hasNext())
{
String str = it.next();
if (str == "world")
{
list.add("JAVASE");
}
}
System.out.println(list);
}
}
说明:异常分析
1、现象:
java.util.ConcurrentModificationException
并发修改异常。当检测到对象不可以并法修改时,但进行了,抛出此异常。快速失败
2、原因:
当使用迭代器遍历时,不可通过集合修改元素(安全性)。也就是说,迭代器遍历元素时,集合修改元素,迭代器不可获知,也是产生了此异常
3、解决方案:
针对 List 集合 和 它特有的迭代器 ListIterator ,主要分为两种解决方式:
- 使用 List 集合的方法遍历修改【示例1:集合遍历元素,集合修改元素】;
- 通过 ListIterator 迭代器的修改值功能【示例2:迭代器遍历元素,迭代器修改元素】
示例1:List 集合方法重构
package com.rupeng.collection;
import java.util.ArrayList;
import java.util.List;
public class ListDemo2
{
public static void main(String[] args)
{
List<String> list = new ArrayList<String>();
list.add("hello");
list.add("world");
list.add("java");
/** 集合遍历 */
for (int i = 0; i < list.size(); i++)
{
String str = list.get(i);
if (str == "world")
{
/** 集合修改 */
/** 集合的添加功能默认是添加在集合的尾部 */
/** list.add("JAVASE"); */
list.add(i + 1, "JAVASE");
}
}
System.out.println(list);
}
}
示例2:ListIterator 迭代方法重构
package com.rupeng.collection;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class ListDemo2
{
public static void main(String[] args)
{
List<String> list = new ArrayList<String>();
list.add("hello");
list.add("world");
list.add("java");
/** 迭代器遍历 */
ListIterator<String> it = list.listIterator();
while (it.hasNext())
{
String str = it.next();
if (str == "world")
{
/** 迭代器修改 */
it.add("JAVASE");
}
}
System.out.println(list);
}
}
4、常用的数据结构
A:栈【Stack】
First In,Last Out
B:队列【Queue】
First in, First Out
C:数组【Array】
定义:存储同一种数据类型的多个元素容器、集合。需要连续的内存空间。有索引,方便操作。
从示例中,体会数组的优缺点:
int[] arr = {11, 22, 33, 44, 55};
1、插入:在元素33之后插入66
- 新建一个比原始数组长度大 1 的数组
int[] newArr = new int[arr.length+1];
- 遍历原始数组 33 (含)之前的元素,之后插入66,然后继续遍历原始数组
2、删除:删除元素 33
- 新建一个比原始数组长度小 1 的数组
int[] newArr = new int[arr.length-1];
- 遍历原始数组 33 之前的元素,遇到 33 跳过(continue),然后继续遍历原始数组
3、查询:获取元素 33 所在索引;获取具体索引位置元素
- 使用 for 循环遍历、比较元素
- 可以使用优化较好的算法进行查找,譬如:
Arrays.binarySearch(value)
二分查找
D:链表【LinkedList】
定义:多个类型的相同的元素通过链式存储方式组成的有序序列。不需要连续的内存空间。结构:每一个元素由数据域和指针域组成,数据域存储元素自身信息,指针域存储后继结点的位置。
从示例中,体会链表的优缺点:
1、插入:在元素 33 之后插入 66
- 创建元素 66 的存储(数据域为 66,指针域为 空)
- 遍历找到元素 33 (因链表结构,姑需遍历找)
- 将元素 33 的指针域赋给 元素 66 的指针域
- 将元素 33 的指针域修改为 元素 66 的地址
2、删除:删除元素 33
- 遍历找到元素 33 及其前一项
- 修改元素 33 的前项的指针域,改为 元素 33 的指针域内容。亦即将元素33 前一项直接指向 元素 33 的后项,将 元素 33 从链表中删除了。
3、查询:获取元素 33 所在索引;获取具体索引位置元素
- 从头遍历,对比指针域内容即可。
面试题
LinkedList 因为继承了Deque 双向队列,所有有一些 双向队列的特有功能
- 1、向集合首尾添加元素:
void addFirst(E e)
、void addLast(E e)
- 2、获取集合首尾的元素:
E getFirst()
、E getLast()
- 3、删除集合首尾元素:
E removeFirst()
、E removeLast()
其中有一些功能可以用来构造其他的数据结构。
/** MyStack 数据结构 */
package com.rupeng.collection;
import java.util.LinkedList;
import java.util.ListIterator;
public class MyStack<E>
{
private LinkedList<E> list;
public MyStack()
{
list = new LinkedList<E>();
}
public boolean empty()
{
if (list.isEmpty())
{
return true;
}
return false;
}
public E peek()
{
return list.getLast();
}
public E pop()
{
/** 返回并删除尾元素 */
return list.removeLast();
}
public E push(E item)
{
list.addLast(item);
return list.getLast();
}
@SuppressWarnings("unchecked")
public int search(Object o)
{
return search0((E) o);
}
private int search0(E e)
{
int index = -1;
ListIterator<E> it = list.listIterator();
while (it.hasNext())
{
E e2 = it.next();
if (e2 == e)
{
index = it.previousIndex();
}
}
return index;
}
@Override
public String toString()
{
return list.toString();
}
}
/** 测试自定义 MyStack 数据结构 */
package com.rupeng.collection;
public class MyStackDemo
{
public static void main(String[] args)
{
MyStack<String> ms = new MyStack<String>();
ms.push("Rupeng");
ms.push("Yzk");
ms.push("JoianSUN");
ms.push("JAVA");
System.out.println(ms);
ms.peek();
System.out.println(ms);
ms.pop();
System.out.println(ms);
ms.push("20150705");
System.out.println(ms);
int index = ms.search("Yzk");
System.out.println(index);
System.out.println(ms.empty());
}
}
实现方式1:使用额外的集合进行去重复 【空间换取时间、时间复杂度O(n)】
package com.rupeng.collection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/** 使用另一个 List 集合,遍历时执行相同值的过滤 */
public class RepeatStringDemo1
{
public static void main(String[] args)
{
List<String> list = new ArrayList<String>();
list.add("WWW");
list.add("Rupeng");
list.add("COM");
list.add("WWW");
list.add("Rupeng");
list.add("JAVA");
System.out.println(list);
List<String> anotherList = new ArrayList<String>();
for (Iterator<String> it = list.iterator(); it.hasNext();)
{
String str = it.next();
if (!anotherList.contains(str))
{
anotherList.add(str);
}
}
System.out.println(anotherList);
}
}
实现方式2:使用集合自身进行去重复 【时间换取空间、时间复杂度O(n2)】
/** 使用集合自身进行去重复 */
package com.rupeng.collection;
import java.util.ArrayList;
import java.util.List;
/**
* 使用自身进行值得过滤:
*
* 方法:第一个元素和第二个(含)以后所有的元素进行对比,重复就移除;
* 第二个和第三个(含)元素之后得所有元素对比,重复就移除;
* 以此类推,直到倒数第二个元素比较之后,就结束
*/
public class RepeatStringDemo2
{
public static void main(String[] args)
{
List<String> list = new ArrayList<String>();
list.add("WWW");
list.add("WWW");
list.add("Rupeng");
list.add("Rupeng");
list.add("COM");
list.add("JAVA");
list.add("Rupeng");
list.add("Rupeng");
list.add("JAVA");
System.out.println(list);
for (int i = 0; i < list.size() - 1; i++)
{
String str1 = list.get(i);
for (int j = i + 1; j < list.size(); j++)
{
String str2 = list.get(j);
if (str1 == str2)
{
list.remove(j);
/**
* 因为ArrayList自身长度的自增自减,
* 导致连续相同的元素会出现两次,
* 所以,对于同一个位置,需要存在相同元素再遍历以次
*/
j--;
}
}
}
System.out.println(list);
}
}
实现方式3:借助 Set 集合的唯一性、LinkedHashSet 的有序且唯一性实现去重复
package com.rupeng.practice;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/*
* 3、使用 Set 集合的唯一性特点实现;
* 存在一个缺点:会使得去重复之后的集合变得无序
* 但是可以使用 LinkedHashSet 实现,避免这个问题!
*/
public class ListDemo1
{
public static void main(String[] args)
{
List<String> list = new ArrayList<String>();
list.add("Hello");
list.add("Java");
list.add("Rupeng");
list.add("JavaSE");
list.add("Java");
list.add("Hello");
list.add("Rupeng");
/** 3、使用 Set 集合的唯一性特点实现 */
// (1)定义一个 LinkedHashSet 实现去重复
Set<String> set = new LinkedHashSet<String>();
// (2)遍历原始集合,将元素逐个添加到 set 中
for (String str : list)
{
set.add(str);
}
// (3)使用继承自 AbstractCollection的toString 打印集合
System.out.println("3、使用 Set 集合的唯一性特点实现 */");
System.out.println(set);
System.out.println("--------------------------------");
}
}
实现方式1:使用额外的集合去重 【空间换取时间、时间复杂度O(n)】
package com.rupeng.collection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class RepeatStudentDemo1
{
public static void main(String[] args)
{
Student s1 = new Student("Joian", 12);
Student s2 = new Student("Joian", 12);
Student s3 = new Student("Joian", 12);
Student s4 = new Student("Dou", 15);
Student s5 = new Student("Dou", 15);
Student s6 = new Student("Yzk", 18);
List<Student> list = new ArrayList<Student>();
list.add(s1);
list.add(s2);
list.add(s3);
list.add(s4);
list.add(s5);
list.add(s6);
System.out.println(list);
List<Student> anotherList = new ArrayList<Student>();
Iterator<Student> it = list.iterator();
while (it.hasNext())
{
Student stu = it.next();
if (!anotherList.contains(stu))
{
anotherList.add(stu);
}
}
System.out.println(anotherList);
}
}
查看 contains 方法的源码:
/** ArrayList 源代码 */
public class ArrayList<E>
{
public boolean contains(Object o)
{
return indexOf(o) >= 0;
}
public int indexOf(Object o)
{
if (o == null)
{
for (int i = 0; i < size; i++)
{
if (elementData[i] == null)
{
return i;
}
}
} else
{
for (int i = 0; i < size; i++)
{
if (o.equals(elementData[i]))
{
return i;
}
}
return -1;
}
}
}
从源码中可以看出来。实际上 contains 方法的底层实现依赖于 equals ,但是自定义对象 Student 中没有重写来自 Object 类的 equals 方法,导致进行 contains 方法的判断时,比较的是 元素的地址值。而 String 类重写了来自 Object 类 的 equals,所以没有此类问题。
实现方式2:使用集合自身去重 【时间换取空间、时间复杂度O(n2)】
package com.rupeng.collection;
import java.util.ArrayList;
import java.util.List;
public class RepeatStudentDemo2
{
public static void main(String[] args)
{
Student s1 = new Student("Joian", 12);
Student s2 = new Student("Joian", 12);
Student s3 = new Student("Joian", 12);
Student s4 = new Student("Dou", 15);
Student s5 = new Student("Dou", 15);
Student s6 = new Student("Yzk", 18);
List<Student> list = new ArrayList<Student>();
list.add(s1);
list.add(s2);
list.add(s3);
list.add(s4);
list.add(s5);
list.add(s6);
System.out.println(list);
for (int i = 0; i < list.size() - 1; i++)
{
Student stu1 = list.get(i);
for (int j = i + 1; j < list.size(); j++)
{
Student stu2 = list.get(j);
if (stu1.equals(stu2))
{
list.remove(j);
j--;
}
}
}
System.out.println(list);
}
}