20191202-编写高质量代码:5-数组和集合

  1. 性能考虑,数组是首选

在Java中数组确实没有List、Set、Map这些集合类用起来方便,但在基本类型处理方面,数组还是占优势的,而且集合类的底层也都是通过数组实现的。

  1. 若有必要,使用变长数组

Java中的数组是定长的,一旦经过初始化声明就不可改变长度。可以通过对数组扩容来解决该问题:

public static <T> T[] expandCapacity(T[] datas,int newLen){
    newLen = newLen < 0 ? 0:newLen;
    return Arrays.copyOf(datas,newLen);
}

上述代码采用的是Arrays.copyOf方法,产生了一个newLen长度的新数组,并把原有的值拷贝了进去。通过这样的处理方式,曲折地解决了数组的变长问题。

开发中如果确实需要变长的数据集,数组也是在考虑范围之内的,不能因固定长度而将其否定之。

  1. 警惕数组的浅拷贝

通过Arrays.copyOf方法产生的数组是一个浅拷贝。

  1. 在明确的场景下,为集合指定初始容量

ArrayList是一个大小可变的数组,其底层必然要进行长度的扩展:

 private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0) newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)   newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

新数组长度的计算方法:

int newCapacity = oldCapacity + (oldCapacity >> 1);

参考:https://blog.csdn.net/zymx14/article/details/78324464

  1. 多种最值算法,适时选择
    一批数据,找出其中的最大值或最小值。
  2. 自行实现,快速查找最大值
public static int max(int[] data){
    int max = data[0];
    for(int i : data){
        max = max > i ? max : i;
    }
    return max;
}

2、先排序,后取值

public static int max(int[] data){
    Arrays.sort(data.clone());
    return data[data.length-1];
}

如果要查找仅次于最大值的元素(第二大),该如何处理?(注意,数组元素是可以重复的)
一个思路:先剔除重复数据,再排序。
数组不能剔除重复数据,Set集合可以,Set的子类TreeSet还能自动排序。

public static int getSecond(Integer[] data){
    List<Integer> dataList = Arrays.asList(data);
    TreeSet<Integer> ts = new TreeSet<Integer>(dataList);
    return ts.lower(ts.last());
}

剔除重复元素并升序排列,这都是由TreeSet类实现的。
最值计算时使用集合最简单,使用数组性能最优。

  1. 避开基本类型数组转换列表陷阱
 int[] data = {1,2,3,4,5};
 List list = Arrays.asList(data);
 System.out.println(list.size());

输出结果:1 ???
看一下Arrays.asList()方法:

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

入参是一个泛型变长参数(基本类型是不能泛型化的),数组是可以泛型化的。
上面的代码把一个int类型的数组作为了T的类型,转换后在list中就只有一个类型为int数组的元素了。
简单的修改一下:

Integer[] data = {1,2,3,4,5};
List list = Arrays.asList(data);
System.out.println(list.size());

输出:5

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

66.asList方法产生的List对象不可更改

 enum WEEK {SUN,MON,TUE,WED,THU,FRI,SAT}

 public static void main(String[] args){
    WEEK[] workDays = {WEEK.MON,WEEK.TUE,WEEK.WED,WEEK.THU,WEEK.FRI};
    List<WEEK> list = Arrays.asList(workDays);
    list.add(WEEK.SAT);
}

运行结果:

Exception in thread "main" java.lang.UnsupportedOperationException
	at java.util.AbstractList.add(AbstractList.java:148)
	at java.util.AbstractList.add(AbstractList.java:108)

看一下Arrays.asList()方法源码:

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

问题出在ArrayList类上,此ArrayList类是Arrays工具类的一个内置类。

/**
     * @serial include
     */
    private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {
        private static final long serialVersionUID = -2764017481108945198L;
        private final E[] a;

        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);
        }

        @Override
        public int size() {
            return a.length;
        }

        @Override
        public Object[] toArray() {
            return a.clone();
        }
/* 其他方法省略 */
}

这里的ArrayList是一个静态私有内部类,除了Arrays能访问外,其他类都不能访问。但该类没有提供add方法,其父类AbstractList的add方法为:

public void add(int index, E element) {
    throw new UnsupportedOperationException();
    }
  1. 不同的列表选择不同的遍历方法

ArrayList:for循环(数组下标)
LinkedLsit:foreach / 迭代器

  1. 频繁的插入和删除时使用LinkedList

69.列表相等只需关心元素数据
ArrayList中的equals方法

 public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof List))
            return false;

        ListIterator<E> e1 = listIterator();
        ListIterator<?> e2 = ((List<?>) o).listIterator();
        while (e1.hasNext() && e2.hasNext()) {
            E o1 = e1.next();
            Object o2 = e2.next();
            if (!(o1==null ? o2==null : o1.equals(o2)))
                return false;
        }
        return !(e1.hasNext() || e2.hasNext());
    }

只是要求实现了List接口,不关心List的具体实现类。只要所有的元素相等,并且长度也相等就表明两个List是相等的,与具体的容量类型无关。

  1. 子列表只是原列表的一个视图

subList产生的列表只是一个视图,所有的修改动作直接作用于原列表
71. 推荐使用subList处理局部列表
100个元素,如果要删除索引位于20~30的元素,
可以用subList先取出一个子列表,然后清空。

list.subList(20,30).clear();

因为subList返回的List是原始列表的一个视图,删除这个视图中的所有元素,最终都会反映到原始字符串上。

  1. 生成子列表后不要再操作原列表

subList生成子列表后,保持原列表的值读状态。

对于子列表操作,因为视图是动态生成的,生成子列表后再操作原列表,必然会导致“视图”的不稳定,最有效的方法是通过 Collections.unmodifiableList()
方法设置列表为只读状态。

  1. 使用Comparator进行排序

  2. 不推荐使用binarySearch对列表进行检索

使用binarySearch首先要考虑排序问题,从这点来看indexOf比binarySearch简单得多

  1. 集合中的元素必须做到compareTo和equals同步
  • indexOf()依赖equals()查找,binarySearch()依赖compareTo()方法查找
  • equals()是判断元素是否相等,compareTo()是判断元素在排序中的位置是否相同
  • compareTo()决定排序位置,equals()决定相等,应该保证当排序位置相同时,其equals也相同,否则会产生逻辑混乱。
  1. 集合运算时使用更优雅的方式
  2. 使用shuffle打乱列表
    Collections.shuffle();
public static void shuffle(List<?> list) {
    Random rnd = r;
    if (rnd == null)            r = rnd = new Random(); 
    // harmless race.
    shuffle(list, rnd);
}
  1. 减少HashMap中元素的数量

尽量让HashMap中的元素少量并简单

79.集合中的哈希码不要重复

  1. 多线程考虑使用Vector或HashTable

  2. 非稳定排序推荐使用List

  3. 集合大家族

List:

- ArrayList 动态数组
- LinkedList:双向链表
- Vector:线程安全的动态数组
- Stack:对象栈 先进先出
......

Set:

- EnumSet
- HashSet
- TreeSet
......

Map:

- TreeMap
- HashMap
- HashTable
- Properties
- EnumMap
......

Queue:

- ArrayBlockingQueue
- PriorityBolockingQueue
- LinkedBlockingQueue
- ArrayDeque
- LinkedBlockngDeque
- LinkedList

《编写高质量代码:改善Java程序的151个建议》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值