大概在数天前投递了字节跳动-抖音方向的实习生岗位
昨天接到了面试通知,今天面试后对涉及到的问题进行一个复盘。(字节跳动果然名不虚传…面试官相当亲切且善于循循善诱来引导我回答问题)

1.Java中的引用类型有没有什么了解?

Java.lang.ref是Java类库中比较特殊的一个包,它提供了与Java垃圾回收器密切相关的引用类。

StrongReference(强引用)
SoftReference(软引用)
WeakReference(弱引用)
PhantomReference(虚引用)

引用类型对比

序号 引用类型 取得目标对象方式 垃圾回收条件 是否可能内存泄漏
1 强引用 直接调用 不回收 可能
2 软引用 通过 get()方法 视内存情况回收 不可能
3 弱引用 通过 get()方法 永远回收 不可能
4 虚引用 无法取得 不回收 可能

1.StrongReference(强引用)

如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

示例

1
String[] arr = new String[]{"a","b","c"};

2.SoftReference(软引用)

如果一个对象只具有软引用,如果内存空间足够,垃圾回收器就不会回收它;如果内存空间不足,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

示例

1
2
3
4
5
6
//Example1
SoftReference<String[]> softBean = new SoftReference<String[]>(new String[]{"a","b","c"});
//Example2
ReferenceQueue<String[]> referenceQueue = new ReferenceQueue<String[]>();
SoftReference<String[]> softBean = new SoftReference<String[]>(new String[]{"a", "b", "c"}, referenceQueue);

3.WeakReference(弱引用)

弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
    弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
    
示例

1
2
3
4
5
//Example1
WeakReference<String[]> weakBean = new WeakReference<String[]>(new String[]{"a", "b", "c"});
//Example2
ReferenceQueue<String[]> referenceQueue = new ReferenceQueue<String[]>();
WeakReference<String[]> softBean = new WeakReference<String[]>(new String[]{"a", "b", "c"}, referenceQueue);

4.PhantomReference(虚引用)

“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

示例

1
2
ReferenceQueue<String[]> referenceQueue = new ReferenceQueue<String[]>();
PhantomReference<String[]> referent = new PhantomReference<String>(new String[]{"a", "b", "c"}, referenceQueue);

2.Java中创建线程的方式有哪些

1.继承Thread类创建线程类

(1) 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
(2) 创建Thread子类的实例,即创建了线程对象。
(3) 调用线程对象的start()方法来启用该线程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class FirstThreadTest extends Thread {
int i = 0; //重写run方法,run方法的方法体就是现场执行体
public void run() {
for (; i < 100; i++) {
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " : " + i);
if (i == 20) {
new FirstThreadTest().start();
new FirstThreadTest().start();
}
}
}
}

上述代码中Thread.currentThread()方法返回当前正在执行的线程对象。GetName() 方法返回调用该方法的线程的名字。

2.通过Runnable接口创建线程类

(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法 体同样是该线程的线程执行体。
(2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread 对象,该Thread对象才是真正的线程对象。
(3)调用线程对象的start()方法来启动该线程。

3.通过Callable和Future创建线程

(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行 体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该 FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值,调用 get()方法会阻塞线程。

创建线程的三种方式的对比

采用实现Runnable、Callable接口的方式创见多线程时,优势是: 线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。 在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程 来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型, 较好地体现了面向对象的思想。 劣势是: 编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。 使用继承Thread类的方式创建多线程时优势是: 编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接 使用this即可获得当前线程。 劣势是: 线程类已经继承了Thread类,所以不能再继承其他父类。

3.Object类对象含有的方法有哪些?

Java中的Object类是所有类的父类,它提供了以下11个方法:

1.public final native Class<?>getClass()
2.public native int hashCode()
3.public boolean equals(Object obj)
4.protected native Object clone() throws CloneNotSupportedException
5.public String toString()
6.public final native void notify()
7.public final native void notifyAll()
8.public final native void wait(long timeout) throws InterruptedException
9.public final void wait(long timeout,int nanos)throws InterruptedException
10.public final void wait() throws InterruptedException
11.protected void finalize() throws Throwable{}

4.Android中的进程间通信有哪些方式?具体说说Binder

Android中的IPC方式

使用Intent
使用文件共享
使用Messenger
使用AIDL
使用ContentProvider
使用Socket

1.使用Intent

1.Activity,Service,Receiver都支持在Intent中传递Bundle数据,而Bundle实现了Parcelable接口,可以在不同的进程间进行传输。

2.在一个进程中启动了另一个进程的Activity,Service和Receiver,可以在Bundle中附加要传递的数据通过Intent发送出去。

2.使用文件共享

1.Windows上,一个文件如果被加了排斥锁会导致其他线程无法对其进行访问,包括读和写;而Android系统基于Linux,使得其并发读取文件没有限制地进行,甚至允许两个线程同时对一个文件进行读写操作,although this may be a problem.

2.可以在一个进程中序列化一个对象到文件系统中,在另一个进程中反序列化恢复这个对象(Attention:并不是同一个对象,只是内容相同)

3.SharePreference是个特例,系统对它的读/写有一定的缓存策略,即内存中会有一份SharePreferences文件的缓存,系统对它的读/写就变得不可靠,当面对高并发的读写访问,SharePreferences有很大的几率丢失数据。因此,IPC不建议采用SharePreferences。

3.使用Messenger

Messenger是一种轻量级的IPC方案,它的底层实现是AIDL,可以在不同进程中传递Message对象,它一次只处理一个请求,在服务端不需要考虑线程同步的问题,服务端不存在并发执行的情形

  • 服务端进程:服务端创建一个Service来处理客户端请求,同时通过一个Handler对象来实例化一个Meaaenger对象,然后在Servive的onBind中返回这个Messenger对象底层的Binder即可。

    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    public class MessengerSercive extends Service{
    private static final String TAG = MessengerSercive.class.getSimpleName();
    private class MessengerHandler extends Handler{
    /**
    * @param msg
    */
    @Override
    public void handleMessage(Message msg){
    switch (msg.what) {
    case Constants.MSG_FROM_CLIENT:
    Log.d(TAG, "receive msg from client: msg = ["
    + msg.getData().getString(Constants.MSG_KEY) + "]");
    Toast.makeText(MessengerService.this, "receive msg from client: msg = [" + msg.getData().getString(Constants.MSG_KEY) + "]", Toast.LENGTH_SHORT).show();
    Messenger client = msg.replyTo;
    Message replyMsg = Message.obtain(null, Constants.MSG_FROM_SERVICE);
    Bundle bundle = new Bundle();
    bundle.putString(Constants.MSG_KEY, "我已经收到你的消息,稍后回复你!");
    replyMsg.setData(bundle);
    try {
    client.send(replyMsg);
    } catch (RemoteException e) {
    e.printStackTrace();
    }
    break;
    default:
    super.handleMessage(msg);
    }
    }
    }
    private Messenger mMessenger = new Messenger(new MessengerHandler());
    @Nullable
    @Override
    public IBinder onBind(Intent intent){
    return mMessenger.getBinder();
    }
    }
  • 客户端进程:首先绑定服务端Service,绑定成功之后用服务端的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息了,消息类型是Message。如果需要服务端响应,则需要创建一个Handler并通过它来创建一个Messenger(和服务端一样),并通过Message的replyTo参数传递给服务端。服务端通过Message的replyTo参数就可以回应客户端了。

    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    public class MainActivity extends AppCompatActivity{
    private static final String TAG = MainActivity.class.getSimpleName();
    private Messenger mGetReplyMessenger = new Messenger(new MessageHandler());
    private Messenger mService;
    private class MessageHandler extends Handler{
    @Override
    public void handleMessage(Message msg){
    switch(msg.what){
    case Constants.MSG_FROM_SERVICE:
    Log.d(TAG, "received msg form service: msg =
    [" + msg.getData().getString(Constants.MSG_KEY) + "]");
    Toast.makeText(MainActivity.this, "received vice: msg = [" + msg.getData().getString(Constants.MSG_KEY) + "]", Toast.LENGTH_SHORT).show();
    break;
    default:
    super.handleMessage(msg);
    }
    }
    }
    @Override
    protected void onCreate(Bundle savedInsttanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    }
    public void bindService(View v){
    Intent mIntent = new Intent(thia,MessengerService.class);
    bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }
    public void sendMessage(View v){
    Message msg = Message.obtain(null,Constants.MSG_FROM_CLIENT);
    Bundle data = new Bunle();
    data.putString(Constants.MSG_KEY, "Hello! This is client.");
    msg.setData(data);
    msg.replyTo = mGetReplyMessenger;
    try{
    mService.send(mag);
    }catch(RemoteException e){
    e.printStackTrace();
    }
    }
    @Override
    protected void onDestroy(){
    unbindService(mServiceConnection);
    super.onDestroy();
    }
    private ServiceConnection mServiceConnection = new ServiceConnection(){
    /**
    * @param name
    * @param service
    */
    @Override
    public void onServiceConnected(ComponentName name, IBinder service){
    mService = new Messenger(service);
    Message msg = Message.obtain(null, Constants.MSG_FROM_CLIENT);
    Bundle data = new Bundle();
    data.putString(Constants.MSG_KEY,"Hello! This is cient.");
    msg.setData(data);
    mag.replyTo = mGetReplyMessenger;
    try{
    mService.send(msg);
    }catch(RemoteException e){
    e.printStackTrace();
    }
    }
    /**
    * @param name
    */
    @Override
    public void onServiceDiscommected(ComponentName name){
    }
    };
    }

注意: 客户端和服务端是通过通过拿到对方的Messenger来发送Message的。只不过客户端通过bindService onServiceConnected而服务端通过message.replyTo来获得对方的Messenger。Messenger中有一个Hanlder以串行的方式处理队列中的消息。不存在并发执行,因此我们不考虑线程同步的问题。

4.使用AIDL

Messenger是以串行的方式处理客户端发来的消息,如果大量消息同时发送到服务端,服务端只能一个一个处理,所以大量并发请求就不适合用Messenger,而且Messenger只适合传递消息,不能跨进程调用方法的问题,要知道Messenger本质上也是AIDL,只不过系统做了封装方便上层的调用而已。

AIDL文件支持的数据类型

  • 基本数据类型;
  • String和CharSequence;
  • ArrayList,里面的元素必须能够被AIDL支持;
  • HashMap,实现Parcelable接口的对象;注意:如果AIDL文件中用到了自定义的Parcelable对象,必须新建一个和它同名的AIDL文件。
  • AIDL,AIDL接口本身也可以在AIDL文件中使用。
    服务端
    服务端创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。
    客户端
    绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,然后就可以调用AIDL中的方法了。客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端的线程会被挂起,如果服务端方法执行比较耗时,就会导致客户端线程长时间阻塞,导致ANR。客户端的onServiceConnected和onServiceDisconnected方法都在UI线程中。

5.Android中的AsyncTask的作用?

6.Android中的消息机制?

7.Android中的事件分发机制了解多少?如何解决滑动冲突

8.Java中的抽象类和接口的区别?

理解抽象

abstract class 和 interface 是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。abstract calss和interface之间对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于abstract class和interface的选择显得比较随意。

其实,两者之间还是有很大区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。

语法定义理解

1.抽象类

1
2
3
4
5
abstract class Demo{
abstract void method1();
abstract void method2();
...
}

2.接口

1
2
3
4
5
interface Demo{
void method1();
void method2();
...
}

在abstract class方式中,Demo可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface方式的实现中,Demo只能够有静态的不能被修饰的数据成员(也就是必须是static final的,不过在interface中一般不定义数据成员),所有的成员方法都是abstract的。从某种意义上说,interface是一种特殊形式的abstract calss。

编程角度理解

首先,abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。也许,这是Java语言的设计者在考虑Java对于多继承的支持方面的一种折中考虑吧。

其次,在abstract class的定义中,我们可以赋予方法的默认行为。

但是在interface的定义中,方法却不能拥有默认行为,不过在JDK1.8中可以使用default关键字实现默认方法。

1
2
3
4
5
interface InterfaceA{
default void foo(){
System.out.println("InterfaceA foo");
}
}

在 Java 8 之前,接口与其实现类之间的耦合度太高了(tightly coupled),当需要为一个接口添加方法时,所有的实现类都必须随之修改。默认方法解决了这个问题,它可以为接口添加新的方法,而不会破坏已有的接口的实现。这在lambda表达式作为Java8语言的重要特性而出现之际,为升级旧接口且保持向后兼容(backward compatibility)提供了途径。

一般性理解

接口和抽象类的概念不一样。接口是对动作的抽象,抽象类是对根源的抽象。从设计理念上,接口反映的是“like-a”关系,抽象类反映的是“is-a”关系。抽象类表示的是,这个对象是什么。接口表示的是,这个对象能做什么。比如,男人,女人,这两个类(if they are class),他们的抽象类是人。说明,他们都是人。人可以吃东西,狗也可以吃东西,你可以把“吃东西”定义成一个接口,然后让这些类去实现它,所以,在高级语言上,一个类只能继承一个类(抽象类)(正如人不可能同时是生物和非生物),但是可以实现多个接口(吃饭接口、走路接口)。

总结

1.抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
2.抽象类要被子类继承,接口要被类实现。
3.接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
4.抽象类里可以没有抽象方法。
5.接口可以被类多实现(被其他接口多继承),抽象类只能被单继承。
6.接口中没有 this 指针,没有构造函数,不能拥有实例字段(实例变量)或实例方法。
7.抽象类不能在Java 8的 lambda 表达式中使用。

9.Java设计模式了解多少?观察者模式在哪里使用过?

10.有没有用过MVC\MVP\MVVM?有什么区别?MVP是如何使model与view进行互通的?