【黑马程序员】第七章:集合

------- android培训java培训、期待与您交流! ----------


一、集合概述

   在前面的章节中介绍过在程序中可以通过数组来保存多个对象,但在某些情况下无法确定到底需要保存多少个对象, 此时数组将不再使用,因为数组的厂部不可变。为了保存这些数目不确定的对象,JDK  中提供了一系列特殊的类,这些类可以存储任意类型的对象,并且长度可变,统称为集合。

   集合按照其存储结构可以分为两大类,即单列集合 Collection 和双列集合 Map,这两个集合的特点如下:

  • Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是 lIst 和 Set。其中 List 的特点是元素有序、元素可重复。Set 的特点是元素无序并且不可重复。List 接口的主要实现类由 ArrayList 和 LinkedList,Set 接口的主要实现类有 HashSet 和 TreeSet。
  • Map:双列集合类的根接口,用于存储具有键(key)和值(value)映射关系的元素,每个元素都包含一对键值,在使用 Map 集合时可以通过制定的 Key 找到对应的Value。Map 接口的主要实现类有 HashMap 和 TreeMap。

二、Cllection 接口

   Collection 是所有单列集合的父接口,因此在 Collection 中定义了单列集合(List 和 Set)通用的一些方法,这些方法可以用于操作所有的单列集合。

三、List 接口

1、List 接口简介

   List 接口继承自 Collection 接口,是单列集合的一个重要分支,习惯性地会将实现了 List 接口的对象成为 List 集合。在 List 结合中允许出现重复的元素,所有的元素是以一种线型方式进行存储的,在程序中可以通过索引来访问集合中的指定元素。另外 List 结合还有一个特点就是元素有序,即元素的存入顺序与取出顺序一致。

   List 作为 Collection 集合的子接口,不但集成了 Collection 接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法。


2、ArrayList 集合

   ArrayList 是 List 接口的一个实现类,它是程序中最常见的一种集合。在 ArrayList 内部封装了一个长度可变的数组对象,当存入的元素超过数组长度时,ArrayList 会在内存中分配一个长度可变的数组对象,当存入的元素超过数组长度时,ArrayList 会在内存中分配一个更大的数组来存储这些元素,因此可以将ArrayList 集合看做是一个长度可变的数组。

   ArrayList 集合中大部分的方法都是从父类 Collection 和 List 继承过来的,其中 add() 方法和 get() 方法用于实现元素的存取。

import java.util.*;
class ArrayListDemo{
	public static void main(String[] args){
		ArrayList list = new ArrayList();
		list.add("stu1");
		list.add("stu2");
		list.add("stu3");
		System.out.println("集合的长度为"+list.size());
		System.out.println("第二个元素为"+list.get(1));
	}
}

   由于 ArrayList 集合底层是使用一个数组来保存元素,在增加或删除指定位置的元素时,会导致创建新的数组,效率比较低,因此不适合做大量的增删操作。蛋这种数组的结构允许程序通过索引的方式来访问元素,因此使用 ArrayList 集合查找元素很方便。

3、LinkedList 集合

   LinkedList 集合内部维护了一个双向循环链表,链表中的每一个元素都使用引用的方式来记住它的前一个元素和后一个元素,从而可以将所有的元素彼此连接起来。当插入新元素时,只需要修改元素之间的这种引用关系接客,删除一个节点也是如此。因为这样的存储结构,所以 LinkedList 集合对于元素增删操作具有很高的效率。

import java.util.*;
class LinkedListDemo{
	public static void main(String[] args){
		LinkedList link = new LinkedList();
		link.add("stu1");
		link.add("stu2");
		link.add("stu3");
		link.add("stu4");
		System.out.println(link.toString());

		link.add(3,"Student");
		link.addFirst("First");
		System.out.println(link);
		System.out.println(link.getFirst());

		link.remove(3);
		link.removeFirst();
		System.out.println(link);
	}
}

4、Iterator 接口

   在程序开发中,经常需要遍历集合中的所有元素,针对这种需求,JDK 专门提供了一个接口 Iterator。Iterator 接口也是 Java 集合框架中的一员,但它与 Collection 、Map接口有所不同,Collection 和 Map 接口主要用于存储元素,而 Iterator 主要用于迭代访问(即遍历)Collection 中的元素,因此 Iterator 对象也被成为迭代器。

import java.util.*;
class IteratorDemo{
	public static void main(String[] args){
		ArrayList list = new ArrayList();
		list.add("data_1");
		list.add("data_2");
		list.add("data_3");
		list.add("data_4");
		Iterator it = list.iterator();
		while(it.hasNext()){
			Object obj = it.next();
			System.out.println(obj);
		}
}

   需要注意的是:

      1)、当通过迭代器获取 ArrayList 集合中的元素时,都会将这些元素当做 Object 类型看待,如果想要得到特定类型的元素,则需要进行强制类型转换。

      2)、在使用 Iterator 迭代器对集合中的元素进行迭代时,如果调用了集合对象的 remove() 方法区删除元素,会出现异常。为了解决这个问题,可以以下采用两种方式:

  • 在 list.remove(obj) 代码后面添加一个 break 语句;
  • 使用迭代器本身的删除方法:iterator.remove()。

5、foreach 循环

   虽然 Iterator 可以用来遍历集合中的元素,但在协防上比较繁琐,为了简化书写,从 JDK5.0 开始提供了 foreach 循环。foreach 循环是一种更加简洁的 for 循环,也称作增强 for 循环。foreach 循环用于遍历数组或集合中的元素,具体语法格式如下:

for(容器中的元素类型 临时变量:容器变量){
	执行语句;
}

   foreach 循环虽然书写起来很简洁,但在使用也存在一定的局限性:

  • 当使用 foreach 循环遍历集合和数组时,只能访问集合中的元素,不能对其中的元素进行修改。

6、ListIterator 接口

   Iterator 迭代器提供了 hasNext() 方法和 next() 方法,通过这两个方法可以实现集合中的元素的迭代,迭代的方向是从集合中的第一个元素向最后一个元素迭代,也就是所谓的正向迭代。为了是迭代方式更加多元化,JDK 还定义了一个 ListIterator 迭代器,它是 Iterator 的子类,该类在父类的基础上增加了一些特有的方法。

   ListIterator 中提供了 hasPrevious() 方法和 previous() 方法,通过这两个方法可以实现反向迭代元素,另外提供了 add() 方法用于添加元素。

import java.util.*;
class ListIteratorDemo{
	public static void main(String[] args){
		ArrayList list = new ArrayList();
		list.add("data_1");
		list.add("data_2");
		list.add("data_3");
		System.out.println(list);

		ListIterator it = list.listIterator(list.size());
		while(it.hasPrevious()){
			Object obj = it.previous();
			System.out.println(obj+"");
		}
	}
}

7、Enumeration 接口

   在 JDK1.2 以前还没有 Iterator 接口的时候,遍历集合需要使用 Enumeration 接口,它的用法和 Iterator 类似。由于很多程序中依然在使用 Enumeration ,因此了解该接口的用法是很有必要的。JDK  中提供了一个 Vector 集合,该集合是 List 接口的一个实现类,用法与 ArrayList 完全相同,区别在于 Vector 集合是线程安全的,而 ArrayList 集合是线程不安全的。在 Vector 类中提供了一个 elements() 方法返回 Enumeration 对象,通过 Enumeration 对象就可以遍历该集合中的元素。

import java.util.*;
class ListIteratorDemo{
	public static void main(String[] args){
		Vector vector = new Vector();
		vector.add("data_1");
		vector.add("data_2");
		vector.add("data_3");

		Element en = vector.elements();
		while(en.hasMoreElements()){
			Object obj = it.nextElement();
			System.out.println(obj);
		}
	}
}

四、Set 接口

1、Set 接口简介

   Set 接口和 List 接口一样,同样继承自 Collection 接口,它与 Collection 接口中的方法基本一致,并没有对 Collection 接口进行功能上的扩充,只是比 Collection 接口更加严格了。与 List 接口不同的是,Set 接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。

   Set 接口主要有两个实现类,分别是 HashSet 和 TreeSet。其中 HashSet 是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。TreeSet 则是以二叉树的方式来存储元素,它可以实现对集合中的元素进行排序。

2、HashSet 集合

   HashSet 是 Set 集合的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的。当向 HashSet 集合中添加一个对象时,首先会调用该对象的 hashCode() 方法来确定元素的存储位置,然后再调用对象的 equals() 方法来确保该位置没有重复元素。

   HashSet 集合之所以能确保不出现重复元素,是因为它在存入元素时做了许多工作。当调用 HashSet 集合的 add() 方法存入元素时,首先调用当前存入对象的 hashCode() 方法获得对象的哈希值,然后根据对象的哈希值计算出一个存储位置。如果该位置上没有元素,则直接将元素存入,如果该位置上有元素存在,则会调用 equals() 方法让单签存入的元素一次和该位置上的元素比较,如果返回的结果为 false 就将钙元素存入集合,返回的结果为 true则说明有重复元素,就将该元素舍弃。

          

3、TreeSet 集合

   TreeSet 是 Set 接口的另一个实现类,它内部采用自平衡的排序二叉树来存储元素,这样的结构可以保证 TreeSet 集合中没有重复的元素,并且可以对元素进行排序。二叉树的存储结构如下:

                  

   TreeSet 的两种比较方式:

  • 自然排序:让元素自身具备比较性,需要实现 Comparable 接口,覆盖 compareTo() 方法。

import java.util.*;
class Student implements Comparable{
	String name;
	int age;
	public Student(String name,int age){
		this.name = name;
		this.age = age;
	}
	public void setName(String name){
		this.name = name;
	}
	public String getName(){
		return name;
	}
	public void setAge(int age){
		this.age = age;
	}
	public int getAge(){
		return age;
	}
	public int hashCode(){
		return name.hashCode()+age;
	}
	public boolean equals(Object obj){
		Student s = (Student)obj;
		return this.name.equals(s.name) && this.age == s.age;
	}
	public int compareTo(Object obj){
		Student s = (Student)obj;
		if(this.age==s.age)
			return this.name.compareTo(s.name);
		return this.age-s.age;
	}
	public String toString(){
		return name+":"+age;
	}
}
class TreeSetDemo1{
	public static void main(String[] args){
		TreeSet<Student> t=new TreeSet<Student>();  
        t.add(new Student("wo1",12));  
        t.add(new Student("wosj",2));  
        t.add(new Student("wo1sdj",12));  
        t.add(new Student("wo6sd",12));  
        t.add(new Student("wo",22));  
  
          
        for (Iterator<Student> it=t.iterator(); it.hasNext(); )  
        {  
            Student s=it.next();  
            System.out.println(s.getName()+"....."+s.getAge());  
        }  
	}
}

  • 比较器:当元素不具备比较性,或具备的比较性不是所需的,这时需要让集合具备比较性。

         在集合初始化时,就有了比较方式,定义一个比较器,将比较器对象作为参数传递给 TreeSet 集合的构造函数,

class  TreeSetDemo{  
    public static void main(String[] args){  
        TreeSet<Student> t=new TreeSet<Student>(new LenCompare());  
        t.add(new Student("wo1",12));  
        t.add(new Student("wosj",2));  
        t.add(new Student("wo1sdj",12));  
        t.add(new Student("wo6sd",12));  
        t.add(new Student("wo",22));  
          
        for (Iterator<Student> it=t.iterator(); it.hasNext(); ) {  
            Student s=it.next();  
            System.out.println(s.getName()+"....."+s.getAge());  
        }  
    }  
  
}  
//定义一个比较器,以姓名长度为主要比较  
class LenCompare implements Comparator<Student> {  
    public int compare(Student s1,Student s2){  
        int num=new Integer(s1.getName().length()).compareTo(new Integer(s2.getName().length()));  
        if (num==0)
            return new Integer(s1.getAge()).compareTo(s2.getAge());  
        return num;  
    }  
}  

五、Map 接口

1、Map接口简介

   在应用程序中,如果想存储这种具有对应关系的数据,则需要使用 JDK 中提供的 Map 接口。Map 接口是一种双列集合,它的每个元素都包含一个键对象 Key 和一个值对象 Value,键和值对象之间存在一种对应关系,称为映射。

   Map 接口中定义的一些通用方法:

2、HashMap 集合

   HashMap 集合是 Map 接口的一个实现类,它用于存储键值映射关系,但是必须保证不出现重复的键。如果出现了相同的键,那么后存储的值会覆盖原有的值。

//获取Map的值
import java.util.*;
class HashMapDemo1{
	public static void main(String[] args){
		Map map = new HashMap();
		map.put("1","Jack");
		map.put("2","Tom");
		map.put("3","Jerry");
		map.put("1","Rose");
		System.out.println("1:"+mao.get("1"));
		System.out.println("2:"+mao.get("2"));
		System.out.println("3:"+mao.get("3"));
	}
}

   在开发中京城需要取出 Map 中所有的键和值,那么如何遍历 Map 中所有的键值对呢?

  • 第一种方式:先遍历 Map 集合中的所有键,再根据键获取相应的值。

import java.util.*;
class HashMapDemo2{
	public static void main(String[] args){
		Map map = new HashMap();
		map.put("1","Jack");
		map.put("2","Tom");
		map.put("3","Jerry");

		Set keySet = map.keySet();
		Iterator it = keySet.iterator();
		while(it.hasNext()){
			Object key = it.next();
			Object value = keySet.get(key);
			System.out.println(key+":"+value);
		}
	}
}

  • 第二种方式:先获取集合汇总的所有映射关系,然后从映射关系中取出键和值。

import java.util.*;
class HashMapDemo3{
	public static void main(String[] args){
		Map map = new HashMap();
		map.put("1","Jack");
		map.put("2","Tom");
		map.put("3","Jerry");

		Set keySet = map.entrySet();
		Iterator it = entrySet.iterator();
		while(it.hasNext()){
			Map.Entry entry = (Map.Entry)it.next();
			Object key = entry.getKey();
			Object value = entry.getValue();
			System.out.println(key+":"+value);
		}
	}
}

  • 第三种方式:在Map中还提供了一个 values() 方法,通过这个方法可以直接获取 Map 中存储所有值的 Collection 集合。

import java.util.*;
class HashMapDemo4{
	public static void main(String[] args){
		Map map = new HashMap();
		map.put("1","Jack");
		map.put("2","Tom");
		map.put("3","Jerry");

		Collection values = map.values();
		Iterator it = values.iterator();
		while(it.hasNext()){
			Object value = it.next();
			System.out.println(value);
		}
	}
}

   从上面的例子可以看出,HashMap 集合迭代出来元素的顺序和存入的顺序是不一致的。如果想让这两个顺序一致,可以使用 Java 中提供的 LinkedHashMap 类,它是 HashMap 的子类,和 LinkedList 一样也是员工双向链表来维护内部元素的关系,使 Map 元素迭代的顺序与存入的顺序一致。

3、TreeMap 集合

   TreeMap 集合是用来存储键值映射关系的,其中不允许出现重复的键。在 TreeMap 中是通过二叉树的原理来保证键的唯一性,这与 TreeMap 集合鵆的原理一样,因此 TreeMap 中所有的键是按照某种顺序排列的。

4、Properties 集合

   Map 接口中还有一个实现类 Hashtable,它和 HashMap 十分相似,区别在于 Hashtable 是线程安全的。Hashtable 存取元素时速度很慢,目前基本上被 HashMap 类所取代,但 Hashtable 类有一个子类 Properties 在实际应用中非常重要,Properties 主要用来存储字符串类型的键和值,在实际开发中经常使用 Properties 集合来存取应用的配置项。

   假设有一个编辑工具,要求默认背景颜色为红色,字体大小为14px,语言为中文。其配置项如下:

background-color=red
font-size=14px
language=chinese

   使用 Properties 集合存取这些配置项,代码如下:

class PropertiesDemo{
	public static void main(String[] args){
		Properties prop = new Properties();
		p.setProperty("background-color","red");
		p.setProperty("font-size","14px");
		p.setProperty("language","chinese");
		Enumeration names = prop.propertyNames();
		while(names.hasMoreElements()){
			String key = (String)names.nextElement();
			String value = p.getProperty(key);
			System.out.println(key+"="+value);
		}
	}
}

六、泛型

1、泛型简介

   当把一个对象存入集合后,集合会“忘记”这个对象的类型,将该对象从集合取出时,这个对象的编译类型就变成了 Object 类型。换句话说,在程序汇总无法确定一个集合中的元素到底是什么类型,那么在取出元素时,如果进行强制类型转换就很容易出错。

   为了解决这个问题,在 Java 中引入类“参数化类型”这个概念,即泛型。它可以限定方法操作的数据类型,在定义集合时使用“<参数化类型>”的方式指定该类中方法操作的数据类型,具体格式如下:

ArrayList<参数化类型> list = new ArrayList<参数化类型>();

2、参数化类型的几个问题

   1)、参数化类型与原始类型的兼容性
  • 参数化类型可引用一个原始类型的对象,编译时只警告。

如:Collection<String>  coll = new Vector();

  • 原始类型可引用一个参数化类型的对象,编译时只警告。

如:Collection  coll = new Vector<String>();

   2)、参数化类型不考虑类型参数的继承关系

  • Vector<String> v = newVector<Objec>();//错误的
  • Vector<Objec> v = newVector<String>();//错误的

   3)、编译器不允许创建泛型类的数组

  • Vector<Integer>vectorList[] = new Vector<Integer>[10];//错误的

3、通配符

   1)、当传入的参数类型不确定时,可以使用通配符 ? ,也可以理解为占位符。

   2)、泛型限定

  • ? extends E:可接收 E 类型或者 E 类型的子类型,称为上限。
  • ? super E:可接收 E 类型或者 E 类型的父类型,称为下限。

4、泛型类

   若类实例对象使用到同一个泛型类型,这时候需要使用泛型类的方式定义,也就是类级别的泛型。泛型类定义的泛型,在整个类中有效。

class Demo<T>{
	private T temp;
	public void set(T temp){
		this.temp=temp;
	}
	public T get(){
		return temp;
	}
}

   需要注意的是:

  • 在对泛型进行参数化时,类型参数的实例必须是引用类型,不能是基本数据类型。
  • 当一个变量被声明为参数时,只能被实例变量和方法调用,不能被静态变量和静态方法调用,因为静态变量是被所有参数化的类锁共享。

5、泛型方法

   为了让不同方法操作不同的类型,而且类型不确定,那么可以将泛型定义在方法上。

class Demo<Q>{
     public  void  run(Q q){}
     public <T> void show(T t) {}
     public <E> void print(E t){}
     public static <W> void method(W t){}
}

七、Collections 工具类

   针对集合中频繁的操作,JDK 提供了一个工具类专门操作集合,这个类就是 Collections,其中提供了大量的方法用于集合中的元素进行排序、查找和修改等操作。

1、排序操作

import java.util.*;
class CollectionsDemo1{
	public static void main(String[] args){
		ArrayList list = new ArrayList();
		Collections.addAll(list,"C","Z","A");
		System.out.println("排序前:"+list);
		Collections.reverse(list);
		System.out.println("反转后:"+list);
		Collections.sort(list);
		System.out.println("按自然排序后:"+list);
		Collections.shuffle(list);
		System.out.println("洗牌后:"+list);
	}
}

2、查找/替换操作

import java.util.*;
class CollectionsDemo2{
	public static void main(String[] args){
		ArrayList list = new ArrayList();
		Collections.addAll(list,-3,2,9,5,8);
		System.out.println("集合中的元素:"+list);
		System.out.println("集合中的最大元素:"+Collections.max(list));
		System.out.println("集合中的最小元素:"+Collections.min(list));
		Collections.replaceAll(list,8,0);
		System.out.println("替换后的元素:"+list);
	}
}

八、Arrays 工具类

1、使用 Arrays 的 sort() 方法排序

   如果想要对数据进行排序就需要自定义一个排序方法,其实也可以通过 Arrays 工具类中的静态方法 sort() 来实现这个功能。

class ArraysDemo1{
	public static void main(String[] args){
		int[] arr = {2,6,9,0,1,3};

		System.out.print("排序前:");
		printArray(arr);

		Arrays.sort(arr);

		System.out.print("排序后:");
		printArray(arr);
	}
	public static void printArray(int[] arr){
		System.out.print("[");
		for(int x=0;x<arr.length;x++){
			if(x != arr.length-1)
				System.out.print(arr[x]+",");
			else
				System.out.println(arr[x]+"]")
		}
	}
}

2、使用 Arrays 的 bianrySearch(Object[] a,Object key) 方法查找元素

import java.util.*;
class ArrayssDemo2{
	public static void main(String[] args){
		int[] arr = {9,8,6,7,3,5};
		Arrays.sort(arr);
		int index = Arrays.binarySearch(arr,3);
		System.out.println("数组排序后元素3的索引是:"+index);
	}
}

3、使用 Arrays 的 copyOfRange(int[] original,int from,int to) 方法拷贝元素

   在程序开发中经常需要在不破坏原数组的情况下使用数组中部分元素,这时可以使用 Arrays 工具类 copyOfRange(int[] original,int from,int to) 方法将数组中指定范围的元素复制到一个新的数组中,该方法中参数 original 表示被复制的数组,from 表示被复制元素的初始索引,to 表示被赋值元素的最后索引。

import java.util.*;
class ArrayssDemo3{
	public static void main(String[] args){
		int[] arr = {9,8,6,7,3,5};
		int[] copy = Arrays.copyOfRange(arr,1,7);
		for(int x=0;x<arr.length;x++){
			System.out.println(copy[x]+"");
		}
	}
}

4、使用 Arrays 的 fill(Object[] a,Object val) 方法填充元素

   经常需要用到一个值替换数组中的所有元素,这时可以使用 Array 的 fill(Object[] a,Object val) 方法,该方法可以将指定的值赋值给数组中的每一个元素。

import java.util.*;
class ArrayssDemo4{
	public static void main(String[] args){
		int[] arr = {9,8,6,7,3,5};
		Arrays.fill(arr,8);
		for(int x=0;x<arr.length;x++){
			System.out.println(x+":"+arr[x]);
		}
	}
}

5、使用 Arrays 的 toString(int[] arr) 方法把数组转换为字符串

   经常需要把数组以字符串的形式输出,这时可以使用 Arrays 工具类的 toString(int[] arr) 方法。需要注意的是,该方法并不是对 Object 类的 toString() 方法的重写,只是用于返回指定数组的字符串形式。

import java.util.*;
class ArrayssDemo5{
	public static void main(String[] args){
		int[] arr = {9,8,6,7,3,5};
		String arrStrin = Arrays.toString(arr);
		System.out.println(arrString);
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值