java 数据结构和泛型

一.Java中的泛型

1.什么是泛型?

     泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原

来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

2.为什么要使用泛型?

    举个栗子,定义一个list类型的集合,先向其中加入了两个字符串类型的值,随后加入一个Integer类型的值。这是完全允许的,因为此时list默认的类型为

Object类型。在之后的循环中,由于忘记了之前在list中也加入了Integer类型的值或其他编码原因,很容易出现类似于//1中的错误。因为编译阶段正常,而运行时会

出现“java.lang.ClassCastException”异常。
List list = new ArrayList();
         list.add("aaaaa");
         list.add("bbbbb");
         list.add(100);

         for (int i = 0; i < list.size(); i++) {
            String name = (String) list.get(i); // ClassCastException
             System.out.println("name:" + name);
         }
    产生原因:当我们将一个对象放入集合中,集合不会记住此对象的类型,当再次从集合中取出此对象时,改对象的编译类型变成了Object类型,但其运行时类

型任然为其本身类型
    采用泛型后
    List<String> list = new ArrayList<>();
    list.add("aaaaaaa");
    list.add("bbbbbb");
    //list.add(10000);//编译不通过-------------1
    for (int i = 0; i < list.size(); i++) {
             String name = list.get(i); // 无需类型转换----------2
             System.out.println("name:" + name);
         }
采用泛型写法后,在--1处想加入一个Integer类型的对象时会出现编译错误,通过List<String>,直接限定了list集合中只能含有String类型的元素,从而在--2处无

须进行强制类型转换,因为此时,集合能够记住元素的类型信息,编译器已经能够确认它是String类型了。
    看看list的源码
public interface List<E> extends Collection<E> {
 
      int size();
 
      boolean isEmpty();
 
     boolean contains(Object o);
 
      Iterator<E> iterator();
 
     Object[] toArray();
 
     <T> T[] toArray(T[] a);
 
     boolean add(E e);
 
     boolean remove(Object o);
 
     boolean containsAll(Collection<?> c);

    ........
在List接口中采用泛型化定义之后,<E>中的E表示类型形参,可以接收具体的类型实参,并且此接口定义中,凡是出现E的地方均表示相同的接受自外部的类型实参。
ArrayList作为List接口的实现类:
public class ArrayList<E> extends AbstractList<E> implements Cloneable, Serializable, RandomAccess {
    
    private static final int MIN_CAPACITY_INCREMENT = 12;

    int size;

    transient Object[] array;

    public ArrayList(int capacity) {
        if (capacity < 0) {
            throw new IllegalArgumentException("capacity < 0: " + capacity);
        }
        array = (capacity == 0 ? EmptyArray.OBJECT : new Object[capacity]);
    }

    public ArrayList() {
        array = EmptyArray.OBJECT;
    }
 public boolean add(E object) {
        Object[] a = array;
        int s = size;
        if (s == a.length) {
            Object[] newArray = new Object[s +
                    (s < (MIN_CAPACITY_INCREMENT / 2) ?
                     MIN_CAPACITY_INCREMENT : s >> 1)];
            System.arraycopy(a, 0, newArray, 0, s);
            array = a = newArray;
        }
        a[s] = object;
        size = s + 1;
        modCount++;
        return true;
    }
.......

List<String>和List<Integer>在运行事实上是相同的类型。他们都被擦除成他们的原生类型,即List。因为编译的时候会有类型擦除,所以不能通过同一个泛型类的

实例来区分方法
这样的写法也是对的:
List<Object> list = new ArrayList<Object>();
        list.add("aaaa");
        list.add("bbbb");
        list.add(100000);
        for(int i = 0;i<list.size();i++){
            Log.e(TAG,"list["+i+"]"+list.get(i));
        }


二.java中的数据结构

    概述:线性表,链表,哈希表是常用的数据结构,在进行Java开发时,JDK已经为我们提供了一系列相应的类来实现基本的数据结构。

   1.Collection 的介绍

   Collection:容纳元素的容器,Collection是最基本的集合接口,Java SDK不提供直接继承自Collection的类,而提供继承自Collection的“子接口”如List和Set,下面是他们之间的关系
    Collection

    ├List

    │├LinkedList

    │├ArrayList

    │└Vector

    │ └Stack

    └Set

    Map(Map没有继承Collection接口)

    ├Hashtable

    ├HashMap

    └WeakHashMap


    Collection的主要方法:
        boolean add(Object o)添加对象到集合
        boolean remove(Object o)删除指定的对象
        int size()返回当前集合中元素的数量
        boolean contains(Object o)查找集合中是否有指定的对象
        boolean isEmpty()判断集合是否为空
        Iterator iterator()返回一个迭代器
        boolean containsAll(Collection c)查找集合中是否有集合c中的元素
        boolean addAll(Collection c)将集合c中所有的元素添加给该集合
        void clear()删除集合中所有元素
        void removeAll(Collection c)从集合中删除c集合中也有的元素
        void retainAll(Collection c)从集合中删除集合c中不包含的元素


     1.1.List接口

    List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。List允许有相同的元素、允许多个null元素。
    实现类包括  LinkedList,Vector,ArrayList 和Stack

    1.1.1 ArrayList

        每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。
    主要方法:
        Boolean add(Object o)将指定元素添加到列表的末尾
        Boolean add(int index,Object element)在列表中指定位置加入指定元素
        Boolean addAll(Collection c)将指定集合添加到列表末尾
        Boolean addAll(int index,Collection c)在列表中指定位置加入指定集合
        Boolean clear()删除列表中所有元素
        Boolean clone()返回该列表实例的一个拷贝
        Boolean contains(Object o)判断列表中是否包含元素
        Boolean ensureCapacity(int m)增加列表的容量,如果必须,该列表能够容纳m个元素
        Object get(int index)返回列表中指定位置的元素
       Int indexOf(Object elem)在列表中查找指定元素的下标
        Int size()返回当前列表的元素个数

ArrayList的遍历方法:
    List<String> list = new ArrayList<String>();
        list.add("aaaaa");
        list.add("bbbb");       
        for(String s:list){
            Log.e(TAG,"s ="+s);
        }
        Iterator<String> itor = list.iterator();
        while(itor.hasNext()){
            Log.e(TAG,"Iterator ="+itor.next());            
        }

    1.1.2 Vector

        Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的Iterator是同一接口,但是,因为Vector是同

步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出

ConcurrentModificationException,因此必须捕获该异常。
    用法及遍历方法
    Vector<Object> vector = new Vector<Object>();
        vector.add("xxxxx");
        vector.add("ooooo");
        vector.add(122345);
        // Vector转换为枚举  
        Enumeration e = vector.elements();  
        while (e.hasMoreElements()) {  
            Log.e(TAG,"Enumeration 枚举 ="+e.nextElement());  
        }

    1.1.3 LinkedList类

    LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在LinkedList的首部或尾部。这些操作使LinkedList可    

被用作堆栈(stack),队列(queue)或双向队列(deque)。
注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:
    List list = Collections.synchronizedList(new LinkedList(...));
    LinkList的用法:
    List<Object> list2 = Collections.synchronizedList(new LinkedList<Object>());
        list2.add("xxxxoooo");
        list2.add("520");
        // 的到链表的迭代器,位置指向链头  
        ListIterator li = list2.listIterator();  
        // 判断迭代器中是否有下一个元素  
        while (li.hasNext()) {  
            // 返回下个元素  
            Log.e(TAG,"Linklist()="+li.next());  
        }

    1.1.4 Stack 类

    Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop方法,还有peek方法得到栈顶

的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。
    Stack的用法:
    Stack stack = new Stack();  
        // 向栈里面压一个整数  
        stack.push(new Integer(123));  
        stack.push("lwc");  
        stack.push(new Double(88.88));  
        // 遍历  
        Enumeration items = stack.elements();  
        while (items.hasMoreElements()) {  
            System.out.print(items.nextElement() + " ");  
        }  
        System.out.println();  
        // 出栈  
        while (stack.size() != 0) {  
            System.out.print(stack.pop() + " ");  
        }
 

    1.2.Set接口

    Set是一种不包含重复的元素的无序Collection。

    1.2.1.HashSet:

    虽然Set同List都实现了Collection接口,但是他们的实现方式却大不一样。List基本上都是以Array为基础。但是Set则是在 HashMap的基础上来实现的,这

个就是Set和List的根本区别。HashSet的存储方式是把HashMap中的Key作为Set的对应存储项。看看 HashSet的add(Object obj)方法的实现就可以一目了然了。
 public boolean add(E object) {
        return backingMap.put(object, this) == null;
    }
backingMap 的定义是这样的:transient HashMap<E, HashSet<E>> backingMap; 所以说HashSet底层是使用HashMap实现的  
    HashSet的add()方法详解:  
        判断已经存储在集合中的对象hashCode值是否与增加对象的hashCode值一致  
        如果不一致,直接加进去  
        如果一致,再进行equals()比较  
        如果equals()返回true,对象已经存在不增加进去  
        如果equals()返回false,把对象增加进去  
    HashSet 的实现其实非常简单,它只是封装了一个 HashMap 对象来存储所有的集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保


Set<String> setA=new HashSet<String>();        
        setA.add(new String("ABC"));
        setA.add(new String("CC"));
        setA.add(new String("ABC"));
        setA.add(new String("BB"));
        setA.add(new String("ABC"));
        Log.e(TAG,"hashset....size="+setA.size()); //3, 相同的项不存储         
        Iterator<String> ite=setA.iterator();        
        while(ite.hasNext()){
            Log.e(TAG,"hashset="+ite.next());
       }
    }

    1.2.2.TreeSet

TreeSet它不同于HashSet的根本就是TreeSet是有序的
    下面是TreeSet的底层代码:可以看到其底层使用的就是TreeMap。
public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>,
        Cloneable, Serializable {
    private static final long serialVersionUID = -2479143000061671589L;
    private transient NavigableMap<E, Object> backingMap;

    private transient NavigableSet<E> descendingSet;

    TreeSet(NavigableMap<E, Object> map) {
        backingMap = map;
    }
 
    public TreeSet() {
        backingMap = new TreeMap<E, Object>();
    }

    2.Map

    Map 是一种把键对象和值对象进行关联的容器,而一个值对象又可以是一个Map,依次类推,这样就可形成一个多级映射。对于键对象来说,像Set一样,一个

Map容器中的键对象不允许重复,这是为了保持查找结果的一致性;如果有两个键对象一样,那你想得到那个键对象所对应的值对象时就有问题了,可能你得到的并不是

你想的那个值对象,结果会造成混乱,所以键的唯一性很重要,也是符合集合的性质的。Map有两种比较常用的实现:HashMap和TreeMap

    2.1.HashMap

    HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
    HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口。
    HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。
    HashMap 的用法:
    HashMap<String, String> hashMap = new HashMap<String, String>();  
        hashMap.put("cn", "中国");  
        hashMap.put("jp", "日本");  
        hashMap.put("fr", "法国");  
               
        System.out.println("cn:" + hashMap.get("cn"));                        
        //采用Iterator遍历HashMap  
        Iterator it = hashMap.keySet().iterator();  
        while(it.hasNext()) {  
            String key = (String)it.next();             
            Log.e(TAG,"value:" + hashMap.get(key));  
        }  
          
        //遍历HashMap的另一个方法  
        Set<Entry<String, String>> sets = hashMap.entrySet();  
        for(Entry<String, String> entry : sets) {   
            Log.e(TAG,"....2 ="+entry.getValue());  
        }

    2.2.TreeMap

    TreeMap类不仅实现了Map接口,还实现了Map接口的子接口java.util.SortedMap。由TreeMap类实现的Map集合,不允许键对象为null,
TreeMap类要比HashMap类的性能差一些,但是其中的映射关系具有一定的顺序,如果不需要一个有序的集合,则建议使用HashMap类;如果需要进行有序的遍历输出,则建议使用TreeMap类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值