JAVA基础11—集合与泛型

0. 前言

我们在前面的学习中,常用数据进行同类型数据的存放。但在JAVA中,数组有个局限性:数组创建出来之后就是定长的,资源的利用不够灵活。

本篇文章我们学习集合与泛型,解决数组局限性的问题。

1. 基本概念与入门

集合是一些已经封装好的API,又称为容器,它的长度是可变的。

常用的集合有:继承 Collection 的 List 、 Set ,如ArrayList、HashSet 以及实现Map接口的 Map , 如HashMap。

1.1入门案例:

定义实验所需要的类:

    public class CollDemo {
	//Attributes
    private int age;
    
    private String name;
    
    public void sayHello(){
    	System.out.println("Hello");
    }
   //篇幅有限,省略掉Getter、Setter、构造方法等
    
}

用集合实现与数组类似的装入数据功能:

    public static void main(String[] args) {
		//定义ArrayList ,插入数据并查看长度
		
		ArrayList al = new ArrayList();
		
		System.out.println(al.size());
		
		al.add(new CollDemo());
		
		System.out.println(al.size());
		
		//取出 ArrayList 中 index为0的数据
		System.out.println(al.get(0));
		
	}

1.2 集合接收的对象

默认情况下,集合接收的都是Object对象。因为集合类中容纳的都是指向Object类对象的指针,所以在默认情况下,任何对象都可以插入集合。同时,在默认情况下取出集合中的数据时,取出的数据类型都是Object,这意味着需要取出来进行运算时都需要进行类型转换,举个例子:

    public static void main(String[] args) {
		//插入不同类型的数据,然后分别取出进行运算
		
		ArrayList al = new ArrayList();
		
		al.add(new Integer(1));
		al.add(new Double(1.1));
		
		//取出数据需要强转
		Integer i = (Integer) al.get(0);
		Double d = (Double) al.get(1);
		
		System.out.println(i);
		System.out.println(d);
	}

从上面的例子以及集合的一些基本概念我们发现,集合比数组要更灵活:它是容器,长度可变;默认情况下它能装入和取出的数据类型都是Object,不受类型的限制。

但是在实际使用情况下,默认情况下的集合由于接收的数据类型是Obejct,过于灵活,一来难以进行统一的类型管理,二来取出数据的时候需要频繁的类型转换,效率比较底。针对这个问题,我们使用泛型来对集合进行数据类型的规范管理。

2. 泛型

泛型本质是一种语法糖,在JDK1.5后出现。现在集合的使用常常配合着泛型对数据类型进行规范管理。

2.1 入门案例

    public static void main(String[] args) {
		//给ArrayList 加入 Integer 泛型
		ArrayList<Integer> al1 = new ArrayList<Integer>();
		
		//省略的表示方式:省略掉new后面的泛型,少一丢丢代码
		ArrayList<Integer> al2 = new ArrayList<>();
		
		al1.add(new Integer(1));
		
		//下面被注释的代码会报错,因为泛型已规定了能插入的数据
		//al1.add(new Double(1.1));
		
		//有了泛型,不需要再进行类型转换即可取出
		Integer i = al1.get(0);
		
	}

2.2 支持子类的插入

泛型规定的集合也可兼容该类型的子类插入,保留了灵活性,但是会默认向上造型,举个例子:

    public static void main(String[] args) {
		//数字类型的包装类都继承自Number类
		ArrayList<Number> al = new ArrayList<>();
		
		al.add(new Integer(1));
		al.add(new Double(1.1));
		
		//存入数据时向上造型,所以此处不需要类型转换
		Number n = al.get(0);
		
		//此处取出数据时向下转型了,需要类型转换
		Integer i = (Integer) al.get(0);
		Double d = (Double) al.get(1);
		
	}

3. 常用集合

本节我们详细介绍常用集合与这些集合的常用方法。

3.1 List 集合

List 集合指的是 实现了 List 接口的集合。常见的List集合有 ArrayList、 LinkedList 等。本小节我们主要学习ArrayList 与 LinkedList。

实现的接口:

    //截取自ArrayList的源码:
    public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

3.1.1 ArrayList

ArrayList 是顺序结构,有序,可重复。

在上面章节的学习中,我们都以ArrayList为例子,已经初步接触了它的常用方法:add、get方法,接下来我们复习一遍,再接着学习、尝试:

(1)add、get 插入、获取数据
    public static void main(String[] args) {
		
		ArrayList<CollDemo> al = new ArrayList<>();
		//CollDemo 是一开始就自定义好的类,下面进行插入、取出
		al.add(new CollDemo());
		al.add(new CollDemo());
		
		CollDemo cd1 = al.get(0);
		System.out.println(cd1);
		
		CollDemo cd2 = al.get(1);
		System.out.println(cd2);
		
	}
(2)remove 移除对象、 size 集合长度
    public static void main(String[] args) {
		
		ArrayList<CollDemo> al = new ArrayList<>();
		//CollDemo 是一开始就自定义好的类,下面进行移除
		al.add(new CollDemo());
		al.add(new CollDemo());
		
		CollDemo cd1 = al.get(0);
		
		boolean isRemove = al.remove(cd1);
		System.out.println("移除第一个元素是否成功:"+isRemove);
		System.out.println("移除后的长度为:"+al.size());
		
	}
(3)toArray 将集合转化为数组

提供转换为数组的方法,更显集合框架的灵活性。

    public static void main(String[] args) {
		ArrayList<Integer> al = new ArrayList<Integer>();
		
		//插入数据,再转换为数组
		for(int i = 0 ; i < 9 ; i++) {
			al.add(i);
		}

		Integer [] i = al.toArray(new Integer[] {});
		System.out.println(i.length);
		System.out.println(i[0]);
	}

3.2.2 LinkedList

LinkedList 是链表结构,插入、删除效率较ArrayList要高。有序、可重复。

下面以LinkedList为例,接着介绍一些List常用方法:

(1)isEmpty判断是否为空
    public static void main(String[] args) {
		LinkedList<Integer> ll = new LinkedList<>();
		
		boolean isEmpty = ll.isEmpty();
		
		System.out.println("刚创建的LinkedList是否为空:"+isEmpty);
		
		ll.add(1);
		
		System.out.println("已插入数据的LinkedList是否为空:"+ll.isEmpty());

	}

(2)contains判断是否包含指定元素
    public static void main(String[] args) {
        LinkedList<Integer> ll = new LinkedList<>();
        
        ll.add(3);
        ll.add(4);
        
        System.out.println("是否包含3:"+ll.contains(3));
        System.out.println("是否包含5:"+ll.contains(5));
	}
(3)set替换某个下标的元素
    public static void main(String[] args) {
        LinkedList<Integer> ll = new LinkedList<>();
        
        ll.add(3);
        ll.add(4);
        
        System.out.println("没有替换时是"+ll.get(0));
        
        ll.set(0, 1);
        System.out.println("替换后的0位元素是"+ll.get(0));
	}
(4)遍历迭代器
    public static void main(String[] args) {
        LinkedList<Integer> ll = new LinkedList<>();
       
        ll.add(1);
        ll.add(1);
        ll.add(2);
        ll.add(2);
        
        //传统遍历方式:
        for(int i = 0 ; i < ll.size(); i++) {
        	System.out.println(ll.get(i));
        }
        
        //迭代器:
        Iterator<Integer> it = ll.iterator();
        while(it.hasNext()) {
        	Integer i = it.next();
        	System.out.println(i);
        }
        
	}

3.2.3 List集合特性总结

通过对ArrayList 、 LinkedList 的学习,我们了解到了一些List集合的常用方法,注意,上面提到的方法在举例时虽然分别用的是ArrayList 、LinkedList 进行举例,但它们在List里都是通用的。

(1)ArrayList 与 LinkedList 的异同

相同之处:都实现了List接口,都是有序、可重复的表结构。

不同之处:
ArrayList 是顺序表,类似于数组,但长度可变。定位快,适合多查找、少增删的情况。

LinkedList 是链表,查找相对ArrayList比较慢,但插入快,适合需要频繁更新操作的情况。这种更新主要针对的是对中间数据的更新,至于头尾数据的更新,两者的效率会因不同情况而不同。

举个例子,就新增插入(尾部操作)来说:

public static void main(String[] args) {
		//插入效率比较
		LinkedList<Integer> ll = new LinkedList<>();
		
		ArrayList<Integer> al = new ArrayList<>();
		
		Long linkTimeBegin = System.currentTimeMillis();
		insert(ll);
		Long linkTimeEnd = System.currentTimeMillis();
		System.out.println("linked增添用时"+(linkTimeEnd-linkTimeBegin)+"ms");
		
		Long ArrTimeBeging = System.currentTimeMillis();
		insert(al);
		Long ArrTimeEnd = System.currentTimeMillis();
		System.out.println("arr增添用时"+(ArrTimeEnd-ArrTimeBeging)+"ms");
	}
	
	public static void insert(List<Integer> l) {
		for(int i = 0 ; i < 1000000 ; i ++) {
			l.add(i);
		}
	}

在测试中,我们发现两种List更新效率比较不是绝对的,因为链表在插入的时候,本身是存在一定的开销。至于其他情况的测试,留给读者拓展。

(2)LinkedList的简单拓展

LinkedList 是链表结构,除了实现List接口,还实现了双向链表、队列接口,以下是例子

    public static void main(String[] args) {
		LinkedList<Integer> ll = new LinkedList<Integer>();
		
		ll.add(1);
		
		//在双向链表尾部插入数据
		ll.addLast(2);
		
		//在双向链表首部插入数据
		ll.addFirst(0);
		
		//查看
		System.out.println(ll.getFirst());
		System.out.println(ll.getLast());
		
	}
    public static void main(String[] args) {
		//队列
		Queue<Integer> ql = new LinkedList<Integer>();
		
		//队列先入先出
		ql.offer(1);
		ql.offer(2);
		ql.offer(3);
		
		//查看
		System.out.println(ql.peek());
		
		//取出
		System.out.println(ql.poll());
		
		//再取一个
		System.out.println(ql.poll());
	}

3.2 Map集合

Map集合是指实现了Map 接口的集合,是由键值对存储的数据结构,也就是哈希表。

Map是无序不重复(key不能重复)的集合,常见的Map主要由HashMap、TreeMap等。

在结合泛型声明Map时,需要同时定义 键 和 值 。

3.2.1 HashMap

我们通过HashMap 来详细学习Map的用法。

(1)常用方法

插入键值对put、根据key取出值get(key)、清空clear(),以下是例子:

    public static void main(String[] args) {
		HashMap<String, String> hm = new HashMap<>();
		
		//使用put 方法插入键值对
		hm.put("No.1", "第一个字符串值");
		hm.put("No.2", "猪八戒");
		hm.put("No.3", "喜羊羊");
		
		//键是唯一的,插入已存在的键的数据会覆盖
		hm.put("No.1", "武松");
		
		//根据key取出值
		System.out.println(hm.get("No.1"));
		System.out.println(hm.get("No.3"));
		
		//清空
		hm.clear();
		System.out.println(hm);
	}

}
(2)数据结构

HashMap 是一种哈希表,也是一种散列表,具体通过数组结合链表或者红黑树实现。

关于该数据结构的具体学习交给读者拓展。

3.2.2 TreeMap

TreeMap是一种红黑树,也实现了put、get、remove等方法。具体实现方式需要参考红黑树的实现方式,要详细搞清楚其中的原理,篇幅有限,请读者移步至红黑树的学习。

3.3 Set集合

Set集合实现了Set接口。

Set中的元素不能重复,同时它也是无序的。

常见的Set有HashSet 、 TreeSet,这里主要介绍HashSet。

3.3.1 HashSet

(1)无序且不重复

我们使用HashSet 来测试 Set的无序且不重复的特性:

    public static void main(String[] args) {
		HashSet<Integer> hs = new HashSet<Integer>();
		
		hs.add(1);
		hs.add(5);
		hs.add(3);
		//输出,发现并不是按照输入顺序来进行排序,具体排序方式根据不同的JVM而定
		System.out.println(hs);

		//插入相同数据,发现并没有任何改变
		
		hs.add(5);
		hs.add(1);
		System.out.println(hs);
	}
(2)遍历

遍历Set集合需要用到迭代器或者增强for循环

    public static void main(String[] args) {
		HashSet<Integer> hs = new HashSet<Integer>();
        
        for (int i = 0; i < 5; i++) {
            hs.add(i);
        }
         
       
        //迭代器iterator
        for (Iterator<Integer> iterator = hs.iterator(); iterator.hasNext();) {
            Integer i = (Integer) iterator.next();
            System.out.println(i);
        }
         
        //增强for循环
        for (Integer i : hs) {
            System.out.println(i);
        }

	}

3.3.2 HashMap与HashSet的关系

HashSet实际上是基于HashMap的,其内部实现了一个HashMap,我们可以通过观察源码得知:

    public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;

    private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

    /**
     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     * default initial capacity (16) and load factor (0.75).
     */
    public HashSet() {
        map = new HashMap<>();
    }
    
    .......以下源码省略

当然,更深层的实现方式读者可自行再进一步探究,此处不赘述。

4. 总结与补充

泛型还支持通配符、泛型转型等操作,且并不是所有的类都支持泛型。这些关于泛型的详细学习,读者可移步自行学习。

最后,我们简单总结以下本章的知识:我们先学习了集合与泛型的基本概念,然后通过有序的List集合进行入门学习,接着我们学习了集合框架中更多的集合:无序且key不重复的Map、无序且值不重复的Set。 我们下一篇见!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值