赞
踩
七月份已经过完了,2023年已经过去一半了,迎来了毕业季,也迎来了秋招,也即将迎来"金九银十”,给大家发一次干货吧,相信有很多的小伙伴都不想浪费暑假这个提升自己的机会,那今天小编给大家一个福利了,分享一些面试题,希望帮助大家早日找到理想的工作。
垃圾回收即garbagecollection,简称GC,作用是在某块内存
不再使用时及时对其进行释放的管理机制。GC的几个重点就是怎么找到无用对象,怎么对其进行释放,何时进行GC等等
另外说一句,Hotspot VM里堆是分代回收的(分出新生代和老年代,分别进行回收),不知道ART里有没有类似的机制
目前来说有两种主流机制,
这个具体是看回收器的实现
任何时候都可能,当系统觉得你内存不足了就会开始回收常见的比如分配对象内存不足时(这里的内存不足有可能不是占用真的很高,可能是内存足够,但是没有连续内存空间去放这个对象),当前堆内存占用超过阈值时,手动调用 System.gc() 建议开始GC时,系统整体内存不足时等
这里举个例子:
你要分配一个大对象(比如一张图片),如果内存不足, 可能会发生下面这个情况:
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存(下文给出示例)。
软引用可以和一个引用队列 ReferenceQueue 联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列
中。
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象, 不管当前内存空间足够与否,都会回收它的内存。不 过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列 联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。
1.概述
虚拟机把描述类的数据从Class文件加载到内存,并对数据 进行校验、转换解析和初始化,最终形成可以被虚拟直接使用的java类型,这就是虚拟机的类加载机制。
2.类加载的过程
其中验证,准备,解析三个部分称为连接。其中解析和初始化的顺序可能会不同(可能先解析后初始化,也可能先初始化后解析)。
2.1 关于初始化
5种情况会触发类的初始化
3.类加载的过程
这里顺带再说下对象的加载过程。
java虚拟机遇到一个new指令
检查new引用代表的类是否被加载,解析和初始化
加载过
没有加载过,先执行相应类的加载过程
虚拟机为对象分配内存
对象所需要的内存大小在类加载过后便可以确定
为对象分配空间的过程等同于把一块确定大小的内存从java堆中划分出来。
java堆绝对规整绝对规整解释:所有用过的内存放在一边,空闲的内存放在另一边中间放着一个指针作为分界点的指示器。
分配过程:指针向空闲空间那边挪动一段与对象大小相等的距离。这种分配方式称为“指针碰撞”。
java堆不规整
不规整解释:已使用的内存和未使用的内存相互交错,虚拟机维护一个列表,记录那些内存是可用的。
分配过程:分配时从列表中找一块足够大的空间划分给对象实例。并更新列表上的记录。这种分配方式称为“空闲列表”。
java堆是否规整是由所采用的垃圾收集器是否带有 压缩整理功能决定的。
大概这么4种
extends Thread;
implRunnable;
implCallable通过 FutureTask包装器来创建线程;
使用ExecutorService、Callable、Future实现有返回结果的多线
程。;
extends Thread 和 implRunnable 的线程没有返回值, 而
FutureTask 和
ExecutorService(ExecutorService.submit(xxx) return Future<?> )
有返回值.
第一是创建状态。在生成线程对象,并没有调用该对象的
start方法,这是线程处于创建状态。
第二是就绪状态。当调用了线程对象的start方法之后,
该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪
状态。
第三是运行状态。线程调度程序将处于就绪状态的线
程设置为当前线程,此时线程就进入了运行状态,开始运
行run函数当中的代码。
第四是阻塞状态。线程正在运行的时候,被暂停,通常
是为了等待某个时间的发生(比如说某项资源就绪)之后再继
续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
第五是死亡状态。如果一个线程的run方法执行结束或
者调用stop方法后,该线程就会死亡。对于已经死亡的线程,
无法再使用start方法令其进入就绪。
多线程同步和异步不是一回事。
几种情况,
1.就是大家说的synchronized 他可以保证原子性,保证多个
线程在操作同一方法时只有一个线程可以持有锁,并且操作
该方法,
2.就是手动调用读写锁,
3.手动操作线程的wait和notify
4.volatile我记得是没有原子性的,他可以保证内存可见性,
在多线程的情况下保证每个线程的数据都是最新的
一、死锁的定义
多线程以及多进程改善了系统资源的利用率并提高了系统
的处理能力。然而,并发执行也带来了新的问题——死
锁。所谓死锁是指多个线程因竞争资源而造成的一种僵局
(互相等待),若无外力作用,这些进程都将无法向前推
进。
二、死锁产生的原因
/** * 一个简单的死锁类 * 当DeadLock类的对象flag==1时(td1),先锁定o1,睡 眠500毫秒 * 而td1在睡眠的时候另一个flag==0的对象(td2)线程启 动,先锁定o2,睡眠500毫秒* td1睡眠结束后需要锁定o2才能继续执行,而此时o2已被 td2锁定; * td2睡眠结束后需要锁定o1才能继续执行,而此时o1已被 td1锁定; * td1、td2相互等待,都需要得到对方锁定的资源才能继续执 行,从而死锁。 */ public class DeadLock implements Runnable { public int flag = 1; //静态对象是类的所有对象共享的 private static Object o1 = new Object(), o2 = new Object(); @Override public void run() { System.out.println("flag=" + flag); if (flag == 1) { synchronized (o1) { try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); }synchronized (o2) { System.out.println("1"); } } } if (flag == 0) { synchronized (o2) { try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } synchronized (o1) { System.out.println("0"); } } } } public static void main(String[] args) { DeadLock td1 = new DeadLock(); DeadLock td2 = new DeadLock(); td1.flag = 1; td2.flag = 0; //td1,td2都处于可执行状态,但JVM线程调度先执行哪 个线程是不确定的。 //td2的run()可能在td1的run()之前运行newThread(td1).start(); new Thread(td2).start(); } }
在有些情况下死锁是可以避免的。三种用于避免死锁的技
术:
Thread 1: lock A lock B Thread 2: wait for A lock C (when A locked) Thread 3: 2. 加锁时限(线程尝试获取锁的时候加上一定的时限,超 过时限则放弃对该锁的请求,并释放自己占有的锁) 3. 死锁检测 加锁顺序 当多个线程需要相同的一些锁,但是按照不同的顺序加 锁,死锁就很容易发生。 如果能确保所有的线程都是按照相同的顺序获得锁,那么 死锁就不会发生。看下面这个例子: wait for A wait for B wait for C
如果一个线程(比如线程3)需要一些锁,那么它必须按照确定的顺序获取锁。它只有获得了从顺序上排在前面的锁之
后,才能获取后面的锁。例如,线程2和线程3只有在获取了锁A之后才能尝试获取锁C(译者注:获取锁A是获取锁C的必要条件)。因为线程1已 经拥有了锁A,所以线程2和3需要一直等到锁A被释放。然 后在它们尝试对B或C加锁之前,必须成功地对A加了锁。按照顺序加锁是一种有效的死锁预防机制。但是,这种方式需要你事先知道所有可能会用到的锁(译者注:并对这些锁做适当的排序),但总有些时候是无法预知的。加锁时限另外一个可以避免死锁的方法是在尝试获取锁的时候加一个超时时间,这也就意味着在尝试获取锁的过程中若超过了这个时限该线程则放弃对该锁请求。若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。这段随机的等待时间让其它线程有机会尝试获取相同的这些锁,并且让该应用在没有获得锁的时候可以继续运行(译者注:加锁超时后可以先继续运行干点其它事情,再回头来重复之前加锁的逻辑)。以下是一个例子,展示了两个线程以不同的顺序尝试获取
相同的两个锁,在发生超时后回退并重试的场景:
Thread 1 locks A
Thread 2 locks B
Thread 1 attempts to lock B but is blocked
Thread 2 attempts to lock A but is blocked
Thread 1's lock attempt on B times out
Thread 1 backs up and releases A as well
Thread 1 waits randomly (e.g. 257 millis)
before retrying. Thread 2's lock attempt on A times out
Thread 2 backs up and releases B as well
Thread 2 waits randomly (e.g. 43 millis)
before retrying.
在上面的例子中,线程2比线程1早200毫秒进行重试加锁,因此它可以先成功地获取到两个锁。这时,线程1尝试获取锁A并且处于等待状态。当线程2结束时,线程1也可以顺利的获得这两个锁(除非线程2或者其它线程在线程1成功获得两个锁之前又获得其中的一些锁)。需要注意的是,由于存在锁的超时,所以我们不认为这种场景就一定是出现了死锁。也可能是因为获得了锁的线程(导致其它线程超时)需要很长的时间去完成它的任 务。此外,如果有非常多的线程同一时间去竞争同一批资源,就算有超时和回退机制,还是可能会导致这些线程重复地尝试但却始终得不到锁。如果只有两个线程,并且重试的超时时间设定为0到500毫秒之间,这种现象可能不会发生,但是如果是10个或20个线程情况就不同了。因为这些线程等待相等的重试时间的概率就高的多(或者非常接近以至于会出现问题)。(译者注:超时和重试机制是为了避免在同一时间出现的竞争,但是当线程很多时,其中两个或多个线程的超时时间一样或者接近的可能性就会很大,因此就算出现竞争而导致超时后,由于超时时间一样,它们又会同时开始重试, 导致新一轮的竞争,带来了新的问题。)这种机制存在一个问题,在Java中不能对synchronized同步块设置超时时间。你需要创建一个自定义锁,或使用Java5中java.util.concurrent包下的工具。写一个自定义锁类不复杂,但超出了本文的内容。后续的Java并发系列会涵盖自定义锁的内容。
死锁检测
死锁检测是一个更好的死锁预防机制,它主要是针对那些不可能实现按序加锁并且锁超时也不可行的场景。每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph等等)将其记下。除此之外,每当有线程 请求锁,也需要记录在这个数据结构中。当一个线程请求锁失败时,这个线程可以遍历锁的关系图 看看是否有死锁发生。例如,线程A请求锁7,但是锁7这个时候被线程B持有,这时线程A就可以检查一下线程B是否已经请求了线程A当前所持有的锁。如果线程B确实有这样的请求,那么就是发生了死锁(线程A拥有锁1,请求锁7; 线程B拥有锁7,请求锁1)。当然,死锁一般要比两个线程互相持有对方的锁这种情况 要复杂的多。线程A等待线程B,线程B等待线程C,线程C 等待线程D,线程D又在等待线程A。线程A为了检测死锁, 它需要递进地检测所有被B请求的锁。从线程B所请求的锁开始,线程A找到了线程C,然后又找到了线程D,发现线程D请求的锁被线程A自己持有着。这是它就知道发生了死锁。下面是一幅关于四个线程(A,B,C和D)之间锁占有和请求 的关系图。像这样的数据结构就可以被用来检测死锁。
那么当检测出死锁时,这些线程该做些什么呢?一个可行的做法是释放所有锁,回退,并且等待一段随机的时间后重试。这个和简单的加锁超时类似,不一样的是只有死锁已经发生了才回退,而不会是因为加锁的请求超时了。虽然有回退和等待,但是如果有大量的线程竞争同一批锁,它们还是会重复地死锁(编者注:原因同超时类似,不能从根本上减轻竞争)。一个更好的方案是给这些线程设置优先级,让一个(或几个)线程回退,剩下的线程就像没发生死锁一样继续保持着它们需要的锁。如果赋予这些线程的优先级是固定不变的,同一批线程总是会拥有更高的优先级。为避免这个问题,可以在死锁发生的时候设置随机的优先级。
run() 和普通的成员方法一样,可以被重复调用。但是如果单独调用 run 方法,则不是在子线程中执行。start()这个方法只能被调用一次。调用这个方法后 程序会启动一个 新的线程来 执行 run 方法。注意 :调用start 后,线程处于可运行状态(并没有运行),一旦得到 cup 时间片,就开始执行run 方法,run 方法结束后,线程则立即终止。
1.volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
2.volatile仅能使用在变量级别;synchronized则可以使用 在变量、方法、和类级别的volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
3.volatile不会造成线程的阻塞;synchronized可能会造成 线程的阻塞。
4.volatile标记的变量不会被编译器优化;synchronized标 记的变量可以被编译器优化
当多个线程要共享一个实例对象的值得时候,那么在考虑安全的多线程并发编程时就要保证下面3个要素:
原子性(Synchronized, Lock)
有序性(Volatile,Synchronized, Lock)
可见性(Volatile,Synchronized,Lock)
当然由于synchronized和Lock保证每个时刻只有一个线程执行同步代码,所以是线程安全的,也可以实现这一功能,但是由于线程是同步执行的,所以会影响效率。
下面是对3个要素的详细解释:
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。在Java中,基本数据类型的变量的读取和赋值操作是原子 性操作,即这些操作是不可被中断的,要么执行,要么不执行。
可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。当一个共享变量被volatile修饰时,它会保证修改的值会立 即被更新到主存,当有其他线程需要读取共享变量时,它会去内存中读取新值。普通的共享变量不能保证可见性,因为普通共享变量被修改后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。更新主存的步骤:当前线程将其他线程的工作内存中的缓存变量的缓存行设置为无效,然后当前线程将变量的值跟新到主存,更新成功后将其他线程的缓存行更新为新的主存地址其他线程读取变量时,发现自己的缓存行无效,它会等待缓存行对应的主存地址被更新之后,然后去对应的主存读取最新的值。
有序性:即程序执行的顺序按照代码的先后顺序执行。 在Java内存模型中,允许编译器和处理器对指令进行重排 序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。可以通过volatile关键字来保证一定的“有序性”。当在处理并发编程的时候,只要程序满足了原子性,可见性和有序性,那么程序就不会发生脏数据的问题。
ThreadLocal用于保存某个线程共享变量:对于同一个staticThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。
1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的
值。
2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的
值。
3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。
4、ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法, 返回此方法值。
notify 方 法
1.方法调用之前由于也需要获取该对象的锁,所以使用的位置: synchronized方法中或者synchronized代码块中
2.通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则线程规划器任意挑选出其中一个wait() 状 态 的 线 程 来 发 出 通 知
3.wait()方法执行完毕之后,会立刻释放掉锁,如果没有再 次使用notify,其他wait()的线程由于没有得到通知,会继续阻塞在wait()的状态,等待其他对象调用notify或者notifyAll来唤醒。
notifyAll方法
1.使用位置和notify一样
2.notifyAll唤醒所有处于wait的线程
作为一个Android开发,Handler机制是一定要了解的。在我面试过程中,发现很多人对Handler和Looper机制非常了解,对答如流, 但 是 却 不 知 道 何 为 HandlerThread
HandlerThread是Thread的子类,严格意义上来说就是一个线程,只是它在自己的线程里面帮我们创建了Looper HandlerThread存在的意义如下:
方便使用:a. 方便初始化,b,方便获取线程looper
保证了线程安全
我 们 一般在Thread里面 线程Looper进行初始化的代码里面,必须要对Looper.prepare(),同时要调用Loop。loop () ;
上面这段代码有没有问题呢?
以上问题
HandlerThread 已经 帮我们 完美的 解决了 ,这就 是 handlerThread存在 的必要 性了。
我们再看看HandlerThread源码
它 的 优点就在于它的多线程操作,可以帮我们保证使用Thread的handler时一定是安全的。
1.Looper 准备和开启轮循:
Looper#prepare() 初 始 化 线 程 独 有 的 Looper 以 及 MessageQueue Looper#loop() 开 启 死 循 环 读 取 MessageQueue 中下 一 个 满 足 执 行 时 间的 Message 尚无 Message 的话,调用 Native 侧的 pollOnce() 进入无限等待 存在 Message,但执行时间 when 尚未满足的话,调用 pollOnce() 时传入剩余时长参数进入有限等待
Looper 实 例 被 管 理 在 静 态 属 性 sThreadLocal 中 ThreadLocal 内 部 通 过 ThreadLocalMap 持 有 Looper, key 为ThreadLocal 实 例 本身,value 即为Looper 实例 每个 Thread 都有一个自己的 ThreadLocalMap,这样可以保证每个线程对应一个独立的 Looper 实 例,进而保证 myLooper() 可以获得线程独有的Looper 彩蛋:一个 App 拥有几个 Looper 实例?几个 ThreadLocal 实例?几 个 MessageQueue 实 例 ? 几 个 Message 实 例 ? 几 个 Handler 实 例
一个线程只有一个 Looper 实例 一个 Looper 实例只对应着一个 MessageQueue 实例 一个 MessageQueue 实例可对应多个 Message 实 例 ,其从 Message 静态池里获取,存在 50 的上限 一个线程可以拥有多个 Handler 实例,其Handler 只是发送和执行 任 务 逻 辑的入口和出口 ThreadLocal 实例是静态的,整个进程共用一个实例。每个 Looper 存放的 ThreadLocalMap 均弱引用它作 为 key
首 先要明确并非不是用来切换线程的,只是为了让每个线程方便程获取自己的 Looper 实例,见 Looper#myLooper()后续可供 Handler 初始化时指定其所属的 Looper 线程 也可用来线程判断自己是否是主线程
区别:
Main Looper 不可 quit 主线程需要不断读取系统消息和用书输入,是进程的入口,只可被系统直接终止。进而其 Looper 在创建 的 时 候 设置了不可 quit 的志,而其他线程的 Looper 则可以也必须手动 quit Main Looper 实例 还被静 态缓存 为了 便于每 个线程 获得主 线程 Looper 实例 ,见 Looper#getMainLooper(),Main Looper 实例 还 作 为 sMainLooper 属性缓存到了 Looper 类中。
相同点:
都是通过 Looper#prepare() 间接调用 Looper 构造函数创建的实例 都被静态实例 ThreadLocal 管理,方便每个线程获取自己的 Looper 实例 彩蛋:主线程为什么不用初始化 Looper?
App 的 入 口 并 非 MainActivity, 也 不 是 Application, 而 是 ActivityThread。其 为 了 Application、ContentProvider、Activity 等组 件的运 行,必 须事先 启动不 停接受 输入的 Looper 机制 ,所以 在main()执 行 的最后将调用 prepareMainLooper() 创建 Looper 并调用 loop() 轮循。不需要我们调用,也不可能有我们调用。可以说如果主线程没有创建 Looper 的话,我们的组件也不可能运行得到!
Handler 创 建的 时候指 定了其 所属线 程的 Looper,进 而持有 了 Looper 独有 的 MessageQueue Looper# loop() 会持 续读取 MessageQueue 中合 适的 Message,没 有 Message 的时 候进入 等待当 向 Handler 发 送 Message 或 Runnable 后 , 会 向 持 有 的 MessageQueue 中 插 入 Message ,Message 抵 达并 满足条 件后会 唤醒 MessageQueue 所属 的线程 ,并将 Message 返回 给 Looper Looper 接 着 回 调 Message 所 指 向 的 Handler Callback 或 Runnable,达 到 线 程 切 换 的 目 的简 言之 ,向 Handler 发送 Message 其实 是向 Handler 所属 线程的 独有 MessageQueue 插入 Message。而线 程独有 的Looper 又会 持 续读取该 MessageQueue。所以向其他线程的 Handler 发送完 Message,该线程的 Looper 将自动响应。
kotlin和java一样也是一门jvm语言最后的编译结果都是.class文件,并且可以通过kotlin的.class文件反编译回去java代码,并且封装了许多语法糖,其中我在项目中常用的特性有
1.扩展,(使用非集成的方式 扩张一个类的方法和变量):比方说 px和dp之间的转换 之前可能需要写个Util现在,通过扩展Float的变量 最后调用的时候仅仅是 123.dp这样px转成dp了
2.lamdba表达式,函数式编程. lamdba表达式并不是kotlin 的专利,java中也有,但是有限制, 像setOnClickListener一样, 接口方法只有一个的情况才能调用, 而在kotlin中对接口的lambda也是如此,有这样的限制,但是他更推荐你使用闭包的方式而不是实现匿名接口的方式去实现这样的功能,闭包对lambda没有接口这么多的限制,另外就是函数式编程 在java8中提供了streamApi对集合进行mapsortreduce等等操作,但是对androidapi有限制,为了兼容低版本,几乎不可能使用streamApi
3.判空语法 省略了许多ifxxx==null的写法 也避免了空指针异常aaa?.toString ?: “空空如也” 当aaa为空的时候 它的值被"空空如也"替代
aaa?.let{ it. bbb
}
当aaa不为空时 执行括号内的方法
4.省略了findViewById ,使用kotlin 就可以直接用xml中定义的id 作为变量获取到这个控件,有了这个 butterknife就可以淘汰了,使用databinding也能做到,但是,非常遗憾,databinding的支持非常不好,每次修改视图,都不能及时生成,经常要rebulid才能生成.
5,默认参数 减少方法重载 fun funName(a :Int ,b:Int =123)通过如上写法 实际在java中要定义两个写法 funName(a)和funName(a,b)
6.kotlin无疑是android将来语言的趋势,我已经使用kotlin 一年了,不仅App工程中使用,而且封装的组件库也是用kotlin,另外说明,kotlin会是apk大小在混淆后增加几百k.但对于更舒适的代码来说这又算的了什么呢
@JvmOverloads注解的作用就是:在有默认参数值的方法加上
@JvmOverloads注解,则Kotlin就会暴露多个重载方法。可以减少写构造方法。例如:没有加注解,默认参数没有起到任何作用。
fun f(a: String, b: Int = 0, c: String="abc") { ... } 那相当于在java中:void f(String a, int b, String c){ } 如果加上注解@JvmOverloads ,默认参数起到作用 @JvmOverloads fun f(a: String, b: Int = 0, c: String="abc") { ...} 相当于Java中: 三个构造方法, void f(String a) void f(String a, int b) void f(String a, int b, String c)
List返回的是EmptyList,MutableList返回的是一个ArrayList,查看EmptyList的源码就知道了,根本就没有提 供add 方法。
internal object EmptyList : List, Serializable, RandomAccess { private const val serialVersionUID: Long = -7390468764508069838L override fun equals(other: Any?): Boolean = other is List<*> && other.isEmpty() override fun hashCode(): Int = 1 override fun toString(): String = "[]" override val size: Int get() = 0 override fun isEmpty(): Boolean = true override funcontains(element: Nothing): Boolean = false override fun containsAll(elements: Collection<Nothing>): Boolean = elements.isEmpty() override fun get(index: Int): Nothing = throw IndexOutOfBoundsException("Empty list doesn't contain element at index $index.") override fun indexOf(element: Nothing): Int = -1 override fun lastIndexOf(element: Nothing): Int = -1 override fun iterator(): Iterator<Nothing> = EmptyIterator override fun listIterator(): ListIterator<Nothing> = EmptyIterator override fun listIterator(index: Int): ListIterator<Nothing> { if (index != 0) throw IndexOutOfBoundsException("Index: $index") return EmptyIterator }override fun subList(fromIndex: Int, toIndex: Int): List<Nothing> { if (fromIndex == 0 && toIndex == 0) return this throw IndexOutOfBoundsException("fromIndex: $fromIndex, toIndex: $toIndex") } private fun readResolve(): Any = EmptyList
属性委托
有些常见的属性操作,我们可以通过委托方式,让它实现,例如:
类委托
可以通过类委托来减少 extend类委托的时,编译器回优使用自身重新函数,而不是委托对象的函数
interface
Base{ fun
print()
}
case BaseImpl(var x: Int):Base{
override fun
print(){ print(x)
}
}
// Derived 的 print 实现会通过构造函数的b对象来完成
class Derived(b: base): Base by b
with不怎么使用,因为它确实不防空; 经常使用的是
run和
apply
Unit : Kotlin 中Any的子类, 方法的返回类型为Unit时,可以省略;
Void:Java中的方法无法回类型时使用,但是不能省略;
Nothing:任何类型的子类,编译器对其有优化,有一定的推导能力,另外其常常和抛出异常一起使用;
由于篇幅原因,这里还有我整理的2021秋招到2023年春招各一、二线互联网公司的Android面试题,内容包括Java基础、Activity、Fragment、Service、IPC、View、性能优化、 设计模式、第三方开源框架、Framework源码等相关面试题就没展示了,需要的威信扫码下方直接获取。
…
…
…
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。