android日记

Posted 是个写代码的

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android日记相关的知识,希望对你有一定的参考价值。

上一篇:android日记(三)

1.java关键字transient

  • 只能修饰变量,不能修饰类和方法。
  • 作用:在对象序列化时,被transient修饰过的变量不会参与到序列化过程。
  • 验证:Activity1携带一个序列化的对象,跳转到Activity2。
    private void testTransient() {
            Intent intent = new Intent(getContext(), Activity2.class);
            Model model = new Model();
            model.setName("transient");
            model.setNumber(123);
            intent.putExtra("model", model);
            startActivity(intent);
        }
    class Model implements Serializable {
    
        private transient String name;
        private int number;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getNumber() {
            return number;
        }
    
        public void setNumber(int number) {
            this.number = number;
        }
    }

    在Activity2中,从Intent中获取Activity1中传过来的对象,实现反序列化过程。

     结果显示,被transient修饰过的name变量,其值是null,表明name变量确实没有参与到序列化过程。

     验证:Activity1携带一个序列化的对象,跳转到Activity2。

  • 那transient用于Parceable的效果一样吗?
    class Model implements Parcelable {
    
        private transient String name;
        private int number;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getNumber() {
            return number;
        }
    
        public void setNumber(int number) {
            this.number = number;
        }
    
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(this.number);
        }
    
        public Model() {
        }
    
        protected Model(Parcel in) {
            this.number = in.readInt();
        }
    
        public static final Parcelable.Creator<Model> CREATOR = new Parcelable.Creator<Model>() {
            @Override
            public Model createFromParcel(Parcel source) {
                return new Model(source);
            }
    
            @Override
            public Model[] newArray(int size) {
                return new Model[size];
            }
        };
    }

     结果显示,在Parceable中,transient同样不会参与到序列化。

  • 听说只要是static修饰的变量,都不会被序列化?没毛病!如果你发现反序列化取出的静态变量不会空,那只是因为,会拿到JVM中对应的static值。
    class Model implements Parcelable {
        static String name = "transient";//static变量原始值
        private int number;
    }
    
     private void testTransient() {
            Intent intent = new Intent(getContext(), ViewBindingActivity.class);
            Model model = new Model();
            model.setNumber(123);
            intent.putExtra("model", model);
    
            model.setName("static");//修改静态变量
            startActivity(intent);
        }

    运行结果:

    name不是填入序列化时的原始值“transient”,而是修改后的“static”。这说明反序列化后类中static型变量username的值为当前JVM中对应static变量的值,而不是序列化时的值Alexia

2.将List转为Map需要注意什么

  • java.util.stream.Collections类中提供了toMap()方法,可以将list转为map。 
    private void convertList2Map() {
            List<Person> list = new ArrayList<>();
            list.add(new Person(1, "Peder"));
            list.add(new Person(2, "Bob"));
            list.add(new Person(3, "Hanhan"));
            Map<Integer, String> map = list.stream()
                    .collect(java.util.stream.Collectors.toMap(person -> person.id, person -> person.name));
        }
  • lis中不能有重复key,否则会遇到illegalStateException,
    private void convertList2Map1() {
            List<Person> list = new ArrayList<>();
            list.add(new Person(1, "Peder"));
            list.add(new Person(2, "Bob"));
            list.add(new Person(2, "Hanhan"));//出现重复key
            try {
                Map<Integer, String> map = list.stream()
                        .collect(java.util.stream.Collectors.toMap(person -> person.id, person -> person.name));
            } catch (IllegalStateException e) {
                e.printStackTrace();//java.lang.IllegalStateException: Duplicate key Bob
            }
        }
  • 使用mergeFunction解决重复key问题,其作用在于出现重复key时,自定义对value的选择策略。比如下面(v1,v2)->v2的mergeFunction,定义当出现重复key时,选择新的value。
    private void convertList2Map3() {
            List<Person> list = new ArrayList<>();
            list.add(new Person(1, "Peder"));
            list.add(new Person(2, "Bob"));
            list.add(new Person(2, "Hanhan"));
            list.add(new Person(2, "Panpan"));
            try {
                Map<Integer, String> map = list.stream()
                        .collect(java.util.stream.Collectors.toMap(person -> person.id, person -> person.name, (v1, v2) -> v2));
                String name = map.get(1);//name = Panpan
            } catch (IllegalStateException e) {
                e.printStackTrace();
            }
        }

    类似的,也可以使用(v1,v2)->v1自定义重复key时,取旧的value。又比如定义(v1,v2)->v1+v2,将新旧value拼接起来。

  • 转为map的list中不能有null值,否则会遇到NullPointerException
    private void convertList2Map() {
            List<Person> list = new ArrayList<>();
            list.add(new Person(1, "Peder"));
            list.add(new Person(2, "Bob"));
            list.add(new Person(3, "Hanhan"));
            list.add(new Person(4, null));//将会导致NullPointer
            try {
                Map<Integer, String> map = list.stream()
                        .collect(java.util.stream.Collectors.toMap(person -> person.id, person -> person.name, (v1, v2) -> v1 + v2));
                String name = map.get(1);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    为什么会出现空指针呢?来看看HashMap的merge()方法的源码就知道了。

     @Override
        public V merge(K key, V value,
                       BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
            if (value == null)
                throw new NullPointerException();
            if (remappingFunction == null)
                throw new NullPointerException();
           ...
    }

3.如何遍历一个Map

  • 使用keySet(),遍历key
    //keySet遍历
        private void traverseMap(HashMap<String, String> map) {
            long start = System.currentTimeMillis();
            for (String key : map.keySet()) {
                String value = map.get(key);
            }
        }
  • 使用keyEntry(),遍历Entry<Key,Value>
    //entrySet遍历
        private void traverseMap(HashMap<String, String> map) {
            long start = System.currentTimeMillis();
            for (Map.Entry<String, String> entry : map.entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue();
            }
        }
  • 使用迭代器Ietrator
    //iterator keySet遍历
    private void traverseMap(HashMap<String, String> map) { long start = System.currentTimeMillis(); Iterator<String> iterator = map.keySet().iterator(); while (iterator.hasNext()) { String key = iterator.next(); String value = map.get(key); } }
    //iterator entrySet遍历
    private void traverseMap(HashMap<String, String> map) { long start = System.currentTimeMillis(); Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, String> entry = iterator.next(); String key = entry.getKey(); String value = entry.getValue(); } }
  • 使用Java_8的forEach()
    //forEach   
    private void traverseMap(HashMap<String, String> map) { map.forEach((k, v) -> { String key = k; String value = v; }); }
  • entrySet()和keySet()效率谁高?
    public void traverseMap() {
            Map<Integer, Integer> map = new HashMap<>();
            for (int i = 0; i < 5000000; i++) {
                map.put(i, i);
            }
    
            //keySet遍历
            long start = System.currentTimeMillis();
            Iterator<Integer> iterator = map.keySet().iterator();
            while (iterator.hasNext()) {
                int key = iterator.next();
                int value = map.get(key); //效率低下原因在此,因为此处会再次遍历Map ,取得key对应的value值。
            }
            long end = System.currentTimeMillis();
            long spendTime = end - start;
            Log.d(TAG, "keySet consume time = " + spendTime);
    
    
            //entrySet遍历
            start = System.currentTimeMillis();
            Iterator<Map.Entry<Integer, Integer>> iterator2 = map.entrySet().iterator();
            Map.Entry<Integer, Integer> entry;
            while (iterator2.hasNext()) {
                entry = iterator2.next();
                int key = entry.getKey();
                int value = entry.getValue();
            }
            end = System.currentTimeMillis();
            spendTime = end - start;
            Log.d(TAG, "entrySet consume time = " + spendTime);
        }

    keSet()遍历过程中,通过map.get(key)实际又进行一次遍历取值,因此效率会比entrySet()低。

  • 结论:keySet()会遍历两遍,遍历map时尽量用entrySet(),而不是keySet()。

4.关于ConcurrentModifiedException应该注意些什么

  • 在对集合进行相关操作时,常常会遇到ConcurrentModifiedException异常。无一例外的,异常都是当modCount不等于expectedModCount时抛出。
    if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }

    那么modCount和expectedModCount分别是什么?在各个集合类中,都定义着变量modCount,其初始值是0,注释说明其值表示当前集合结构变更的次数。

     /**
       * The number of times this list has been <i>structurally modified</i>.*/
    protected transient int modCount = 0;

    当集合的结构变更时modCount就会加1,以ArrayList#sort()为例,

     @Override
        @SuppressWarnings("unchecked")
        public void sort(Comparator<? super E> c) {
            final int expectedModCount = modCount;
            Arrays.sort((E[]) elementData, 0, size, c);
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
            modCount++;
        }

    expectedModCount是操作方法内的局部变量,在具体操作执行前,expectedModCount被赋值为modCount本身,而操作执行完成后,会检查modCount与expectedModCount是否相等。如果发现不相等,就抛了ConcurrentModificationException。

  • 什么情况下modCount会不等于expectedModCount呢?当集合操作方法执行期间,如果发生了结构变更,使modCount++得到执行,比如多线程执行arrayList.sort(),就可能出现一个线程执行完sort()后使modCount++,这时另一个线程来比较modCount与expectedModCount,就出现不相等的情况。
  • 如果是单线程,ConcurrentModificationException常常出现在迭代器中。比如下面的代码,
    private void testForeach() {
            List<Integer> list = new ArrayList<>();
            list.add(1);
            list.add(2);
            try {
                for (Integer i : list) {
                    if (i == 1) {
                        list.remove(i);
                    }
                }
            } catch (ConcurrentModificationException e) {
                e.printStackTrace();
            }
        }
    public boolean remove(Object o) {
            if (o == null) {
                for (int index = 0; index < size; index++)
                    if (elementData[index] == null) {
                        fastRemove(index);
                        return true;
                    }
            } else {
                for (int index = 0; index < size; index++)
                    if (o.equals(elementData[index])) {
                        fastRemove(index);
                        return true;
                    }
            }
            return false;
        }
    private void fastRemove(int index) {
            modCount++;
            int numMoved = size - index - 1;
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            elementData[--size] = null; // clear to let GC do its work
        }

    上面的代码引申出好几个问题:1)foreach内部怎么实现循环?2)为什么遭遇ConcurrentModificationException异常?3)数据结构上如何实现删除操作?

  • foreach内部如何实现循环?按照惯例,androidStudio build/javac目录下查看对应的字节码,可以看到foreach实际使用迭代器Iterator来实现。
    private void testForeach() {
            List<Integer> list = new ArrayList();
            list.add(1);
            list.add(2);
    
            try {
                Iterator var2 = list.iterator();
                while(var2.hasNext()) {
                    Integer i = (Integer)var2.next();
                    if (i == 1) {
                        list.remove(i);
                    }
                }
            } catch (Exception var4) {
                var4.printStackTrace();
            }
        }
  • 为什么循环会遭遇ConcurrentModificationException异常?看下面ArrayList.Itr的源码,遍历时总是以hashNext()为条件,通过next()方法取下一条数据。
    private class Itr implements Iterator<E> {
            // Android-changed: Add "limit" field to detect end of iteration.
            // The "limit" of this iterator. This is the size of the list at the time the
            // iterator was created. Adding & removing elements will invalidate the iteration
            // anyway (and cause next() to throw) so saving this value will guarantee that the
            // value of hasNext() remains stable and won\'t flap between true and false when elements
            // are added and removed from the list.
            protected int limit = ArrayList.this.size;
    
            int cursor;       // index of next element to return
            int lastRet = -1; // index of last element returned; -1 if no such
            int expectedModCount = modCount;
    
            public boolean hasNext() {
                return cursor < limit;
            }
    
            @SuppressWarnings("unchecked")
            public E next() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
                int i = cursor;
                if (i >= limit)
                    throw new NoSuchElementException();
                Object[] elementData = ArrayList.this.elementData;
                if (i >= elementData.length)
                    throw new ConcurrentModificationException();
                cursor = i + 1;
                return (E) elementData[lastRet = i];
            }
      public void remove() {
    if (lastRet < 0)
    throw new IllegalStateException();
    if (modCount != expectedModCount)
    throw new ConcurrentModificationException();

    try {
    ArrayList.this.remove(lastRet);
    cursor = lastRet;
    lastRet = -1;
    expectedModCount = modCount;
    limit--;
    } catch (IndexOutOfBoundsException ex) {
    throw new ConcurrentModificationException();
    }
    }

    在创建迭代器的时候,赋值了变量expectedModCount = modCount。在而next()方法中,会比较modCount和expectedModCount,一旦在循环过程中,发生了改变集合结果的操作,比如上面代码中的remove()操作执行时会modCount++。从而进入到下一次迭代时,modCount > expectedModCount,抛出异常。哦对了,这个就是大名鼎鼎的fast-fail机制。

  • 数据结构上如何实现删除操作?上面fastRemove()方法中,remove的操作是通过System.arrayCopy()完成的,这是个native方法,顾名思义,就是通过数组复制。
    System.arraycopy(int[] arr, int star,int[] arr2, int start2, length);
    
    5个参数,
    #第一个参数,是要被复制的数组
    #第二个参数,是被复制的数字开始复制的下标
    #第三个参数,是目标数组,也就是要把数据放进来的数组
    #第四个参数,是从目标数据第几个下标开始放入数据
    #第五个参数,表示从被复制的数组中拿几个数值放到目标数组中

    比如: 数组1:
    int[] arr = { 1, 2, 3, 4, 5 }; 数组2:int[] arr2 = { 5, 6,7, 8, 9 }; 运行:System.arraycopy(arr, 1, arr2, 0, 3); 得到: int[] arr2 = { 2, 3, 4, 8, 9 };

    因此,remove操作的过程为,先计算需要移动的长度(从删除点index到数组末尾)

    int numMoved = size - index - 1;

    然后,数组自身复制到自身,将[index+1, size-1]复制到[index, size-2]

    System.arraycopy(elementData, index+1, elementData, index, numMoved);

    可见,remove完数组的物理长度没有改变的,改变的是数据长度,在 elementData[--size] = null中执行了--size操作。

    /**
         * Returns the number of elements in this list.
         *
         * @return the number of elements in this list
         */
        public int size() {
            return size; 
        }
  • 如何防范ConcurrentModificationException?
    • 在for循环中,如果在集合操作后,不需要再遍历,应该使用break结束循环。
    • 使用Iterator内的remove()操作代替List本身的remove(),每次remove操作完,会重置expectedModCount的值。
      private void testForeach() {
              List<Integer> list = new ArrayList<>();
              list.add(1);
              list.add(2);
              Iterator<Integer> iterator = list.iterator();
              while (iterator.hasNext()) {
                  Integer integer = iterator.next();
                  if (integer == 1) {
                      iterator.remove();
                  }
              }
          }
      private class Itr implements Iterator<E> {
              public void remove() {
                  if (lastRet < 0)
                      throw new IllegalStateException();
                  if (modCount != expectedModCount)
                      throw new ConcurrentModificationException();
      
                  try {
                      ArrayList.this.remove(lastRet);
                      cursor = lastRet;
                      lastRet = -1;
                      expectedModCount = modCount;//每次remove操作完,会重置expectedModCount的值
                      limit--;
                  } catch (IndexOutOfBoundsException ex) {
                      throw new ConcurrentModificationException();
                  }
          }

5.关于集合转换时发生的UnsportedOperationException

  • AarryList.asList(array)实现将数组转为List,对转换得到的list进行add/remove操作,就会遇到UnsportedOperationException
    private void aboutUnsupportedOperateException() {
            String[] array = {"1", "3", "5"};
            List<String> list = Arrays.asList(array);
            try {
                list.set(0, "11");
                array[1] = "33";
    
                list.remove(2);//throw UnsupportedOperationException
                list.add("77");//throw UnsupportedOperationException
            } catch (UnsupportedOperationException e) {
                e.printStackTrace();
            }
        }

    上面代码将array转为list后,有两个注意事项:

    • list和array发生变更时,会相互之间映射影响,原因在第二点分析源码时一起说。

    • list内部是定长数组,进行add/remove就会抛遇到UnsportedOperationException。
      public static <T> List<T> asList(T... a) {
              return new ArrayList<>(a);
          }
      
      //Arrays内中的静态内部类,是java.util.Arrays.ArrayList,而不是java.util.ArrayList
      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);//数组引用赋值 } }

      Arrays.asList()得到的是Arrays的静态内部类java.util.Arrays.ArrayList,并不是java.util.ArrayList,这个同名内部类也是继承于AbstractList,但是并没有重写实现add/remove方法,故而上面的list的remove和add操作遇到了UnsuportedOperationException。另外,Arrays.ArrayList内部实际也是一个数组,在创建Arrays.ArrayList的方法中,其实是把转换的数组引用赋值给了内部数组变量,它们都指向了同一个数组,因此array与list在变更时会相互影响。所谓数组转成集合,只是提供了一个集合的视图,本质还是数组。

解决方法:用Arrays.asList()的结果,去new新的java.util.ArrayList。

       List<String> list = new ArrayList<>(Arrays.asList(array));
  • java.util.Collections类中提供了一些生成immutable列表的方法,比如生成一个空列表Collections.emptyList(),又比如生成一个单元素的列表Collects.singletonList(),这些方法返回的都是Collections旗下的内部类,比如EmptyList、SingletonList,他们都是继承于AbstractList,但是并没有实现父类的remove()和add()方法,也就是不可变的集合。如果对他们使用add()、remove()操作,就会遭遇UnsupportedOperationException。类似的操作还有Collections.emptyMap(),Collections.emptySet(),Collections.singletonMap()等。
     //生成一个空的immutable列表,java.util.Collections.EmptyList
     List<String> emptyList = Collections.emptyList();
    
     //生成一个就包含1个元素的immutable列表,java.util.Collections.SingletonList
     List<String> singletonList = Collections.singletonList("7");
    
    public static <T> List<T> singletonList(T o) {
       return new SingletonList<>(o);
    }
    private static class SingletonList<E>
      extends AbstractList<E>
      implements RandomAccess, Serializable {
        //未实现add()、remove()
    }
  • 使用Map的keySet()/values()/entrySet()方法,返回的集合对象时,有些也没有实现,或者全部实现add()和remove(),使用不当也会报UnSupportedOperationException。以HashMap中的实现为例:
    //hashMap
    public Set<K> keySet() {
            Set<K> ks = keySet;
            if (ks == null) {
                ks = new KeySet();//KeySet是HashMap内部集合类,只实现了remove()没有实现add()
                keySet = ks;
            }
            return ks;
        }
    //hashMap
    public Collection<V> values() {
            Collection<V> vs = values;
            if (vs == null) {
                vs = new Values();//Vaules是HashMap内部immutable集合类
                values = vs;
            }
            return vs;
        }
    //hashMap
    //KeySet是HashMap内部集合类,只实现了remove(),没有实现add()
    public Set<Map.Entry<K,V>> entrySet() {
            Set<Map.Entry<K,V>> es;
            return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
        }
  • 想想为什么转换得到的集合要做immutable限制呢?一个显然易见的解释是,转换结果只是被转对象的一个视图,其本质还是那个转换前的对象。而转换前的集合结构,比如一个数组,本身可能就不支持remove()或者add()操作。

6.RandomAccess接口的作用

  • 翻看ArrayList源码注意到,它实现了RandomAccess接口。这是个空架子接口,注释写道,只是用于标记实现类,具有快速随机访问的能力。
    /**
     * Marker interface used by <tt>List</tt> implementatons to indicate that
     * they support fast (generally constant time) random access.  The primary
     * purpose of this interface is to allow generic algorithms to alter their
     * behavior to provide good performance when applied to either random or
     * sequential access lists.
     * 
     */
    public interface RandomAccess {
    }

    看完好像还是不太清晰它的作用,或者说要具体要怎么用。这时候,可以先检索下,看看系统源码中是怎么使用RandomAccess的。发现Collections类中有使用到它。

  • 从Collections.binarySearch()源码说起,方法用于二分查找,其具体实现逻辑前,有list instanceof RandomAccess的判断。根据判断结果,分别执行indexBinarySearch()和iteratorBinarySearch()。
    public static <T>
        int binarySearch(List<? extends Comparable<? super T>> list, T key) {
            if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
                return Collections.indexedBinarySearch(list, key);
            else
                return Collections.iteratorBinarySearch(list, key);
        }
    private static <T>
        int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) {
            int low = 0;
            int high = list.size()-1;
    
            while (low <= high) {
                int mid = (low + high) >>> 1;
                Comparable<? super T> midVal = list.get(mid);
                int cmp = midVal.compareTo(key);
    
                if (cmp < 0)
                    low = mid + 1;
                else if (cmp > 0)
                    high = mid - 1;
                else
                    return mid; // key found
            }
            return -(low + 1);  // key not found
        }
    private static <T>
        int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key)
        {
            int low = 0;
            int high = list.size()-1;
            ListIterator<? extends Comparable<? super T>> i = list.listIterator();
    
            while (low <= high) {
                int mid = (low + high) >>> 1;
                Comparable<? super T> midVal = get(i, mid);
                int cmp = midVal.compareTo(key);
    
                if (cmp < 0)
                    low = mid + 1;
                else if (cmp > 0)
                    high = mid - 1;
                else
                    return mid; // key found
            }
            return -(low + 1);  // key not found
        }

    也就是说,如果是RandomAccess,遍历时那就直接list.get(mid);否则,使用迭代器get(iterator.get(i,mid))。

  • 由此联想到,同样是List家族成员,ArrayList实现了RandomAccess,而LinkedList则没有。ArrayList内部是一个数组来说,遍历取值时,直接通过get(index)更快。而LinkedList通过iterator的next()方法取值效率更高。
  • 拿实验数据佐证一下,让ArrayList和LinkedList,都分别执行for循环遍历取值和迭代器遍历取值。记录算法执行耗时。
    private void testRandomAccess() {
            List arrayList = createList(new ArrayList<Integer>(), 20000);
            List linkedList = createList(new LinkedList<Integer>(), 20000);
    
            long loopArrayListTime = traverseByLoop(arrayList);
            long iteratorArrayListTime = traverseByIterator(arrayList);
    
            long loopLinkedListTime = traverseByLoop(linkedList);
            long iteratorLinkedListTime = traverseByIterator(linkedList);
        }
    
        private List createList(List list, int size) {
            for (int i = 0; i < size; i++) {
                list.add(i);
            }
            return list;
        }
    
        //使用for循环遍历
        private long traverseByLoop(List list) {
            long startTime = System.currentTimeMillis();
            for (int i = 0; i < list.size(); i++) {
                list.get(i);
            }
            long endTime = System.currentTimeMillis();
            return endTime - startTime;
        }
    
        //使用迭代器遍历
        private long traverseByIterator(List list) {
            Iterator iterator = list.iterator();
            long startTime = System.currentTimeMillis();
            while (iterator.hasNext()) {
                iterator.next();
            }
            long endTime = System.currentTimeMillis();
            return endTime - startTime;
        }

     从实验结果上看,对ArrayLisy使用for循环取值更快,对LinkedList使用迭代器遍历取值更快。而一旦对List用了不合适的遍历方式,会引起严重的性能损耗。

  • 从而,当实际coding中,遇到需要对一个List遍历取值的场景,应该先判断是否是RandomAccess,根据List具体的类型,选择合适遍历方式。一个科学的遍历方法如下。
    private void traverse(List<Integer> list) {
            if (list instanceof RandomAccess) {
                System.out.println("实现了RandomAccess接口,不使用迭代器");
                for (int i = 0; i < list.size(); i++) {
                    System.out.println(list.get(i));
                }
            } else {
                System.out.println("没实现RandomAccess接口,使用迭代器");
                Iterator<Integer> it = list.iterator();
                while (it.hasNext()) {
                    System.out.println(it.next());
                }
            }
        }

7.StateLoss带来的Exception:Can not perform this action after onSaveInstanceState