集合(一)
一、集合框架的概述
1.集合、数组都是对多个数据进行存储操作的结构,简称JAVA容器。
说明:此时的存储,主要指的时内存层面的存储,不涉及到持久化的存储(.txt.jpg.avi)
2.1数组在存储多个数据方面的特点:
1)一旦初始化以后,其长度就确定了。
2)数组一旦定义好,其元素的类型也就确定了。我们也只能操作指定类型的数据了。
比如:String[] arr;int[] arr1;Object[] arr2;
2.2数组在存储多个数据方面的缺点:
1)一旦初始化以后,其长度就不可修改。
2)数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
3)获取数组照片那个的实际元素的个数的需求,数组没有现成的属性或方法可以用。
4)数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。
二、集合框架
Collection接口:单例集合,用来存储一个一个的对象
下面有List接口:存储有序的、可重复的数据 -->动态数组
实现类:ArrayList、LinkedList、Vector
和Set接口:存储无序的,不可重复的数据 -->高中讲的集合
实现类:HashSet、LinkedHashSet、TreeSet
Map接口:双列集合,用来存储一对(key-value)一对的数据 -->高中函数
实现类:HashMap、LinkedHashMap、TreeMap、Hashtable、Properties
public class CollectionTest {
@Test
public void test1() {
Collection coll= new ArrayList();
//add(Object e);将元素e添加到集合coll中
coll.add("AA");
coll.add("BB");
coll.add("CC");
coll.add("DD");
coll.add("EE");
System.out.println(coll.size());
//addAll(coll);将coll集合中的元素添加到当前集合中
Collection coll2= new ArrayList();
coll2.add(123);
coll2.add(456);
coll2.addAll(coll);
//System.out.println(colls.size());
System.out.println(coll2.size());
System.out.println(coll2);
//clear()清空集合元素
coll2.clear();
//isEmpty():判断当前集合是否为空
System.out.println(coll2.isEmpty());
}
}
三、 Colletion接口声明的方法的测试
向collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals()
public class CollectionTest {
@Test
public void test1() {
Collection coll = new ArrayList();
coll.add(123);
coll.add(434);
coll.add(new String("Tom"));
Person p = new Person("Tom2",22);
coll.add(p);
coll.add(new Person("Ton3",20));
//1.contains(Object obj)判断当前集合是否包含obj
//我们会在判断时会调用obj对象所在类的equals()
boolean contains = coll.contains(123);
System.out.println(contains);
System.out.println(coll.contains(new String("Tom")));
System.out.println(coll.contains(p));
System.out.println(coll.contains(new Person("Ton3",20)));
//2.containsAll(Collection coll);
Collection coll1 = Arrays.asList(123,456);
System.out.println(coll.containsAll(coll1));
}
@Test
public void test2() {
//3.remove(Object obj)从当前集合中移除obj元素
Collection coll = new ArrayList();
coll.add(123);
coll.add(434);
coll.add(new String("Tom"));
Person p = new Person("Tom2",22);
coll.add(p);
coll.add(new Person("Ton3",20));
coll.remove(123);
System.out.println(coll);
//4.removeAll(Collection coll2);差集:从当前集合移除coll2中所有元素
Collection coll2 = Arrays.asList(123,467);
coll.removeAll(coll2);
System.out.println(coll);
}
@Test
public void test3() {
Collection coll = new ArrayList();
coll.add(123);
coll.add(434);
coll.add(new String("Tom"));
//5.retainAll(Collection coll2):交集:获取当前集合和coll2集合的交集,并返回给当前集合
// Collection coll2 = Arrays.asList(123,467);
// coll.retainAll(coll2);
// System.out.println(coll);
//6.equals(Object obj):要想返回是true,需要当前集合和形参集合的元素都相同
Collection coll3 = new ArrayList();
coll3.add(123);
coll3.add(434);
coll3.add(new String("Tom"));
System.out.println(coll.equals(coll3));
}
@Test
public void test4() {
Collection coll = new ArrayList();
coll.add(123);
coll.add(434);
coll.add(new String("Tom"));
//7.hashCode():返回当前对象的哈希值
System.out.println(coll.hashCode());
//8.集合到数组:toArray();
Object[] arr = coll.toArray();
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
//把数组转换为集合:d调用Arrays类的静态方法asLIst();
List<String> list = Arrays.asList(new String[] {"AA","BB","CC"});
System.out.println(list);
// List arr1 = Arrays.asList(new int[] {123,456});
List arr1 = Arrays.asList(123,456);
System.out.println(arr1.size());
List arr2 = Arrays.asList(new Integer[] {123,456});
System.out.println(arr2.size());
//9.iterator():返回Iterator接口实例,用于遍历集合元素,放在IteratorTest.java中调试
}
}
Person类
public class Person {
private String name;
private int age;
public Person() {
}
public Person(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 "Person [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) {
System.out.println("Person equals()");
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;
}
}
四、集合元素遍历操作,使用迭代器Iterator接口
1.内部方法hasNext()和next();
2.集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前
3.内部定义remove(),可以在遍历的时候,删除集合中的元素。此方法不同于集合直接调用remove()
public class IterarorTest {
@Test
public void test1() {
Collection coll = new ArrayList();
coll.add(123);
coll.add(434);
coll.add(new String("Tom"));
Iterator iterator = coll.iterator();
// 方式一
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// //NoSuchElementException
// System.out.println(iterator.next());
// 方式二:不推荐
// for (int i = 0; i < coll.size(); i++) {
// System.out.println(iterator.next());
// }
//方式三
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
}
@Test
public void test2() {
Collection coll = new ArrayList();
coll.add(123);
coll.add(434);
coll.add(new String("Tom"));
Iterator iterator = coll.iterator();
//错误方式一
// while(iterator.next()!=null) {
// System.out.println(iterator.next());
// }
//错误方式二
//集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前
// while(coll.iterator().hasNext()) {
// System.out.println(coll.iterator().next());
// }
}
//测试Iterator中的remove()
//如果调用next()或在上一次调用next方法之后已经调用了remove方法
//再调用remove都会报IllegalSateException
@Test
public void test3() {
Collection coll = new ArrayList();
coll.add(123);
coll.add(434);
coll.add(new String("Tom"));
coll.add(new Person("DT",12));
Iterator iterator = coll.iterator();
//删除集合中"Tom"
while(iterator.hasNext()) {
Object obj = iterator.next();
if("Tom".equals(obj)) {
iterator.remove();
}
}
iterator = coll.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
JDK5.0新增了foreach循环,用于遍历集合和数组
public class ForTest {
@Test
public void test1() {
Collection coll = new ArrayList();
coll.add(123);
coll.add(434);
coll.add(new String("Tom"));
coll.add(new Person("DT",12));
//for(集合元素的类型 局部变量 : 集合对象)
//内部仍然使用了迭代器
for(Object obj : coll) {
System.out.println(obj);
}
}
@Test
public void test2() {
int[] arr = new int[] {1,2,3,4,5};
//for(数组元素的类型 局部变量 : 数组对象)
for(int i : arr) {
System.out.println(i);
}
}
@Test
public void test3() {
String[] arr = new String[]{"MM","MM","MM"};
//方式一:普通for赋值
// for(int i = 0;i<arr.length;i++) {
// arr[i] = "GG";
// }
//方式二:增强for循环
for(String s : arr) {
s="GG";
}
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
五、list接口框架
List接口:
存储有序的、可重复的数据 -->动态数组,替换原有的数组
实现类:
ArrayList:作为List接口的主要实现类,线程不安全,效率高
LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrrayList高;底层使用双向链表存储
Vector:作为List接口古老实现类,线程安全的,效率低;底层使用object[]elementData存储
2.ArrayList的源码分析:
2.1jdk7的情况下
ArrayList list = new ArrayList();//底层创建了长度是10的object[]数组elementData
list.add(123)//elementData[0] = new Integer(123);
…
list.add(11);如果此次的添加导致底层elementData数组容量不够,则扩容,默认情况下,扩容为原来的容量的1.5倍,同时需要将原来数组中的数组复制到新的数组中
结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
2.2jdk8中ArrayList的变化:
ArrayList list = new ArrayList();//底层Objcet[]elementData初始化为{}并没有创建长度为10的数组
list.add(123);//第一次调用add()时,底层才创建长度为10的数组,并将123添加到elementData[0]
…
后续的添加和扩容与JDK7一样
2.3小结:JDK7中的ArrayList的创建类类似于单例的饿汉式,而JDK8中的ArrayList的对象的创建
类似于单例的懒汉式,延迟了数组的创建,节省内存。
3.LinkedList的源码分析
LinkedList list = new LinkedList();内部声明了Node类型的first和last属性,默认值为null
list.add(123);将123封装到Node中,创建了Node对象,
Node定义为:体现了LinkedList的双向链表的说法
private static class Node {
E item;
Node next;
Node prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
4.Vector的源码分析
JDK7和JDK8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组,
在扩容方面,默认扩容为原来数组长度的两倍
总结:常用方法
增:add(Object obj)
删:remove(int index)/remove(Object obj)
改:set(int index, Object ele)
查:get(int index)
插:add(int index, Object ele)
长度:size()
遍历:①Iterator迭代器方式。②增强for循环。③普通循环
*/
public class ListTest {
private List<Integer> asList;
@Test
public void test1() {
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("AAA");
list.add(new Person("Tom",16));
list.add(123);
System.out.println(list);
//add()添加元素
list.add(1,"XX");
System.out.println(list);
//addAll()添加此集合的所有元素
List list1 = Arrays.asList(1,2,3);
list.addAll(list1);
System.out.println(list.size());
//Object get(int index):获取指定index位置的元素
System.out.println(list.get(0));
//int indexOf(Object obj):返回obj在集合中首次出现的位置。如果不存在返回-1
int indexOf = list.indexOf(456);
System.out.println(indexOf);
//int lastIndexOf(Object obj):返回在当前集合中末次出现的位置。如果不存在返回-1
System.out.println(list.lastIndexOf(123));
//Object remove(int index):移除指定index位置的元素,并返回此元素
Object obj = list.remove(0);
System.out.println(obj);
System.out.println(list);
//Object set(int index,Object ele):设置指定index位置的元素为ele
list.set(1, "KK");
System.out.println(list);
//List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的左闭右开区间的子集
List subList = list.subList(2, 5);
System.out.println(subList);
System.out.println(list);
//方式一:Iterator迭代器方式
System.out.println("方式一:");
Iterator iterator = list.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
System.out.println("方式二:");
for(Object obj1 : list) {
System.out.println(obj1);
}
System.out.println("方式三:");
for(int i = 0; i<list.size();i++) {
System.out.println(list.get(i));
}
}
}
/*
Set接口
存储无序的,不可重复的数据 -->高中讲的集合
实现类:
HashSet:作为set接口的主要实现类;线程不安全的;可以存储null值
LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
TreeSet:可以按照添加对象的指定属性,进行排序。
1.Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法。
2.要求:向Set中添加的数据,其所在的类一定要重写hashCode()和equals()
要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码
重写两个方法的小技巧:对象中用作equals()方法比较Field,都应该用来计算hashCode()
1.set:存储无序的、不可重复的数据
以HashSet为例说明
1).无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值
2).不可重复性:保证添加的元素按照equals()判断时,不能返回true。即:相同的元素只能添加一个。
2.添加元素的过程,以HashSet为例:
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算机元素a的哈希值
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(集为:索引位置),判断数组位置
上是否已经有元素:
如果此位置上没有其他元素,则元素a添加成功。—>情况1
如果此位置上有其他元素b,(或以链表形式存在的多个元素),则比较元素a与元素b的hash值
如果hash值不相同,则元素a添加成功—>情况2
如果hash值相同,进而需要调用元素a所在类的equals()方法
equals()返回true,元素a添加失败
equals()返回false,则元素a添加成功—>情况3
对于添加成功的情况2和情况3而言:元素a与已经存在指定索引位置上数据以链表的方式存储。
JDK7:元素a放到数组中,指向原来的元素
JDK8:原来的元素在数组中,指向元素a
总结:七上八下
HashSet底层:数组+链表的结构
public class SetTest {
@Test
public void test1() {
Set set = new HashSet();
set.add(456);
set.add(123);
set.add("AA");
set.add("CC");
set.add(new String("Tom"));
set.add(new User("Tom",13));
set.add(new User("Tom",13));
set.add(new User("Tom",13));
set.add(129);
Iterator iterator = set.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
}
//LinkedHashSet的使用
//LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个
//数据和后一个数据
//有点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet
@Test
public void test2() {
Set set = new LinkedHashSet();
set.add(456);
set.add(123);
set.add("AA");
set.add("CC");
set.add(new String("Tom"));
set.add(new User("Tom",13));
set.add(new User("Tom",13));
set.add(new User("Tom",13));
set.add(129);
Iterator iterator = set.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
TreeSet
1.向TreeSet中添加的数据,要求是相同的对象
2.两种排序方式:自然排序(实现Comparable接口)和定制排序(Comparator)
3.自然排序中,比较两个对象是否相同的标准为:compareTo()返回0,不再是equals()
4.制定排序中:比较两个对象是否相同的标准为:compare()返回0,不再是equals()。
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
import org.junit.Test;
public class TreeTest {
@Test
public void test1() {
TreeSet set = new TreeSet();
// 失败:不能添加不同类的对象
// set.add(123);
// set.add(456);
// set.add("AA");
// set.add(new User("Tom",12));
// 举例一:
// set.add(24);
// set.add(12);
// set.add(-12);
// 举例二
set.add(new User("Tom",12));
set.add(new User("jerry",22));
set.add(new User("jim",31));
set.add(new User("jack",45));
set.add(new User("jack",66));
Iterator iterator = set.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
}
@Test
public void test2() {
Comparator com = new Comparator() {
//按照年龄从小到大排序
@Override
public int compare(Object o1, Object o2) {
// TODO Auto-generated method stub
if(o1 instanceof User && o2 instanceof User) {
User u1 = (User)o1;
User u2 = (User)o2;
return Integer.compare(u1.getAge(), u2.getAge());
}else {
throw new RuntimeException("输入的数据类型不匹配");
}
}};
TreeSet set = new TreeSet(com);
set.add(new User("Tom",12));
set.add(new User("jerry",22));
set.add(new User("jim",31));
set.add(new User("jack",45));
set.add(new User("jack",66));
Iterator iterator = set.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
User类和上面代码配套使用
public class User implements Comparable{
private String name;
private int age;
public User() {
super();
}
public User(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 "User [name=" + name + ", age=" + age + "]";
}
@Override
public int hashCode() {
//return name.hashCode() + age;
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) {
System.out.println("User equals()....");
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) 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 int compareTo(Object o) {
if(o instanceof User) {
User user = (User)o;
// return -this.name.compareTo(user.name);
int compare = -this.name.compareTo(user.name);
if(compare!=0) {
return compare;
}else {
return Integer.compare(this.age, user.age);
}
}else {
throw new RuntimeException("输入的类型不匹配");
}
}
}
面试题ArrayList、LinkedList、Vector三者的异同
相同点:
三个类都实现了List接口,存储数据的特点相同,存储有序的、可重复的数据;底层使用object[]elementData存储
不同点:
ArrayList:作为List接口的主要实现类,线程不安全,效率高
LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrrayList高;底层使用双向链表存储
Vector:作为List接口古老实现类,线程安全的,效率低;底层使用object[]elementData存储
集合Collection中存储的如果是自定义的对象,需要自定义类重写那个方法?为什么?
equals()方法。contains()/remove()/retainsAll();
List:equals()方法
Set:(HashSet、LinkedHashSet为例):equals()、hashCode()
TreeSet:Comparable:CompareTo(Object obj)
Comparator:compare(Object o1,object o2)
set存储数据的特点是什么?常见的实现类有什么?说明一下彼此的特点。
HashSet对应HashMap
LinkedHashSet对应LinkedHashMap
TreeSet对应TreeMap
区分list中remove(int index)和remove(Object obj)
默认是在索引,newInteger删除对象2
public class ListExer {
@Test
public void testListRemove() {
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
updateList(list);
System.out.println(list);
}
private void updateList(List list) {
// list.remove(2);
list.remove(new Integer(2));
}
}