一名3年工作经验的Java程序员应该具备的技能,这可能是Java程序员们比较关心的内容。
我这里要说明一下,以下列举的内容不是都要会的东西—-但是如果你掌握得越多,最终能得到的评价、拿到的薪水势必也越高。
1、基本语法
static:
修饰变量、修饰方法、静态块、静态内部类、静态导包。
final:
final在Java中是一个保留的关键字,可以声明成员变量、方法、类以及本地变量。一旦你将引用声明作final,你将不能改变这个引用了,编译器会检查代码,如果你试图将变量再次初始化的话,编译器会报编译错误。
1、final关键字可以用于成员变量、本地变量、方法以及类。
2、final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。
3、你不能够对final变量再次赋值。
4、本地变量必须在声明时赋值。
5、在匿名类中所有变量都必须是final变量。
6、final方法不能被重写。
7、final类不能被继承。
8、final关键字不同于finally关键字,后者用于异常处理。
9、final关键字容易与finalize()方法搞混,后者是在Object类中定义的方法,是在垃圾回收之前被JVM调用的方法。
10、接口中声明的所有变量本身是final的。
11、final和abstract这两个关键字是反相关的,final类就不可能是abstract的。
12、final方法在编译阶段绑定,称为静态绑定(static binding)。
13、没有在声明时初始化final变量的称为空白final变量(blank final variable),它们必须在构造器中初始化,或者调用this()初始化。不这么做的话,编译器会报错“final变量(变量名)需要进行初始化”。
14、将类、方法、变量声明为final能够提高性能,这样JVM就有机会进行估计,然后优化。
15、按照Java代码惯例,final变量就是常量,而且通常常量名要大写。
transient:
Java中transient关键字的作用,简单地说,就是让某些被修饰的成员属性变量不被序列化,那么什么情况下,一个对象的某些字段不需要被序列化呢?如果有如下情况,可以考虑使用关键字transient修饰:
1、类中的字段值可以根据其它字段推导出来,如一个长方形类有三个属性:长度、宽度、面积(示例而已,一般不会这样设计),那么在序列化的时候,面积这个属性就没必要被序列化了;
2、其它,看具体业务需求吧,哪些字段不想被序列化;
foreach的实现原理:
基于迭代器的遍历。
Volatile:
volatile关键字能够保证代码的有序性,保证变量的可见性(两种特性保证保证线程安全)。
2、集合

(2)集合框架的接口
Collection接口:存储一组不唯一,无序的对象。
List接口:存储一组不唯一,有序的对象。
Set接口:存储一组唯一,无序的对象。
Map接口:存储一组键值对象,提高键(key)到值(value)的映射。
(3)
List接口的2个常用实现类:ArrayList和LinkedList
ArrayList类:在它的底层代码中,实际是有一个Object型数组,通过一些方法实现数组的扩容,数组本身长度是在定义的时候就不能改变,JDK的源码底层就是通过新创建一个数组,长度比原来的长,把之前原数组的每个元素复制过来,然后把新数组的首地址赋值给了原数组的引用,就这样实现了可变长度数组;所以ArrayList的特点就是:遍历和随机访问的速度快,插入对象和删除对象的效率就低,因为底层的实现是通过数组来实现。
LinkedList类:底层通过结点来实现的,它有2个Node结点属性,一个根结点first,一个尾结点last。添加的元素是保存在Node结点属性中item,看下面JDK源码,然后通过Node还有2个属性next和prev分别指向后一个保存元素的结点和指向前一个保存元素的结点;就这样形成一条逻辑上的一条链,它们在堆内存中内存是不连续的;所以LinkedList类的特点:访问元素的效率不高,但是插入和删除元素的效率高,因为通过结点改变指向就可以实现了。
HashMap底层就是通过结点数组(数组+结点)来实现的,为什么put()方法存key和value的时候,key不能重复,重复的话value会覆盖原先的value,key值在底层是通过它的hashCode来保证唯一性。
hashtable的底层数据结构叙述:
(1)实现Map集合。
(2)底层数据结构是数组链表结构,数组的初始容量是10,每一次扩容的大小是上一次的2倍+1,源码中是 (oldCapacity << 1) + 1。
(3)允许null的key和value。
(4)Hashtable在底层将key-value当成一个整体进行处理,这个整体就是一个Entry对象。Hashtable底层采用一个Entry[]数 组来保存所有的key-value对,当需要存储一个Entry对象时,会根据key的hash算法来决定其在数组中的存储位置,在 根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据key的hash算法找到 其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。
(5)线程同步,有synchronized对其中的方法进行修饰。
ConcurrentHashMap的底层:
(1)ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术
(2)它使用了多个锁来控制对hash表的不同段进行的修改,每个段其实就是一个小的hashtable,它们有自己的锁。只要 多个并发发生在不同的段上,它们就可以并发进行。
(3)ConcurrentHashMap在底层将key-value当成一个整体进行处理,这个整体就是一个Entry对象。Hashtable底层采用 一个Entry[]数组来保存所有的key-value对,当需要存储一个Entry对象时,会根据key的hash算法来决定其在数组中 的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据key 的hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。
(4)与HashMap不同的是,ConcurrentHashMap使用多个子Hash表,也就是段(Segment)
(5)ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁。
如果使用传统的技术,如HashMap中的实现,如果允许可以在hash链的中间添加或删除元素,读操作不加锁将得到 不一致的数据。ConcurrentHashMap实现技术是保证HashEntry几乎是不可变的。
HashSet :
(1)HashSet就是为了提高查找效率的。
(2)HashSet的add机制后,直接根据数据的散列码和散列表的数组大小计算除余后,就得到了所在数组的位置, 然后再查找链表中是否有这个数据即可。查找的代价也就是在链表中,但是真正一条链表中的数据很少,有的 甚至没有。几乎没有什么迭代的代价可言了。所以散列表的查找效率建立在散列单元所指向的链表中的数据要 少 。
(3) HashSet不能添加重复的元素,当调用add(Object)方法时候,首先会调用Object的hashCode方法判 hashCode是否已经存在,如不存在则直接插入元素;
1、HashSet不能重复存储equals相同的数据 。原因就是equals相同,数据的散列码也就相同(hashCode必须和equals兼 容)。大量相同的数据将存放在同一个散列单元所指向的链表中,造成严重的散列冲突,对查找效率是灾难性的。
2、HashSet的存储是无序的 ,没有前后关系,他并不是线性结构的集合。
3、框架
老生常谈,面试必问的东西。一般来说会问你一下你们项目中使用的框架,然后给你一些场景问你用框架怎么做,比如我想要在Spring初始化bean 的时候做一些事情该怎么做、想要在bean销毁的时候做一些事情该怎么做、MyBatis中$和#的区别等等,这些都比较实际了,平时积累得好、有多学习 框架的使用细节自然都不成问题。
如果上面你的问题答得好,面试官往往会深入地问一些框架的实现原理。
问得最多的就是Spring AOP的实现原理,当然这个很简单啦,两句话就搞定的的事儿,即使你不会准备一下就好了。我遇到的最变态的是让我画一下Spring的Bean工厂实 现的UML图。
4、数据库
数据库十有八九也都会问到。一些基本的像和 all的区别、left join、几种索引及其区别就不谈了,比较重要的就是数据库性能的优化,如果对于数据库的性能优化一窍不通,那么有时间,还是建议你在面试前花一两天专门 把SQL基础和SQL优化的内容准备一下。
不过数据库倒是不用担心,一家公司往往有很多部门,如果你对数据库不熟悉而基本技术又非常好,九成都是会要你的,估计会先把你放到对数据库使用不是要求非常高的部门锻炼一下。
(1)--导致查询缓慢的原因
1、数据量过大。
2、表设计不合理。
3、sql语句写得不好。
4、没有合理使用索引。
(2)-- 针对SQL语句的优化
1、查询语句中不要使用 *
2、尽量减少子查询,使用关联查询(left join,right join,inner join)替代
3、减少使用IN或者NOT IN ,使用exists,not exists或者关联查询语句替代
4、or 的查询尽量用 union或者union all 代替(在确认没有重复数据或者不用剔除重复数据时,union all会更好)
5、合理的增加冗余的字段(减少表的联接查询)
6、增加中间表进行优化(这个主要是在统计报表的场景,后台开定时任务将数据先统计好,尽量不要在查询的时候去统 计)
7 、建表的时候能使用数字类型的字段就使用数字类型(type,status...),数字类型的字段作为条件查询比字符串的快
8、那些可以过滤掉最大数量记录的条件必须写在WHERE子句的最末尾。
(3)-- 索引优化
1、如果针对sql语句已经没啥可以优化的,那我们就要考虑加索引了。
--索引类型
主键索引,唯一索引,组合索引,普通索引
--什么是索引
数据库索引是数据库管理系统中的一个排序的数据结构,以协助快速查询,更新数据库表中数据,索引的实现通常使用B 树(B-tree)以及其变种B+tree(一些高效率的算法)。
5、Web方面的一些问题
Java主要面向Web端,因此Web的一些问题也是必问的。
我碰到过问得最多的两个问题是:
(1)谈谈分布式Session的几种实现方式。(大家可以聊下你们知道的实现方法)
1.基于数据库的Session共享。
2.基于NFS共享文件系统。
3.基于memcached 的session,如何保证 memcached 本身的高可用性。
4.基于resin/tomcat web容器本身的session复制机制。
5.基于TT/Redis 或 jbosscache 进行 session 共享。
6.基于cookie 进行session共享。
(2)讲一下Session和Cookie的区别和联系以及Session的实现原理。
1、session保存在服务器,客户端不知道其中的信息;cookie保存在客户端,服务器能够知道其中的信息。
2、session中保存的是对象,cookie中保存的是字符串。
3、session不能区分路径,同一个用户在访问一个网站期间,所有的session在任何一个地方都可以访问到。而cookie中如果设置了路径参数,那么同一个网站中不同路径下的cookie互相是访问不到的。
4、session需要借助cookie才能正常<nobr οncοntextmenu="return false;" οnmοusemοve="kwM(3);" id="key3" οnmοuseοver="kwE(event,3, this);" style="COLOR: #6600ff; BORDER-BOTTOM: 0px dotted; BACKGROUND-COLOR: transparent; TEXT-DECORATION: underline" οnclick="return kwC();" οnmοuseοut="kwL(event, this);" target="_blank">工作</nobr>。
如果客户端完全禁止cookie,session将失效。http是无状态的协议,客户每次读取web页面时,服务器都打开新的会话,而且服务器也不会自动维护客户的上下文信息,那么要怎么才能实现网上商店中的购物车呢,session就是一种保存上下文信息的机制,它是针对每一个用户的,变量的值保存在服务器端,通过SessionID来区分不同的客户,session是以cookie或URL重写为基础的,
默认使用cookie来实现,系统会创造一个名为JSESSIONID的输出cookie,我们叫做session cookie,以区别persistent cookies,也就是我们通常所说的cookie,注意session cookie是存储于浏览器内存中的,并不是写到硬盘上的,这也就是我们刚才看到的JSESSIONID,我们通常情是看不到JSESSIONID的,但是当我们把浏览器的cookie禁止后,web服务器会采用URL重写的方式传递Sessionid,
我们就可以在地址栏看到 sessionid=KWJHUG6JJM65HS2K6之类的字符串。明白了原理,我们就可以很容易的分辨出persistent cookies和session cookie的区别了,网上那些关于两者安全性的讨论也就一目了然了,session cookie针对某一次会话而言,会话结束session cookie也就随着消失了,而persistent cookie只是存在于客户端硬盘上的一段文本(通常是加密的),
而且可能会遭到cookie欺骗以及针对cookie的跨站脚本攻击,自然不如 session cookie安全了。通常session cookie是不能跨窗口使用的,当你新开了一个浏览器窗口进入相同页面时,系统会赋予你一个新的sessionid,这样我们信息共享的目的就达不到了,此时我们可以先把sessionid保存在persistent cookie中,然后在新窗口中读出来,就可以得到上一个窗口SessionID了,
这样通过session cookie和persistent cookie的结合我们就实现了跨窗口的session tracking(会话跟踪)。在一些web开发的书中,往往只是简单的把Session和cookie作为两种并列的http传送信息的方式,session cookies位于服务器端,persistent cookie位于客户端,可是session又是以cookie为基础的,明白的两者之间的联系和区别,我们就不难选择合适的技术来开发web service了。
(3)web.xml里面的内容是重点,Filter、Servlet、Listener。
加载顺序与它们在 web.xml 文件中的先后顺序无关。即不会因为 filter 写在 listener 的前面而会先加载 filter。最终得出的结论是:listener -> filter -> servlet。
servlet:
servlet是一种运行服务器端的java应用程序,具有独立于平台和协议的特性,并且可以动态的生成web页面,它工作在客户端请求与服务器响应的中间层。
filter:
filter是一个可以复用的代码片段,可以用来转换HTTP请求、响应和头信息。Filter不像Servlet,它不能产生一个请求或者响应,它只是修改对某一资源的请求,或者修改从某一的响应。
listener:
监听器,从字面上可以看出listener主要用来监听。通过listener可以监听web服务器中某一个执行动作,并根据其要求作出相应的响应。通俗的语言说就是在application,session,request三个对象创建消亡或者往其中添加修改删除属性时自动执行代码的功能组件。
(4)get/post的区别。
POST和GET都是向服务器提交数据,并且都会从服务器获取数据。
区别:
1、传送方式:get通过地址栏传输,post通过报文传输。
2、传送长度:get参数有长度限制(受限于url长度),而post无限制
3、GET和POST还有一个重大区别,简单的说:
GET产生一个TCP数据包;POST产生两个TCP数据包
长的说:
对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
1、get方式的安全性较Post方式要差些,包含机密信息的话,建议用Post数据提交方式;
2、在做数据查询时,建议用Get方式;而在做数据添加、修改或删除时,建议用Post方式;
(5)forward/redirect的区别。
Forward和Redirect代表了两种请求转发方式:重定向和转发。对应到代码里,分别是RequestDispatcher类的 forward()方法和 HttpServletResponse类的sendRedirect()方法。
重定向redirect:该方法跳转后的页面,不能获得之前页面用户提交的数据。response.sendRedirect (“xxx.jsp”);
转发forward:RequestDispatcher对象可以把用户对当前的JSP页面或 servlet的请求 转发给另一个JSP页面或servlet,并且将请求对象 和相应对象传递给目标 页面。request.getRequestDispatcher(“xxx.jsp”).forward(request,response);
对于重定向redirect,服务器端在响应第一次请求的时候,让浏览器再 向另外一个URL发出请求,从而达到转发的目的。它本质上是两次HTTP请求,对应两个request对象。
对于转发forward,客户端浏览器只发出一次请求,Servlet把请求 转发给Servlet、HTML、JSP或其它信息资源,由第2个信息资源响应该请求, 两个信息资源共享同一个request对象。
(6)https的实现原理。
80端口:80端口是为HTTP(HyperText Transport Protocol)即超文本传输协议开放的默认端口。
443端口:443端口即网页浏览端口,主要是用于HTTPS服务,是提供加密和通过安全端口传输的另一种HTTP。是https的默认端口。
小结:访问网页如不加端口号,使用http协议访问网页,是请求的服务器的80端口;使用https协议请求的是服务器的443端口。
HTTPS经由HTTP进行通信,但利用SSL/TLS来加密数据包。HTTPS开发的主要目的,是提供对网站服务器的身份认证,保护交换数据的隐私与完整性。它其实就是HTTP+加密+身份认证+完整性保护。
6、数据结构和算法分析
数组、链表是基础,栈和队列深入一些但也不难,树挺重要的,比较重要的树AVL树、红黑树,可以不了解它们的具体实现,但是要知道什么是二叉查找树、什么是平衡树,AVL树和红黑树的区别。
Collections.sort方法使用的是哪种排序方法?用的是一种叫做TimSort的排序法,也就是增强型的归并排序法。
7、Java虚拟机
值得推荐的一本书:《深入理解Java虚拟机:JVM高级特性与较佳实践》。
谈谈Java虚拟机中比较重要的内容:
(1) Java虚拟机的内存布局。
(2) GC算法及几种垃圾收集器。
(3) 类加载机制,也就是双亲委派模型。
(4) Java内存模型。
(5) happens-before规则。
(6) volatile关键字使用规则。
8、设计模式
设计模式在工作中还是非常重要、非常有用的,23种设计模式中重点研究常用的十来种就可以了,面试中关于设计模式的问答主要是三个方向:
(1) 你的项目中用到了哪些设计模式,如何使用。
(2) 知道常用设计模式的优缺点。
(3) 能画出常用设计模式的UML图。
9、多线程
因为三年工作经验,所以基本上不会再问你怎么实现多线程了,会问得深入一些比如说Thread和Runnable的区别和联系、多次start一个线程会怎么样、线程有哪些状态。
总结起来是这么一个意思:
假如有Thread1、Thread2、ThreaD3、Thread4四条线程分别统计C、D、E、F四个盘的大小,所有线程都统计完毕交给Thread5线程去做汇总,应当如何实现?
java.util.concurrent下就有现成的类可以使用。(大家可以留言讨论下用的什么方法实现)
另外,线程池也是比较常问的一块,常用的线程池有几种?这几种线程池之间有什么区别和联系?线程池的实现原理是怎么样的?
实际一些的,会给你一些具体的场景,让你回答这种场景该使用什么样的线程池比较合适。
最后,虽然这次面试问得不多,但是多线程同步、锁这块也是重点。
synchronized和ReentrantLock的区别、 synchronized锁普通方法和锁静态方法、死锁的原理及排查方法等等。
10、JDK源码
要想拿高工资,JDK源码不可不读。上面的内容可能还和具体场景联系起来,JDK源码就是实打实地看你平时是不是爱钻研了。
我面试过程中被问了不少JDK源码的问题,其中最刁钻的一个问了我,String的hashCode()方法是怎么实现的,幸好我平时String源代码看得多,答了个大概。
JDK源码其实没什么好总结的,纯粹看个人,总结一下比较重要的源码:
(1) List、Map、Set实现类的源代码。
(2) ReentrantLock、AQS的源代码。
(3) AtomicInteger的实现原理,主要能说清楚CAS机制并且AtomicInteger是如何利用CAS机制实现的。
(4) 线程池的实现原理。
(5) Object类中的方法以及每个方法的作用。
这些其实要求蛮高的,我去年一整年基本把JDK中重要类的源代码研究了个遍,真的花费时间、花费精力,当然回头看,是值得的—-不仅仅是为了应付面试。