安卓面试题一:java基础

安卓面试题一:java基础

提出问题:

  • 简单描述什么是面向对象。
  • 什么是多态?多态的实现机制是什么
  • 接口(Interface)和抽象类(Abstract Class)的区别
  • 重写(Override)和重载(Overload)的区别
  • 什么是内部类?静态内部类、匿名内部类以及局部内部类的区别和作用
  • ==、equals()、hashCode的区别
  • 简述八大基本数据类型。简述Integer和int的区别
  • 简述对string的理解以及string为什么被设计为final的
  • final,finally,finalize的区别
  • static关键字的理解
  • 列举java中的集合以及集合间的继承关系
  • list、set、map的区别
  • ArrayList、LinkedList的区别
  • HashMap、HashTable、ConcurrentHashmap的区别
  • HashMap和HashSet怎么判断集合元素重复
  • 什么是序列化,怎样实现?有哪些方式
  • 简述对反射的理解
  • 简述对泛型的理解,泛型中extends和super的区别
  • 简述对注解和依赖注入的理解
  • 简述java的异常体系
  • GC的工作简述
  • JVM的基本构成
  • 类的加载,什么是双亲机制

解决问题

  • 面向对象是指将功能等通过对象的来实现。将功能、属性等封装在对象中,让对象去实现具体的细节。这种思想是将数据作为第一位的,而方法或者说算法作为其次,是一种对数据的优化,方便操作。
    面向对象的三大特征:封装性、继承性、多态性
    封装是指将对象的属性和实现的细节进行隐藏,仅对面提供公用的访问方式,隔离了具体的细节变化,提高了复用性和安全性
    继承是指两种事务之间存在一定的所属关系,那么继承的类可以从被继承的类中获取一些属性和方法,提高了程序的复用性。
    多态:继承是多态的前提,多态是指父类或者接口的引用指向了子类对象,提高了代码的扩展性。

  • java提供了编译时多态和运行时多态两种多态机制,前者通过方法的重载实现,后者通过方法的重写覆盖实现。子类可以覆盖父类的方法,因此相同方法会在子类和父类中有不同的表现。
    在java语言中,基类的引用变量不仅可以指向基类的实例对象,也可以指向子类的实例对象。同样,接口的引用变量也可以指向实现类的实例对象,而程序调用的方法在运行时才进行绑定,绑定的是引用变量所指向的具体实例对象的方法,而不是引用变量的类型中定义的方法,通过这种动态绑定实现多态机制。

  • 抽象类和接口的特性:
    抽象类是用来捕捉子类共同特性的,它不能被实例化,只能作为子类的超类。抽象类是被用来创建继承层级里子类的模板
    接口是抽象方法的集合,如果一个类实现了某个接口,那么它就继承了这个接口的抽象方法。这就像契约模式,如果实现了这个接口,那就必须确保使用这个接口中的方法,接口只是一种形式或者说规范,自身不做任何事。
    区别:
    抽象类和接口的区别

  • 重载(Overload)和重写(Override)是java多态的不同表现。重写是父子类之间多态的一种表现,重载可以理解为多态的具体表现形式
    (1)方法的重载是在一个类中定义多个方法名相同,参数的数量或者类型或者顺序不同,称为方法的重载
    (2)方法的重写是子类中存在方法和父类的方法名相同,参数,返回值也相同的方法,具体的实现细节依据子类个性进行实现,称为方法的重写
    (3)方法的重载是一个类的多态表现,重写是父子类的多态的表现
    注意细节:方法的重写中,子类的方法权限修饰符不能比父类的低,父类为public,子类也只能为public。final修饰的方法无法被子类重写,static修饰的也不能被重写,但可以再次声明。子类重写方法中,可以用super关键字去调用父类的这个方法。
    方法的重载中被重载的方法必须改变参数列表,可以改变访问修饰符。

  • java中静态方法和属性可以被继承,但是不会被重写而是被隐藏、
    静态属性和方法是属于类的,不需要继承也可以通过类名.方法名来调用。静态属性和方法都可以继承和隐藏而不能被重写,不可能实现父类引用静态方法指向子类的情况。子类在重新声明静态方法时不能用override来修饰,直接编译报错。只能是用谁的静态方法就用哪个类来引用,静态属性和方法属于类。
    图片名称

  • java中内部类的详细介绍:java内部类点击查看-

  • java中= =号在基本数据类型中是比较的双方的值是否相等,如果用来比较引用类型,比较的是内存的存放地址,确切的说是堆内存地址 equals如果没有重写过,在Object类中equals比较的其实还是==,也就是堆内存地址,一些引用类型进行了重写,String,Date,integer等进行了重写,如String的equals比较的是两个字符串字符是否相同。如果没有重写,则类的equals和==的对比结果是相同的

  • Java的八种基本数据类型
    java中4种整数类型:
    byte 8位 1字节,整数最小的单位 -128-127;short 16位 2字节, -32768 ~ 32767
    int 32位 4字节 -231-1~231(21亿) ; long 64位 8字节 -263-1~263
    两种浮点类型
    float 32位 ;double 64位
    布尔值 boolean true或者false 8位,1字节
    字符型 char 16位 2字节
    Integer是int的包装类,int是基本数据类型
    Integer的变量必须实例化,而int不需要,
    Integer实际是对象的引用,new一个Integer实际上是生成一个指针指向此对象,而int是直接存储这个数值,==判断是false。

  • java.lang.String类使用了final修饰,不可被继承。java中的所有字面值,即被双引号括起来的字符串,都是作为String类的实例存在的。String是常量,其对象一旦构造就不能被改变,任何修改字符串的方法其内部都是在创建一个新的Sring对象。java中的String在内存中采用unicode编码,任何一个字符(无论中英文)对应两个字节长度。
    常量池(constant pool)指的是在编译期间被确定,并保存在已编译的.class文件中的一些数据,它包括了类、方法、接口等中的常量,包括字符串常量。java为了提高性能,静态字符串在常量池中创建,并尽量使用同一个对象,重用静态字符串。对于重复出现的静态字符串,jvm会现在字符常量池中查找并返回。
    String类内部是一个不可变的char数组,如果需要进行字符拼接,一般采用StringBuilder或者StringBuffer两个包装类,StringBuilder、StringBuffer都是继承自AbstractStringBuilder,内部是可延展的char数组,初始长度为16。StringBuffer在字符拼接等重要方法上添加有synchronized关键字,所以StringBuffer是线程安全的,但是单线程下性能不如StringBuilder。

  • final是用来声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可被继承。
    finally是异常处理语句结构的一部分,表示总是执行,是对异常状况处理的最佳优化,一般用于在出现异常时也可关闭数据库连接等场景。
    finalize是Object类的一个方法,用于在GC垃圾回收对象前,做必要的对象清理工作。

  • java中static关键字解析

  • java集合中类的关系:
    (1)Collection接口是集合类的根接口,java中没有直接提供这个接口的实现类,但是通过被继承产生了List和Set两个接口。Set中不能包含重复数据,且数据是无序的,List中可以包含重复数据,数据是有序,可以按索引获取到。
    (2)Map是java.util中的另一个接口,它和Collection相互独立,但是都属于集合类中的一部分。Map包含了key-value键值对,键不可重复但是值可以重复。
    (3)Iterator是所有的集合类都实现了的接口,这个接口主要是集合用来遍历元素,它内部主要方法为:hasNext()方法是查看是否还有下一个元素;next()是直接返回下一个集合元素;remove()是用来删除当前的元素。
    详细的集合类的继承关系图

  • (1)List中元素有序,可重复,继承Collection,主要实现类为Arraylist、LinkedList、Vector
    (2)Set中元素无序,不可重复,继承Collection,主要实现类为HashSet、TreeSet、LinkedHashSet
    (3)Map中元素以键值对形式存在(key-value),key不可重复,value可以,主要实现类为:HashMap,TreeMap,HashTable、LinkedHashMap

  • ArrayList是基于数组的,在初始化时会生成空数组,它的元素按照添加顺序进行先后排序,可以用sort进行重新排序。
    LinkedList是基于双向链表的,每个节点维护一个prev和next指针,LinkedLst按照插入的先后顺序排序,无法进行重新排序。
    Vector的数据结构和ArrayList差不多,通过在关键方法加锁来保证多线程下数据安全,是线程并发时使用List集合的最佳选择。

  • 如果要查看HashMap源码解析可以点击这里
    (1)HashMap是非线程安全的,HashTable是线程安全的,HashMap效率要更高一些。
    (2)HashMap的key和value都允许有null值的存在,而HashTable不允许
    (3)HashTable 基于synchronized锁,读写只能单线程访问,阻塞保证读写一致,多线程高并发下阻塞严重、效率低下
    (4)ConcurrentHashMap高并发,高效率的,基于lock锁, concurrentLevel划分出了多个Segment来对key-value进行存储,默认16个线程无阻塞。缺点:有可能读写不一致,不一定能保证数据的完整性

  • HashMap和HashSet都是通过equals()方法和hashCode()方法来判断元素是否重复。区别在于HashMap元素重复会直接覆盖掉原来的数据,而HashSet只会返回false进行提示。
    HashMap的put方法中对key是否重复的判断代码如下:

public V put(K key, V value) {
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

很明显是先进行了hashCode值的判断,然后再进行相等或equals方法的比较。需要注意的是如果值重复,HashMap会先进行覆盖,然后将原来的值oldValue进行return返回,如果不重复就会添加然后返回值为null。
而HashSet则是利用HashMap的这一返回特性来进行判断,代码如下

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

    public HashSet() {
    map = new HashMap<E,Object>();
    }

    public boolean contains(Object o) {
    return map.containsKey(o);
    }

    public boolean add(E e) {
    return map.put(e, PRESENT)==null;
    }
}

可以看到HashSet的add方法实际调用了HashMap的put方法,将HashSet需要add的值当做Hashmap的key进行put,去判断key是否重复,如果重复返回的值为原来的值,这是add方法返回就为false,如果不重复,add返回true。

  • 序列化是指将java对象转化为二进制字节序列的技术;
    在android中序列化的主要作用为:1是数据的持久化存储,用对象处理流ObjectOutputStream来把这个类对象存储在本地(这个过程,就是序列化-持久化过程)。当你想使用这个已经被序列化-持久化的类对象时,用对象处理流(ObjectInputStream)取得类对象数据,加载到程序中。
    2是进程间对象传递。将对象进行序列化之后,通过binder或者intent进行传递。
    方式:java类需要实现Serializable接口,且要保证属性类型都是可以被序列化的,定义一个serialVersionUID 进行区分。
    实现Parcelable接口,并将接口中的方法按照固定的形式进行实现。
    两种方式的比较:
    序列化方式比较

  • java中的反射机制(有兴趣可以点击这里查看深入理解java反射

  • java泛型的基本使用:点击跳转

  • android中反射、注解、依赖注入的总结

  • java中异常类的继承关系:
    异常体系
    Throwable:有两个重要的子类:Exception(异常)和Error(错误),两者都包含了大量的异常处理类。

    1、Error(错误):是程序中无法处理的错误,表示运行应用程序中出现了严重的错误。此类错误一般表示代码运行时JVM出现问题。通常有Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如说当jvm耗完可用内存时,将出现OutOfMemoryError。此类错误发生时,JVM将终止线程。

    这些错误是不可查的,非代码性错误。因此,当此类错误发生时,应用不应该去处理此类错误。

    2、Exception(异常):程序本身可以捕获并且可以处理的异常。
    Exception这种异常又分为两类:运行时异常和编译异常。

    (1)运行时异常(不受检异常):RuntimeException类极其子类表示JVM在运行期间可能出现的错误。比如说试图使用空值对象的引用(NullPointerException)、数组下标越界(ArrayIndexOutBoundException)。此类异常属于不可查异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。

    (2)编译异常(受检异常):Exception中除RuntimeException极其子类之外的异常。如果程序中出现此类异常,比如说IOException,必须对该异常进行处理,否则编译不通过。在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。
    图片名称
    可查异常与不可查异常:java的所有异常可以分为可查异常(checked exception)和不可查异常(unchecked exception)。

    (1)可查异常:编译器要求必须处理的异常。正确的程序在运行过程中,经常容易出现的、符合预期的异常情况。一旦发生此类异常,就必须采用某种方式进行处理。除RuntimeException及其子类外,其他的Exception异常都属于可查异常。编译器会检查此类异常,也就是说当编译器检查到应用中的某处可能会此类异常时,将会提示你处理本异常——要么使用try-catch捕获,要么使用throws语句抛出,否则编译不通过。

    (2)不可查异常:编译器不会进行检查并且不要求必须处理的异常,也就说当程序中出现此类异常时,即使我们没有try-catch捕获它,也没有使用throws抛出该异常,编译也会正常通过。该类异常包括运行时异常(RuntimeException极其子类)和错误(Error)。
    详细资料可以点击: 深入理解java中异常体系

  • 垃圾回收或GC(Garbage Collection),是一种自动的存储管理机制,它是Java语言的一大特性,方便了我们这些程序员编码,把内存释放工作的压力都转让到了系统,故而是以消耗系统性能为代价的。
    垃圾回收机制存在的好处:可以让程序员专注于代码的逻辑处理,提高了编码效率;能够保护程序的完整性。
    存在的问题:系统层面负责垃圾回收,明显会加大系统资源的开销,影响系统性能;垃圾回收机制存在着不完备性,并不能百分百保证垃圾被回收掉。
    垃圾回收机制主要工作分为两步:1是对可回收对象的判断;2是通过某些算法进行垃圾回收。
    目前市面上存在的两种算法来判断对象:
    一种是引用计数算法,原理如下:
    (1)首先给每一个对象添加一个引用计数器,
    (2)当程序中某个地方引用该对象,这个技术值就加1,引用失效时就减1,
    (3)当某个对象的计数值为0时,表示该对象不可能被使用,可回收;7
    这种算法比较简单、高效、运行快,缺点是难以处理相互引用的垃圾对象。
    图片名称
    另一种是Root可达性分析算法,原理如下:
    (1)以被称为“GC ROOT”的对象作为起点向下搜索;
    (2)每走过一个对象,就生成一条引用链;
    (3)从根开始到搜索完,生成一颗引用树,那些到GC Root不可达的对象就是可回收的。
    能够作为GC Root的对象为:虚拟机栈中的对象,方法区静态属性引用的对象,方法区中常量引用的对象,本地方法栈中JNI引用的对象。
    java中GC就是采用的这种方式判断对象。关于对象可回收的判定,我们还需要注意的是,当系统开始启动GC的时候,为了保证引用链的状态不变,就需要停止该进程中所有的程序(Stop The World),我们Android中的现象就是UI卡顿了,但一般这样的卡顿时间是非常短的,当然,要是频繁的产生GC,那就另当别论了。还有值得注意的是,不可达对象也并非立即就被回收了,还需要经过两次的标记过程后才被会被真正回收。
    回收的算法这边就不展开讲述了,感兴趣的可以点击查看:Android性能调优篇之探索垃圾回收机制

  • JVM的基本构成:
    首先一张图看一下jvm的内部构成
    图片名称
    (1)程序计数器是一块比较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。每条线程都有一个独立的程序计数器,独立存储,互不影响,称为线程私有的内存。
    (2)java虚拟机栈和计数器都是线程私有的,它的生命周期和线程相同。虚拟机栈描述的是java方法执行的内存模型:每个方法在执行时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程
    人们经常区分的栈内存(Stack)就是虚拟机栈,或者说虚拟机栈中局部变量表部分。局部变量表存放了编译期可知的各种基本数据类型、对象引用和returnAddress类型。
    在虚拟机规范中,如果线程请求的栈深度大于虚拟机所允许的深度,将会抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,但是无法申请到足够的内存,就会抛出OutOfMemoryError异常。
    (3)本地方法栈与虚拟机栈所发挥的作用十分相似,它们之间的区别不过是虚拟机执行java方法服务,而本地方法栈则为虚拟机使用到的navtive方法服务。
    (4)java堆(Heap)是java虚拟机所管理的内存中最大的一块。java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。java堆是垃圾收集器管理的主要区域,因此也被称作GC堆。
    (5)方法区和java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据局。常量池也属于方法区。
    图片名称

  • 类的加载: JAVA虚拟机类加载机制和双亲委派模型
    推荐大家去看《深入理解JAVA虚拟机》这本书,更加深入。

猜你喜欢

转载自blog.csdn.net/m0_37194191/article/details/87785045