Java面试核心基础知识之Java基础

Java基础

`
针对常用的Java基础知识展开详细的介绍,具体包含Java的集合、异常分类及处理、反射机制、注解、内部类、泛型、序列化


集合

  • Java基础
  • 一、集合
    • 1、List:可重复
    • 2、Queue
    • 3、Set:不可重复
    • 4、Map
  • 二、异常分类及处理
    • 1、Java 语言如何进行异常处理?
    • 2、Java异常中关键字:throws、throw、try、catch、finally 分别如何使用?
    • 3、请写出几种常见到的运行时异常?
    • 4、error 和 exception 有什么区别?、
    • 5、异常处理完成以后,Exception对象会发生什么变化?
    • 6、try{} 里有一个 return 语句,那么紧跟在这个 try 后的 finally{} 里的 code 会不会被执行,什么时候被执行,在 return 前还是后?
    • 7、finally 是不是一定会被执行到?
  • 总结

一、集合

Java 的集合类被定义在 Java.util 包中,主要有4种集合,分别为 List、Queue、Set和Map,每种集合的具体分类,如下图所示:
Java面试核心基础知识之Java基础_第1张图片

1、List:可重复

List 是非常常用的数据类型,是有序的Collection,一共有三个实现类,分别是ArrayList、 Vector 和 LinkedList。

ArrayList:基于数组实现,增删慢,查询快,线程不安全

  • ArrayList是使用最广泛的 List实现类,其内部数据结构基于数组实现,提供了对List的增加(add)、删除(remove)和访问(get)功能。
  • ArrayList 的缺点是元素必须连续存储,当需要在 ArrayList的中间位置插入或者删元素时,需要将待插入或者删除的节点后的所有元素进行移动,其修改代价较高,因此,ArrayList不适合随机插入和删除的操作,更适合随机查找和遍历的操作。
  • ArrayList 不需要在定义时指定数组的长度,在数组长度不能满足存储要求时,ArrayList会创建一个新的更大的数组并将数组中已有的数据复制到新的数组中。

Vector:基于数组实现,增删慢,查询快,线程安全

  • Vector 的数据结构和ArrayList一样,都是基于数组实现的,不同的是 Vector 支持线程同步,即同一时刻只允许一个线程对 Vector 进行写操作(新增、删除、修改),以保证多线程环境下数据的一致性,但需要频繁地对 Vector实例进行加锁和释放锁操作,因此,Vector 的读写效率在整体上比ArrayList低。

LinkedList:基于双向链表实现,增删快,查询慢,线程不安全

  • LinkedList 采用双向链表结构存储元素,在对LinkedList 进行插入和删除操作时,只需在对应的节点上插入或删除元素,并将上一个节点元素的下一个节点的指针指向该节点即可,数据改动较小,因此随机插入和删除效率很高。但在对 LinkedList 进行随机访问时,需要从链表头部一直遍历到该节点为止,因此随机访问速度很慢。除此之外,LinkedList 还提供了在List接口中未定义的方法,用于操作链表头部和尾部的元素,因此有时可以被当作堆栈和队列使用。

2、Queue

Queue 是队列结构,Java中的常用队列有

  • ArrayBlockingQueue:基于数组数据结构实现的有界阻塞队列。
  • LinkedBlockingQueue:基于链表数据结构实现的有界阻塞队列。
  • PriorityBlockingQueue:支持优先级排座的无界阻塞队列。
  • PriorityBlockingQueue:支持优先级排座的无界阻塞队列。
  • DelayQueue:支持延迟操作的无界阻塞队列。
  • SynchronousQueue:用于线程同步的阻塞队列。
  • LinkedTransferQueue:基于链表数据结构实现的无界阻塞队列。
  • LinkedBlockingDeque:基于链表数据结构实现的双向阻塞队列。

3、Set:不可重复

Set的核心特性是独一无二,适用于存储无序且值不相等的元素。对象的相等性在本质上是对象的HashCode 值相同,Java依据对象的内存地址计算出对象的 HashCode值。如果想要比较两个对象是否相等,则必须同时覆盖对象的hashCode 方法和equals 方法,并且hashCode方法和equals方法的返回值必须相同。

HashSet: HashMap 实现,无序

  • HashSet存放的是散列值,它是按照元素的散列值来存取元素的。元素的散列值是通过元素的 hashCode 方法计算得到的,HashSet首先判断两个元素的散列值是否相等,如果散列值相等,则接着通过 equals 方法比较,如果 equals 方法返回的结果也为 true,HashSet 就将其视为同一个元素;如果equals方法返回的结果为false,HashSet将其视为不同的元素。

TreeSet:二叉树实现

  • TreeSet 基于二叉树的原理对新添加的对象按照指定的顺序排序(升序、降序),每
    添加一个对象都会进行排序,并将对象插入二叉树指定的位置。
  • Integer和String 等基础对象类型可以直接根据TreeSet的默认排序进行存储,而自定义的数据类型必须实现 Comparable接口,并且覆写其中的compareTo函数才可以按照预定义的顺序存储。若覆写compare 函数,则在升序时在 this.对象小于指定对象的条件下返回-1,在降序时在this.对象大于指定对象的条件下返回1。

LinkHashSet:继承HashSet,HashMap实现数据存储,双向链表记录顺序

  • LinkedHashSet 在底层使用 LinkedHashMap存储元素,它继承了 HashSet,所有的方法和操作都与HashSet相同,因此 LinkedHashSet的实现比较简单,只提供了4个构造方法,并通过传递一个标识参数调用父类的构造器,在底层构造一个LinkedHashMap来记录数据访问,其他相关操作与父类HashSet相同,直接调用父类HashSet的方法即可。

4、Map

HashMap:数组+链表存储数据,线程不安全

  • HashMap 基于键的 HashCode 值唯一标识一条数据,同时基于键的 HashCode 值进行数据的存取,因此可以快速地更新和查询数据,但其每次遍历的顺序无法保证相同。HashMap 的key 和 value 允许为 null。
  • HashMap是非线程安全的,即在同一时刻有多个线程同时写HashMap时将可能导致数据的不一致。如果需要满足线程安全的条件,则可以用 Collections 的 synchronizedMap方法使 HashMap具有线程安全的能力,或者使用ConcurrentHashMap。
  • HashMap 的数据结构如下图所示,其内部是一个数组,数组中的每个元素都是一个单向链表,链表中的每个元素都是嵌套类Entry的实例,Entry实例包含4个属性:key、value、hash值和用于指向单向链表下一个元素的next。
    Java面试核心基础知识之Java基础_第2张图片

HashMap 常用的参数如下。

  • capacity:当前数组的容量,默认为 16,可以扩容,扩容后数组的大小为当前的
    两倍,因此该值始终为2的n次方。
  • loadFactor:负载因子,默认为0.75。
  • threshold:扩容的阈值,其值等于capacity * loadFactor。
  • HashMap 在查找数据时,根据 HashMap的Hash值可以快速定位到数组的具体下标,但是在找到数组下标后需要对链表进行顺序遍历直到找到需要的数据,时间复杂度为O(n)。
  • 为了减少链表遍历的开销,Java8对HashMap进行了优化,将数据结构修改为数组+链表或红黑树。在链表中的元素超过8个以后,HashMap 会将链表结构转换为红黑树结构以提高查询效率,因此其时间复杂度为O(logN)。Java 8 HashMap的数据结构如图下图所示:
    Java面试核心基础知识之Java基础_第3张图片

JDK1.7及之前版本的ConcurrentHashMap:分段锁实现,线程安全

  • 与HashMap 不同,JDK 1.7及之前版本的ConcurrentHashMap采用分段锁的思想实现并发操作,因此是线程安全的。JDK1.7及之前版本的ConcurrentHashMap由多个Segment 组成(Segment的数量也是锁的并发度),每个 Segment 均继承自 ReentrantLock并单独加锁,所以每次进行加锁操作时锁住的都是一个 Segment,这样只要保证每个Segment都是线程安全的,也就实现了全局的线程安全。JDK1.7及之前版本的ConcurrentHashMap的数据结构如下图所示:
    Java面试核心基础知识之Java基础_第4张图片
  • 在JDK 1.7及之前版本的ConcurrentHashMap中有个concurrencyLevel 参数表示并行级别,默认是16,也就是说 JDK 1.7及之前版本的 ConcurrentHashMap 默认由 16个Segments 组成,在这种情况下最多同时支持16个线程并发执行写操作,只要它们的操作分布在不同的Segment上即可。并行级别concurrencyLevel可以在初始化时设置,一旦初始化就不可更改。JDK 1.7及之前版本的ConcurrentHashMap的每个Segment 内部的数据结构都和HashMap相同。
  • 在JDK 1.8以后的版本中,ConcurrentHashMap 弃用了Segment 分段锁,改用Synchronized+CAS实现对多线程的安全操作。同时,JDK 1.8在ConcurrentHashMap中引人了红黑树,具体的数据结构如图下图所示:
    Java面试核心基础知识之Java基础_第5张图片

HashTable:线程安全

  • HashTable 是遗留类,很多映射的常用功能都与HashMap类似,不同的是它继承自Dictionary类,并且是线程安全的,同一时刻只有一个线程能写HashTable,并发性不如ConcurrentHashMap

TreeMap:基于二叉树数据结构

  • TreeMap 基于二叉树数据结构存储数据,同时实现了SortedMap接口以保障元素的顺序存取,默认按键值的升序排序,也可以自定义排序比较器。
  • TreeMap常用于实现排序的映射列表。在使用TreeMap 时其key 必须实现Comparable 接口或采用自定义的比较器,否则会抛出java.lang.ClassCastException异常。

LinkedHashMap:继承HashMap,使用链表保存插入顺序

  • LinkedHashMap为HashMap的子类,其内部使用链表保存元素的插入顺序,当通过Iterator 遍历LinkedHashMap时,会按照元素的插入顺序访问元素。

二、异常分类及处理

1、Java 语言如何进行异常处理?

  • Java 通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接口。在 Java 中,每个异常都是一个对象,它是 Throwable 类或其子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并进行处理。

2、Java异常中关键字:throws、throw、try、catch、finally 分别如何使用?

  • Java 的异常处理是通过5个关键词来实现的:try、catch、throw、throws 和 finally。
  • 一般情况下是用try来执行一段程序,如果出现异常,系统会抛出throw的异常,这时候可以通过catch来捕捉异常,或用finally缺省处理器来处理;try用来指定一块预防所有“异常”的程序;catch子句紧跟在try块后面,用来指定你想要捕捉的“异常”的类型;throw语句用来明确地抛出一个“异常”;throws用来标明一个成员函数可能抛出的各种“异常”;finally为确保一段代码不管发生什么“异常”都被执行一段代码;可以在一个成员函数调用的外面写一个try语句,在这个成员函数内部写另一个try语句保护其他代码。每当遇到一个try语句,“异常”的框架就放到栈上面,直到所有的try语句都完成。如果下一级的try语句没有对某种“异常”进行处理,栈就会展开,直到遇到有处理这种“异常”的 try 语句。

3、请写出几种常见到的运行时异常?

  • NullPointerException:当操作一个空引用时会出现此错误(空指针异常)。
  • ClassCastException:强制类型转换类型不匹配时出现此异常。
  • NumberFormatException:数据格式转换出现问题时出现此异常。
  • ArrayIndexOutOfBoundsException:数组下标越界,当使用一个不存在的数组下标时出现此异常。
  • ArithmeticException:数学运行错误时出现此异常

4、error 和 exception 有什么区别?、

  • error 表示系统级的错误和程序不必处理的异常,是一种恢复不是不可能,但很困难的情况下的一种严重问题;比如内存溢出,不可能指望程序能处理这样的情况;
  • exception 表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题;也就是说,它表示如果程序运行正常,从不会发生的情况。

5、异常处理完成以后,Exception对象会发生什么变化?

  • Exception对象会在下一个垃圾回收过程中被回收掉。
  • 常处理对象在异常处理完后,没有引用指向它,变成了不可达对象。它将在接下来JVM进行GC操作时被标记为"不可达",如果该Exception实例实现了finalize方法,那么就会安排到F-queue队列中等待执行finalize方法(但是由于F-queue所在线程的优先级很低,所以可能一直得不到执行,而长时间留在该队列中);
    再下一次执行GC时,如果Exception对象已经执行完成finalize方法,它将被回收(彻底抹去内存中的数据)。

6、try{} 里有一个 return 语句,那么紧跟在这个 try 后的 finally{} 里的 code 会不会被执行,什么时候被执行,在 return 前还是后?

  • 会执行,在方法返回调用者前执行。Java允许在finally中改变返回值的做法是不好的,因为如果存在finally代码块,try 中的return语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行完毕之后再向调用者返回其值,然后如果在 finally 中修改了返回值,这会对程序造成很大的困扰,C# 中就从语法上规定不能做这样的事。

7、finally 是不是一定会被执行到?

  • 不一定。下面列举两种执行不到的情况:
  • (1)当程序进入 try 块之前就出现异常时,会直接结束,不会执行 finally 块中的代码;
  • (2)当程序在 try 块中强制退出时也不会去执行 finally 块中的代码,比如在 try 块中执行 exit 方法。

总结

持续迭代中…

你可能感兴趣的:(Java,java,面试,经验分享)