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在变更时会相互影响。所谓数组转成集合,只是提供了一个集合的视图,本质还是数组。
- list内部是定长数组,进行add/remove就会抛遇到UnsportedOperationException。
解决方法:用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
- 最近在做权限申请时,需要先在dialog中申请到打电话权限,获取权限成功就跳转页,同时dismiss当前dialog,使用DialogFragment来实现。
private void startCallActivity() { if (account == null) { return; } CallActivity.start(mContext, number, account);//跳转到电话页 try { dismiss(); Log.d("tag", "dismissAllowingStateLoss() no error "); } catch (Exception e) {//Can not perform this action after onSaveInstanceState Log.d("tag", "error: " +
以上是关于android日记的主要内容,如果未能解决你的问题,请参考以下文章