天天看点

Android高级工程师面试题整理——java面试题

 相信每次面试之前,大家都会大量刷一下面试题来应该对各种公司的面试吧,下面整理一下android方面的面试题分享给大家。

本文主要分为以下几部分:

  • java面试题
  • Android面试题
  • 高级开发技术面试题
  • 跨平台Hybrid 开发

1、Java中equals、hascode和==的区别

  ==(双等号):对于基本数据类型(byte、short、char、int、long、float、double、boolean)来说,比较的是他们的值;对于引用类型(各种new出来的对象)来说比较的是他们的地址。

  equals:默认情况(没有覆盖equals方法)下equals方法都是调用Object类的equals方法,而Object的equals方法是判断对象的内存地址。一般地都是覆盖这个方法,根据对象的内容是否相等来判断对象是否相等。如:String类就是覆盖这个方法,先判断对象的内存地址是否相等,之后再判断内容是否相等。

  hascode:hascode()方法返回的就是一个数值,其目的是生成一个hash码,hash码的主要用途就是在对对象进行散列的时候作为key输入,我们需要每个对象的hash码尽可能不同,这样才能保证散列的存取性能。

  在java中一般的equals()和hascode()通常要遵从如下几点约定,这样才能保证这两个方法在java中的一个规范,这也是为什么重写equals()方法需要重写hascode()方法的原因:

  1. 如果两个对象equals,java运行时环境会认为他们的hashcode一定相等;
  2. 如果两个对象不equals,他们的hashcode有可能相等。
  3. 如果两个对象hashcode相等,他们不一定hashcode。
  4. 如果两个对象hashcode不相等,他们一定不equals。

2、int、char、long各占多少字节数

  int占4个字节、char占2个字节、long占8个字节。

3、int与integer的区别

  int是基本数据类型,直接存数据。而integer是对象,用一个引用指向这个对象,是int的封装类。

4、谈谈对java多态的理解

  多态是指:父类引用指向子类对象,在执行期间会根据引用对象的实际类型,调用具体对象的相应方法。

  一般可以通过两种方式实现多态:重写、重载。

  实现多态的三要素:集成、重写、父类引用指向子类对象。

5、String、StringBuffer、StringBuilder区别

 String:

  1. 是字符串常量,一旦创建就不能修改。对于已经存在了的String对象的修改都是重新创建一个新的对象,然后把新的值保存进去。
  2. String是final类,不能被继承。
  3. String覆盖了equals方法和hashCode方法。

 StringBuffer:

  1. 是字符串可变对象,可以对字符串进行操作,修改字符串是不会新建一个对象。
  2. 执行效率较慢,但是线程安全。
  3. StringBuffer没有覆盖了equals方法和hashCode方法。

 StringBuilder:

  1. 也是字符串可变对象,同StringBuffer一样,可以对字符串进行操作,也 不会新建对象。
  2. 执行效率高,但是线程不安全。

6、什么是内部类?内部类的作用

 什么是内部类:

  将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。

 作用:

  1. 解决多继承问题。在java中一个类只能单继承,而内类提供了一种多继承的解决方案。
  2. 方便将存在一定逻辑关系的组织在一起,又可以对外界隐藏。
  3. 方便编写事件驱动程序。
  4. 方便编写线程代码。

7、抽象类和接口的区别

  1. 抽象类要被类继承,接口要被类实现;
  2. 接口只能做方法的声明,抽象类中可以作方法的声明,也可以作方法的实现;
  3. 接口里定义的变量只能是公共的静态常量,抽象类中的变量可以是普通变量;
  4. 接口是设计的结果,抽象类是重构的结果;
  5. 抽象类和接口都是用来抽象具体对象的,但是接口的抽象级别最高;
  6. 抽象类可以有具体的方法和属性,接口只能有抽象方法和常量;
  7. 抽象类主要用来抽象类别,接口主要用来抽象功能;

8、抽象类的意义

  1. 为子类提供一个公共的类型;
  2. 封装子类中重复内容(成员变量和方法);
  3. 定义抽象方法,子类虽然有不同的实现,但改方法的定义是一致的。

9、抽象类与接口的应用场景

 接口的应用场景:

  1. 类与类之间需要特定的接口进行协调,而不在乎如何实现; 
  2. 作为能够实现特定功能的标识存在,也可以是什么接口方法都没有的纯粹标识。
  3. 需要将一组类视为单一的类,而调用者只通过接口来与这组类发生联系;
  4. 需要实现特定的多项功能,而这些功能之间可能完全没有任何联系;

 抽象类的应用场景:

  1. 定义了一组接口,但又不想强迫每个实现类都必须实现所有的接口,可以用abstract定义一组方法,甚至可以是空方法体,然后有子类选择自己所感兴趣的方法来覆盖;
  2. 某些场合下,只靠纯粹的接口不能满足类与类之间的协调,还必须类中表示状态的变量来区别不同的关系。Abstract的中介作用可以很好地满足这一点;
  3. 规范了一组相互协调的方法,其中一些方法是共同的,与状态无关的,可以共享的,无需子类分别实现;而另一些方法却需要各个子类根据自己特定的状态来实现特定的功能;

10、接口的意义

  1. 重要性:在java中,abstract class 和interface 是支持抽象类定义的两种机制,接口是特殊的抽象类,在java中一个类只能继承一个父类却可以实现多个接口。正是由于这两种机制的存在,才赋予了java强大的面向对象能力。java的特性封装、继承、多态。在java中有两种形式可以实现多态:继承和接口;
  2. 简单、规范性:如果一个项目比较庞大,那么就需要一个能理清所有业务的架构师来定义一些主要的接口,这些接口不仅告诉开发人员你需要实现哪些业务,而且也将命名规范限制住了(防止一些开发人员随便命名导致别的程序员无法看明白);
  3. 维护、拓展性:由于接口只做定义,而不关注实现,在某些功能模块中,可以通过接口去应用实现类,这能很好的降低功能模块之间的耦合性,从而修改某些模块的实现,不会影响到其他模块,方便后期的维护、拓展。
  4. 安全、严密性:接口是实现软件松耦合的重要手段,它描述了系统对外的所有服务,而不涉及任何具体的实现细节。这样就比较安全、严密一些。

11、泛型中extends和super的区别

  泛型中extends主要作用是类型的设定上界通配符,<? extends T>对应的为T及其子类对象;

  泛型中super与extends是完全相反的,其定义是下界通配符,<? super T>对应的为T及其子类对象;

12、父类的静态方法能否被子类重写

  不能。父类的静态方法能够被子类继承,但是不能够被子类重写,即使子类中的静态方法与父类中的静态方法完全一样,也是两个完全不同的方法。对于静态方法和静态变量来说,虽然在代码中可以通过子类对象来调用,当时在编译的时候就将其与类绑定在一起,既然它们在编译的时候就决定了调用的方法、变量,那就和重写没有关系了。重写指的是根据运行时对象的类型来决定调用哪个方法,而不是根据编译时的类型。

13、进程和线程的区别

  • 进程是资源分配的最小单位,线程是资源调度的最小单位(程序执行的最小单位);
  • 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
  • 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。
  • 但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

14、final,finally,finalize的区别

final:

  在java中,final可以用来修饰类,方法和变量(成员变量或局部变量)。

  • 修饰类:当用final修饰类的时,表明该类不能被其他类所继承;
  • 修饰方法:即此方法不能被重写;
  • 修饰变量:表示常量,只能被赋值一次,赋值后其值不再改变;

finally:

  finally作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块,表示这段语句最终一定会被执行(不管有没有抛出异常,哪怕有return语句,都会执行),经常被用在需要释放资源的情况下。

finalize

  finalize()是在java.lang.Object里定义的,也就是说每一个对象都有这么个方法。这个方法在gc启动,该对象被回收的时候被调用。其实gc可以回收大部分的对象(凡是new出来的对象,gc都能搞定,一般情况下我们又不会用new以外的方式去创建对象),所以一般是不需要程序员去实现finalize的。

  特殊情况下,需要程序员实现finalize,当对象被回收的时候释放一些资源,比如:一个socket链接,在对象初始化时创建,整个生命周期内有效,那么就需要实现finalize,关闭这个链接。

  一个对象的finalize()方法只会被调用一次,而且finalize()被调用不意味着gc会立即回收该对象,所以有可能调用finalize()后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会调用finalize(),产生问题。 所以,推荐不要使用finalize()方法,它跟析构函数不一样。

15、堆与栈内存的区别

  堆:是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。

  栈:是个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是 thread safe的。操作系统在切换线程的时候会自动的切换栈,就是切换 SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。

16、序列化的方式

  1. 实现Serializable接口(隐式序列化):通过实现Serializable接口,这种是隐式序列化(不需要手动),这种是最简单的序列化方式,会自动序列化所有非static和 transient关键字修饰的成员变量。
  2. 实现Externalizable接口(显式序列化):Externalizable接口继承自Serializable, 我们在实现该接口时,必须实现writeExternal()和readExternal()方法,而且只能通过手动进行序列化,并且两个方法是自动调用的,因此,这个序列化过程是可控的,可以自己选择哪些部分序列化。
  3. 实现Serializable接口+添加writeObject()和readObject()方法。(显+隐序列化):先实现Serializable接口,并且添加writeObject()和readObject()方法。注意这里是添加,不是重写或者覆盖。但是添加的这两个方法必须有相应的格式。
  • 方法必须要被private修饰; ----->才能被调用
  • 第一行调用默认的defaultRead/WriteObject(); ----->隐式序列化非static和transient
  • 调用read/writeObject()将获得的值赋给相应的值; —>显式序列化

17、Serializable 和Parcelable 的区别

1、平台区别

  • Serializable是属于 Java 自带的,表示一个对象可以转换成可存储或者可传输的状态,序列化后的对象可以在网络上进行传输,也可以存储到本地。
  • Parcelable 是属于 Android 专用。不过不同于Serializable,Parcelable实现的原理是将一个完整的对象进行分解。而分解后的每一部分都是Intent所支持的数据类型。

2、编写上的区别

  • Serializable代码量少,写起来方便;
  • Parcelable代码多一些,略复杂;

3、选择的原则

  • 如果是仅仅在内存中使用,比如activity、service之间进行对象的传递,强烈推荐使用Parcelable,因为Parcelable比Serializable性能高很多。因为Serializable在序列化的时候会产生大量的临时变量, 从而引起频繁的GC。
  • 如果是持久化操作,推荐Serializable,虽然Serializable效率比较低,但是还是要选择它,因为在外界有变化的情况下,Parcelable不能很好的保存数据的持续性。

4、本质的区别

  • Serializable的本质是使用了反射,序列化的过程比较慢,这种机制在序列化的时候会创建很多临时的对象,比引起频繁的GC;
  • Parcelable方式的本质是将一个完整的对象进行分解,而分解后的每一部分都是Intent所支持的类型,这样就实现了传递对象的功能了。

18、静态属性和静态方法是否可以被继承?是否可以被重写?以及原因?

  可以继承、不能重载;

原因:

  因为静态属性、方法从程序开始运行后就已经分配了内存。所有通过对象来访问该属性、方法的方式,都会在编译的时候就将其与类绑定在一起,就决定了调用的方法、变量,对象无关。

19、静态内部类的设计意图

20、成员内部类、静态内部类、局部内部类和匿名内部类的理解,以及项目中的应用

21、谈谈对kotlin的理解

22、闭包和局部内部类的区别

  闭包:是能获取其他函数内部变量的函数。是一种能被调用的对象,它保存了创建它的作用域的信息。JAVA并不能显式地支持闭包,但是在JAVA中,闭包可以通过“接口+内部类”来实现。

  局部内部类:局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。

23、string 转换成 integer的方式及原理

  integer.parseInt(string str)方法调用Integer内部的parseInt(string str,10)方法,默认基数为10,parseInt内部首先判断字符串是否包含符号(-或者+),则对相应的negative和limit进行赋值,然后再循环字符串,对单个char进行数值计算Character.digit(char ch, int radix)在这个方法中,函数肯定进入到0-9字符的判断(相对于string转换到int),否则会抛出异常,数字就是如上面进行拼接然后生成的int类型数值。

24、哪些情况下的对象会被垃圾回收机制处理掉?

  1. 没有引用指向;
  2. 只有弱引用指向并且不回收弱引用对象的话存储区无空间;
  3. 虚引用指向的对象;
  4. 不能通过GC Roots寻找到(Java虚拟机采用可达性原理回收时),以下几点对象可以被看做是GC Roots:

      ● 虚拟机栈(栈桢中的本地变量表)中的引用的对象;

      ● 方法区中的类静态属性引用的对象;

      ● 方法区中的常量引用的对象;

      ● 本地方法栈中JNI(Native方法)的引用的对象;

25、讲一下常见编码方式?

  常见的一些字符编码方式无非有:ASCII、拓展ASCII编码、Unicode编码集、GBK/GB2312/GB18030、UTF-8;

  • ASCII编码:用来表示英文,它使用1个字节表示,其中第一位规定为0,其他7位存储数据,一共可以表示128个字符。
  • 拓展ASCII编码:用于表示更多的欧洲文字,用8个位存储数据,一共可以表示256个字符GBK/GB2312/GB18030:表示汉字。GBK/GB2312表示简体中文,GB18030表示繁体中文。
  • Unicode编码:包含世界上所有的字符,是一个字符集。
  • UTF-8:是Unicode字符的实现方式之一,它使用1-4个字符表示一个符号,根据不同的符号而变化字节长度。

26、utf-8编码中的中文占几个字节;int型几个字节?

  UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。UTF-8的编码规则很简单,只有两条:

  1. 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
  2. 对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。

  少数是汉字每个占用3个字节,多数占用4个字节。int类型在java中占4个字节。

27、静态代理和动态代理的区别,什么场景使用?

28、Java的异常体系

29、谈谈你对解析与分派的认识。

30、修改对象A的equals方法的签名,那么使用HashMap存放这个对象实例的时候,会调用哪个equals方法?

31、Java中实现多态的机制是什么?

  java中实现多态的机制是依靠父类或接口的引用指向子类。从而实现了一个对象多种形态的特性。其中父类的引用是在程序运行时动态的指向具体的实例,调用该引用的方法时,不是根据引用变量的类型中定义的方法来运行,而是根据具体的实例的方法。

32、如何将一个Java对象序列化到文件里?

33、说说你对Java反射的理解

34、说说你对Java注解的理解

35、说说你对依赖注入的理解

  依赖注入也叫依赖倒转原则,是java设计理论中一条非常著名的原则。其核心思想就是要将这种具体类之间的依赖,尽量转换成抽象依赖,也就是说类A应该依赖于抽象类IB,而不是具体的类B。这个注入的过程,通常是由一个控制程序来完成的,无需对象去关心,举例如下:

Public Person{
	private ICar car;
	public Person(ICar onecar){
  		car=onecar;
	}
	public void drive(){
  		car.挂档;
  		car.踩油门;
 		 car.打方向;
	}
}
           

这个时候,进行注入并且调用的过程,就很简单了,如下:

Car car=new Car ();
Person boy=new Person(car);
boy.drive();
           

36、说一下泛型原理,并举例说明

  其实Java中的泛型是伪泛型,在编译期间,所有的泛型信息都会被擦除掉。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的List和List等类型,在编译后都会编程List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。

可以通过两个简单的例子,来证明java泛型的类型擦除:

ArrayList<String> array1=new ArrayList<String>();  
  array1.add("aaaa");  
  ArrayList<Integer> array2=new ArrayList<Integer>();  
  array2.add(10);  
  System.out.println(array1.getClass()==array2.getClass());  
           

  我们通过array1对象和array2对象的getClass方法获取它们的类的信息,最后发现结果为true。说明泛型类型String和Integer都被擦除掉了,只剩下了原始类型。

ArrayList<Integer> arrayList3=new ArrayList<Integer>();  
  arrayList3.add(1);//这样调用add方法只能存储整形,因为泛型类型的实例为Integer  
  arrayList3.getClass().getMethod("add", Object.class).invoke(arrayList3, "asd");  
  for (int i=0;i<arrayList3.size();i++) {  
      System.out.println(arrayList3.get(i));  
  }  
           

  当我们利用反射调用add方法的时候,却可以存储字符串。这说明了Integer泛型实例在编译之后被擦除了,只保留了原始类型。

37、Java中String的了解

38、String为什么要设计成不可变的?

  1. 字符串常量池的需要

      字符串常量池(String pool, String intern pool, String保留池) 是Java堆内存中一个特殊的存储区域, 当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。 假若字符串对象允许改变,那么将会导致各种逻辑错误,比如改变一个对象会影响到另一个独立对象。严格来说,这种常量池的思想,是一种优化手段。

  2. 允许String对象缓存HashCode

      Java中String对象的哈希码被频繁地使用, 比如在hashMap 等容器中。字符串不变性保证了hash码的唯一性,因此可以放心地进行缓存.这也是一种性能优化手段,意味着不必每次都去计算新的哈希码。

  3. 安全性

      String被许多的Java类(库)用来当做参数,例如 网络连接地址URL,文件路径path,还有反射机制所需要的String参数等, 假若String不是固定不变的,将会引起各种安全隐患。

39、Object类的equal和hashCode方法重写,为什么?

  因为当把Object对象放到集合中时,通过equals比较对象,不做处理还是会出现重复的问题,根据hash原则,对象的映射地址是根据算法生成,因为hash碰撞的存在,即两个不同的对象hash地址可能一样的情况,这样在hash地址相等的情况下还需要去重写equal方法进行比较。有两种办法可以解决这个问题,第一个就是重写Object类的equal和hashCode方法;第二个就是把对象转换成String再放入集合中,因为String类源码已经重写了这两个方法。

继续阅读