本人的春招就告一段落了,也找到了理想的工作,特分享一下自己整理的资料来做成的一个面经(都是本人亲自经历过的面试题),用于自己以后的学习和进步,由于都是网上搜集而来如有错误,望各位指正。并且会实时更新。。。 PS:如果想知道每个公司问我的什么可以私聊我。
目录
5、Try..catch..finally中return的执行顺序
9、String、StringBuffer与StringBuilder的区别以及应用
12、HashMap与ConcurrentHashMap、HashTable的区别
13、ArrayList与LinkedList和vector的区别
1、final关键字
Final修饰的变量无法被修改,如果修饰引用,表示引用不可变,引用的指向的内容可变
Final修饰的方法无法被重写
Final修饰的类无法被继承
Final修饰的常量会在编译阶段分配空间初始化放入到常量池中。
Final修饰的属性必须得有值。
Final修饰的变量引用,子类可以使用
2、传参方式
值传递:八种基本数据类型和String
引用传递:除String以外的所有复合数据类型,包括数组、类和接口
3、抽象类和接口的区别
抽象类是用来捕捉子类的通用特性的,而接口则是抽象方法的集合。抽象类不能被实例化,只能被用作子类的超类,是被用来创建继承层级里子类的模板,而接口只是一种形式,接口自身不能做任何事情。
抽象类abstract和接口interface都不能够实例化,但可以定义抽象类和接口类型的引用。 一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类。接口比抽象类更加抽象,因为抽象类中可以定义构造器,可以有抽象方法和具体方法,而接口中不能定义构造器而且其中的方法全部都是抽象方法。抽象类中的成员可以是private、默认、protected、public的,而接口中的成员全都是public的。抽象类中可以定义成员变量,而接口中定义的成员变量实际上都是常量。有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法。
4、This与super
This:this是当前对象的引用,就是说当前用构造函数建的对象是谁,这个this就代表谁它是一个引用。另外一个问题,如果构造函数想调用另一个构造函数该怎么办呢?这个时候就需要使用this()了。如果在构造函数里调用其他重载的构造函数时,则被调用的那个构造函数必须放在代码的第一行。
Super:super仅仅只是指向了子类从父类继承的成员变量和方法,构造函数里默认的都有一个隐式的super();去调用父类无参的构造,如果想显式调用,则调用父类的supper(xxx) 必须放在第一行
5、Try..catch..finally中return的执行顺序
- 不管有木有出现异常,finally块中代码都会执行;
2)当try和catch中有return时,finally仍然会执行;
3)finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;
4)finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
举例:
情况1:try{} catch(){}finally{} return;
显然程序按顺序执行。
情况2:try{ return; }catch(){} finally{} return;
程序执行try块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,最后执行try中return;
finally块之后的语句return,因为程序在try中已经return所以不再执行。
情况3:try{ } catch(){return;} finally{} return;
程序先执行try,如果遇到异常执行catch块,
有异常:则执行catch中return之前(包括return语句中的表达式运算)代码,再执行finally语句中全部代码,最后执行catch块中return. finally之后也就是4处的代码不再执行。
无异常:执行完try再finally再return.
情况4:try{ return; }catch(){} finally{return;}
程序执行try块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,因为finally块中有return所以提前退出。
情况5:try{} catch(){return;}finally{return;}
程序执行catch块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,因为finally块中有return所以提前退出。
情况6:try{ return;}catch(){return;} finally{return;}
程序执行try块中return之前(包括return语句中的表达式运算)代码;
有异常:执行catch块中return之前(包括return语句中的表达式运算)代码;
则再执行finally块,因为finally块中有return所以提前退出。
无异常:则再执行finally块,因为finally块中有return所以提前退出。
6、Exception与error
Error 类型的错误通常为虚拟机相关错误,如系统崩溃,内存不足,堆栈溢出等,编译器不会对这类错误进行检测,JAVA 应用程序也不应对这类错误进行捕获,一旦这类错误发生,通常应用程序会被终止,仅靠应用程序本身无法恢复;
Exception 类的错误是可以在应用程序中进行捕获并处理的,通常遇到这种错误,应对其进行处理,使应用程序可以继续正常运行。
Exception又分为编译时异常和运行时异常
运行时异常:编译器不会去检查它,如IndexOutOfBoundException、fail-fast机制的ConcurrentModificationException、NullPointerException等
编译时异常:Exception类本身及其子类除了运行时异常外的其他子类都是编译时异常,Java编译器会检查它,此类异常通过throws抛出异常或者try..catch捕获异常,否则不能通过编译。如IOException、FileNotFoundException、SQLException
7、Int与Integer的区别、自动封箱与自动装箱
Int属于Java八大基本数据类型(byte、short、int、long、float、double、char、boolean),默认初始值为0,int直接存储数据值。
Integer属于引用数据类型,是int的包装类,必须要实例化后才能使用,默认初始值为null,integer实际是对象的引用,指向new的integer对象,当给integer对象赋int值时,调用valueOf方法。
1)Integer变量实际上是对一个Integer对象的引用,所以两个通过new生成的Integer变量永远是不相等的。
2)Integer变量和int变量比较时,只要两个变量的值是向等的,则结果为true(自动拆箱)
3)非new生成的Integer变量和new Integer()生成的变量比较时,结果为false。因为非new生成的Integer变量指向的是静态常量池中cache数组中存储的指向了堆中的Integer对象,而new Integer()生成的变量指向堆中新建的对象,两者在内存中的对象引用(地址)不同。
4)对于两个非new生成的Integer对象,进行比较时,如果两个变量的值在区间-128到127之间,则比较结果为true,如果两个变量的值不在此区间,则比较结果为false(自动装箱时对于在-128~127之内的数值,它们被装箱为Integer对象后,会存在内存中被重用,始终只存在一个对象)
两者可以通过自动封箱,自动装箱机制进行转换
自动装箱:将基本数据类型重新转化为对象。
自动拆箱:将对象重新转化为基本数据类型。
8、OOP
面向对象编程:是“万物皆对象”——我们可以尽可能的发挥想象,把现实中的物(包括动物、人物、甚至无生命的事物)抽象为一种计算机语言(比如 Java)能懂的模型
OOP核心思想:封装,继承,多态。
1)封装,每个类(对象的模板)可以自由地定义属性和方法,使其有别于其他的类。封装可以隐藏对象的内部细节,使其对外形成一道边界,只保留有限的属性和方法与其他对象进行交互。封装的原则是使对象以外的部分不能随意的访问和操作对象的内部属性,从而避免了外界对对象内部属性的破坏。
2)继承,子类可以轻松地复用父类的代码(非 private 修饰的属性和方法),只需要一个关键字——extends。如果父类的属性不满足子类的需求,可以追加;如果父类的方法不满足子类的需求,可以覆盖。
3)多态,父类中定义的方法被子类继承之后,可以表现出不同的行为。这使得同一个方法在父类及其各个子类中具有不同的语义。
9、String、StringBuffer与StringBuilder的区别以及应用
String 字符串常量
StringBuffer 字符串变量(线程安全)
StringBuilder 字符串变量(非线程安全)
String 和 StringBuffer 的主要性能区别在于 String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。
而如果是使用 StringBuffer 类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,再改变对象引用。字符串对象经常改变的情况下使用。而在某些特别情况下, String 对象的字符串拼接其实是被 JVM 解释成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,而特别是以下的字符串对象生成中, String 效率是远要比 StringBuffer 快的:
String S1 = “This is only a” + “ simple” + “ test”;
StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);
你会很惊讶的发现,生成 String S1 对象的速度简直太快了,而这个时候 StringBuffer 居然速度上根本一点都不占优势。其实这是 JVM 的一个把戏,在 JVM 眼里,这个
String S1 = “This is only a” + “ simple” + “test”; 其实就是:
String S1 = “This is only a simple test”; 所以当然不需要太多的时间了。但大家这里要注意的是,如果你的字符串是来自另外的 String 对象的话,速度就没那么快了,譬如:
String S2 = “This is only a”;
String S3 = “ simple”;
String S4 = “ test”;
String S1 = S2 +S3 + S4;
这时候 JVM 会规规矩矩的按照原来的方式去做
StringBuffer
Java.lang.StringBuffer线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。
可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。
StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。
java.lang.StringBuilde
java.lang.StringBuilder一个可变的字符序列是5.0新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。两者的方法基本相同。
String 重写了 Object 的 equals 方法,把引用比较改成了值比较
10、重载和重写的区别?
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分
重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏代换原则);如果父类方法访问修饰符为private则子类中就不是重写。
11、聚合与组合的关系
聚合关系(Aggregation)体现的是A对象可以包含B对象,但B对象不是A对象的组成部分。具体表现为,如果A由B聚合成,表现为A包含有B的全局对象,但是B对象可以不在A创建的时刻创建。
组合关系(Composition):如果A由B组成,表现为A包含有B的全局对象,并且B对象在A创建的时刻创建。
现实生活中,人和人和手,脚是组合关系,因为当人死亡后人的手也就不复存在了。人和他的电脑是聚合关系
12、HashMap与ConcurrentHashMap、HashTable的区别
HashMap是一个使用Entry对象用于存储Key-Value键值对的集合,实现了Map、Cloneable、Serializable不是线程安全的(多个线程对一个集合的内容进行操作时,会抛出ConcurrentModification异常,产生fail-fast事件)HashMap的底层数据结构是散列表,即数组+链表+红黑树(JDK1.8之前是数组+链表,当链表数量超过8时变为红黑树,小与6变为链表)。
HashMap的初始容量为16,必须得是2的n次幂(这是因为在进行Hash函数映射时取模运算时采用了的是位运算 index=HashCode(key)& (length-1)的方法,这样就可以提交计算效率,还可以使Hash算法均匀分布的原则),加载因子为0.75,当HashMap的数量 >= 容量*加载因子时,一次扩容两倍(扩容之后会新建一个两倍的空数组,遍历原数组会rehash,因为长度扩大hash规则改变,即重新进行hash运算,但是在多线程并发插入元素的时候,有可能会导致链表有环,造成下一次读操作的死循环)。HashMap的插入和查找方法,具体是通过hash运算得到hash值作为插入位置的参数,当插入数组hash碰撞时,采用链表头插法的方式解决。
ConcurrentHashMap是线程安全的,它一个二级的哈希表,在一个总的哈希表下面,有若干个哈希表,即segment,segment本身相当于一个HashMap对象,segments初始化为长度为16的数组。segment包含一个Hashentry数组,有2的n次方个segment在segments数组中,采用了分段锁的技术实现了高度自治,segment之间互不影响,每个segment都是有一把锁的,不同的segment是可以并发写入的,同一个segment的写和读可以并发执行,但是同一个segment的并发写入是需要上锁的,进行阻塞。
读取操作:对key做Hash运算,得到hash值定位到Segment对象,再通过hash值,定位到Segment当中的数组具体位置。
写入操作:对key做Hash运算,得到hash值定位到Segment对象,获取可重入锁,定位到Segment当中的数组具体位置,插入或覆盖HashEntry对象,释放锁。
Size:在不上锁的前提逐个Segment计算3次size,如果某相邻两次计算获取的所有Segment的更新次数(每个Segment都与HashMap一样通过modCount跟踪自己的修改次数,Segment每修改一次其modCount加一)相等,说明这两次计算过程中无更新操作,则这两次计算出的总size相等,可直接作为最终结果返回。如果这三次计算过程中Map有更新,则对所有Segment加锁重新计算Size
在jdk1.8中ConcurrentHashMap 是基于CAS+Node+Synchronized来保证并发安全put方法和remove方法都会通过addCount方法维护Map的size。size方法通过sumCount获取由addCount方法维护的Map的size。
HashTable 是线程安全的,也是一个散列表每个方法上都加了synchronized。初始化容量是11,扩容后变为2n+1;
HashTable继承于Dictionary,而HashMap继承于AbstractMap且实现Map接口。
HashTable的key不能为null,value也不能为空,HashMap可以为null,但是key的null只能有一个。
都实现了Map、Cloneable、Serializable接口;
13、ArrayList与LinkedList和vector的区别
List接口是有序可重复的。
1)ArrayList底层基于动态数组实现,ArrayList集合默认初始化容量为:10(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量为10,集合扩容采用位运算,一次扩容1.5倍)LinkedList底层基于双向链表实现,没有初始化容量。
2)对于随机访问(get/set方法),ArrayList通过index直接定位到数组对应位置的节点,而LinkedList需要从头结点或尾节点开始遍历,直到寻找到目标节点,因此在效率上ArrayList优于LinkedList。
3)对于插入和删除(add/remove方法),ArrayList需要移动目标节点后面的节点(使用System.arraycopy方法移动节点),而LinkedList只需修改目标节点前后节点的next或prev属性即可,因此在效率上LinkedList优于ArrayList。当 ArrayList 是顺序插入的时候,由于不需要移动节点,因此在效率上并不会比 LinkedList 差。
4)LinkedList比ArrayList开销更大,因为LinkedList的节点除了存储数据,还需要存储引用。
Vector集合底层调用了数组这种数据结构,是线程安全的,Vector集合的初始化容量为10,
扩容之后是原容量的2倍。
14、LinkedHashMap
LinkedHashMap继承了HashMap,是基于HashMap和双向链表(保证顺序)来实现的。HashMap是无序的,当我们希望有顺序地去存储key-value时,就需要使用LinkedHashMap。可分为插入顺序和访问顺序两种。如果是访问顺序,那put和get操作已存在的Entry时,都会把Entry移动到双向链表的表尾(其实是先删除再插入)。也提供了key-value的存储方式,并提供了put和get方法来进行数据存取。LinkedHashMap的默认初始容量 (16)和默认负载因子(0.75),是线程不安全的。
15、HashSet
Set集合存储元素的特点:无序不可重复
底层是HashMap,并且HashSet中的set相当于在HashMap的key,而value则是new Object出来的常量,默认初始化容量为16,加载因子为0.75,按HashMap的扩容方法扩容。
- 存储时顺序和取出的顺序不同
- 不可重复
- 放到HashSet集合中的元素实际上是放到HashMap集合的key部分了
16、TreeMap
TreeMap继承AbstractMap,实现NavigableMap、Cloneable、Serializable三个接口。其中AbstractMap表明TreeMap为一个Map即支持key-value的集合。底层是红黑树。能够把它保存的记录根据key排序,默认是按升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。TreeMap不允许key的值为null。非同步的。