一、Java异常机制中,异常Exception与错误Error区别

这道题想考察什么?

在开发时需要时候需要自定义异常时,应该选择定义Excption还是Error?编写的代码触发Excption或者Error分别代表什么?

考察的知识点

Java异常机制

考生应该如何回答

在Java中存在一个Throwable可抛出类,Throwable有两个重要的子类,一个是Error,另一个则是Exception。

【金九银十面试冲刺】Android岗面试题每日分享——Java篇_面试

Error是程序不能处理的错误,表示程序中较严重问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError等等。这些错误发生时,JVM一般会选择线程终止。这些错误是不可查的,它们在程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。

Exception是程序可以处理的异常。而Exception又分为运行时异常(RuntimeException)与非运行时异常。

  • 运行异常
    运行时异常,又称不受检查异常 。所谓的不受检查,指的是Java编译检查时不会告诉我们有这个异常,需要在运行时候才会暴露出来,比如下标越界,空指针异常等。
  • 非运行时异常
    RuntimeException之外的异常我们统称为非运行时异常,比如IOException、SQLException,是必须进行处理的异常(检查异常) ,如果不处理(throw到上层或者try-catch),程序就不能编译通过 。

二、说说反射的应用场景,哪些框架?

这道题想考察什么?

是否了解反射相关的理论知识

考察的知识点

  1. 反射机制
  2. 反射在框架中的应用
  3. 获取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虚拟机的差异

考察的知识点

  1. JVM虚拟的基本知识
  2. DVM虚拟机的基本知识
  3. ART虚拟机的基本知识

考生应该如何回答

JVM

JVM是基于栈的虚拟机,对于基于栈的虚拟机来说,每一个运行时的线程,都有一个独立的栈。栈中记录了方法调用的历史,每有一次方法调用,栈中便会多一个栈桢。最顶部的栈桢称作当前栈桢,其代表着当前执行的方法。基于栈的虚拟机通过操作数栈进行所有操作。

【金九银十面试冲刺】Android岗面试题每日分享——Java篇_面试_02

在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文件读取指令和数据。

【金九银十面试冲刺】Android岗面试题每日分享——Java篇_泛型_03

.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中有着重要的地位,在面向对象编程及各种设计模式中都有非常广泛的应用。

泛型的优点

我们为什么需要使用泛型:

  1. 适用于多种数据类型执行相同的代码,例如两个数据相加:
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;
}
  1. 编译检查,例如下面代码
List<String> list = new ArrayList();
list.add(10);//①
list.add("享学");
String name = list.get(2);//②

因为我们指定了List泛型类型为String,因此在代码1处编译时会报错。而在代码2处,不再需要做类型强转。

泛型的缺点

  1. 静态域或者方法里不能引用泛型变量,因为泛型是在new对象的时候才知道,而类的构造方法是在静态变量之后执行。
  2. 不能捕获泛型类对象

泛型擦除

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_BRIDGEACC_SYNTHETIC 。其中ACC_BRIDGE用于说明这个方法是由编译生成的桥接方法,ACC_SYNTHETIC说明这个方法是由编译器生成,并且不会在源代码中出现。

setSrc(Object)桥方法可以看到实际上会使用checkcast先进行类型转换检查,然后执行invokevirtual调用setSrc(String)方法,这样就避免了我们还能调用到父类的方法。

最后

我整理了一套Android面试题合集,也包括以上面试题,有需要的小伙伴,可以点击下方课程链接详细了解!!!

https://edu.51cto.com/course/32703.html

【金九银十面试冲刺】Android岗面试题每日分享——Java篇_面试_04