一、Java异常机制中,异常Exception与错误Error区别
这道题想考察什么?
在开发时需要时候需要自定义异常时,应该选择定义Excption还是Error?编写的代码触发Excption或者Error分别代表什么?
考察的知识点
Java异常机制
考生应该如何回答
在Java中存在一个Throwable
可抛出类,Throwable
有两个重要的子类,一个是Error,另一个则是Exception。
Error
是程序不能处理的错误,表示程序中较严重问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError等等。这些错误发生时,JVM一般会选择线程终止。这些错误是不可查的,它们在程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。
Exception
是程序可以处理的异常。而Exception又分为运行时异常(RuntimeException)与非运行时异常。
- 运行异常
运行时异常,又称不受检查异常 。所谓的不受检查,指的是Java编译检查时不会告诉我们有这个异常,需要在运行时候才会暴露出来,比如下标越界,空指针异常等。 - 非运行时异常
RuntimeException之外的异常我们统称为非运行时异常,比如IOException、SQLException,是必须进行处理的异常(检查异常) ,如果不处理(throw到上层或者try-catch),程序就不能编译通过 。
二、说说反射的应用场景,哪些框架?
这道题想考察什么?
是否了解反射相关的理论知识
考察的知识点
- 反射机制
- 反射在框架中的应用
- 获取Class类的主要方式
考生应该如何回答
反射是Java程序开发语言的特征之一,它允许动态地发现和绑定类、方法、字段,以及所有其他的由于有所产生的的元素。通过反射,能够在需要时完成创建实例、调用方法和访问字段的工作。
反射机制主要提供功能有:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的方法,通过反射甚至可以调用到private修饰的方法
- 生成动态代理
反射在框架中用的非常多。我们可以利用反射机制在Java程序中,动态的去调用一些protected甚至是private的方法或类,这样可以很大程度上满足我们的一些比较特殊需求。例如Activity的启动过程中Activity的对象的创建。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//省略
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
}
//上面代码可知Activity在创建对象的时候调用了mInstrumentation.newActivity();
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
//这里的className就是在manifest中注册的Activity name.
return (Activity)cl.loadClass(className).newInstance();
}
//Activity对象的创建是通过反射完成的。java程序可以动态加载类定义,而这个动态加载的机制就是通过ClassLoader来实现的.
Java的每个.class文件里承载了类的所有信息内容,包含父类、接口、属性、构造、方法等;这些class文件会在程序运行状态下通过ClassLoad类机制加载到虚拟机内存中并产生一个对象实例,然后这个对象就可以对这块内存地址进行引用;一般我们通过New一个对象创建就行,而利用反射我们通过JVM查找并指定加载的类名,就可以创建对象实例,而在java中,类的对象被实例化可以在内存中被引用时,就可以获取该类的所有信息。可以说反射是所有插件化框架的基础。
//反射获取类的实例化对象
Class<?> cls=Class.forName("com.fanshe.Person"); //forName(包名.类名)
Person p=(Person)cls.newInstance();
//或
Person p = new Person();
Class<?> cls=p.getClass();
Person p2=(Person)cls.newInstance();
//或
Class<?> cls=Person.Class();
Person p=(Person)cls.newInstance();
//反射获取类的属性的值
Field getField = cls.getDeclaredField(fieldName);//传入属性名
Object getValue =getField.get(newclass);//传入实例值
//静态属性不用借助实例
Field getField = cls.getDeclaredField(fieldName);
Object getValue =getField.get(null);
反射在插件化、热修复、Retrofit等等几乎所有的常见框架中均有使用。
三、JVM DVM ART的区别
这道题想考察什么?
Android虚拟机与Java虚拟机的差异
考察的知识点
- JVM虚拟的基本知识
- DVM虚拟机的基本知识
- ART虚拟机的基本知识
考生应该如何回答
JVM
JVM是基于栈的虚拟机,对于基于栈的虚拟机来说,每一个运行时的线程,都有一个独立的栈。栈中记录了方法调用的历史,每有一次方法调用,栈中便会多一个栈桢。最顶部的栈桢称作当前栈桢,其代表着当前执行的方法。基于栈的虚拟机通过操作数栈进行所有操作。
在JVM中执行字节码,将
int a = 1;
int b = 2;
int c = a + b;
编译为字节码,得到的指令为:
ICONST_1 #将int类型常量1压入操作数栈
ISTORE 0 #将栈顶int类型值存入局部变量0
ICONST_2
ISTORE 1
ILOAD 0 #从局部变量表加载0:int类型数据
ILOAD 1
IADD #执行int类型加法
ISTORE 2
数据会不断在操作数栈与局部变量表之间移动。
Dalvik
Dalvik虚拟机( Dalvik Virtual Machine ),简称Dalvik VM或者DVM。DVM是Google专门为Android平台开发的虚拟机,它运行在Android运行时库中。
与JVM区别
基于的架构不同
DVM是基于寄存器的虚拟机,并没有JVM栈帧中的操作数栈,但是有很多虚拟寄存器。其实和操作数栈相同,这些寄存器也存放在运行时栈中,本质上就是一个数组。与JVM相似,在Dalvik VM中每个线程都有自己的PC和调用栈,方法调用的活动记录以帧为单位保存在调用栈上。
与JVM版相比,Dalvik无需从栈中读写数据,所需的指令更少,数据也不再需要频繁的移动。
#int a = 1
const/4 v0, #int 1 // #1
#int b = 2
const/4 v1, #int 2 // #2
#int c = a + b
add-int v2, v0, v1
执行的字节码不同
- 在Java中,Java类会被编译成一个或多个.class文件,打包成jar文件,而后JVM会通过相应的.class文件和jar文件获取相应的字节码。
- DVM会用dx工具将所有的.class文件或者jar文件转换为一个.dex文件,然后DVM会从该.dex文件读取指令和数据。
.jar文件在中包含多个.class文件,每个.class文件里面包含了该类的常量池、类信息、属性等等。当JVM加载该.jar文件的时候,会加载里面的所有的.class文件,JVM的这种加载方式很慢,对于内存有限的移动设备并不合适。
而.dex文件中也包含很多类信息,但是dex将多个类信息整合在一起了,多个类复用常量池等区域。
ART
ART(Android Runtime)是Android 4.4发布的,用来替换Dalvik虚拟,Android 4.4默认采用的还是DVM,系统会提供一个选项来开启ART。在Android 5.0时,默认采用ART。
与DVM的区别
Dalvik下应用在安装的过程,会执行一次优化,将dex字节码进行优化生成odex文件。
ART能够兼容Dalvik中的字节码执行。但是ART 引入了预先编译机制(Ahead Of Time),在Android4.4到Android 7.0以下的设备中安装应用时,ART 会使用 dex2oat 程序编译应用,将应用中dex编译成本地机器码。但是此过程会导致应用安装变慢。
因此,从Android N(Android 7.0)开始,ART 混合使用AOT、JIT与解释执行:
1、最初安装应用时不进行任何 AOT 编译(避免安装过慢),运行过程中解释执行,对经常执行的方法进行JIT,经过 JIT 编译的方法将会记录到Profile配置文件中。
2、当设备闲置和充电时,编译守护进程会运行,根据Profile文件对常用代码进行 AOT 编译。待下次运行时直接使用。
四、泛型是什么,泛型擦除呢?
这道题想考察什么?
泛型
考察的知识点
泛型的特点和优缺点以及泛型擦除
考生应该如何回答
泛型就是一种就是一种不确定的数据类型。在Java中有着重要的地位,在面向对象编程及各种设计模式中都有非常广泛的应用。
泛型的优点
我们为什么需要使用泛型:
- 适用于多种数据类型执行相同的代码,例如两个数据相加:
public int addInt(int x,int y){
return x+y;
}
public float addFloat(float x,float y){
return x+y;
}
不同的类型,我们就需要增加不同的方法,但是使用泛型那我们的代表将变为:
public <T> T addInt(T x,T y){
return x+y;
}
- 编译检查,例如下面代码
List<String> list = new ArrayList();
list.add(10);//①
list.add("享学");
String name = list.get(2);//②
因为我们指定了List泛型类型为String,因此在代码1处编译时会报错。而在代码2处,不再需要做类型强转。
泛型的缺点
- 静态域或者方法里不能引用泛型变量,因为泛型是在new对象的时候才知道,而类的构造方法是在静态变量之后执行。
- 不能捕获泛型类对象
泛型擦除
Jdk中实现的泛型实际上是伪泛型,例如泛型类 Fruit<T> ,编译时 T 会被擦除,成为 Object。但是泛型擦除会带来一个复杂的问题:
桥方法
有如下代码:
public class Parent<T> {
public void setSrc(T src){
}
}
public class Child extends Parent<String>{
@Override
public void setSrc(String src) {
super.setSrc(src);
}
}
Parent类是一个泛型类,在经过编译时泛型擦除后其中setSrc(T)
将会变为setSrc(Object)
;而Child类继承与Parent并且指定了泛型类型为String。那么经过编译后这两个类应该变为:
public class Parent {
public void setSrc(Object src){
}
}
public class Child extends Parent{
@Override
public void setSrc(String src) {
super.setSrc(src);
}
}
父类存在setSrc(Object)
,而子类则是setSrc(String)
。这明显是两个不同的方法,按照Java的重写规则,子类并没有重写父类的方法,而是重载。
所以实际上子类中存在两个setSrc
方法。一个自己的,一个是继承自父类的:
public void setSrc(String src)
public void setSrc(Object src)
那么当我们:
Parent o = new Child();
o.setSrc("1");
此时o实际类型是Child,静态类型是Parent。按照Java规则,会调用父类中的setSrc(Object)
,如:
public class A{
public void setValue(Object value){
System.out.println("Object");
}
}
public class B extends A{
public void setValue(String value){
System.out.println("String");
}
}
public static void main(String[] args) {
A a = new B();
a.setValue("1");
a.setValue(11);
}
上诉代码会输出两次”Object“。然而在泛型中却不符合此规则,因为 Java 编译器帮我们处理了这种情况,在泛型中引入了"Bridge Method"—桥方法。通过查看Child.class的字节码文件 :
public void setSrc(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: invokespecial #2 // Method Parent.setSrc:(Ljava/lang/Object;)V
5: return
LineNumberTable:
line 4: 0
line 6: 5
public void setSrc(java.lang.Object);
descriptor: (Ljava/lang/Object;)V
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: checkcast #3 // class java/lang/String
5: invokevirtual #4 // Method setSrc:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 1: 0
}
可以看到 Child 类中有两个 setSrc
方法,一个参数为 String 类型,一个参数为 Object 类型,参数为 Object 类型。而参数为Object的setSrc
方法可以在flags中看到ACC_BRIDGE和ACC_SYNTHETIC 。其中ACC_BRIDGE用于说明这个方法是由编译生成的桥接方法,ACC_SYNTHETIC说明这个方法是由编译器生成,并且不会在源代码中出现。
在setSrc(Object)
桥方法可以看到实际上会使用checkcast
先进行类型转换检查,然后执行invokevirtual
调用setSrc(String)
方法,这样就避免了我们还能调用到父类的方法。
最后
我整理了一套Android面试题合集,也包括以上面试题,有需要的小伙伴,可以点击下方课程链接详细了解!!!
https://edu.51cto.com/course/32703.html