泛型
可以将运行期的错误转移到编译器来
泛型是JDK5中引入的一种参数化类型特性
把类型当作参数一样传递
<数据类型>只能是引用类型(泛型的副作用)
Plate<T>中的T称为类型参数
Plate<Banana> Banana称为实际类型参数
1.代码更加健壮,只要编译期没有警告,那么运行期就不会出现ClassCastExecption
2.代码更加简洁(不用强转)
3.代码更灵活 复用
Java是如何处理泛型的 泛型类的本质
如果没有指定类型,会被擦除成为Object类型32
@GET(“xxx/xxx")
Call<Apple>getApple()
擦除了但仍然保存在类的常量池,可以获取到Type信息
泛型是jdk5引入的,为了向下兼容,实际上虚拟机是不支持泛型的,所以Java实现的是一种伪泛型机制,也就是说在编译时期擦出了所有的泛型信息,这样Java就不需要产生的新的类型到字节码,所有的泛型最终都是一种原始类型,在Java运行时根本不存在泛型信息。
具体是如何擦除泛型类型的?
在不指定泛型的情况下,泛型变量的类型为该方法中的几种类型的同一父类的最小级,直到Object
在指定泛型的情况下,该方法的几种类型必须是该泛型的实例的类型或者其子类
PECS原则: 如果只需要从集合中获取类型T 使用<? extends T>通配符
如果只需要将类型T放到集合中 使用<? super T> 通配符
使用通配符只有一个目的:灵活转型
注解和反射
注解的作用:本身么有任何意义,单独的注解就是一种注释。 它需要集合其他如反射、插桩等技术才有意义
元注解:注解上的注解 Target :指明注解作用的位置/节点
Retention:声明保留级别:
RetentionPolicy.SOURCE -保留到源码阶段 APT技术:注解处理器,一般用于生成额外的辅助类,运行在编译阶段。 .java -> javac ->.class javac采集到所有的注解信息
继承AbstractProcessor -> 注册到配置文件(手动创建) ->写全类名到文件
实现process方法-> message = processingEnv.getMessager
RetentionPolicy.CLASS 保留到class(字节码)阶段 ,字节码增强技术-热修复
字节码增强:在字节码(.class)中写代码
RetentionPolicy.RUNTIME 保留到运行时
SOURCE<CLASS<RUNTIME
注解处理程序:是javac ->class阶段执行
IntDef注解 语法检查
@IntDef 也是一种元注解
@IntDef (可以限定)
IDE的语法检测也是类似的原理
RUNTIME级别 -反射
反射:在运行时只需要知道类的信息,而不需要知道类对象和去new一个类。
注解+反射 :自动完成findViewById()
Field:获取自己+父类的成员(不包括private,只能public)
DeclaredField:只能获取自己的成员(不包括父类)
1.为要处理的view打标签
作业: 使用注解+反射实现自动取得Intent参数功能
String StringBuffer StringBuilder区别
String是不可变对象,字符串常量,对该字符串操作会产生一个新的常量
StringBuffer和StringBuilder是字符串变量,每次操作都是对本身的字符串操作,Buffer是线程安全,而后者是线程不安全的,也因此Buffer的效率更低。
少量字符串:String
单线程大量字符串:StringBuilder
多线程大量字符串:StringBuffer
强引用>软引用>弱引用>虚引用
强:不会被垃圾回收,对象的一般状态,JVM停止运行时终止
软:内存不足时回收,对象缓存,内存不足时终止
弱:正常GC时,对象缓存,垃圾回收后终止
虚:正常GC时,跟踪对象的垃圾回收,垃圾回收后终止
Handler.removeCallbacksAndMessages(null)方便快捷,在onDestroy里面Handler.removeCallbacksAndMessages(null);,无论runnbale还是message消息全清空,自然也不会关联上Activity。下次GC就能顺利回收了。
代理模式
比如要将Volley框架切换到Okhttp框架,如果使用代理模式,只需要修改代理即可。代理模式起到了隔离的作用,
通过一个代理类,实现全部的代理功能,就需要使用动态代理了。
Retrofit 使用动态代理获取ApiService的代理 ->反射获取方法上的注解/方法参数上的注解 ->记录下这些参数 ->构建okHttp请求
利用反射,注解,动态代理实现OnClick的注入
volatile Object mData
volatile 多个线程 -> 工作内存 ->操作主内存。 volatile保证数据的一致性
notification消息过来,有三个Activity可以处理这个消息,boolean值标记是否被处理。但偶现有一个页面在子线程处理导致数据不一致。 这个时候使用volatile标记bool值就好了。
轮询所有观察者 回调onChange函数
lifeCycle
组件化:合理的组件是如何定义的
liveData二次封装
线程和进程
进程:操作系统管理的最小单元
线程:CPU调度的最小单元
1进程有1个或多个线程
线程依附进程
并行:两个跑道
并发:时间,吞吐量,比如10秒内服务器的吞吐量,10秒钟多少车流量
高并发编程:平衡使用线程,不要CPU过累
new Thread() 每次开辟栈空间,每次至少1MB result:1GB内存
Java默认就是多线程
三种启动方式 Thread.start()
Runnable
Callable
run()和start()区别?
run是函数调用,和线程没有任何关系。run包裹的部分叫做线程体
start 会调用native方法 - 调用系统的线程操作
.start会走底层 ->系统层 ->最终调用到run执行线程体内的函数 这才是线程
锁
加锁后,其他线程无法进来,执行后解锁释放,解决安全性问题。
notify()唤醒wait()中的线程
wait: 获取对象的锁,持有对象的锁,所以要包裹在syn中
CAS操作 1:31:37
ThreadLocal:线程本地变量,让每个线程拥有属于自己的变量的副本
线程池:使用阻塞队列管理任务
先在corePoolSize数量之内承接任务, 超过就把任务放在阻塞队列中,如果阻塞队列依然被填满,则新启动线程执行任务,线程数小于最大线程数,如果最大线程数也满了,就启动拒绝策略。
四种拒绝策略Handler:
1.直接丢弃最老的
2.(默认)直接抛出异常
3.让调用者线程执行任务
4.丢弃最新提交的任务
shutDown线程池:发送中断信号
shutDownNow:发送中断信号,取决于线程内任务对中断信号的处理,也不一定会立即关闭
线程池:配置取决于任务特性:
CPU密集型:CPU核心数:Runtime.getRuntime().avaliableProcessors(); 最多+1,比核心数多1(可能虚拟内存,避免CPU出现空闲)
IO密集型:经验值:CPU核心数*2
混合型
面试题:资料中有 看一下***AQS: AbstractQueuedSynchronizer 同步工具类的内部,内部类继承AQS 功能
volatile:可见性(对于一个volatile变量的读,总是能看到任意线程对这个volatile变量最后的写入)。原子性:对任意单个volatile变量的读写具有原子性,但复合操作不具有原子性
实现原理:使用CPU提供的lock前缀指令,可以强迫当前CPU对变量的修改写回到系统内存,使其他CPU里缓存了该内存地址的数据无效。
synchronized关键字的原理:
Java虚拟机会插入字节码指令,monitorenter,monitorexit,enter会插入到同步代码块开始的位置,ext会插入到结束的位置
拿到Monitor对象的所有权,就表示进入了锁,执行完成后释放了所有权,表示释放了锁。
轻量级锁:通过CAS来加锁解锁 自旋锁 适应性自旋锁 :虚拟机自行动态判定自旋次数:大概是一个线程的上下文切换所需时间,一个锁总是同一个线程获得:偏向锁
序列化
Parcelable性能更好,Serializable在序列化时会产生大量的临时变量(由于它大量使用反射),从而引起频繁的GC
而Parcelable底层使用了binder,Android专有,一般用于进程间通信
Serializable用于网络间通信
但Parcelable不能使用在将数据存储在磁盘上的情况,因为Parcelable不能很好的保证数据的持续性,在外界有变化的情况下建议使用Serializable
1.反序列化的对象无须再调用构造函数构造,因为使用二进制数据构造了
2.序列化前后的对象,是两个不同的对象,对象的地址是变了的,但是使用equals比较是true(深拷贝)
Intent传递数据需要序列化,实际上是经过了AMS操作来启动Activity,实际上是跨进程操作。
序列化主要是为了跨进程传输,而持久化是关注将数据存储
Bundle主要是使用了Parcelable打包数据,因为Bundle也是用于跨进程传输对象的(小对象)
JSON解析:
每种数据类型都有对应的TypeAdapter,如需自定义可以自定义Adapter
文件操作
APK加固:dex文件加密,文件IO stream IO
将dex文件与壳dex文件合并后打包到apk中
1.壳文件从何而来?
2.dex文件可以随便拼凑吗?
3.如何签名?
4.如何运行新的apk(如何脱壳 )
DEX文件(文件头、索引区、数据区(类定义区、数据区、链接数据区))
apk打包流程:合并到dex文件 ->打包到apk ->签名 :META-INF目录下是签名文件
加密过程:源apk ->解压并过滤查找到dex文件 -> 使用fis.readFully()获取到dex文件的所有字节 ->使用AES对字节加密 ->写回到dex文件 ->重命名dex文件
找到壳dex文件
JVM
跨平台:Java - Java字节码 -操作系统函数
跨语言,Kotlin等
网友评论