面向对象编程和面向过程编程的区别
1、面向过程在解决问题的时候,会分析出解决问题的步骤,然后用函数一步步的将这些步骤实现,依次调用函数。而面向对象在解决问题的时候,会将问题拆散成一个个对象,建立对象的目的不是为了完成某个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为。这来自于思考问题方式的不同。
2、从结构上说,面向过程的特点是过程化和模块化,而面向对象的特点则是类封装、继承和多态(多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法)。
3、执行效率不同,面向过程不需要封装类再实例化对象调用,只定义了函数和调用,所以执行效率会更高一些。
总的来说,面向过程执行效率更高也更直接,面向对象更灵活也更丰满。
抽象类和接口的区别
相同点:无法直接创建对象
- 抽象类
1、抽象类中可以有抽象属性、非抽象属性、抽象方法和非抽象方法
2、抽象方法有方法体、空实现
3、抽象类只能单继承 - 接口
(可以看成特殊的抽象类)
1、接口只能有方法名,不能有方法体
2、在Java中,接口的属性只能是public final
3、接口可以多继承
静态内部类和非静态内部类
- 1、静态内部类不持有外部类的引用
在普通的内部类中,可以直接访问外部类的属性和方法,即使是private类型的也可以,这是因为普通内部类持有外部类的引用。而静态内部类只能访问外部类的静态属性和静态方法(即使是private)。 - 2、静态内部类不依赖外部类
普通内部类和外部类之间是相互依赖关系,内部类实现不能脱离外部类实例,一起声明,一起被回收。而静态内部类是可以独立存在的,即使外部类消亡了,静态内部类也可以独立存在。 - 3、普通内部类不能声明static的方法和变量
普通内部类不能声明static方法和变量,允许static常量;静态内部类的方法可以是非静态方法也可以是静态方法,没有限制。
组合(引用)跟继承的使用场景区别
- 继承与组合的区别
继承是子类继承父类,父类的所有属性和方法都可以被子类访问和调用。组合是指将已存在的类型作为一个新建类的成员变量类型,两个类之间无上下级关系。 - 使用场景
当你只需要使用另一个类的方法时使用组合。使用继承关系时,可以实现类型的回溯,即用父类变量引用子类对象,这样便可以实现多态,而组合没有这个特性。
Android和Java序列化原理
- 序列化:将数据标记打包保存起来
- 反序列化 :根据标记读取解析保存的数据
- Serializable和Parcelable的区别
1、Serializable 使用 I/O 读写存储在硬盘上,而 Parcelable 是直接在内存中读写
2、Serializable 会使用反射,序列化和反序列化过程需要大量 I/O 操作, Parcelable 自已实现封送和解封(marshalled &unmarshalled)操作不需要用反射,数据也存放在 Native 内存中,效率要快很多。
谈谈Serializable和Parcelable接口的区别
- Serializable(Java自带)
Serializable是序列化的意思,表示将一个对象转换成可存储或可传输的状态。序列化的对象可以在网络上进行传输,也可以存储到本地。 - Parcelable(Android专用)
除了Serializable以外,Parcelable也可以实现相同的效果,
不过不同于将对象进行序列化,Parcelable的实现原理是将一个完整的对象进行分解,
而分解后的每一部分都是Intent所支持的数据类型,这样也就实现传递对象的功能了。性能更高
选择序列化方法的原理 - 内存中使用Parcelable
- 持久化使用Serializable
Serializable更趋向于使用一种二进制的方式进行序列化,Serializable在序列化的过程中会产生大量的临时变量,会导致频繁GC。但Serializable在持久化时占用的内存空间就更小,在反序列化的过程中,Serializable也不会因为对象的增减字段造成反序列化的失败。
是匿名内部类 ,它有什么特征?
- 匿名内部类也就是没有名字的类
- 正因为没用名字,所以匿名内部类只能使用一次,它通常用来简化代码编写
- 实现匿名内部类必须有一个前提条件:必须继承一个父类或实现一个接口
- 匿名内部类中是不能定义构造函数的(没有名字)
使用的形参为何要为final?
- 我们给匿名内部类传递参数的时候,若该形参在内部类中需要被使
用,那么该形参必须要为final。也就是说:当所在的方法的形参需要
被内部类里面使用时,该形参必须为final。- 为什么必须要为final呢?
首先我们知道在内部类编译成功后,它会产生一个class文件,该class文件与外部类并不是同一class文件,仅仅只保留对外部类的引用。当外部类传入的参数需要被内部类调用时,从java程序的角度来看是直接被调用在内部类中的属性和外部方法的参数两者从外表上看是同一个东西,但实际上却不是,所以他们两者是可以任意变化的,也就是说在内部类中我对属性的改变并不会影响到外部的形参,而然这从程序员的角度来看这是不可行的,毕竟站在程序的角度来看这两个根本就是同一个,如果内部类该变了,而外部方法的形参却没有改变这是难以理解和不可接受的,所以为了保持参数的一致性,就规定使用final来避免形参的不改变。简单理解就是,拷贝引用,为了避免引用值发生改变,例如被外部类的方法修改等,而导致内部类得到的值不一致,于是用final来让该引用不可改变。故如果定义了一个匿名内部类,并且希望它使用一个其外部定义的参数,那么编译器会要求该参数引用是final的。
String中“==”与equals()的区别?
“==”比较在内存中存放的位置
equals()比较内容
为什么说String是不可变的?
String类以及其属性都是final修饰。
什么是内存泄漏?Java是怎么处理它的?
- 内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果
- 产生原因:一个长声明周期的对象持有一个短生命周期的引用
- 通俗讲就是该回收的对象,因为引用问题没有被回收,最终会产生OOM
- Java本身不会帮我们处理内存泄漏,需要自行定位处理。
ArrayList和LinkedList的区别
- ArrayList(顺序表):
优点:存储空间连续,允许随机访问,尾插、尾删方便
缺点:插入效率低、删除效率低,长度固定 - LinkedList(双链表):
优点:随意进行增删改,插入效率高、删除效率高,长度可以随意修改,查找效率比单链表快一倍
缺点:内存不连续,不能随机查找,但是效率比单链表快一倍
堆与栈的区别
堆与栈的区别有:1、栈由系统自动分配,而堆是人为申请开辟;2、栈获得的空间较小,而堆获得的空间较大;3、栈由系统自动分配,速度较快,而堆一般速度比较慢;4、栈是连续的空间,而堆是不连续的空间。
Java中线程与堆栈的关系?
- 栈是线程私有的,每个线程都有自己的栈,每个线程中的每个方法在执行的同时会创建一个栈帧用于存局部变量表、操作数栈、动态链接、方法返回地址等信息。每一个方法从调用到执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。其中局部变量表,存放基本类型(boolean、byte、char、short、int、float)、对象的引用等等,对象的引用不是对象实例本身,而是指向对象实例的一个指针。
- 堆是线程共享的,所有的对象的实例和数组都存放在堆中,任何线程都可以访问。Java的垃圾自动回收机制就是运用这个区域的。
- 方法区也是线程共享的,用于存放类信息(包括类的名称、方法信息、字段信息)、常量、静态变量以及即时编译器编译后的代码等等。
Java回收机制?如何减少OOM的概率?
JVM中,程序计数器、虚拟机栈、本地方法栈都是随线程而生随线程而灭,栈帧随着方法的进入和退出做入栈和出栈操作,实现了自动的内存清理,因此,我们的内存垃圾回收主要集中于 java 堆和方法区中,在程序运行期间,这部分内存的分配和使用都是动态的。
- Java的回收机制采用引用计数和可达性分析算法
(1)引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。
(2)可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。不可达对象。 -
在Java中,可作为GCRoot的对象包括:
1、方法区:类静态属性的对象;
2、方法区:常量的对象;
3、虚拟机栈(本地变量表)中的对象;
4、本地方法栈JNI(Native方法)中的对象。
- 减少OOM(Out of Memory)的概率
1、尽可能的减少内存泄漏;
2、尽可能不在循环中申请内存;
3、尽可能不在调用次数多的函数中申请内存。
Java中强引用、软引用、弱引用以及虚引用?
- 强引用 Object obj = new Object();
- 软引用 内存不足时回收(图片加载)
- 弱引用 GC到来时回收(Activity中处理内存泄漏)
- 虚引用 GC回收时可得到一个通知
什么是依赖注入,能说说几个依赖注入的库吗?
- 依赖是类与类之间的连接,依赖关系表示一个类依赖于另一个类的定义。通俗来讲就是,一个类的创建需要另一个类的实例。
Android中常使用的依赖注入类有Dagger、Hilt。
关键字synchronized的作用是什么?
- synchronized关键字可以在多线程环境下用来作为线程安全的同步锁。
- 任何对象都有synchronized
- synchronized关键字主要有以下三种用法:
1、修饰代码块
2、修饰成员方法
3、修饰静态方法
如果是成员方法,synchronized修饰的是this(两个线程不同步)
如果是静态方法,synchronized修饰的是class对象
Java volatile关键字解析
volatile关键字的作用:保证了变量的可见性(visibility)。被volatile关键字修饰的变量,如果值发生了变更,其他线程立马可见,避免出现脏读的现象。
自旋锁,乐观锁,悲观锁
- 自旋锁:自旋锁就是让不满足条件的线程循环等待,而不是立即挂起。
通过占用处理器(CPU)的时间来避免线程切换带来的开销。 - 乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。
乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。 - 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。
比如Java里面的同步原语synchronized关键字的实现就是悲观锁,volatile关键字虽然是synchronized关键字的轻量级实现,但是其无法保证原子性,所以一般也要搭配锁使用。
如何根据泛型T获取它代表的具体类的类名?
- 使用反射获取T对应的真实类型
private Class<?> analysisClassInfo(Object object) {
//getGenericSuperclass()得到包含原始类型,参数化,数组,类型变量,基本数据
Type getType=object.getClass().getGenericSuperclass();
//获取参数化类型
Type[] params=((ParameterizedType)getType).getActualTypeArguments();
return (Class<?>)params[0];
}
注解的原理?
https://www.jianshu.com/p/6d4d8a5e1e42
Android四大组件
- Activity
- Service
- ContentProvider
- BroadcastReceiver(广播接收器)
两个fragment如何进行通信?
使用LiveDataBus
Activity的生命周期
- onCreate():资源的创建、初始化,最好使用懒加载或延迟加载,提高Activity启动的速率
- onStart():此时界面已显示,onStart()完成之后就可以就行页面交互了,这里面也可以做一些初始化工作
- onResume():Activity退到后台然后恢复时,做一些数据的恢复以及相关的工作。
- onPause():当切换到另一个Activity但还为被完全覆盖时,此Activity就处于onPause()
- onStop():当此Activity完全被覆盖时,处于onStop()阶段
- onRestart():回到上一个Activity
- onDestroy():退出程序,Activity就onDestroy(),然后shut down
关于Activity生命周期的切换
如果Activity A切换到另一个Activity B,那么Activity A生命周期执行到onPause()时B执行onCreate()、onStart()、onResume(),接着A才执行onStop()
如果弹出一个对话框,那么A将处于onPause()阶段。
当打开其他程序,之前的程序就会被挂到后台,那么如果内存不够用呢?
挂在后台的App的Activity将被销毁
异常情况下
onSaveInstanceState()保存数据
onRestoreInstanceState()或onCreate()中恢复数据
Activity之间如何进行通信?
从18年谷歌IO大会开始,官方建议:
在Activity与Activity
Activity与Fragment
Fragment与Fragment之间通信使用LivedataBus
Fragment生命周期
可以看到Fragment的生命周期和Activity非常相似,只是多了几个方法:
- onAttach():在Fragment与Activity建立关联时调用(Activity传递到此方法内)
- onCreateView():当Fragment创建视图时调用
- onActivityCreated():在相关联的Activity的onCreate()方法已返回时调用
- onDestroyView():当Fragment中的视图被移除时调用。
- onDetach():当Fragment和Activity取消关联时调用。
下面是关于Fragment生命周期变化场景
如果Fragment B覆盖Fragment A,Fragment A将执行到onDestroyView,其他部分雷同Activity。
什么是Fragment?它和Activity的区别是什么?
- Fragment是依赖于Activity的,不能独立存在,Activity是Fragment的一个容器。
- 一个Activity里可以有多个Fragment。
- 一个Fragment可以被多个Activity重用。
- Fragment有自己的生命周期,并能接受输入事件。
- 我们能在Activity运行时动态地添加或删除Fragment。
- Fragment的使用能解决以下问题:
1、模块化:我们不必把所有代码全部不写在Activity中,而是把代码写在各自Fragment中,以方便不同业务的UI可以分离出来。
2、可重用:多个Activity可以重用一个Fragment。
3、可适配:根据硬件的屏幕尺寸、屏幕方向,能够方便地实现不同的布局,这样用户体验更好。
为什么只使用默认的构造方法来创建Fragment
- 当系统内存不足的情况下,app处于后台条件下,内存会被系统回收,这时用户将app切换到前台,系统会重新将之前回收的内容实例化回来。
- 这个过程是Android系统通过两个方法来实现的:
onSaveInstanceState():是系统要回收该界面(Activity、Fragment)时调用的方法,用于保存该(Activity、Fragment)的实例变量到外存。
onRestoreInstanceState():是系统恢复该界面(Activity、Fragment)时调用的方法,用于之前被回收的(Activity、Fragment)实例。
Bundle被用来传递数据,为什么不用HashMap代替?
问题转化:为什么Android传递数据的时候不使用HashMap,而使用ArrayMap?
- Bundle的优势
1、ArrayMap适合于小数据量操作,如果在数据量比较大的情况下,它的性能将退化 。HashMap内部则是数组+链表的结构,所以在数据量较少的时候,HashMap的Entry Array比ArrayMap占用更多的内存。而使用Bundle的场景大多数为小数据量。所以使用ArrayMap实现更合适。
2、Android中如果使用Intent携带数据的话,需要数据是基本类型或者是可序列化类型,Bundle使用Parcelable进行序列化,而HashMap使用Serializable进行序列化。
解释一下Android中的Intent(显示&隐式)
-
显示Intent
显示Intent是明确目标Activity的类名
1、通过Intent(Context packageContext,Class<?>cls)构造方法
2、通过Intent的setComponent()方法
3、通过Intent的setClass/setClassName方法
-
隐式Intent
隐式Intent通过设置Action、Data、Category,让系统来挑选出合适的Activity。筛选是根据所有的<intent-filter>来筛选。用于不同应用之间跳转。
广播和EventBus的区别?
1、广播是四大组件之一,EventBus是开源框架。
2、广播不能直接执行耗时操作,如果超过10秒,会导致ANR。
3、广播非常消耗资源,而EventBus非常轻量。
4、广播很容易获取Context和Intent。
5、EventBus切换线程非常方便,只需要修改一下注解就行了。
6、广播可以跨进程,而EventBus不可以。