Android 基础问题 2204

Android 基础问题2022-04

从网上收集的一些基础面试问题

1. Android启动模式

参考:https://blog.csdn.net/ElisonX/article/details/80397519

在一个项目中会包括着多个Activity,系统中使用任务栈来存储创建的Activity实例,任务栈是一种“后进先出”的栈结构。

启动模式种类

在“标准模式”下若我们多次启动同一个Activity。系统会创建多个实例依次放入任务栈中。当按back键返回时,每按一次,一个Activity出栈,直到栈空为止。并不利于减少内存压力。

Android共有四种启动模式:Standard, SingleTop, SingleTask, SingleInstance

  1. Standard

    启动Activity的默认启动模式。这种情况下,新建的Activity将会创建一个新的实例加入栈顶。

    生命周期钩子onCreateonStartonResume都会被调用。

  2. SingleTop

    栈顶复用启动模式,当栈顶的Activity再次创建时,会直接复用栈顶的实例,不会创建新的实例。如果创建的Activity不在栈顶,将会如同Standard模式一样创建新的实例。

    若栈顶Activity被复用,生命周期钩子onCreateonStart不会被系统调用,由于它并没有发生改变,可是一个新的方法 onNewIntent会被回调。

  3. SingleTask

    栈内复用启动模式,如果栈内已有的Activity被再次创建时,将销毁该Activity栈之上的所有实例,使该Activity实例成为栈顶。

    仅仅会又一次回调Activity中的 onNewIntent方法

  4. SingleInstance

    单实例启动模式,类似于SingleTask,但是该Activity只能处于单独的一个栈中。即,不会通过back返回上一个栈中的Activity。

启动模式的使用方式

  1. manifest.xml清单文件中指令Activity的启动方式

    1
    <activity android:name=".activity.MultiportActivity" android:launchMode="singleTask"/>
  2. 启动Activity的Intent中指定启动方式

    1
    2
    3
    4
    Intent intent = new Intent();
    intent.setClass(context, MainActivity.class);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);

区别:

优先级:Intent指定方式即另外一种比清单指定优先级要高,若两者同一时候存在,以另外一种方式为准。

限定范围:清单指定方式无法为Activity直接指定 FLAG_ACTIVITY_CLEAR_TOP 标识,Intent方式无法为Activity指定 singleInstance 模式。

Intent启动时的flags

  1. FLAG_ACTIVITY_NEW_TASK指定为SingleTask模式
  2. FLAG_ACTIVITY_SINGLE_TOP指定为SingleTop模式
  3. FLAG_ACTIVITY_CLEAN_TOP启动时会将与该Activity在同一任务栈的其他Activity出栈
  4. FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS启动此Activity后不会出历史Activity的列表中,即用户无法通过返回进入该Activity

2. Handler机制

参考:

https://cdmana.com/2022/03/202203230559249472.html

https://www.jianshu.com/p/592fb6bb69fa

Handler机制主要是用作线程间通信,尤其是主线程和子线程之间的通信。

Handler 涉及的对象

Handler机制里面涉及到四个对象:HandlerMessageMessageQueueLooper

  1. Message:信息的携带者,持有了Handler,存在MessageQueue中,一个线程可以有多个
  2. Hanlder:消息的发起者,发送Message以及消息处理的回调实现,一个线程可以有多个Handler对象
  3. Looper:消息的遍历者,从MessageQueue中循环取出Message进行处理,一个线程最多只有一个
  4. MessageQueue:消息队列,存放了Handler发送的消息,供Looper循环取消息,一个线程最多只有一个
  • Handler

    消息的处理者,负责将Message添加到消息队列以及对消息队列中的Message进行处理。

    • 主线程创建一个Handler对象,重写handleMessage()方法
  • 在子线程中创建一个Message对象,保存要传递的消息。通过HandlersendMessage()方法发出消息

    1. Handler.sendMessage: 把消息加入到主线程的MessageQueue中,主线程中的LooperMessageQueue中取出消息,调用Message.target.handleMessage方法
    2. Handler.post: 基于Handler.sendMessage,把消息加入到主线程的MessageQueue中,主线程中的LooperMessageQueue中取出消息,调用Message.callback.run方法
  • 这条message被添加到MessageQueue中等待处理。MessageQueue: 消息队列,用来存放通过Handler发布的消息,按照先进先出执行。

  • Looper(消息队列管家):Looper发现有新消息到来时,就会处理这个消息。会调用Looper.loop()方法来

    • 一个线程最多只有一个Looper对象。当没有Looper对象时,去创建一个Looper
    • 在Looper的构造方法里面,会创建消息队列MessageQueue并让它供Looper持有,因为一个线程最多只有一个Looper对象,所以一个线程最多也只有一个消息队列。然后再把当前线程赋值给mThread

    创建Handler还是需要调用Looper.prepare的,我们平常在主线程不需要手动调用,是因为系统在启动App时,就帮我们调用了。并且还需要调用Looper.loop方法

使用Handler通信之前需要有以下三步

  1. 调用Looper.prepare()

    • 所以Looper.prepare()的作用主要有以下三点
      1. 创建Looper对象 & 存放在ThreadLocal变量中
      2. 创建MessageQueue对象,并让Looper对象持有(在Looper的构造方法里面,会创建消息队列MessageQueue,并让它供Looper持有)
      3. Looper对象持有当前线程
  2. 创建Handler对象

    • 在Handler的构造方法里面:得到当前线程调用sThreadLocal.set保存的Looper对象,让Handler持有它。接下来就会判断得到的Looper对象是否为空,如果为空,就会抛出异常(得到当前线程的Looper对象,并判断是否为空
    • 让创建的Handler对象持有LooperMessageQueueCallback的引用
    • 当创建Handler对象时,则通过 构造方法 自动关联当前线程的Looper对象 & 对应的消息队列对象(MessageQueue),从而 自动绑定了 实现创建Handler对象操作的线程
  3. 调用Looper.loop()

    • 从当前线程的MessageQueue从不断取出Message,并调用其相关的回调方法。
    1. 判断了当前线程是否有Looper,然后得到当前线程的MessageQueue
    2. (死循环)不断调用MessageQueuenext方法取出MessageQueue中的Message,注意,当MessageQueue中没有消息时,next方法会阻塞,导致当前线程挂起
    3. 拿到Message以后,会调用它的target的dispatchMessage方法,这个target其实就是发送消息时用到的Handler。并调用其相关的回调方法(拿到Message之后,调用相关的回调方法

如何区分handler发送的信息

可以通过msg.target获取发送msgHandler的信息。

也可以通过Message.obtain()重载。

3. Android生命周期

参考:https://developer.android.google.cn/guide

Activity生命周期

onCreate()onStart()onResume()onPause()onStop()onDestroy()

lifecircle

Service生命周期

  • onStartCommand()

    当另一个组件(如 Activity)请求启动服务时,系统会通过调用 startService() 来调用此方法。执行此方法时,服务即会启动并可在后台无限期运行。如果您实现此方法,则在服务工作完成后,您需负责通过调用 stopSelf()stopService() 来停止服务。(如果您只想提供绑定,则无需实现此方法。)

  • onBind()

    当另一个组件想要与服务绑定(例如执行 RPC)时,系统会通过调用 bindService() 来调用此方法。在此方法的实现中,您必须通过返回 IBinder 提供一个接口,以供客户端用来与服务进行通信。请务必实现此方法;但是,如果您并不希望允许绑定,则应返回 null。

  • onCreate()

    首次创建服务时,系统会(在调用 onStartCommand()onBind() 之前)调用此方法来执行一次性设置程序。如果服务已在运行,则不会调用此方法。

  • onDestroy()

    当不再使用服务且准备将其销毁时,系统会调用此方法。服务应通过实现此方法来清理任何资源,如线程、注册的侦听器、接收器等。这是服务接收的最后一个调用。

4. Android持久化存储的方式

参考:

https://blog.csdn.net/qq_35008279/article/details/80783726

https://developer.android.com/training/data-storage/room

Shared Preferences

一个轻量级的存储类,特别适合用于保存软件配置参数,是用xml文件存放数据。

文件存放在/data/data/<package>/shared_prefs目录下

Shared Preferences可以保存的数据类型有:intbooleanfloatlongStringStringSet

Internal Storage

通过文件的形式将数据保存到手机内部存储空间中,并且这些文件是私有的,其他程序无法访问。当卸载掉程序之后,这些文件也会被相应移除。

文件存放在/storage/emulated/0/Android/data/<package>/

External Storage

通过文件的形式存储在手机模拟SD card中,需要在清单中声明外部存储空间读写权限,并且需要请求权限。

文件存放在/sdcard <-> /storage/emulated/0/

SQLite Databases

Android内建轻量化数据库SQLite,推荐通过Android Room中间层对数据库进行操作。

Room 包含三个主要组件:

  • 数据库类,用于保存数据库并作为应用持久性数据底层连接的主要访问点。
  • 数据实体,用于表示应用的数据库中的表。
  • 数据访问对象 (DAO),提供您的应用可用于查询、更新、插入和删除数据库中的数据的方法。

数据库类为应用提供与该数据库关联的 DAO 的实例。反过来,应用可以使用 DAO 从数据库中检索数据,作为关联的数据实体对象的实例。此外,应用还可以使用定义的数据实体更新相应表中的行,或者创建新行供插入。下图说明了 Room 的不同组件之间的关系。

android room

5. Android Broadcast

参考:https://developer.android.com/guide/components/broadcasts

Android 应用与 Android 系统和其他 Android 应用之间可以相互收发广播消息,这与_发布-订阅_设计模式相似。这些广播会在所关注的事件发生时发送。

分类

  • 系统广播

    系统会在发生各种系统事件时自动发送广播,例如当系统进入和退出飞行模式时。系统广播会被发送给所有同意接收相关事件的应用。

    广播消息本身会被封装在一个 Intent 对象中,该对象的操作字符串会标识所发生的事件。

  • 自定义广播

接收与发送广播

接收广播

  • 清单声明的接收器

    如果您在清单中声明广播接收器,系统会在广播发出后启动您的应用(如果应用尚未运行)。

    注意:如果您的应用以 API 级别 26 或更高级别的平台版本为目标,则不能使用清单为隐式广播(没有明确针对您的应用的广播)声明接收器,但一些不受此限制的隐式广播除外。在大多数情况下,您可以使用调度作业来代替。

    要在清单中声明广播接收器,请执行以下步骤:

    1. 在应用清单中指定<receiver>元素。

      1
      2
      3
      4
      5
      6
      <receiver android:name=".MyBroadcastReceiver"  android:exported="true">
      <intent-filter>
      <action android:name="android.intent.action.BOOT_COMPLETED"/>
      <action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
      </intent-filter>
      </receiver>
    2. 创建 BroadcastReceiver 子类并实现 onReceive(Context, Intent)。以下示例中的广播接收器会记录并显示广播的内容:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      public class MyBroadcastReceiver extends BroadcastReceiver {
      private static final String TAG = "MyBroadcastReceiver";
      @Override
      public void onReceive(Context context, Intent intent) {
      StringBuilder sb = new StringBuilder();
      sb.append("Action: " + intent.getAction() + "\n");
      sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
      String log = sb.toString();
      Log.d(TAG, log);
      Toast.makeText(context, log, Toast.LENGTH_LONG).show();
      }
      }

      系统软件包管理器会在应用安装时注册接收器。然后,该接收器会成为应用的一个独立入口点,这意味着如果应用当前未运行,系统可以启动应用并发送广播。

      系统会创建新的 BroadcastReceiver 组件对象来处理它接收到的每个广播。此对象仅在调用 onReceive(Context, Intent) 期间有效。一旦从此方法返回代码,系统便会认为该组件不再活跃。

  • 上下文注册的接收器

    要使用上下文注册接收器,请执行以下步骤:

    1. 创建 BroadcastReceiver 的实例:

      1
      BroadcastReceiver br = new MyBroadcastReceiver();
    2. 创建 IntentFilter 并调用 registerReceiver(BroadcastReceiver, IntentFilter) 来注册接收器:

      1
      2
      3
      4
      IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
      filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
      this.registerReceiver(br, filter);

      注意:要注册本地广播,请调用 LocalBroadcastManager.registerReceiver(BroadcastReceiver, IntentFilter)

      只要注册上下文有效,上下文注册的接收器就会接收广播。例如,如果您在 Activity 上下文中注册,只要 Activity 没有被销毁,您就会收到广播。如果您在应用上下文中注册,只要应用在运行,您就会收到广播。

    3. 要停止接收广播,请调用 unregisterReceiver(android.content.BroadcastReceiver)。当您不再需要接收器或上下文不再有效时,请务必注销接收器。

      请注意注册和注销接收器的位置,比方说,如果您使用 Activity 上下文在 onCreate(Bundle) 中注册接收器,则应在 onDestroy() 中注销,以防接收器从 Activity 上下文中泄露出去。如果您在 onResume() 中注册接收器,则应在 onPause() 中注销,以防多次注册接收器(如果您不想在暂停时接收广播,这样可以减少不必要的系统开销)。请勿在 onSaveInstanceState(Bundle) 中注销,因为如果用户在历史记录堆栈中后退,则不会调用此方法。

发送广播

Android 为应用提供三种方式来发送广播:

  • sendOrderedBroadcast(Intent, String) 方法一次向一个接收器发送广播。当接收器逐个顺序执行时,接收器可以向下传递结果,也可以完全中止广播,使其不再传递给其他接收器。接收器的运行顺序可以通过匹配的 intent-filter 的 android:priority 属性来控制;具有相同优先级的接收器将按随机顺序运行。
  • sendBroadcast(Intent) 方法会按随机的顺序向所有接收器发送广播。这称为常规广播。这种方法效率更高,但也意味着接收器无法从其他接收器读取结果,无法传递从广播中收到的数据,也无法中止广播。
  • LocalBroadcastManager.sendBroadcast 方法会将广播发送给与发送器位于同一应用中的接收器。如果您不需要跨应用发送广播,请使用本地广播。这种实现方法的效率更高(无需进行进程间通信),而且您无需担心其他应用在收发您的广播时带来的任何安全问题。

以下代码段展示了如何通过创建 Intent 并调用 sendBroadcast(Intent) 来发送广播:

1
2
3
4
Intent intent = new Intent();
intent.setAction("com.example.broadcast.MY_NOTIFICATION");
intent.putExtra("data","Notice me senpai!");
sendBroadcast(intent);

广播消息封装在 Intent 对象中。Intent 的操作字符串必须提供应用的 Java 软件包名称语法,并唯一标识广播事件。您可以使用 putExtra(String, Bundle) 向 intent 附加其他信息。您也可以对 intent 调用 setPackage(String),将广播限定到同一组织中的一组应用。

注意:虽然 intent 既用于发送广播,也用于通过 startActivity(Intent) 启动 Activity,但这两种操作是完全无关的。广播接收器无法查看或捕获用于启动 Activity 的 intent;同样,当您广播 intent 时,也无法找到或启动 Activity。

6. Content Provider

参考:https://developer.android.com/guide/topics/providers/content-providers

内容提供程序管理对中央数据存储区的访问。提供程序是 Android 应用的一部分,通常提供自己的界面来处理数据。但是,内容提供程序主要目的是供其他应用使用,这些应用使用提供程序客户端对象进行访问。提供程序与提供程序客户端共同提供一致的标准数据界面,该界面还可处理进程间通信并保护数据访问的安全性。

通常,您会在以下两种场景中使用内容提供程序:

  • 一种是通过实现代码访问其他应用中的现有内容提供程序

  • 另一种是在应用中创建新的内容提供程序,从而与其他应用共享数据

访问提供程序

如需访问内容提供程序中的数据,您可以客户端的形式使用应用的 Context 中的 ContentResolver 对象与提供程序进行通信。ContentResolver 对象会与提供程序对象(即实现 ContentProvider 的类的实例)通信。提供程序对象从客户端接收数据请求、执行请求的操作并返回结果。此对象的某些方法可调用提供程序对象(ContentProvider 某个具体子类的实例)中的同名方法。ContentResolver 方法可提供持久性存储空间的基本“CRUD”(创建、检索、更新和删除)功能。

使用“存储访问框架”打开文件

Android 4.4(API 级别 19)引入了存储访问框架 (SAF)。借助 SAF,用户可轻松浏览和打开各种文档、图片及其他文件,而不用管这些文件来自其首选文档存储提供程序中的哪一个。

SAF 包含以下元素:

  • 文档提供程序 - 一种内容提供程序,可让存储服务(如 Google 云端硬盘)提供其管理的文件。文档提供程序以 DocumentsProvider 类的子类形式实现。文档提供程序的架构基于传统的文件层次结构,但其实际的数据存储方式由您决定。Android 平台包含若干内置的文档提供程序,如 Downloads、Images 和 Videos。
  • 客户端应用 - 一种定制化的应用,它会调用 ACTION_CREATE_DOCUMENTACTION_OPEN_DOCUMENTACTION_OPEN_DOCUMENT_TREE intent 操作并接收文档提供程序返回的文件。
  • 选择器 - 一种系统界面,可让用户访问所有文档提供程序内满足客户端应用搜索条件的文档。

SAF 的核心是一个内容提供程序,它是 DocumentsProvider 类的一个子类。在文档提供程序内,数据结构采用传统的文件层次结构:

saf

7. 自定义View

参考:

https://developer.android.google.cn/guide/topics/ui/custom-components

https://www.jianshu.com/p/705a6cb6bfee

分类

类型 定义
完全自定义组件 完全自定义的组件可用于创建外观完全如您所需的图形组件。
复合组件 这会将许多更原子的控件(或视图)整合到可被视为一件事的项的逻辑分组中
继承系统View控件 继承自TextView等系统控件,在系统控件的基础功能上进行扩展
继承View 不复用系统控件逻辑,继承View进行功能定义
继承系统ViewGroup 继承自LinearLayout等系统控件,在系统控件的基础功能上进行扩展
继承ViewViewGroup 不复用系统控件逻辑,继承ViewGroup进行功能定义

View绘制流程

函数 作用 相关方法
measure() 测量View的宽高 measure(),setMeasuredDimension(),onMeasure()
layout() 计算当前View以及子View的位置 layout(),onLayout(),setFrame()
draw() 视图的绘制工作 draw(),onDraw()

坐标系

在Android坐标系中,以屏幕左上角作为原点,这个原点向右是X轴的正轴,向下是Y轴正轴。

View的构造函数

无论是我们继承系统View还是直接继承View,都需要对构造函数进行重写,构造函数有多个,至少要重写其中一个才行。

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
public class TestView extends View {
/**
* 在java代码里new的时候会用到
* @param context
*/
public TestView(Context context) {
super(context);
}

/**
* 在xml布局文件中使用时自动调用
* @param context
*/
public TestView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}

/**
* 不会自动调用,如果有默认style时,在第二个构造函数中调用
* @param context
* @param attrs
* @param defStyleAttr
*/
public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}


/**
* 只有在API版本>21时才会用到
* 不会自动调用,如果有默认style时,在第二个构造函数中调用
* @param context
* @param attrs
* @param defStyleAttr
* @param defStyleRes
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
}

8. Java同步方法转异步方法

参考:https://blog.csdn.net/weixin_38106322/article/details/104492086

使用CachedThreadPool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 6; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("运行第" + number.incrementAndGet() + "个线程,当前时间【" + sim.format(new Date()) + "】");
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}

9. synchnoized作用

参考:https://www.cnblogs.com/cg961107/p/10923114.html

同步方法支持一种简单的策略来防止线程干扰和内存一致性错误:如果一个对象对多个线程可见,则对该对象变量的所有读取或写入都是通过同步方法完成的。

一句话总结出Synchronized的作用:

  • 能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果

Synchronized的特点:

  • synchronized是Java的关键字,被Java语言原生支持

  • 是最基本的互斥同步手段

synchronized的两个用法

对象锁:

  • 包含方法锁(默认锁对象为this当前实力对象),同步代码块锁(自己制定锁对象)
  • 代码块形式:手动指定锁对象
  • 方法锁形式:synchronized修饰普通方法,锁默认对象为this

类锁:

  • 指sychronized修饰静态的方法或指锁为Class对象

  • 概念:java类可能有有很多个对象,但是只有一个class对象

  • 本质:所以所谓的类锁,不过是Class对象的锁而已

  • 用法和效果:类锁只能在同一时刻被一个对象拥有

  • 形式1:synchronized加载static方法上

  • 形式2:synchronized(*.class)代码块

多线程访问同步方法的7种情况

1.两个线程同时访问一个对象的同步方法

  串行

2.两个线程访问的是两个对象的同步方法

  锁对象不同,互不干扰,并行

3.如果两个线程访问的是Synchronized的静态方法

  串行

4.同时访问同步方法与非同步方法

  并行

5.访问同一对象的不同的普通同步方法

  同一对象锁,串行

6.同时访问静态synchronized和非静态synchronized方法

  锁不同,并行

7.方法抛异常后,会释放锁吗

  如果一个线程在进入同步方法后抛出了异常,则另一个线程会立刻进入该同步方法

10. Java 抽象类和接口的区别

参考:https://zhuanlan.zhihu.com/p/94770324

Java中接口和抽象类的定义语法分别为interfaceabstract关键字。

抽象类:在Java中被abstract关键字修饰的类称为抽象类,被abstract关键字修饰的方法称为抽象方法,抽象方法只有方法的声明,没有方法体。抽象类的特点:

  1. 抽象类不能被实例化只能被继承;
  2. 包含抽象方法的一定是抽象类,但是抽象类不一定含有抽象方法;
  3. 抽象类中的抽象方法的修饰符只能为public或者protected,默认为public;
  4. 一个子类继承一个抽象类,则子类必须实现父类抽象方法,否则子类也必须定义为抽象类;
  5. 抽象类可以包含属性、方法、构造方法,但是构造方法不能用于实例化,主要用途是被子类调用。

接口:Java中接口使用interface关键字修饰,特点为:

  1. 接口可以包含变量、方法;变量被隐式指定为public static final,方法被隐式指定为public abstract(JDK1.8之前);

  2. 接口支持多继承,即一个接口可以extends多个接口,间接的解决了Java中类的单继承问题;

  3. 一个类可以实现多个接口;

  4. JDK1.8中对接口增加了新的特性:

    1. 默认方法(default method):JDK1.8允许给接口添加非抽象的方法实现,但必须使用default关键字修饰;定义了default的方法可以不被实现子类所实现,但只能被实现子类的对象调用;如果子类实现了多个接口,并且这些接口包含一样的默认方法,则子类必须重写默认方法;
    2. 静态方法(static method):JDK1.8中允许使用static关键字修饰一个方法,并提供实现,称为接口静态方法。接口静态方法只能通过接口调用(接口名.静态方法名)。

相同点

  1. 都不能被实例化
  2. 接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。

不同点

  1. 接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可在抽象类中实现。
  2. 实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。
  3. 接口强调特定功能的实现,而抽象类强调所属关系。
  4. 接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是publicabstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被privatestaticsynchronizednative等修饰,必须以分号结尾,不带花括号。

11. equals==的区别

参考:https://zhuanlan.zhihu.com/p/338350987

  1. equals():用来检测两个对象是否相等,即两个对象的内容是否相等。

  2. ==:用于比较引用和比较基本数据类型时具有不同的功能

    1. 基础数据类型:比较的是他们的值是否相等,比如两个int类型的变量,比较的是变量的值是否一样。
    2. 引用数据类型:比较的是引用的地址是否相同,比如说新建了两个User对象,比较的是两个User的地址是否一样。

对equals重新需要注意五点:

1、自反性:对任意引用值X,x.equals(x)的返回值一定为true;

2、对称性:对于任何引用值x,y,当且仅当y.equals(x)返回值为true时,x.equals(y)的返回值一定为true;

3、传递性:如果x.equals(y)=true, y.equals(z)=true,则x.equals(z)=true ;

4、 一致性:如果参与比较的对象没任何改变,则对象比较的结果也不应该有任何改变;

5、非空性:任何非空的引用值X,x.equals(null)的返回值一定为false 。

==:

== 比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象。比较的是真正意义上的指针操作。

1、比较的是操作符两端的操作数是否是同一个对象。

2、两边的操作数必须是同一类型的(可以是父子类之间)才能编译通过。

3、比较的是地址,如果是具体的阿拉伯数字的比较,值相等则为true,如:

int a=10long b=10Ldouble c=10.0都是相同的(为true),因为他们都指向地址为10的堆。

equals:

equals用来比较的是两个对象的内容是否相等,由于所有的类都是继承自java.lang.Object类的,所以适用于所有对象,如果没有对该方法进行覆盖的话,调用的仍然是Object类中的方法,而Object中的equals方法返回的却是==的判断。

String s="abce"是一种非常特殊的形式,和new 有本质的区别。它是java中唯一不需要new就可以产生对象的途径。

String s="abce";形式赋值在java中叫直接量,它是在常量池中而不是象new一样放在压缩堆中。这种形式的字符串,在JVM内部发生字符串拘留,即当声明这样的一个字符串后,JVM会在常量池中先查找有有没有一个值为"abcd"的对象。

如果有,就会把它赋给当前引用.即原来那个引用和现在这个引用指点向了同一对象,如果没有,则在常量池中新创建一个"abcd",下一次如果有Strings1="abcd"又会将s1指向"abcd"这个对象,即以这形式声明的字符串,只要值相等,任何多个引用都指向同一对象。

String s=new String("abcd")和其它任何对象一样.每调用一次就产生一个对象,只要它们调用。

也可以这么理解:”String str=”hello””先在内存中找是不是有"hello"这个对象,如果有,就让str指向那个"hello"。如果内存里没有"hello",就创建一个新的对象保存"hello"String str=new String("hello")就是不管内存里是不是已经有"hello"这个对象,都新建一个对象保存"hello"

12. volatile和synchronized的区别

参考:https://blog.csdn.net/suifeng3051/article/details/52611233

  1. volatile本质是在告诉JVM当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;
  2. synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住;
  3. volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的;
  4. volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性;
  5. volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞;
  6. volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
打赏
  • 版权声明: 本博客采用 Apache License 2.0 许可协议。
    转载请注明出处: https://ryzenx.com/2022/04/Android-questions-2204/

谢谢你的喜欢~

支付宝
微信