当前位置:   article > 正文

java常见面试题_java activity面试题

java activity面试题

1、Java中equal和==的区别

== : 关系操作符, 比较的是两个变量本身的
equal:Object类中的方法,比较的是字符串中包含的内容是否相同

  • == 指的是两个对象或示例是否指向同一个内存空间,equals指的是两个对象或示例所指的内存空间的值是否相同
  • ==表示引用是否相同,equals表示是否相同

2、安卓五大存储方式

1、SharedPreference:

适用于存储少量数据,以key+value形式存储数据,存储的数据以xml文件的形式存储于设备

2、ContentProvider

进程之间进行数据的共享,即跨进程通信
ContentProvider的简单使用
3、数据库存储
安卓五大存储方式之SQlite
4、网络存储
5、文件存储

3、安卓服务启动的两种方式

1、startService()
2、bindService()

区别:

1、生命周期的区别

startService启动的服务:
Service会经历onCreate()------->onStartCommand()
多次开始时候onStartCommand会被多次调用

bindService启动的服务:
Service会经历onCreate()----->onBind()
多次开始时候onBind只会调用一次

2、只有bindService启动服务才能获取绑定后的Service的方法
3、服务和主进程关系的区别

startService在activity被终止后,服务依旧存在,用这个方法创建的服务是个单独的进程,占用一定资源
可通过stopService结束进程
bindService:主进程被终止后,服务也会被终止掉

4、安卓四大组件

1、Activity
2、Service
3、contentProvider:主要用于对外共享数据
ContentProvider的简单使用
4、BroadcastReceiver:是一个全局的监听器
使用示例

  var mLocalBroadcastManager = LocalBroadcastManager.getInstance(this)
  var mBroadcastReceiver = MyBroadcastReceiver()
    val intentFilter = IntentFilter()
    intentFilter.addAction("action.type.thread")
    mLocalBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter)
 /**
     * 非静态内部类,关键字inner
     *可以访问外部类的成员变量
     */
    inner class MyBroadcastReceiver : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent) {
            when (intent.action) {
                "action.type.thread" -> {
                    //更改UI
                    val progress = intent.getIntExtra("progress", 0)
                    tv_statue.text = progress.toString()
                    if (progress == 0) {
                        tv_statue.text = "下载结束"
                    }
                }
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

5、Activity的生命周期

onCreate()-onResume()-onStart()-onPause()-onStop()-onDeatory()

6、安卓的系统框架

1、应用层
2、应用框架层
3、系统运行层
4、Linux内核层

7、MVC、MVP、MVVM

安卓框架学习之MVC、MVP、MVVM小结

8、安卓项目开发流程

需求分析、原型设计、功能开发、产品测试、应用上架

9、安卓多线程

安卓线程异步处理的小结

10、线程优先级的理解

每一个线程都是有优先级的,一般来说,高优先级的线程在运行时会具有优先权,但这依赖于线程调度的实现,这个实现是和操作系统相关的(OS dependent)。我们可以定义线程的优先级,但是这并不能保证高优先级的线程会在低优先级的线程前执行。线程优先级是一个int变量(从1-10),1代表最低优先级,10代表最高优先级。
启动一个线程是调用start()方法 , run()方法是线程启动后要进行回调(callback)的方法。

11、蓝牙

安卓BLE蓝牙开发详解
安卓蓝牙开发填坑之路
蓝牙ble数据传输最大字节数20
蓝牙连接数据传输
权限

   <!-- 声明蓝牙权限 -->
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <!-- 允许程序发现和配对蓝牙设备 -->
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!--为适配安卓6.0以及以上版本需要添加一个模糊定位的权限 否则会出现无法搜索到设备的情况。-->
 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

判断是否支持蓝牙
(BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE).getAdapter()==null
判断蓝牙是否开启
(BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE).getAdapter().isEnabled()
搜索设备
startLeScan(BluetoothAdapter.LeScanCallback)
连接设备
connectGatt+BluetoothGattCallback
开启扫描服务 扫描BLE设备服务是安卓系统中关于BLE蓝牙开发的重要一步,一般在设备连接成功后调用,扫描到设备服务后回调onServicesDiscovered()函数
mBluetoothGatt.discoverServices();
开启通信服务 通信服务通过硬件工程师提供的UUID获取
BluetoothGattService service = mBluetoothGatt.getService(UUID.fromString(“蓝牙模块提供的负责通信UUID字符串”));
开启监听
BluetoothGattCallback
写入数据
mBluetoothGatt.writeCharacteristic(writeCharacteristic)
接收数据
BluetoothGattCallback
断开连接
mBluetoothGatt.disconnect();
mBluetoothGatt.close();

12、BLE低功耗蓝牙和传统蓝牙

蓝牙BLE低功耗蓝牙介绍:
蓝牙BLE相对于传统蓝牙的优点:最大化的待机时间、快速连接和低峰值的发送/接收功耗。

有关BLE低功耗蓝牙和传统蓝牙的五大区别:
1、低功耗蓝牙的发送和接受任务会以最快的速度完成,完成之后蓝牙BLE会暂停发射无线(但是还是会接受),等待下一次连接再激活;
     传统蓝牙是持续保持连接。

2、低功耗蓝牙的广播信道(为保证网络不互相干扰而划分)仅有3个;传统蓝牙是32个。

3、低功耗蓝牙“完成”一次连接(即扫描其它设备、建立链路、发送数据、认证和适当地结束)只需3ms;传统蓝牙完成相同的连接周期需要数百毫秒。

4、低功耗蓝牙使用非常短的数据包,多应用于实时性要求比较高,但是数据速率比较低的产品,遥控类的如键盘,遥控鼠标,传感设备的数据发送,
     如心跳带,血压计,温度传感器等;传统蓝牙使用的数据包长度较长,可用于数据量比较大的传输,如语音,音乐,较高数据量传输等。

5、低功耗蓝牙无功率级别,一般发送功率在+4dBm,一般在空旷距离,达到70m的传输距离;传统蓝牙有3个功率级别,Class1,Class2,Class3,
     分别支持100m,10m,1m的传输距离。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

13、异步消息处理机制

Android 中的异步消息处理机制主要由四个部分组成:MessageHandlerMessageQueueLooper。下面对这4个部分进行简单的介绍。

1、Messager

Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同的线程之间交换数据。比如使用Message的what字段携带消息具体是
哪一条,用arg1和arg2字段来携带一些整形数据,使用obj字段携带一个Object对象。

2、Handler

Handler顾名思义也就是处理者的意思,它主要是用于发送和处理消息的。发送消息一般是使用到Handler的sendMessage()方法,而发出的消息经过一
系列的辗转处理后,最终会传递到Handler的handleMessage()方法中。

3、MessageQueue

.MessageQueue是消息队列的意思,它主要用于存放所有通过Handler发送的消息。这部分消息会一直存在于消息队列中,等待被处理。
每个线程中只会有一个MessageQueue对象

4、Looper

Looper是每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入到一个无限循环当中,然后每当发现MessageQueue中存在
着一条消息,就会将它取出,并传递到Handler的handleMessage()方法中。每个线程中也只会有一个Looper对象。

14、跨进程通讯

1、ContentProvider
2、AIDL

15、安卓各个版本新特性以及有相关适配经验

Android版本差异适配方案(5.0-11.0)

Android6.0

1、动态权限
一些权限6.0以后使用需要在代码中动态申请,例如:拍照、读写、定位等
若想规避则将targetSdkVersion 降为23以下

-------------Java基础----------------

Java基础解析

16、JDK、JRE、JVM的区别

JRE和JDK的区别是什么?

  • JDK是面向开发人员使用的SDK
  • JRE是JAVA的运行环境,面向JAVA程序的使用者
  • JVM是Java虚拟机,是Java跨平台的核心部分,他的作用是将.class文件翻译给本地系统

17、== 和 equals 的区别是什么?

1、功能方面
  • ==:两个变量或者实例对应的内存空间是否相同。--------空间
  • equals:两个变量或者实例所对应的内存空间的值是否相同。------------值
2、对象类型不同
  • equals():是超类Object中的方法。
  • ==:是操作符。
3、运行速度不同
  • equals()没有==运行速度快。

18.两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?

  • 两个对象值相同(x.equals(y) == true),则一定有相同的hash code
    反之则不一定

19、final 在 java 中有什么作用?

  • 修饰类
    final修饰类的时候表示该类不能被集成
  • 修饰方法
    则该方法不能被重写
  • 修饰变量
    只能赋值一次,不能再改变

20、Java中Math.round(-1.5)

  • Math.round(-1.5)=-1.5+0.5=-1
  • Math.round(1.5)=1.5+0.5=2

21、String 属于基础的数据类型吗?

  • 不属于。
  • Java8种基础的数据类型:byte、short、char、int、long、float、double、boolean。
  • String是final修饰的java类在这里插入图片描述

22、java 中操作字符串都有哪些类?它们之间有什么区别?

java 中操作字符串都有哪些类?它们之间有什么区别

  • String
    String: 是final修饰的类,每次使用都返回new String新对象,即每次使用都不影响原对象
  • StringBuffer
    StringBuffer:常用拼接功能,对方法添加了线程锁,保证线程安全
  • StringBuild
    StringBuild:不保证线程安全,可使用append、replace、delete等方法修改字符串。

22、线程安全的好处

  • 更好的利用资源。
  • 更快的响应程序。

23、String str="i"与 String str=new String(“i”)一样吗?

  • 内存分配方式不一样
    String str=“i” 会被分配到常量池中 -----不会创建新对象
    String str=new String(“i”) 会被分配到堆内存中 ------会创建新对象

24、如何将字符串反转?

  • StringBuild(str).reverse().toString()
  • for循环

25、String 类的常用方法都有那些?

  • .length()
  • .split()
  • .charAt()
  • .contains()
  • .indexof()
  • .trim()
  • .startWith()
  • .endWith()

26、抽象类必须要有抽象方法吗?

  • abstract 修饰的类是抽象类
  • 抽象类不一定必须要抽象方法, 但有抽象方法的类一定是抽象类

27、普通类和抽象类有哪些区别?

  • 普通类不能包含抽象方法,抽象类可以
  • 普通类能实例化,抽象类不能

28、抽象类能使用 final 修饰吗?

  • 不能
  • 抽象类的作用就是为了继承
  • 而final修饰的类是不能继承的

29、接口和抽象类有什么区别?

  • 实现: 抽象类用extends继承,接口用 implements 来实现
  • 构造参数:抽象类能有构造参数,而接口没有
  • main:抽象类能有main方法,并且可以运行,而接口不能
  • 实现数量:类可以实现多个接口,但只能继承一个抽象类

30、java 中 IO 流分为几种?

  • 功能:输入流(input)、输出流(output)
  • 类型:字节流、字符流
    字节流:以8位长度以字节为单位输出
    字符流:以16位长度一字符位单位输出

31、BIO、NIO、AIO 有什么区别?

  • BIO:Block IO 同步阻塞式IO,即最传统方式的IO,特点是模式简单、使用方便,处理并发能力低
  • NIO:New IO 同步非阻塞IO,在BIO基础上的升级,客户端和服务端通过Channel(通道)通信,实现了多路复用
  • AIO:Asynchronous IO 异步非阻塞IO,是NIO的升级,也叫作NIO2,异步IO的操作基于事件和回调机制

32、Files的常用方法都有哪些?

  • file.exists() 检测文件领是否存在
  • file.createFile() 创建文件
  • file.cerateDirectory() 创建文件夹
  • file.delete() 删除文件
  • file.copy() 复制文件
  • file.move() 移动文件
  • file.read() 读
  • file.write() 写
  • file.size() 文件数

容器

33、java 容器都有哪些?

在这里插入图片描述

  • List
  • ArrayList
  • Map
  • HashMap
  • Set
  • Collection
  • Vector等

34、Collection 和 Collections 有什么区别?

  • Collection 是一个集合接口 它提供了对集合操作的通用接口方法
  • Collections是一个工具类 它提供了对集合操作的静态多态方法

35、List、Set、Map 之间的区别是什么?

比较类型ListSetMap
继承CollectionCollection
常见实现类ArrayList,LinkedList,VectorHashSet,LinkedSetHashMap,HashTable
元素可重复不可重复不可重复
顺序有序无序
线性安全Vector线性安全HashTable线性安全

36、HashMap 和 Hashtable 有什么区别?

  • 相同点
    1、都实现了Map接口
  • 不同点
    1、HashMap允许键值对为null,HashTable不允许
    2、HashTable是同步的,而hashMap不是
    3、HashMap适合单线程,HashTable适合多线程

37、如何决定使用 HashMap 还是 TreeMap?

  • 对元素进行插入删除等操作使用HashMap
  • 需要对有序的集合进行遍历则使用TreeMap

38、说一下 HashMap 的实现原理?

  • 基于哈希原理(Hashing):HashMap是基于哈希表的Map接口的非同步实现
  • 使用put()和get()方法来存储获取数据
  • 当put()数据时候 会调用hashCode()方法来计算hashcode,然后找到bucket位置来存储对象
  • 当get()对象是 通过键对象的equals()的方法找到正确的键值对,然后返回值对象

39、说一下 HashSet 的实现原理?

  • 基于HashMap实现,由哈希表支持
  • 实现Set接口
  • 实际上是一个HashMap实例
  • HashSet的值存放在HashMap的Key上

40、ArrayList 和 LinkedList 的区别是什么?

  • 最明显的区别
    ArrayList的底层数据结构是数组,支持随机访问,而LinkedList的底层数据结构是双向循环链表,不支持随机访问
  • 数据结构不同
    ArrayList是Array(动态数组)的数据结构,LinkedList是Link(链表)的数据结构
  • 效率不同
    ArrayList的效率更高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后一次查询,所以LinkedList的效率较慢
  • 自由行不同
    ArrayList自由行低,需要手动设置固定大小容量,但它使用方便
    LinkedList的自由行较高,但是使用不方便
  • ArrayList适合查找,LinkedList适合增删

41、如何实现数组和 List 之间的转换?

List转换为数组:ArrayList的toArray()
数组转List:.asList()

42、ArrayList 和 Vector 的区别是什么?

  • ArrayList是异步的,Vector是同步的
  • Vector是线性安全的,但ArrayList不是
  • ArrayList性能方面要优于Vector

43、 Array 和 ArrayList 有何区别?

  • Array能存放基本类型和对象,而ArrayList只能存放对象
  • Array指定大小后不可变,ArrayList指定大小后可变
  • Array没有ArrayList的功能多,比如addAll(),removeAll()

44、什么是Queue?

  • Queue和List、Set属于一级,都继承于Collection类
  • Queue类是队列数据结构管理类。在它里边的元素可以按照添加它们的相同顺序被移除。

45、在 Queue 中 poll()和 remove()有什么区别?

  • poll()和remove()都会从队列中取元素
  • poll()获取元素失败时货返回null,remove()获取元素失败则会抛出异常

46、哪些集合类是线程安全的?

  • Vector和HashTable

47、迭代器 Iterator 是什么?

  • 迭代器Iterator是Java常用的一种设计模式
  • 它是一个对象,它能遍历并选择序列中的对象
  • 被称为“轻量级”对象,创建它的代价小

48、Iterator 怎么使用?有什么特点?

  • 使用
class JavaKnowledgeActivity : AppCompatActivity() ,View.OnClickListener{
    var list_data=ArrayList<String>()
    var str= arrayOf("1","2","3")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_java_knowledge)
        init_Listener()
        init()
    }
    fun init(){
        str.forEach {
            list_data.add(it)
        }
    }
    fun init_Listener(){
        tv_iterator.setOnClickListener(this)
    }

    override fun onClick(v: View?) {
        when(v?.id){
            R.id.tv_iterator->{//迭代器
                useIterator()
            }
        }
    }
    /**
     * iterator迭代器的使用
     */
    fun useIterator(){
        var iteratortext=list_data.iterator()
        while (iteratortext.hasNext()){
            Log.i("useIterator==",iteratortext.next())
        }
    }
}
  • 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

输出:
com.light.mytext I/useIterator==: 1
com.light.mytext I/useIterator==: 2
com.light.mytext I/useIterator==: 3

  • 特点
    (1)功能简单,只能单向移动
    (2)next():获取下一个元素
    (3)hasNext():判断是否还有元素
    (4)remove():移除迭代器新返回的元素

49、Iterator 和 ListIterator 有什么区别?

  • Iterator能遍历Set和List,而ListIterator只能遍历lIst
  • Iterator只能单向遍历,而ListIterator能双向遍历
  • ListIterator功能比Iterator要多,例如:增加元素,替换元素,获取亲一个或后一个元素的索引等

49-1、怎么确保一个集合不能被修改?

  • java.util.Collections中提供的有对应方法
  • unmodifiableMap()返回的map是不可修改的
  • Collections包也提供了对list和set集合的方法。
    Collections.unmodifiableList(List)
    Collections.unmodifiableSet(Set)

------------多线程-----------

50、并行和并发有什么区别?

你吃饭吃一半儿了电话来了,你吃完饭才去接电话,说明你既不支持并发也不支持并行
你吃饭吃一半儿了电话来了,你停下吃饭去接电话,说明你支持并发
你吃饭吃一半儿了电话来了,你一边吃饭一边接电话说明支持并行

  • 是否【同时】
    并发要求的是你有能处理多个问题的能力,不需要同时
    并行要求你能同属处理多个问题

51、线程和进程的区别?

进程=火车 线程=车厢

  • 线程在进程下行进
  • 一个进程可以包含多个线程(一个火车可以有多个车厢)
  • 不同进程间数据很难共享(不同火车间沟通不易)
  • 不同线程间数据共享很简单(同一列车上不同车厢进行沟通很简单)
  • 进程要比线程小号更多计算机资源
  • 进程间不会相互影响,但一个线程挂了有可能导致整个进程挂掉(一列火车着火了不会影响其他火车,但一节车厢着火了会导致整个火车毁掉)
  • 进程使用的内存地址可以上锁,即一个进程使用某些共享内存时,其他线程必须等它结束才能使用这一块儿(例如火车上的公共厕所)—互斥锁
  • 进程使用的内存地址可以限定使用量(即火车上的餐厅,同一时间段只允许多少人数使用,如果满了要等有人出来才能进去)–信号量

52、 守护线程是什么?

  • 一个服务线程,作用是服务其他线程

53、创建线程有哪几种方式?

  • 继承Thread类
             Thread{
                    tv_statue.post(Runnable {
                        tv_statue.text = "下载中"
                        ToastUtils.show(baseContext, "下载中")
                    })
                    Thread.sleep(2000)
                    tv_statue.post(Runnable {
                        tv_statue.text = "下载完成"
                        ToastUtils.show(baseContext, "下载完成")
                    })

                }.start()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • Runnable接口创建
class SynchronizedDemo : Runnable {
    //共享资源
    var i = 0

    /**
     * synchronized 修饰实例方法
     */
    @Synchronized
    fun increase() {
        i++
    }

    override fun run() {
        for (j in 0..9999) {
            increase()
        }
    }

    fun main() {
        val test = SynchronizedDemo()
        val t1 = Thread(test)
        val t2 = Thread(test)
        t1.start()
        t2.start()
        t1.join()
        t2.join()
        Log.i("Lock==",i.toString())

    }


}
  • 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

返回示例:
Lock==: increase: 1

Lock==: increase:20000

  • Callable+Future创建线程
class CallableFutureActivity : AppCompatActivity(), View.OnClickListener {

    private var task: FutureTask<Int>?=null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_callable_future)
        init_Listener()
        init()
    }

    fun init() {
         task = FutureTask<Int>(
            Callable<Int> {
                var i = 0
                while (i < 100) {
                    Log.i("CallableFuture==",Thread.currentThread().name + "的循环变量主的值:" + i)
                    i++
                }
                i
            } as Callable<Int>?
        )


    }

    fun init_Listener() {
        tv_start.setOnClickListener(this)
    }

    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.tv_start -> {
                Thread(task,"有返回值的线程").start()
                try {
                    //获取线程返回值
                    Log.i("CallableFuture==","子线程返回的值:" + task!!.get())
                } catch (ex: Exception) {
                    ex.printStackTrace()
                }

            }
        }
    }

}
  • 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

返回示例:
有返回值的线程的循环变量主的值:0

有返回值的线程的循环变量主的值:99
子线程返回的值:100

54、JNI是什么

  • JNI接口:实现了Java和其他语言的通信(例如C++、C)

55、说一下 runnable 和 callable 有什么区别?

  • Runnable规定方法是run(),没有返回值
  • Callable规定方法是call(),有返回值,是个泛型,和Future、FutureTask配合可获取异步执行的结果
  • call()能跑出异常,run()方法不能

56、线程有哪些状态?

在这里插入图片描述

  • 创建状态:在生成线程对象,但并没有调用start()方法的时候处于创建状态
  • 就绪状态:当线程对象调用了start()方法后,就进入了就绪状态,但此时线程调度程序还没把该线程设置为当前线程
  • 运行状态:当线程调度程序将处于就绪状态的线程设置为当前线程的时候就处于运行状态,此时运行run()方法
  • 阻塞状态:当线程运行中,被暂停,通常是为了等待某一时刻后再继续运行,sleep()、wait()方法都能使线程处于阻塞状态
  • 死亡状态:当一个线程run()方法运行结束或者直接调用了stop()方法后,线程就处于死亡状态,死亡后的线程无法通过start()方法重新唤醒

57、sleep() 和 wait() 有什么区别?

  • 同步锁对待不同
    sleep()后,没有释放同步锁
    wait()后,或释放同步锁
  • 用法不同
    sleep():可以设定指定时间后自动醒来,如果要强行醒来需要使用interreput()
    wait():可以用notify()直接唤醒
  • 所属类不同
    sleep()属于Thread类
    wait()属于Object类

58、notify()和 notifyAll()有什么区别?

object类、对象唤醒线程

  • 两者都是object类提供的用于提醒处于等待该对象的线程的方法
  • notufy():唤醒一个等待该对象的线程
  • notifyall():唤醒所有等待该对象的线程

59、线程的 run()和 start()有什么区别?

  • start()用来启动线程 ,线程处于可运行状态,真正实现了多线程运行,无需等待run方法执行完毕就可以执行下面的方法,轮到该线程执行时候会自动调用run方法
  • run()方法称为线程体,它包含了该线程要执行的方法,run()执行完毕后该线程终止
  • run()方法是单线程内的,不是多线程

59、创建线程池有哪几种方式?

  • newSingleThreadExecuto()
    单个线程的线程池,即线程池中每次只有一个线程工作
    			//创建一个可重用固定线程数的线程池
                val pool: ExecutorService = Executors.newSingleThreadExecutor()
                //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口;
                val t1: Thread = MyThread()
                val t2: Thread = MyThread()
                val t3: Thread = MyThread()
                val t4: Thread = MyThread()
                val t5: Thread = MyThread()
                //将线程放到池中执行;
                pool.execute(t1)
                pool.execute(t2)
                pool.execute(t3)
                pool.execute(t4)
                pool.execute(t5)
                //关闭线程池
                pool.shutdown()
-----------------------------------------------
inner class MyThread : Thread() {
        override fun run() {
            super.run()
            var name=currentThread().name
            tv_reult.post {
                var str=StringBuffer("")
                str.append(tv_reult.text.toString()).append("\n").append(name + "正在执行....")
                tv_reult.text=str.toString()
            }
            println(name + "正在执行....");
        }

    }
  • 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

输出结果
pool-1-thread-1正在执行…
pool-1-thread-1正在执行…
pool-1-thread-1正在执行…
pool-1-thread-1正在执行…
pool-1-thread-1正在执行…

  • newFixedThreadPool(数量)
    创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小
 //创建一个可重用固定线程数的线程池
                var pool: ExecutorService? =
                    Executors.newFixedThreadPool(3)
                //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口;
                val t1: Thread = MyThread()
                val t2: Thread = MyThread()
                val t3: Thread = MyThread()
                val t4: Thread = MyThread()
                val t5: Thread = MyThread()
                //将线程放到池中执行;
                pool!!.execute(t1)
                pool.execute(t2)
                pool.execute(t3)
                pool.execute(t4)
                pool.execute(t5)
                //关闭线程池
                pool.shutdown()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

输出结果
pool-1-thread-1正在执行…
pool-1-thread-1正在执行…
pool-1-thread-1正在执行…
pool-1-thread-1正在执行…
pool-1-thread-2正在执行…

  • newCachedThreadPoo()
    创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务
//创建一个可重用固定线程数的线程池
                val pool =
                    Executors.newCachedThreadPool()
                //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口;
                val t1: Thread = MyThread()
                val t2: Thread = MyThread()
                val t3: Thread = MyThread()
                val t4: Thread = MyThread()
                val t5: Thread = MyThread()
                //将线程放到池中执行;
                pool.execute(t1)
                pool.execute(t2)
                pool.execute(t3)
                pool.execute(t4)
                pool.execute(t5)
                //关闭线程池
                pool.shutdown()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

输出结果
pool-1-thread-2正在执行…
pool-1-thread-1正在执行…
pool-1-thread-3正在执行…
pool-1-thread-4正在执行…
pool-1-thread-5正在执行…
* newScheduledThreadPool(数量)
创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。

      var exec: ScheduledThreadPoolExecutor = ScheduledThreadPoolExecutor(1)
                exec.scheduleAtFixedRate(
                    Runnable //每隔一段时间就触发异常
                    {
                        // TODO Auto-generated method stub
                        println("AAAAAAAA")
                    }, 1000, 5000, TimeUnit.MILLISECONDS
                )

                exec.scheduleAtFixedRate(
                    Runnable //每隔一段时间打印系统时间,证明两者是互不影响的
                    {
                        // TODO Auto-generated method stub
                        println(System.nanoTime())
                    }, 1000, 2000, TimeUnit.MILLISECONDS
                )
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

输出结果
AAAAAAAA
23119318857491
23121319071841
23129318176148
23123319007891
AAAAAAAA
23125318176937
23127318190359
AAAAAAAA
23131318344312
23133318465896
AAAAAAAA
23135319645812

60、线程池都有哪些状态?

  • RUNNING(Running):线程池的初始化状态,线程池一旦被创建就处于RUNNING状态,任务数0,接受新的任务,对已排队的任务进行处理
  • SHUTDOWN(Shutdown):不接受新任务,但会处理等待队列的任务
  • STOP(Stop):不接受,不处理
  • TIDYING(Tidying):所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。因为terminated()在ThreadPoolExecutor类中是空的,所以用户想在线程池变为TIDYING时进行相应的处理;可以通过重载terminated()函数来实现。
  • TERMINATED(Terminated):线程彻底终止

61、 线程池中 submit()和 execute()方法有什么区别?

  • 接收的参数不一样:execute()方法只能执行实现Runnable接口的线程,submit()能执行实现Runnable和Callable接口的线程
  • submit()有返回值,execute()没有
  • sbbmit()方便Exception处理

62、在 java 程序中怎么保证多线程的运行安全?

java 程序中怎么保证多线程的运行安全?

  • 线程安全体现在:
    1、原子性:一个或多个操作在CPU执行的过程中不被中断的特性
    2、可见性:一个线程对共享变量的修改,另外一个线程能立即看到
    3、有序性:程序执行的顺序按照代码的先后顺序来执行

  • 导致原因:
    1、缓存导致的可见性的问题
    2、线程切换导致的原子性问题
    3、编译优化带来的有序性问题

  • 解决办法:
    1、synchronized、Lock可以解决原子性问题
    2、synchronized、Lock、volatile能解决可见性问题

63、多线程锁的升级原理是什么?

  • 在Java中锁共有四种状态:级别从低到高分别为:无锁状态,偏向锁,轻量级锁和重量级锁
    这几种状态会随着急症情况逐步升级
  • 锁只能升级不能降级
    在这里插入图片描述

64、什么是死锁?

  • 死锁指两个或两个以上线程在执行过程中,由于资源竞争或者互相通信而造成的一种阻塞现象,若无外力作用,他们将一直阻塞下去,此时称系统处于死锁状态或系统产生了死锁

65、怎么防止死锁?

  • 使用tryLock设置超时时间,超时退出可以防止死锁
  • 尽量使用java.util.concurrent并发类代替自己手写锁
  • 尽量不要多个功能使用同一个锁
  • 尽量减少同步的代码块儿

66、ThreadLocal 是什么?有哪些使用场景?

  • ThreadLocal是线程本地存储,在每个线程中都创建一个ThreadLocalMap对象,每个线程都可以访问自己内部ThreadLocalMap中的value
  • ThreadLocal是一个数据结构,可以保存键值对,但一个ThreadLocal只能保存一对,并且各个线程互不干扰
  • Th
  • 使用方法
class ThreadLocalActivity : AppCompatActivity(), View.OnClickListener {
    lateinit var THREAD_LOCAL_NUM: ThreadLocal<Int?>
    var stringBuffer = StringBuffer("")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_thread_local)
        init()
        init_Listener()
    }

    fun init() {
        //线程本地存储变量
        THREAD_LOCAL_NUM = object : ThreadLocal<Int?>() {
            override fun initialValue(): Int {
                return 0
            }
        }
    }

    fun init_Listener() {
        bt_0.setOnClickListener(this)
    }

    fun main() {
        for (i in 0..1) {
            var thread = Thread {
                changeLocalValue()
            }.start()

        }
    }

    /**
     * 修改本地存储数据
     */
    fun changeLocalValue() {
        for (i in 0..2) {
            var n = THREAD_LOCAL_NUM.get()!!.toInt()
            n++
            THREAD_LOCAL_NUM.set(n)
            Log.i("ThreadLocal==", Thread.currentThread().name + " : ThreadLocal num=" + n)
            stringBuffer.append(Thread.currentThread().name + " : ThreadLocal num=" + n)
            stringBuffer.append("\r\n")
            runOnUiThread() {
                tv_text.text = stringBuffer.toString()
            }
        }
    }

    override fun onClick(v: View?) {
        main()
    }

}
  • 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

返回示例
Thread-4 : ThreadLocal num=1
Thread-4 : ThreadLocal num=2
Thread-5 : ThreadLocal num=1
Thread-5 : ThreadLocal num=2
Thread-4 : ThreadLocal num=3
Thread-5 : ThreadLocal num=3

  • 作用
    1、保证线程安全
    2、在线程级别传递信息
  • 使用场景
    把获取日期转换的方法用在线程中
/**
     * 日期转换
     */
    var sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
    fun getChangeData(dataStr: String): Date? {
        var date: Date? = null
        date = sdf.parse(dataStr)
        return date
    }
   fun main() {
        var service = Executors.newFixedThreadPool(20)
        for (i in 0..19) {
            service.execute {
                Log.i("ThreadLocal==", getChangeData("2019-06-01 16:34:30").toString())
            }
        }
        service.shutdown()
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

结果main中打印:
Sat Jun 01 16:34:30 GMT+08:00 2019
Sat Jun 01 16:34:30 GMT+08:00 2019
Sun Jun 01 16:34:30 GMT+08:00 2200
Sun Jun 01 16:34:30 GMT+08:00 2200
Mon Apr 01 16:34:30 GMT+08:00 1202
Sat Jun 01 16:34:30 GMT+08:00 2019
Sat Jun 01 16:34:30 GMT+08:00 2019

发现数据获取不对

  • 解决方案:使用ThreadLocal,一个线程new一个SimpleDataFormat对象
  private static ThreadLocal<DateFormat> threadLocal = ThreadLocal.withInitial(
            ()-> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

    public static Date getChangeData(String dateStr) {
        Date date = null;
        try {
            date = threadLocal.get().parse(dateStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

注意:ThreadLocal支持安卓8.0系统,即Build.VERSION_CODES.O=26的时候可以使用
更为详细的介绍请参考
ThreadLocal的应用场景和注意事项有哪些?

67、说一下 synchronized 底层实现原理?

  • Synchronized作用
    1、原子性:原子性意味着不可分,即同一时刻只有一个线程能操作他
    2、可见性:在被synchronized修饰的代码块儿中,如果线程A执行结束,会强制将缓存内容更新到内存,并通知被synchronized修饰的线程X的值无效,需要重新读取,这样B线程在执行的时候就能读到线程A对线程X的修改了
    3、有序性:即同一时刻只有一个线程对该变量进行操作
  • synchroized的使用
    1、修饰实例方法
    2、修饰静态方法
    3、修饰代码块儿
  • 使用示例
    1、修饰实例方法
 fun getInstance(): RequestUtils {
        if (mRequestUtils == null) {
            synchronized(RequestUtils::class) {//双重检验锁
                if (mRequestUtils == null) {
                    mRequestUtils = RequestUtils()
                    Log.i(Tag, "new RequestUtils()")
                }
            }
        }
        return mRequestUtils
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2、修饰静态方法

 public synchronized static void methodTwo() {
        System.out.println("method two executed!");
    }
  • 1
  • 2
  • 3

3、修饰代码块儿

 public void methodTwo() {
        synchronized (obj) {
            System.out.println("method two executed!");
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5

68、synchronized 和 volatile 的区别是什么?

  • volatile的本质在于告诉jvm当前变量在工作内存中的值是不确定的需要重新读取,synchronized是锁定当前变量,保证只有当前线程能访问该变量,其他线程别阻塞住
  • volatile只能使用在变量级别,synchronized能使用在变量,方法和类级别
  • volatile只能实现变量修改的可见性,不能保证其原子性,synchronized能保证伯娘修改的可见性和原子性
  • volatile不会造成线程阻塞,synchronized可能会造成线程阻塞
  • volatile标记的变量不会被编译器优化,synchronized标记的变量会被编译器优化
    注释
    JVM:java虚拟机的缩写

69、synchronized 和 Lock 有什么区别?

  • synchronized属于jvm层面的关键字,Lock是api层面的工具类
  • synchronized不需要用户手动去释放锁,而Lock若不手动释放可能会造成死锁现象
  • synchronized不能被中断,除非抛出异常或者运行完成,Lock可以通过设置tryLock()方法设置超时方法
  • synchronized是非公平锁,Lock默认非公平锁,可设置为公平锁
  • synchronized要么唤醒一个线程,要么唤醒所有线程,Lock可以实现精准唤醒

70、synchronized 和 ReentrantLock 区别是什么?

  • synchronized是关键字,ReentrantLock是类
  • synchronized竞争锁的时候会一直等待,ReentrantLock可以尝试获取锁,并得到获取结果
  • synchronized获取锁时候无法设置超时,ReentrantLock可以设置超时时间
  • synchronized无法获得公平锁,reentrantLock可以后的公平锁,即先等待先获得锁
  • synchronized会在抛出异常或者执行完毕后自动释放锁,ReentrantLock需要在finally{}代码块儿中手动释放

71、说一下 atomic 的原理?

  • 我们在使用多线程的时候,为了保证数据安全,会考虑使用同步的方法,通常我们会使用synchronized和Lock,但使用synchronized意味着内核态的一次切换,是比较重的操作,此时我们就可以使用
    atomic来进行轻量级的数据同步
class MyThread implements Runnable {
 
    static AtomicInteger ai=new AtomicInteger(0);
 
    public void run() {
        for (int m = 0; m < 1000000; m++) {
            ai.getAndIncrement();
        }
    }
};
 
public class TestAtomicInteger {
    public static void main(String[] args) throws InterruptedException {
        MyThread mt = new MyThread();
 
        Thread t1 = new Thread(mt);
        Thread t2 = new Thread(mt);
        t1.start();
        t2.start();
        Thread.sleep(500);
        System.out.println(MyThread.ai.get());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 原理
    在多个线程下,当多个线程对同一变量进行操作是时,具有排他性,即同一时间只有一个线程能成功对变量进行操作,失败的线程会排队继续尝试,直至成功

------------反射-----------

72、什么是反射?

  • java的反射机制是,在运行过程中对于任何一个类,都能知道他的所有属性和方法,对于任何一个对象,都能调用他的所有属性和方法,这种动态获取信息以及动态调用方法的方法叫做java的反射机制
  • 指程序可以访问、检测和修改它本身状态或行为的一种能力

73、什么是 java 序列化?什么情况下需要序列化?

  • 序列化:将Java对象转为字节流的过程
  • 返序列化:将字节流转化为java对象的过程
  • 使用情况:将java对象进行网络传输,或者保存本地文件的时候需要进行序列化
  • 实现方法:将类实现Serializable接口,

74、动态代理是什么?有哪些应用?

  • 当想要给实现了某个接口类中的方法加一些额外的处理,例如加日志和事务等,可以给这个类创建一个
    代理,即创建一个新的类,这类不仅包含原来类的所有方法,而且还在原来的基础上增加了额外处理的新类,这个代理类不是定义好的,是动态生成的,具有解耦意义,灵活,扩展性强
  • 动态代理的应用
    (1)Spring的AOP
    (2)加事务
    (3)加权限
    (4)加代理

75、怎么实现动态代理?

android:对于动态代理的一些理解

------------对象拷贝-----------

76、为什么要使用克隆?

  • 想对一个对象进行处理,又要保留他的原始数据进行接下来的操作,这时候就需要使用克隆,java中克隆针对的是类的实例

77、如何实现对象克隆?

如何实现对象克隆?

  • 实现Cloneable 接口 重写clone()方法
1、浅拷贝
class DPerson implements Cloneable {
    public DPerson() {
    }

    private int pid;

    private String name;

    private DFood food;

    public DPerson(int pid, String name, DFood food) {
        this.pid = pid;
        this.name = name;
        this.food = food;
        System.out.println("Person constructor call");
    }

    public int getPid() {
        return pid;
    }

    public void setPid(int pid) {
        this.pid = pid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @NonNull
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "Person [pid:"+pid+", name:"+name+", food:"+food.getName()+"]";
    }

    public DFood getFood() {
        return food;
    }

    public void setFood(DFood food) {
        this.food = food;
    }
    class DFood implements Cloneable{

        private String name;

        public DFood(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }

    }
}


----------------------------我是分割线------------------------

  fun init(){
        var bean1=DPerson(1,"文阿花", DPerson().DFood("大米饭"))
        var bean2= bean1.clone() as DPerson
        bean2.food.name="面包"
        bean2.name="wenahua"
        Log.i("Clone==","bean1="+bean1+"bean2="+bean2)
    }
  • 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
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85

输出结果
bean1=Person [pid:1, name:文阿花, food:面包]
bean2=Person [pid:1, name:wenahua, food:面包]

我们发现bean2变了,但是bean1也变了,这样并不能满足我们的需求,因此我们需要深克隆!!

2、深拷贝

(1)方法1、在克隆对象时候手动属性

class DPerson implements Cloneable {
    public DPerson() {
    }

    private int pid;

    private String name;

    private DFood food;

    public DPerson(int pid, String name, DFood food) {
        this.pid = pid;
        this.name = name;
        this.food = food;
        System.out.println("Person constructor call");
    }

    public int getPid() {
        return pid;
    }

    public void setPid(int pid) {
        this.pid = pid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        DPerson p = (DPerson)super.clone();
        p.setFood((DFood)p.getFood().clone());
        return p;
    }

    @Override
    public String toString() {
        return "Person [pid:"+pid+", name:"+name+", food:"+food.getName()+"]";
    }

    public DFood getFood() {
        return food;
    }

    public void setFood(DFood food) {
        this.food = food;
    }
    class DFood implements Cloneable{

        private String name;

        public DFood(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }

    }
}
-------------------------------我是分割线-------------
  fun init(){
        var bean1=DPerson(1,"文阿花", DPerson().DFood("大米饭"))
        var bean2= bean1.clone() as DPerson
        bean2.food.name="面包"
        bean2.name="wenahua"
        Log.i("Clone==","bean1="+bean1+"bean2="+bean2)
    }
  • 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
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83

输出结果
bean1=Person [pid:1, name:文阿花, food:大米饭]
bean2=Person [pid:1, name:wenahua, food:面包]

可以看出 bean2变了,而bean1没变

(2)方法2、结合序列化Serializable 实现深拷贝

package constxiong.interview;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class TestSeriazableClone {
 
	public static void main(String[] args) {
		SPerson p1 = new SPerson(1, "ConstXiong", new SFood("米饭"));//创建 SPerson 对象 p1
		SPerson p2 = (SPerson)p1.cloneBySerializable();//克隆 p1
		p2.setName("其不答");//修改 p2 的 name 属性
		p2.getFood().setName("面条");//修改 p2 的自定义引用类型 food 属性
		System.out.println(p1);//修改 p2 的自定义引用类型 food 属性被改变,p1的自定义引用类型 food 属性未随之改变,说明p2的food属性,只拷贝了引用和 food 对象
		System.out.println(p2);
	}
	
}
 
class SPerson implements Cloneable, Serializable {
	
	private static final long serialVersionUID = -7710144514831611031L;
 
	private int pid;
	
	private String name;
	
	private SFood food;
	
	public SPerson(int pid, String name, SFood food) {
		this.pid = pid;
		this.name = name;
		this.food = food;
		System.out.println("Person constructor call");
	}
 
	public int getPid() {
		return pid;
	}
 
	public void setPid(int pid) {
		this.pid = pid;
	}
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	/**
	 * 通过序列化完成克隆
	 * @return
	 */
	public Object cloneBySerializable() {
		Object obj = null;
		try {
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(baos);
			oos.writeObject(this);
			ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
			ObjectInputStream ois = new ObjectInputStream(bais);
			obj = ois.readObject();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return obj;
	}
 
	@Override
	public String toString() {
		return "Person [pid:"+pid+", name:"+name+", food:"+food.getName()+"]";
	}
 
	public SFood getFood() {
		return food;
	}
 
	public void setFood(SFood food) {
		this.food = food;
	}
	
}
 
class SFood implements Serializable {
	
	private static final long serialVersionUID = -3443815804346831432L;
	
	private String name;
	
	public SFood(String name) {
		this.name = name;
	}
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
	
}
  • 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
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110

输出结果
Person constructor call
Person [pid:1, name:ConstXiong, food:米饭]
Person [pid:1, name:其不答, food:面条]

也可以看出 bean2变了,而bean1没变

78、深拷贝和浅拷贝区别是什么?

  • 浅拷贝:对象A1中有对B1的引用,B1中有对C1的引用,浅拷贝A1到A2,A2中依旧包含对B1的引用,B1中包含对C1的引用
  • 深拷贝:对象A1中有对B1的引用,B1中有对C1的引用,深拷贝A1到A2,A2中包含对B2(copy B1)的引用,B2中包含对C2(copy C1)的引用
  • 如果不对clone()进行重写则为浅拷贝

------------异常-----------

79、throw 和 throws 的区别?

  • throw表示方法内抛出某种具体异常对象
  • 执行到throw语句后面的语句不再执行
    public static void send(WebSocketClient client, byte[] bytes) {
        if (client.isClosed()) {
            throw new RuntimeException("client connect closed!");//处理异常
        }
        client.send(bytes);//正常情况下需要执行的方法
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • throws 表示该方法可能抛出的所有异常信息,throws不处理异常,只上传,即谁调用谁处理

80、final、finally、finalize 有什么区别?

  • final-修饰符(关键字)
    final修饰类、方法、变量,不能被被继承,final修饰的类不能被继承,final修饰的变量不能被修改,final修饰的方法不能被重写
  • finally在异常处理中提供finally代码块儿来执行任何清除操作
    不管有没有异常抛出,finally块代码都会被执行
  • finalize方法名
    finalized是Object中的方法,在垃圾回收器回收该对象使用的内存之前被调用,即一个对象在被虚拟机宣告死亡前调用finalize方法,让她处理生前事

81、try-catch-finally 中哪个部分可以省略?

  • 三种使用方法
    try-catch-finally
    try-catch
    try-finally
    可以省略catch或者finally 但是catch和finally不能同时省略

82、try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?

  • 会,会在return之前执行

83、常见的异常类有哪些?

  • NullPointerException:当应用程序试图访问空对象时,则抛出该异常。
  • SQLException:提供关于数据库访问错误或其他错误信息的异常。
  • IndexOutOfBoundsException:指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
  • NumberFormatException:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
  • FileNotFoundException:当试图打开指定路径名表示的文件失败时,抛出此异常。
  • IOException:当发生某种I/O异常时,抛出此异常。此类是失败或中断的I/O操作生成的异常的通用类。
  • ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出该异常。
  • ArrayStoreException:试图将错误类型的对象存储到一个对象数组时抛出的异常。
  • IllegalArgumentException:抛出的异常表明向方法传递了一个不合法或不正确的参数。
  • ArithmeticException:当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例。
  • NegativeArraySizeException:如果应用程序试图创建大小为负的数组,则抛出该异常。
  • NoSuchMethodException:无法找到某一特定方法时,抛出该异常。
  • SecurityException:由安全管理器抛出的异常,指示存在安全侵犯。
  • UnsupportedOperationException:当不支持请求的操作时,抛出该异常。
  • RuntimeExceptionRuntimeException:是那些可能在Java虚拟机正常运行期间抛出的异常的超类。

------------网络-----------

84、http 响应码 301 和 302 代表的是什么?有什么区别?

  • 301、302都是HTTP状态的编码
  • 301:代表永久性转移
  • 302:代表暂时性转移

85、forward 和 redirect 的区别?

  • forward:直接转发 客户端和浏览器执法处一次请求
  • redirect:间接转发 实际是HTTP两次请求 服务器在响应第一次请求的时候,让浏览器再向另外一个URL发出请求,从而达到转发的目的
  • 举个例子:
    间接转发:A找B借钱,B没有,让A去找C借
    直接转发:A找B借钱,B没有,B找C借,B借到借不到都会把信息传递给A

87、简述 tcp 和 udp的区别?

  • UDP是面向无线连接的通讯协议,UDP数据包括目的端口号和IP
    优点:操作简单,速度快,占用资源少,由于不需要连接,所以可以使用广播发送
    缺点:由于不建立连接,不确定数据是否被正确接收,也不重复发送,不可靠
  • TCP是面向连接的通讯协议,通过三次握手建立连接,通信完成后四次挥手
    优点:发送时能保证数据的正确性,可靠
    缺点:速度慢,占用资源多

88、tcp 为什么要三次握手,两次不行吗?为什么?

  • TCP是双全工的,即客户端再给服务端发信息的同时,服务端也在给客户端发信息

  • 半全工的意思是,A能给B发信息,B也能给A发信息,但是不能同时

  • 单工是指A和B之间的通信是单向的,即A能给B发,但B不能给A发,或者B能给A发,A不能对B发

  • 举个例子
    第一次握手:A跟B打电话问,你能听到我说话吗
    第二次握手:B收到A的信息,并回信息说我可以听到你说话
    第三次握手:A跟B说,可以,那我要开始向你发信息了
    经过三次握手,确认A能听到B说话,B能听到A说话,这样就能开始正常通信了

  • 由于TCP协议中,建立见连接后,AB双方谁都能先发送信息,所以只握手两次的话,无法确定A是否能听到B说话,会出现问题,但是四次的话,又会造成浪费

  • 正式情况下 三次握手都做了什么
    TCP为什么是三次握手,为什么不是两次或者四次 && TCP四次挥手

  • 四次挥手
    A:我不说了
    B:好的,等我把最后一句说完
    B:我说完了
    A:好的

89、说一下 tcp 粘包是怎么产生的?

tcp 粘包是怎么产生的?

  • 什么是粘包
    发送方发送的数据在到达到接受方的缓存区时候,首尾相连,粘成一包,被接收
  • 原理
    TCP协议默认Nagle算法可能会把多个数据包一次发送到接受方
    程序读取速度小于接收速度,缓存中 的多个数据包会被应用程序当成一个包一次读取
  • 解决办法
    发送方使用TCP_NODELAY 关闭Nagle算法
    数据包增加开始符和结束符
    在数据包的头部定义数据包的长度,应用程序先读取长度,在读取改长度的数据,保证读取的整个包数据完整

89、NDK、JDK、SDK、JNI

  • ndk:安卓开发使用的一系列工具集合,使用C语言
  • sdk: 安卓软件开发工具包,使用Java语言
    常见SDK:

(1)地图:百度map,高德。

(2)推送:小米推送

(3)聊天:环信、腾讯。

(4)第三方登陆:新浪,qq,微信等

(5)json解析:fastjson,速度最快,简单易用

(6)网络请求:okhttp

(7)图片加载:ImageLoader

  • jni:java本地接口
    定义:Java Native Interface,即 Java本地接口
    作用: 使得Java 与 本地其他类型语言(如C、C++)交互
    即在 Java代码 里调用 C、C++等语言的代码 或 C、C++代码调用 Java 代码

特别注意:
JNI是 Java 调用 Native 语言的一种特性
JNI 是属于 Java 的,与 Android 无直接关系

90、OSI 的七层模型都有哪些?

  • OSI:开放式系统互联通信参考模型,是一种概念模型,有国际标准化组织提出,一个试图使各种计算机在世界范围内互联为网络的标准框架
  • OSI模型分为7层,从下往上分别为:物理层、数据链路层、网络层、传输层、会话层、表达层、应用层
    记忆方式:物数网传会表应
    在这里插入图片描述

91、get 和 post 请求有哪些区别?

  • 功能不同
    get是向服务器传数据
    post是从服务器获取数据
  • 产生数据包个数不同
    get方法产生一个TCP数据包,post产生两个数据包
    对于get来说,将http header和data一起发送出去,服务器响应200(返回数据)
    对于post来说,先发送header 获取服务器100,在提交data,服务器响应200(返回数据)
    也就是说:get一趟就把数据送到了,post需要跑两趟,第一趟先去跟服务器打个招呼“嗨,我要给你送东西了,你打开门迎接我”,然后回去再把数据送来
  • 过程不同
    get请求是把数据队列加到提交表单的ACTION属性所指的URL中,值和表单中的字段一一对应,在URL中可以看出
    post请求是通过Http post机制将变淡中各个字段和其内容放在HTML Header中一起传送到Action属性所指的URL地址,这个过程用户看不到
    因为POST需要两步,时间上消耗的要多一点,看起来GET比POST更有效。因此Yahoo团队有推荐用GET替换POST来优化网站性能。但这是一个坑!跳入需谨慎。为什么?
  1. GET与POST都有自己的语义,不能随便混用。
  2. 据研究,在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下,两次包的TCP在验证数据包完整性上,有非常大的优点。
  3. 并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。

------------设计模式-----------

92、说一下你熟悉的设计模式?

  • 创建型模式 共5种
    工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
  • 结构型模式 共七种
    适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式
  • 行为型模式
    策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式

93、简单工厂和抽象工厂有什么区别?

  • 简单工厂:用来生产同一等级结构中的任意产品(对于增加新产品无能为力)
  • 工厂方法:用于生产同一等级中的固定产品(支持增加任意产品)
  • 抽象工厂:用于生产不同产品族的全部产品(对于增加新产品无能为力,支持增加新产品族)

------------JVM(Java虚拟机)-----------

94、说一下 jvm 的主要组成部分?及其作用?

  • 类加载子系统
    负责加载class类信息,加载出的类信息存放在方法区中
  • 直接内存
    存在于java堆外,直接向系统申请的内存空间,访问直接内存的速度会优于java堆,所以一些读写频繁的情况会选择使用直接内存。
  • 垃圾回收器
    会对堆、方法区、直接内存进行回收
  • 执行引擎
    会执行虚拟的的字节码,会使用及时编译技术将方法编译为机器码后再执行

95、说一下 jvm 运行时数据区?

  • 程序计数器
  • 虚拟机栈
  • 本地方法栈
  • 方法区
    有的区域随着虚拟机的启动而存在,有的区域则依赖用户进程的启动和结束而创建和销毁

96、说一下堆栈的区别?

  • 空间不同
    (1)堆栈:是自动分配变量,以及函数调用时候所使用的空间
    (2)堆:是由malloc(动态内存分配)之类函数分配的空间所在地
  • 地址方向不同
    (1)堆栈:地址方向是由高到低减少性扩展,有总长度大小限制
    (2)堆:地址方向是由低到高增加型扩展,没有总长度大小限制
  • 释放不同
    (1)堆栈:由编译器释放
    (2)堆、由程序人员释放

97、队列和栈是什么?有什么区别?

  • 队列:是限定只能在表的一端进行插入,在另一端进行删除的线性表-------插入删除两头进行
  • 栈:是限定只能在表的一端进行插入和删除的线性表-------插入删除同端进行
  • 规则不同
    (1)对列:先进先出
    (2)栈:先进后出
  • 遍历数据的速度不同
    (1)队列:基于地址指针进行遍历,而且可以从头开始遍历也可以从尾部开始遍历,但不能同时进行,无需开辟空间,因为遍历过程不改变数据结构,所以遍历速度要快
    (2)栈:只能从顶部取数据,即最先进入栈底的需要遍历整个队列才能取得,遍历数据时需要开辟临时空间,保持数据数据在遍历前的一致性

98、说一下类加载的执行过程?

  • 前言:一个Java文件从编译到最终执行完成一般要经历两个过程:
    (1)编译:即将我们写好的java文件通过javac命令编译成class文件
    (2)运行:即将编译好的class文件交给JVM(Java虚拟机)执行
    JVM 并不是一开始就把所有的类都加载进内存中,而是第一次遇到某个需要运行的类才会加载,且只加载一次
  • 类加载
    (1)记载
    (2)链接
    (3)初始化
    而链接又可以分为三部分
    (1)验证
    (2)准备
    (3)解析
    每一部分的详细解释清参照:
    请你谈谈Java的类加载过程

99、怎么判断对象是否可以被回收?

  • 引用计数法(已摒弃)
    为每个对象创建一个引用计数,当对象那个被引用时,计数器+1,释放时,计数器-1
    但这个方法存在一个问题,就是循环引用问题,所以发方法已被摒弃
  • 可达性分析
    从GC Roots开始向下搜索,搜索所走过的路径称为引用链。
    当一个对象到GC Roots没有任何引用链时,则认为此对象可以被回收。
    大家可以认为就是一个树的根节点开始计算引用情况。

100、java 中都有哪些引用类型?

  • 什么叫引用
    Object o=new Object()
    o 即为引用对象
  • 如何取消引用
    o=null
  • 引用的四种方式
    强引用,软引用,弱引用,虚引用。引用强度从强到弱
    (1)强引用
    类似Object o=new Object()只要强引用还在,垃圾回收器就永远不会回收掉被引用的对象实例
    (2)软引用
    User mUser=new User(“1”)
    SoftReference softUser=new SoftReference (mUser)
    mUser=null
    使用软引用的实例对象,会在系统发生ooM之前,被回收掉
    (3)弱引用
    User mUser=new User(“1”)
    WeakReference softUser=new WeakReference (mUser)
    mUser=null
    使用弱引用的对象,只能生存到下一次垃圾回收前,GC(内存溢出)发生时时候,不管内存够不够都会被回收
    (4)虚引用
    虚引用需要和引用队列一起使用
  • 引用队列
    引用队列可以和软、弱、虚引用一起使用
    当垃圾回收器准备回收一个对象时候,如果发现他还在引用就会在这个对象回收之前将这个引用加入到与之关联的引用队列里面。程序可以通过判断引用队列中是否加入了引用,来判断对象是否将要被回收,这样就可以在被回收前采取一定的措施
    注意:虚引用必须和引用队列一起使用

101、说一下 jvm 有哪些垃圾回收算法?

  • Mark-Sweep(标记-清除)算法
  • Copying(复制)算法
  • Mark-ComPact(标记-整理)算法—压缩法
  • Generational Collection(分代收集)算法

102、说一下 jvm 有哪些垃圾回收器?

  • 串行垃圾回收器
  • 并行垃圾回收器
  • CMS收集器
  • G1垃圾收集器
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/526784
推荐阅读
相关标签
  

闽ICP备14008679号