标签:
JAVA集合类是一个特别有用的工具类,可用于存储数量不确定的对象,并可以实现常用的数据结构,如栈,队列等。除此之外集合还可用于存储具有映射关系的关联数组。
JAVA集合大致可以分为Set,List,Map,Queue四种体系,其中Set代表无序不可重复的集合;List代表有序可重复的集合,Map代表具有映射关系的集合,java5又新增了Queue,代表一种队列集合实现。如果想要访问List集合的元素,可以通过索引来访问,访问Map集合的元素,通过key值来访问value值,访问Set集合元素,只能通过元素本身来访问这也是Set集合元素不可重复的原因。
集合和数组的主要区别:
1>数组是定长的,而集合的长度可变
2>数组可以存储基本数据类型和对象,而集合只能存储对象(实际上是对象的引用,习惯上称为对象)
Collection接口和Iterator接口:
Collection接口是Set,List,Queue接口的父接口,该接口里定义的方法同样可用于操作Set,List,Queue集合。下面是一些常用的方法,更多详细的方法请参见java的API
Iterator iterator() 返回一个Iterator对象,用于遍历集合
boolean add(Object o) 向集合中添加一个元素,如果集合别添加操作改变了,则返回true
boolean addAll(Collection c) 把集合中所有元素都添加到指定集合中,如果集合被添加操作改变了,则返回true
void clear() 清除集合中的所有元素,是的集合的长度变为0
boolean remove(Object o) ; 删除集合中指定的元素,如果集合中包含多个指定元素,则只删除第一个符合条件的
boolean contains(Object o) ; 判断集合中是否包含指定元素,包含则返回true
boolean isEmpty(); 判断集合是否为空,若为空则返回true
int size() ; 返回集合元素的个数
Object[] toArray() ; 把集合转换为数组,所有的集合元素变成对应的数组元素
import java.util.*;
public class CollectionTest
{
public static void main(String[] args)
{
Collection c1 = new ArrayList();
c1.add("孙悟空");
c1.add("猪八戒");
c1.add(6); //虽然集合不能存储基本数据类型,但java支持自动装箱
System.out.println(c1.size()); //3
System.out.println(c1); //[孙悟空, 猪八戒, 6],集合都实现了toString()方法
c1.remove("猪八戒");
System.out.println(c1.size()); //2
Collection c2 = new HashSet();
c2.add("孙悟空");
c2.add(66);
c1.addAll(c2);
System.out.println(c1); //[孙悟空, 6, 66, 孙悟空] ,元素可以重复
c2.addAll(c1);
System.out.println(c2); //[6, 66, 孙悟空] ,元素不重复
System.out.println(c1.contains("猪八戒")); //false
c1.removeAll(c2);
System.out.println(c1); //[] 两个孙悟空都会被删除
Object[] obj = c2.toArray();
//obj.add("张三"); 变成定长数组,不能再添加元素
c2.clear();
System.out.println(c2); //[] 数组被清空
}
}
虽然两个集合的实现类不同,但是当作Collection来操作时使用上面的方法并没有任何区别
Lambda表达式遍历集合:
import java.util.*;
public class Iterator
{
public static void main(String[] args)
{
Collection c = new HashSet();
c.add("孙悟空");
c.add("猪八戒");
c.add("唐僧");
c.forEach(obj->System.out.println(obj));
}
}
Iterator迭代器
import java.util.*;
public class Iterat
{
public static void main(String[] args)
{
Collection c = new HashSet();
c.add("孙悟空");
c.add("猪八戒");
c.add("唐僧");
Iterator it = c.iterator(); //获得集合的迭代器对象
it.forEachRemaining(obj->System.out.println(obj)); //直接用java8新增的方法遍历集合元素
while(it.hasNext())
{
String p = (String) it.next();
System.out.println(p);
if(p == "猪八戒")
{
//c.remove(p); 1...将会产生异常
p = "沙和尚"; //2... 不会改变集合元素
it.remove();
}
}
System.out.println(c);
}
}
Iterator接口也是集合框架中的重要成员,它主要用于迭代访问Collection中的元素,所以Iterator的对象也被称为迭代器。Iterator依附于Collection集合,若没有Collection,Iterator将没有存在的意义
由上面的代码看出,Iterator接口提供了两种方法遍历Collection中的元素,还可以使用remove()删除迭代指针指向的元素。这里需要注意,在迭代器迭代Collection集合时,集合的元素不能被改变,只能通过迭代器的remove()方法删除元素,因为迭代器本身对这种改变是可预知的,否则将会产生运行时异常,如1....处的测试代码
再看2...处的测试代码,最终的集合输出结果并没有得到“沙和尚”这个字符串,这可以得出结论,当使用Iterator迭代访问Collection时,并不把几何元素本身传递给迭代变量,而是把集合元素的值传递给迭代变量,所以修改迭代变量的值并不会对改变集合
使用foreach遍历集合:
for (Object object : c)
{
System.out.println(object); //会引发异常
c.remove(object);
}
使用Predicate操作集合:
public class Predica
{
public static void main(String[] args)
{
Collection books = new ArrayList();
books.add("操作系统");
books.add("剑指offer");
books.add("疯狂java讲义");
books.add("java从入门到精通");
books.removeIf(book->((String) book).length()<10); //将满足条件的都删除
System.out.println(books);
}
}Predicate的主要作用是筛选符合要求的集合元素。例如需要统计书名中包含“java”的书籍;统计长度不小于10的书;统计出现“疯狂”字样的书。如果按照之前的方法去做,则要用到循环,若是分别统计三种情况,则需三次循环,很麻烦。但是使用Predicate则要方便很多
import java.util.*;
import java.util.function.Predicate;
public class Predica
{
@SuppressWarnings("all")
public static void main(String[] args)
{
Collection books = new HashSet();
books.add(new String("操作系统"));
books.add(new String("剑指offer"));
books.add(new String("疯狂java讲义"));
books.add(new String("java从入门到精通"));
System.out.println(total(books,ele->((String) ele).length()<10)); //3
System.out.println(total(books, ele->((String) ele).contains("java"))); //2
System.out.println(total(books, ele->((String) ele).contains("疯狂"))); //1
}
public static int total(Collection c , Predicate p)
{
int cnt = 0;
for (Object object : c)
{
if(p.test(object)) //满足筛选条件
cnt++;
}
return cnt;
}
}
import java.util.*;
import java.util.function.Predicate;
public class Predica
{
@SuppressWarnings("all")
public static void main(String[] args)
{
Collection books = new HashSet();
books.add(new String("操作系统"));
books.add(new String("剑指offer"));
books.add(new String("疯狂java讲义"));
books.add(new String("java从入门到精通"));
System.out.println(books.stream().filter(ele->((String) ele).length()<10).count());
System.out.println(books.stream().filter(ele->((String) ele).contains("java")).count());
System.out.println(books.stream().filter(ele->((String) ele).contains("疯狂")).count());
//附加代码用于进一步了解Stream对集合的操作
books.stream().mapToInt(ele->((String) ele).length()).forEach(obj->System.out.println(obj));
//获得集合的Stream之后,可以对集合整体进行操作
System.out.println(books.stream().count());
}
}
使用Stream省去了对集合的遍历操作来判断集合元素是否满足条件,由于Stream使用较少,更多Stream的用法参考API文档。
Set集合:
Set集合就像一个罐子,不能记住元素添加的顺序,也不允许元素重复,若添加重复元素,add()方法会返回false
HashSet类:
HashSet类是Set接口典型的实现类,也是我们经常使用到的类,线程不安全,元素值可以为空,通过hash算法决定元素的存储位置,因此具有较好的存取和查找性能
下面我们来看一看HashSet判断集合元素相等的标准
import java.util.Collection;
import java.util.HashSet;
//A类只重写equals方法,总是返回true
class A
{
public boolean equals(Object obj)
{
return true;
}
}
//B类重写hashCode方法,总是返回相同的值
class B
{
public int hashCode()
{
return 1;
}
}
//C类重写两个方法
class C
{
public boolean equals(Object obj)
{
return true;
}
public int hashCode()
{
return 2; //注意,这里不能再返回1,因为类C的hashCode()返回值为1,不然C的对象都添加失败
}
}
public class HashSetTest
{
@SuppressWarnings("all")
public static void main(String[] args)
{
Collection hs = new HashSet();
hs.add(new A());
hs.add(new A());
hs.add(new B());
hs.add(new B());
System.out.println(hs.add(new C()));
System.out.println(hs.add(new C()));
System.out.println(hs.size());
System.out.println(hs);
}
}
如果要把某个类的对象保存到HashSet中,在重写类的equals()和hashCode()方法时,要尽量保证两者返回值的一致性,即equals()判断元素相等,则他们的哈希值就相等,通常equals()方法中用于比较元素相等的实例变量,都应该用于计算哈希值
如果集合中包含可变元素,若修改了用于判断相等与计算哈希值的实例变量,将导致无法正确操作集合中被修改的元素。看一个例子:
import java.util.*;
class Q
{
int number;
public Q(int number)
{
this.number = number;
}
public boolean equals(Object obj)
{
if(this == obj)
return true;
if(obj!=null&obj.getClass()==Q.class)
{
Q s = (Q)obj;
return s.number == this.number;
}
return false;
}
public int hashCode()
{
return this.number;
}
public String toString()
{
return "number:"+number;
}
}
public class HashSetTest2
{
public static void main(String[] args)
{
Collection N = new HashSet();
N.add(new Q(-1));
N.add(new Q(0));
N.add(new Q(1));
N.add(new Q(2));
System.out.println(N); //[number:-1, number:0, number:1, number:2]
Iterator it = N.iterator();
Q first = (Q) it.next(); //将对象的引用传给first
first.number=2;//将第一个元素修改成与第二个元素相等的元素,equals,hashCode
System.out.println(N); //[number:2, number:0, number:1, number:2]
N.remove(new Q(2));
System.out.println(N); //[number:2, number:0, number:1]只删除了未被修改过的number为2的对象
System.out.println("集合中是否包含number为2的元素?"+N.contains(new Q(2))); //false
N.remove(new Q(2));
System.out.println(N);//[number:2, number:0, number:1],无法删除被修改过的第一个元素
System.out.println("集合中是否包含number为-1的元素?"+N.contains(new Q(-1))); //false
N.remove(new Q(-1)); //同样无法删除第一个元素
}
}
LinkedHashSet类:
LInkedHashSet是HashSet的子类,它用链表维护了元素的插入顺序,因此性能略低于HashSet,但也是由于它用链表维护了内部顺序,所以在迭代访问数组元素时,效率较高。依然不允许元素重复
TreeSet类:
TreeSet是SortedSet接口的实现类,正如SortedSet名字所暗示的一样,TreeSet确保集合中的元素处于排序状态,注意是排序状态,而不是有序状态
下面是TreeSet较HashSet多出来的几种常见用法:
public class TreeSetTest
{
public static void main(String[] args)
{
SortedSet c = new TreeSet(); //此处不能再使用Collection接口
c.add(0);
c.add(6);
c.add(-1);
c.add(4);
System.out.println(c); //[-1, 0, 4, 6] 已排好序
System.out.println(c.first());
System.out.println(c.last());
System.out.println(c.headSet(2)); //[-1,0] 边界可以不是集合中的元素 不包括边界
System.out.println(c.tailSet(4)); //[4,6] 包括边界
System.out.println(c.subSet(0, 4)); //[0] 包括上边界 不包括下边界
System.out.println(((TreeSet) c).lower(0)); //比0小的第一个元素
}
}自然排序:
java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法用于比较连个元素的大小,obj1.compareTo(Object obj2),若返回值为正数,则obj1大于obj2,若为负数,则obj2大,若返回值为0,则表示相等
若试图把某一对象添加到TreeSet和中,则该对象的类必须实现Comparable接口,并将比较对象强制转换为相同类型,也就是说想TreeSet中添加的对象必须为同一个类对象
public class TreeSetTest
{
public static void main(String[] args)
{
SortedSet c = new TreeSet();
c.add(new String());
c.add(new Date()); //引发异常
}
}
import java.util.*;
class Person implements Comparable
{
int age;
public Person(int age)
{
this.age = age;
}
public int compareTo(Object o)
{
return 1;
}
public boolean equals(Object obj)
{
return true;
}
public String toString()
{
return "age:"+age;
}
}
public class TreeSetTest
{
public static void main(String[] args)
{
Person p = new Person(22);
TreeSet ts = new TreeSet();
ts.add(p);
ts.add(p); //虽然equals返回true,但是compareTo方法返回1,则会判定对象与本身都会不相等
System.out.println(ts); //[age:22, age:22]添加成功
}
}
由上面代码得出结论,TreeSet判定两个对象是否相等的条件只有compareTo()方法,若方法返回值为0,则两个对象视为相等,无法重复添加。但是如果equals判断对象相等,但compareTo返回值不等于0,则违反了Set集合元素不重复的规则,若equals返回不相等,但compareTo返回值为0,则无法正确添加元素,所以要保证equals与compareTo方法的返回值一致。
与HashSet集合相同,如果修改了集合中保存的可变对象用于判断equals和compareTo的变量,将导致钙元素不可操控,所以为了程序更加健壮,不要轻易修改集合元素的关键实例变量
定制排序:
TreeSet ts = new TreeSet((o1,o2)->
{
stu s1 = (stu)o1;
stu s2 = (stu)o2;
return s1.age>s2.age?-1:s1.age<s2.age?1:0;
}
);
创建TreeSet时键入Comparator比较器,可以指定排列顺序,判断两个元素是否相等的依据便是compare方法的返回值是否为0。
EnumSet类:
EnumSet是专为枚举类设计的集合类,EnumSet集合元素也是有序的,以EnumSet中枚举值在Enum类中的定义顺序来决定集合元素的顺序。且集合中的元素不能为空,否则会引发异常。
EnumSet类没有任何构造器,创建对象只能使用静态方法
import java.util.*;
enum Season
{
spring,summer,fall,winter;
}
public class EnumSetTest
{
public static void main(String[] args)
{
//创建EnumSet集合,集合元素就是Session枚举类全部枚举值
EnumSet es1 = EnumSet.allOf(Season.class);
System.out.println(es1);
//创建空的EnumSet
EnumSet es2 = EnumSet.noneOf(Season.class);
System.out.println(es2);
//手动添加值
es2.add(Season.spring);
es2.add(Season.winter);
System.out.println(es2);
EnumSet es3 = EnumSet.of(Season.winter, Season.summer);
System.out.println(es3); //自动调整顺序
EnumSet es4 = EnumSet.range(Season.summer, Season.winter); //这里前后顺序不能颠倒
System.out.println(es4);
EnumSet es5 = EnumSet.complementOf(es4); //es4+es5=Season
System.out.println(es5);
}
}
各Set实现类的性能分析:
HashSet的性能总是比TreeSet好,尤其是查询,添加元素等操作,由于TreeSet内部需要额外维护一个红黑树,只有当总是需要保持一个排好序的集合时,才考虑使用TreeSet
HashSet还有一个子类LinkedHashSet,它的查询和删除等操作等性能都要比HashSet差,因为它额外维护了一个链表,但是对于遍历操作,则会有更好的效率
EnumSet是所有Set实现类中性能最好的,但是它的取值有限。
List集合:
List是个有序,可重复的集合,集合中每个元素都有其对应的顺序索引,所以在Collection集合操作方法的基础上增加了一些使用索引的方法。下面列举一些常用的方法
import java.util.*;
public class ListTest
{
@SuppressWarnings("all")
public static void main(String[] args)
{
List list = new ArrayList();
list.add(new String("孙悟空"));
list.add(new String("猪八戒"));
list.add(new String("沙和尚"));
System.out.println(list); //[孙悟空, 猪八戒, 沙和尚] 有序
list.add(1, new String("唐僧"));
System.out.println(list); //[孙悟空, 唐僧, 猪八戒, 沙和尚]添加到索引为1的位置
System.out.println(list.get(0)); //孙悟空
list.remove(0);
System.out.println(list); //[唐僧, 猪八戒, 沙和尚]
list.set(1, new String("唐僧"));
System.out.println(list); //[唐僧, 唐僧, 沙和尚] 元素可重复
System.out.println(list.indexOf(new String("唐僧")));//0,第一次出现时索引的位置
System.out.println(list.subList(0, 2)); //[唐僧, 唐僧],包括0,不包括2
}
}
import java.util.*;
class MyComparator implements Comparator
{
public int compare(Object o1, Object o2)
{
String obj1 = (String)o1;
String obj2 = (String)o2;
return obj1.length()>obj2.length()?1:obj1.length()<obj2.length()?-1:0;
}
}
public class ListTest2
{
@SuppressWarnings("all")
public static void main(String[] args)
{
List books = new ArrayList();
books.add(new String("Java编程思想"));
books.add(new String("Linux程序设计"));
books.add(new String("鸟哥私房菜"));
System.out.println(books); //[Java编程思想, Linux程序设计, 鸟哥私房菜]
books.sort(new MyComparator());
System.out.println(books); //[鸟哥私房菜, Java编程思想, Linux程序设计],按长度从小到大排序
//也可以使用lambda表达式
books.sort((o1,o2)->((String)o2).length()-((String)o1).length()); //1.....
System.out.println(books); //[Linux程序设计, Java编程思想, 鸟哥私房菜],反序输出
books.removeIf(ele->((String)ele).length()<8); //删除符合过滤条件的元素 //2.....
System.out.println(books);
books.replaceAll(ele->((String)ele).length()); //3.....
System.out.println(books);//[9, 8],将元素都替换成其对应长度
}
}import java.util.*;
public class ListTest3
{
@SuppressWarnings("all")
public static void main(String[] args)
{
List books = new ArrayList();
books.add(new String("Java编程思想"));
books.add(new String("Linux程序设计"));
books.add(new String("鸟哥私房菜"));
ListIterator it = books.listIterator();
while(it.hasNext())
{
System.out.println(it.next());
it.add("------分隔符-------"); //在迭代指针的位置添加字符串
}
System.out.println("=========开始反向迭代=========");
while(it.hasPrevious())
{
System.out.println(it.previous());
it.remove();
}
System.out.println(books);
}
}ArrayList和Vector都是List的实现类,Vector是个比较古老的集合,早在jdk1.0的时候就存在,所以包含了很多名称很长的方法,后来让它继承了List集合,所以一些功能会有重复。这两个实现类最主要的区别就是Vector是线程安全的
而ArrayList是线程不安全的,所以ArrayList的效率要高一些。他们都封装了一个动态的,可重新分配的Object[]数组,初始长度为10(也可以指定),Vectory可以设置存储空间增加的数量,而ArrayList不可以。
Vectory还有一个子类stack,用于模拟栈,同样线程安全,性能较差,所以渐渐的被ArrayDeque所取代。
在多线程并发的环境中,可以使用Collections工具类将ArrayList包装成为线程安全的。所以Vectory很少用
固定长度的List集合:
List fixedList = (List) Arrays.asList("孙悟空","猪八戒","沙和尚");
System.out.println(fixedList); //[孙悟空, 猪八戒, 沙和尚],变成数组
fixedList.forEach(ele->System.out.println(ele));
fixedList.add("唐僧"); //异常
fixedList.remove("孙悟空"); //异常
该List集合是Arrays的内部类ArrayList的实例,固定长度,不能增加或删除元素,但是可以进行修改操作
标签:
原文地址:http://blog.csdn.net/liuxins/article/details/51216949