zoukankan      html  css  js  c++  java
  • Java中Array、List、Set、Map

    一、Java中数组

    数组用来存放固定数量的同类元素,声明方法:

    T[] ref,T ref[],如int[] intAry; int intAry[]。推荐用T[]的方式,后一种方式为兼容C++习惯写法。

    初始化方法:

    new设置数组长度,或者直接列出数组元素,如下:

     // 创建数组,如果在创建的同时不初始化数组则必须指定其大小
     intArray0 = new int[3];
    
     // 创建数组时,不指定数组大小则必须在创建的同时初始化数组
      intArray1 = new int[]{0,1,2};  
    
    // 静态初始化简化方式
     int intArray3 [] = {30,31,32};

    二、Java 数组和集合之间的关系

    世间上本来没有集合,(只有数组参考C语言)但有人想要,所以有了集合  
    有人想有可以自动扩展的数组,所以有了List  
    有的人想有没有重复的数组,所以有了set  
    有人想有自动排序的组数,所以有了TreeSet,TreeList,Tree**  
      
    而几乎有有的集合都是基于数组来实现的.  
    因为集合是对数组做的封装,所以,数组永远比任何一个集合要快  
      
    但任何一个集合,比数组提供的功能要多  
      
    一:数组声明了它容纳的元素的类型,而集合不声明。这是由于集合以object形式来存储它们的元素。  
      
    二:一个数组实例具有固定的大小,不能伸缩。集合则可根据需要动态改变大小。  
      
    三:数组是一种可读/可写数据结构---没有办法创建一个只读数组。然而可以使用集合提供的ReadOnly方法,以只读方式来使用集合。该方法将返回一个集合的只读版本。

    注:java中可创建只读集合和同步集合,方法如下:

    // 只读集合
    List<String> readOnlyList = Collections.unmodifiableList(list);
    System.out.println(readOnlyList.getClass());
    // 同步集合
    List<String> synchronizedList = Collections.synchronizedList(list);
    System.out.println(synchronizedList.getClass());

    如果试图对只读集合进行添加或修改,将会抛出UnsupportedOperationException异常:
    如果不希望方法返回的集合被修改,就可以使用只读集合;如果想要获取线程安全的集合,就可以使用同步集合。

    三、数组和集合的差异

    数组array是固定大小不能动态改变,只能存同一种类型,可传递继承类型,即Number是Integer的父类,则Number[]是Integer[]的父类型,即可将Integer[]当做实参传入形参要求为Number[]的函数中。

    集合是对数组能力的扩展,基于数据或链表结构实现,只能存放引用类型,不能存放基本类型;不能进行类型传递,即Listt<Number>不能看作List<Integer>的父类型。

    四、数组集合的使用建议

    建议65:避开基本类型数组转换列表陷阱

       我们在开发中经常会使用Arrays和Collections这两个工具类和列表之间转换,非常方便,但也有时候会出现一些奇怪的问题,来看如下代码: 

    复制代码
    1 public class Client65 {
    2     public static void main(String[] args) {
    3         int data [] = {1,2,3,4,5};
    4         List list= Arrays.asList(data);
    5         System.out.println("列表中的元素数量是:"+list.size());
    6     }
    7 }
    复制代码

      也许你会说,这很简单,list变量的元素数量当然是5了。但是运行后打印出来的列表数量为1。

      事实上data确实是一个有5个元素的int类型数组,只是通过asList转换成列表后就只有一个元素了,这是为什么呢?其他4个元素到什么地方去了呢?

      我们仔细看一下Arrays.asList的方法说明:输入一个变长参数,返回一个固定长度的列表。注意这里是一个变长参数,看源码:

      public static <T> List<T> asList(T... a) {
            return new ArrayList<>(a);
        }

      asList方法输入的是一个泛型变长参数,我们知道基本类型是不能泛型化的,也就是说8个基本类型不能作为泛型参数,要想作为泛型参数就必须使用其所对应的包装类型,那前面的例子传递了一个int类型的数组,为何程序没有报编译错误呢?

      在Java中,数组是一个对象,它是可以泛型化的,也就是说我们的例子是把一个int类型的数组作为了T的类型,所以在转换后在List中就只有一个类型为int数组的元素了,我们打印出来看看,代码如下: 

    复制代码
    1 public class Client65 {
    2     public static void main(String[] args) {
    3         int data [] = {1,2,3,4,5};
    4         List list= Arrays.asList(data);
    5         System.out.println("元素类型是:"+list.get(0).getClass());
    6         System.out.println("前后是否相等:"+data.equals(list.get(0)));    
    7     }
    8 }
    复制代码

      输出的结果是: 元素类型是:class [I   前后是否相等:true

      很明显,放在列表中的元素时一个int数组,可能有人要问了,为什么"元素类型:"后的class是"[I"?我们并没有指明是数组(Array)类型呀!这是因为JVM不可能输出Array类型,因为Array是属于java.lang.reflect包的,它是通过反射访问数组元素的工具类。在Java中任何一个一维数组的类型都是 " [I " ,究其原因就是Java并没有定义数组这一个类,它是在编译器编译的时候生成的,是一个特殊的类,在JDK的帮助中也没有任何数组类的信息。

      弄清楚了问题,修改也很easy,直接使用包装类即可,部分代码如下: 

    Integer data [] = {1,2,3,4,5};

      把int替换为Integer即可让输出元素数量为5.需要说明的是,不仅仅是int类型的数组有这个问题,其它7个基本类型的数组也存在相似的问题,这就需要大家注意了,在把基本类型数组转换为列表时,要特别小心asList方法的陷阱,避免出现程序逻辑混乱的情况。

      注意:原始类型数组不能作为asList的输入参数,否则会引起程序逻辑混乱。

    建议66:asList方法产生的List的对象不可更改

      上一个建议指出了asList方法在转换基本类型数组时存在的问题,接着我们看一下asList方法返回的列表有何特殊的地方,代码如下:  

    复制代码
     1 public class Client66 {
     2     public static void main(String[] args) {
     3         // 五天工作制
     4         Week days[] = { Week.Mon, Week.Tue, Week.Wed, Week.Thu, Week.Fri };
     5         // 转换为列表
     6         List<Week> list = Arrays.asList(days);
     7         // 增加周六为工作日
     8         list.add(Week.Sat);
     9         /* do something */
    10     }
    11 }
    12 enum Week {
    13     Sun, Mon, Tue, Wed, Thu, Fri, Sat
    14 }
    复制代码

      很简单的程序呀,默认声明的工作日(workDays)是从周一到周五,偶尔周六也会算作工作日加入到工作日列表中,不过,这段程序执行时会不会有什么问题呢?编译没有任何问题,但是一运行,却出现了如下结果:

      

    UnsupportedOperationException,不支持的操作,居然不支持list的add方法,这是什么原因呢?我们看看asList方法的源代码:

     public static <T> List<T> asList(T... a) {
            return new ArrayList<>(a);
        }

      直接new了一个ArrayList对象返回,难道ArrayList不支持add方法,不可能呀!可能,问题就出现在这个ArrayList类上,此ArrayList非java.util.ArrayList,而是Arrays工具类的一个内部类,其构造函数如下所示: 

    复制代码
     1 private static class ArrayList<E> extends AbstractList<E>
     2         implements RandomAccess, java.io.Serializable
     3     {
     4         private static final long serialVersionUID = -2764017481108945198L;
     5         private final E[] a;
     6 
     7         ArrayList(E[] array) {
     8             if (array==null)
     9                 throw new NullPointerException();
    10             a = array;
    11         }
    12      /*其它方法略*/   
    13 }    
    复制代码

      这个ArrayList是一个静态私有内部类,除了Arrays能访问外,其它类都不能访问,仔细看这个类,它没有提供add方法,那肯定是父类AbstractList提供了,来看代码:  

    复制代码
    1  public boolean add(E e) {
    2         add(size(), e);
    3         return true;
    4     }
    5 
    6  public void add(int index, E element) {
    7         throw new UnsupportedOperationException();
    8     }
    复制代码

      父类确实提供了,但没有提供具体的实现,所以每个子类都需要自己覆写add方法,而Arrays的内部类ArrayList没有覆写,因此add一个元素就报错了。

      我们深入地看看这个ArrayList静态内部类,它仅仅实现了5个方法:

    1. size:元素数量
    2. get:获得制定元素
    3. set:重置某一元素值
    4. contains:是否包含某元素
    5. toArray:转化为数组,实现了数组的浅拷贝

      把这几个方法的源代码展示一下: 

    复制代码
     1  //元素数量
     2        public int size() {
     3             return a.length;
     4         }
     5 
     6         public Object[] toArray() {
     7             return a.clone();
     8         }
     9         //转化为数组,实现了数组的浅拷贝
    10         public <T> T[] toArray(T[] a) {
    11             int size = size();
    12             if (a.length < size)
    13                 return Arrays.copyOf(this.a, size,
    14                                      (Class<? extends T[]>) a.getClass());
    15             System.arraycopy(this.a, 0, a, 0, size);
    16             if (a.length > size)
    17                 a[size] = null;
    18             return a;
    19         }
    20         //获得指定元素
    21         public E get(int index) {
    22             return a[index];
    23         }
    24         //重置某一元素
    25         public E set(int index, E element) {
    26             E oldValue = a[index];
    27             a[index] = element;
    28             return oldValue;
    29         }
    30 
    31         public int indexOf(Object o) {
    32             if (o==null) {
    33                 for (int i=0; i<a.length; i++)
    34                     if (a[i]==null)
    35                         return i;
    36             } else {
    37                 for (int i=0; i<a.length; i++)
    38                     if (o.equals(a[i]))
    39                         return i;
    40             }
    41             return -1;
    42         }
    43         //是否包含元素
    44         public boolean contains(Object o) {
    45             return indexOf(o) != -1;
    46         }
    复制代码

      对于我们经常使用list.add和list.remove方法它都没有实现,也就是说asList返回的是一个长度不可变的列表,数组是多长,转换成的列表也就是多长,换句话说此处的列表只是数组的一个外壳,不再保持列表的动态变长的特性,这才是我们关注的重点。有些开发人员喜欢这样定义个初始化列表: 

        List<String> names= Arrays.asList("张三","李四","王五");

      一句话完成了列表的定义和初始化,看似很便捷,却隐藏着重大隐患---列表长度无法修改。想想看,如果这样一个List传递到一个允许添加的add操作的方法中,那将会产生何种结果,如果有这种习惯的javaer,请慎之戒之,除非非常自信该List只用于只读操作。

    针对ArrayList,使用高级for循环实质是通过Iterator来循环效率比基本for循环遍历元素稍差,在数据较多要用到性能时就不能为了简单用高级for循环。

    频繁插入和删除元素时用LinkList,修改元素操作多时用ArrayList。

    编写高质量代码:改善Java程序的151个建议(第5章:数组和集合___建议65~69)

  • 相关阅读:
    redis数据持久化
    redis安全:给redis设置密码
    redis命令总结
    redis事务
    redis发布订阅
    Java中的日期
    链式队列
    删除链表中的结点(链表)、比较含退格的字符串(栈)、棒球比赛(栈)
    物理层
    链式栈
  • 原文地址:https://www.cnblogs.com/doit8791/p/7487935.html
Copyright © 2011-2022 走看看