0%

泛型解决的是什么问题?

在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。

通配符表示什么?

通配符?表示未知类型

泛型存在什么问题?

泛型的类型存在继承关系,不代表使用泛型的类也存在继承关系。

例如Integer是Number的子类,List不是List的子类,不能把List的变量赋值给List的变量。

为什么不能把List的变量赋值给List的变量?

因为List可能还存储Float、Double等类型的元素,List如果可以赋值给List,List里就可以存储Float、Double等非Integer元素了,这与泛型的定义冲突

上界通配符是什么?

<? extends T>:是指 “上界通配符(Upper Bounds Wildcards)”。

上界通配符只能读取,因为读取到的类型进行类型转换是安全的,所有的子类都可以安全的转换为父类。

但是不能插入,因为不知道具体类型。

下界通配符是什么?

<? super T>:是指 “下界通配符(Lower Bounds Wildcards)”

当你知道集合里所持有的元素类型都是A及其父类的时候,此时往list集合里面插入A及其子类(B或C)是安全的。

泛型通配符具体的使用场景?

有一组类继承关系如下:

一个常见的获取容器元素的需求就是,写一个基于容器元素基类的方法,从容器中去取出元素进行打印,而不用针对每一个具体子类定义打印方法。

但实际List<Apple>不能赋值给List<Fruit>,编译会报错,如下:

因为List<Fruit>可能还持有Banana或Pear,与List<Apple>不能等价。

List<Fruit>改为List<? extends Fruit>就可以了,这个上界通配符的意思是,List里持有的都是Fruit的子类,具体是哪些子类不知道,List<Apple>可以看做是List<? extends Fruit>子类,从List<? extends Fruit>也只能取出Fruit,不能取出Fruit的子类。但是不能给List<? extends Fruit>里add任何的Apple、Banana、Pear,因为 ? extends Fruit 并不知道具体的子类是什么。

需要读取就用上界匹配符。

当需要往容器添加元素,List<Object>不能直接赋值给List<Fruit>变量。

因为List<Object>可能存在非Fruit的元素,如果允许赋值,跟类型定义就矛盾了。

解决方法就是把List<Fruit>改为List<? super Fruit>

addVariousFruits(List<? super Fruit> fruitList)

下界通配符表示容器中都是Fruit的父类元素,List<? super Fruit>List<Object>的父类,所以List<Object>可以赋值给List<? super Fruit>变量。

什么时候用上界通配符?什么时候用下界通配符?

PECS原则:Producer extends Consumer super

  • 如果你只需要从集合中获得类型T , 使用<? extends T>通配符
  • 如果你只需要将类型T放到集合中, 使用<? super T>通配符
  • 如果你既要获取又要放置元素,则不使用任何通配符。

泛型擦除是什么意思?

仅于编译时类型检查,在运行时擦除类型信息。

生成的字节码中不包含泛型的类型信息。

泛型擦除会带来什么问题?

在编译时不一定能靠编译器坚持出类型错误,擦除使得使用泛型的代码可以和没有使用泛型的代码随意互用,这样运行时有可能发生类型转换异常。

比如通过反射调用代码,可以绕过编译器的类型错误检查,因为反射是在运行时发生的,编译后泛型的类型就被擦除了,所以不会报错。


深入理解Java虚拟机(第2版)10.3.1 泛型与类型擦除

如下的代码输出结果为true

1
2
3
List<String> l1 = new ArrayList<String>(); 
List<Integer> l2 = new ArrayList<Integer>();
System.out.println(l1.getClass() == l2.getClass());

既然泛型擦除有问题,为什么当初不设计成跟C++模板那种一样呢?

C++的模板实例化类时会为每种类型都创建新的类,就会造成代码膨胀。

同时为了保持JDK5之前的代码能够兼容运行。

泛型擦除的优点

  1. 兼容JDK5之前的代码
  2. 不会像C++模板造成代码膨胀

泛型擦除的缺点

  1. 编译时不一定能靠编译器坚持出类型错误,这样运行时有可能发生类型转换异常,例如反射可以绕过编译器的类型错误检查。
  2. 不能创建具体类型的泛型数组。
  3. 方法重载时不同的泛型具体类会被认为擦除后是一个类型从而禁止重载。

为什么不能创建具体类型的泛型数组?

List<Integer>[] list = new ArrayList<Integer>[];

这段代码编译会不通过

List<Integer>List<Boolean>在 jvm 中等同于List<Object>,所有的类型信息都被擦除,程序也无法分辨一个数组中的元素类型具体是 List<Integer>类型还是 List<Boolean>类型。

Integer的缓存机制是怎么回事?

学会看字节码就知道自动装箱拆箱实际调用的是什么。

阅读全文 »

为什么要有装箱和拆箱?

有些情况下基本类型无法实现对应功能。

有哪些场景不得不使用包装类?

  1. 集合类只能使用包装类型,不允许使用基本类型,所以需要有包装类
  2. 由于基本类型没有null的状态,所以需要有包装类对象能够表示null
  3. 泛型只针对类,不能使用基本类型
  4. 反射也只能反射类,不能反射基本类型
  5. 包装类一旦创建,是值不可变的对象,不容易赋值出错
    阅读全文 »

RxJava背景

RxJava是NetFlix出品的Java框架, 官方描述为 a library for composing asynchronous and event-based programs using observable sequences for the Java VM,翻译过来就是“使用可观察序列组成的一个异步地、基于事件的响应式编程框架”。

RxJava可以解决什么问题?

主要是对数据流的组合变换非常强大和方便。

  • 各种偏函数式风格的操作符
  • 冷热流
  • 异步转同步
  • 解决回调地狱

主要缺点是学习和理解成本过高。

阅读全文 »

序列化的意义和价值是什么?

序列化就是把对象转换为字节序列。

反序列化就是把字节序列还原为对象。

对象是程序运行时的信息载体,序列化机制使得对象信息可以脱离程序的运行而独立存在,这样便于进行网络传输或持久性存储到磁盘。

序列化考量的重点是什么?

  1. 协议是否跨平台
  2. 序列化速度
  3. 字节序列大小

Serializable底层运作机制是怎样的?

查看ObjectInputStream的readObject方法 和 ObjectOutputStream的writeObject的代码。

主要运用了反射。

Serializable如何自定义决定哪些字段写入和读取?

  1. 通过transient关键字修饰属性,排除在序列化之外
  2. 在类中增加writeObejct和readObject方法,JDK中的ArrayList、HashMap等都复写了writeObject
  3. 在类中增加writeReplace和readResolve方法,提供了writeReplace就不会执行writeObject,提供了readResolve就不会执行readObject
  4. 实现Externalizable接口,显式实现readExternal和writeExternal

Java为用户定义了默认的序列化、反序列化方法,其实就是ObjectOutputStream的defaultWriteObject方法和ObjectInputStream的defaultReadObject方法。

为什么HashMap要自定义序列化逻辑呢?
可能的原因是,存储数据的数组table,一般都是不满的(因为HashMap的负载因子默认0.75,超过就会扩容),里面肯定会有很多null,如果是默认的序列化,这些null也会被被序列化,显然这些null是没有必要的做序列化的。

ArrayList自定义序列化方法,也是因为elementData数组可能没存满。

serialVersionUID是干什么的?

反序列化需要指定一个类,但是类的结构会变更,如何保证已经序列化的字节序列与当前的类结构是对应上的,可以在字节序列和类中分别存储一个版本号,只有版本号相同,就当做是可转行的,版本号不同反序列化就报错。

类中指定版本号是使用private static final long serialVersionUID属性,值为任意。如果不声明这个属性,虚拟机会根据类信息自动生成一个版本号。

这样随着class的升级,就无法正确反序列化;不指定版本号另一个明显隐患是,不利于jvm间的移植,可能class文件没有更改,但不同jvm可能计算的规则不一样,这样也会导致无法反序列化。

什么情况下需要修改serialVersionUID呢?

分三种情况:

  1. 如果只是修改了方法,反序列化不容影响,则无需修改版本号;
  2. 如果只是修改了静态变量,瞬态变量(transient修饰的变量),反序列化不受影响,无需修改版本号;
  3. 如果修改了非瞬态变量,则可能导致反序列化失败。如果新类中实例变量的类型与序列化时类的类型不一致,则会反序列化失败,这时候需要更改serialVersionUID。如果只是新增了实例变量,则反序列化回来新增的是默认值;如果减少了实例变量,反序列化时会忽略掉减少的实例变量。

Serializable序列化会有什么问题?

当程序试图序列化一个对象时,会先检查此对象是否已经序列化过,只有此对象从未(在此虚拟机)被序列化过,才会将此对象序列化为字节序列输出。

如果序列化一个可变对象(对象内的内容可更改)后,更改了对象内容,再次序列化,并不会再次将此对象转换为字节序列,而只是保存序列化编号。

Serializable反序列化会有什么问题?

反序列化不会调用类的构造函数,会调用第一个非序列化父类的无参构造函数

这点可以通过 ObjectInputSteam -> readObject -> readObject0 -> readOrdinaryObject -> desc.newInstance() 得知,desc是ObjectStreamClass的实例,ObjectStreamClass.newInstance()的注释是:

1
2
3
4
5
6
7
8
9
/**
* Creates a new instance of the represented class. If the class is
* externalizable, invokes its public no-arg constructor; otherwise, if the
* class is serializable, invokes the no-arg constructor of the first
* non-serializable superclass. Throws UnsupportedOperationException if
* this class descriptor is not associated with a class, if the associated
* class is non-serializable or if the appropriate no-arg constructor is
* inaccessible/unavailable.
*/

这样设计的目的是什么?

推测是因为如果调用了类的构造函数,如果构造函数中有改变类属性状态的代码,反序列化后就得不到序列化时的状态了,所以不能调用。

Serializable的具体序列化执行过程中哪些地方用到了大量的反射?

因为没有在代码层面手动写入特定字段,序列化都是全自动化的,在ObjectOutputStream底层都是通过反射获取类的字段信息,defaultWriteFields()读取类的属性字段是通过Field,Field从ObjectStreamClass的getFields()得来,getFields()内部还是反射。

ObjectInputStream也通过反射创建类。

Serializable的具体序列化执行过程中哪些地方创建了大量的临时对象?会有什么问题?

创建大量的临时对象会引发频繁的垃圾回收,垃圾回收会stop the world,进而引起进程的卡顿。

Serializable的序列化过程比较复杂,有很多对象参与工作,例如ObjectStreamClass。

Parceable底层运作机制是怎样的?

Interface for classes whose instances can be written to and restored from a Parcel. Classes implementing the Parcelable interface must also have a non-null static field called CREATOR of a type that implements the Parcelable.Creator interface.
A typical implementation of Parcelable is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class MyParcelable implements Parcelable {
private int mData;

public int describeContents() {
return 0;
}

public void writeToParcel(Parcel out, int flags) {
out.writeInt(mData);
}

public static final Parcelable.Creator<MyParcelable> CREATOR
= new Parcelable.Creator<MyParcelable>() {
public MyParcelable createFromParcel(Parcel in) {
return new MyParcelable(in);
}

public MyParcelable[] newArray(int size) {
return new MyParcelable[size];
}
};

private MyParcelable(Parcel in) {
mData = in.readInt();
}
}

Parcelable通过Parcel的一系列read和write方法实现序列化,Parcel的read和write方法都是在native层实现。

Parcel把对象数据直接序列化输出在内存,并且数据结构很紧凑,几乎只存储数据值,不存储额外的信息,而Serializable会存储很多额外的信息,所以Parcel序列化的输出的信息量就比Serializable少,同时没有用反射,而是直接操作内存指针,因此速度更快。
缺点:
1. 由于数据紧凑,没有存储必要的判断信息,不能用于持久化存储,因为反序列化时,缺乏足够的信息来保证数据恢复的正确性。
2. 需要手动指定哪些字段写入,并且写入顺序和读入顺序要一致

Parceable与Serializable有什么区别?

Serializable 会使用反射,序列化和反序列化过程需要大量 I/O 操作。

Parcelable 自已实现封送和解封(marshalled &unmarshalled)操作不需要用反射,数据也存放在 Native 内存中,效率要快很多。

前言

在自定义ViewGroup的过程中,如果涉及到View的拖动滑动,ViewDragHelper的使用应该是少不了的,它提供了一系列用于用户拖动子View的辅助方法和相关的状态记录,像Navigation Drawer的边缘滑动、QQ5.x的侧滑菜单、知乎里的页面滑动返回都可以由它实现,所以有必要完全掌握它的使用。

要想完全掌握ViewDragHelper的使用和原理,最好的办法就是读懂它的源码,所以就有了这篇分析,以便在印象模糊之时可以再次快速回顾ViewDragHelper的原理、用法、注意事项等。

阅读全文 »

在日常开发过程中,只要涉及到activity,那么对task相关的东西总会或多或少的接触到,不过对task相关的一些配置的作用理解的还不是很透彻,官方文档在细节上说的也不够清楚,要透彻理解还是得自己写demo实践检验,所以便有了这篇总结。

阅读全文 »