多线程,并发
java中能否创建volatile数组
可以,但是只是一个指向数组的引用,而不是整个数组。也就是如果改变引用指向的数据,将会受到volatile的保护,但是如果多个线程同时改变数组内的元素,就不能起到保护作用
volatile能使得一个非原子操作变成原子操作吗
如果在类中有一个long类型的成员变量,如果了解这个成员变量会被多个线程访问,最好是将其设置为volatile,因为在java中long类型变量的读取并非是原子的,需要分成两步,如果一个线程在修改这个long变量,的值,那么另一个线程有可能只看见了该数值的一半(前32位)但是对一个volatile类型的变量读写就会是原子的double也是一样的。
volatile变量类型提供什么保证?
volatile变量提供顺序和可见性保证,例如,jvm为了获得更好的性能会对语句重新排序,但是volatile类型变量即使在没有同步块的情况下赋值也不会与其他语句重排序。volatile提供memory-barrier,意思就是当正在写一个volatile域的时候,能够保证任何线程都能够看见你写的值。
十个线程和两个线程的同步代码块那个更容易写?
从写代码的角度来说,两者的复杂度是相同的,因为同步代码和线程数量是相互独立的,但是同步策略的选择依赖于线程的数量,越多的线程意味着越多的竞争,所以需要利用同步技术,例如锁分离,要求更复杂的代码和专业知识
如何调用wait()?用if 还是while why?
wait()应该在循环内被调用,因为当线程获取cpu开始执行的时候,其他条件可能还没有满足,所以在处理前,循环检查条件是否会满足会更好
什么是Busy spin?为什么要使用它
Busy spin 是一种不释放cpu的基础上等待事件的技术,它经常用于避免丢失CPU中的缓冲数据(如果线程先暂停,之后在其他cpu上运行就会消失) 所以如果工作要求低延迟,并且你的线程目前没有任何顺序,这样你就可以通过循环检测队列中的新消息来代替调用wait或者是sleep方法
java如何获取一份线程中的dump文件?
在linux下,通过kill -3 PID来获取java应用的dump文件,jvm会把dump文件打入标准输出或者是错误日志文件中
什么是线程局部变量?
线程局部变量就是局限于线程内部的变量,属于线程自身所有,不在多个线程之间共享,java提供ThreadLocal类来支持局部线程,但是在web服务器环境下使用线程局部变量的时候要非常小心,这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成之后没有释放,java就存在内存泄漏的危险。
java中的sleep和wait方法
虽然两种都是用来暂停当前运行的线程,但是sleep()实际上只是短暂停顿,因为他不会释放锁,而wait是条件等待,这就是为什么该方法会释放锁,因为只有这样,其他等待的线程才能在满足条件的时候获取到该锁。
immutable object ,能否创建一个包含可变对象的不可变对象?
immutable object 一旦被创建,状态就不能被改变,任何的修改都只会创建一个新的对象,如String,Integer以及其他包装类。我们可以创建一个包含可变对象的不可变对象的,只要注意不要共享可变对象的引用就可以了,如果需要变化的话,就返回元对象的一个拷贝
数据类型和java基础
java中应该使用什么数据类型来代表价格?
如果不是特别关心内存和性能的话,使用BigDecimal,否则使用预定义精度的double类型。
在java中怎样将bytes转换成long.int…….类型
代码如下
public class Utilities{
public static byte[] int2bytes(int num){
byte[] byteNum = new byte[4];
for(int ix = 0; ix < 4; ++ix){
int offset = 32 - (ix + 1)*8;
byteNum[ix] = (byte)((num >> offset) & 0xff);
}
return byteNum;
}
public static int bytes2Int(byte[] byteNum){
int num = 0;
for(int ix = 0;ix < 4;++ix){
num<<=8;
num |= (byteNum[ix] & 0xff);
}
return num;
}
public static byte int2OneByte(int num){
return (byte)(num & 0x000000ff);
}
public static int oneByte2Int(byte byteNum){
//针对正数的int
return byteNum > 0 ? byteNum : (128 + (128 + byteNum));
}
public static byte[] long2Bytes(long num){
byte[] byteNum = new byte[8];
for(int ix = 0;ix < 8; ++x){
int offset = 64 - (ix + 1)*8;
byteNum[ix] = (byte) ((num >> offset) & 0xff);
}
return byteNum;
}
public static long bytes2Long(byte[] byteNum) {
long num = 0;
for (int ix = 0; ix < 8; ++ix) {
num <<= 8;
num |= (byteNum[ix] & 0xff);
}
return num;
}
}
代码转自 http://blog.csdn.net/zgyulongfei/article/details/7738970
3.那个类包含clone方法?是Cloneable还是Object?
java.lang.Cloneable是一个标识性接口,不包含任何方法,clone方法在object类中定义。并且需要知道clone()方法是一个本地方法,这意味着他是由c或者是c++或者是其他本地语言实现的
4.java 中++操作符是线程安全的吗?
不是线程安全的操作,它涉及到多个指令,如读取变量值,增加,然后储存回内存,这个过程有可能出现多个线程交叉。
5. a = a+b 与 a+=b的区别
+=隐式的将+操作的结果类型强制转换为持有结果的类型。如果两个类型相加,如byte,short,int首先会将它们提升到int类型,然后再执行加法操作,则a+b会出现编译错误,但是a += b 就没有问题,如下
byte a = 127;
byte b = 127;
b = a+b;//error cannot convert from int to byte
b += a; //ok
6.能否在不进行强制转换的情况下将一个double赋值给long类型的变量?
不行,不能因为double类型的范围比long类型的范围更广,必须要进行强制转换。
7.3*0.1 == 0.3将会返回什么》true还是false
false ,因为有些浮点数不能完全精确的表示出来。
8. 能在switch使用String吗?
从java7开始,可以在case上写字符串,但是系统内部是通过String的hashcode来记录的,其实还是数字类型
jvm底层与GC
64位jvm中,int的长度是多少?
java中 int类型的变量长度是固定值,与平台无关,都是32 位
Serial 和 Parallel GC之前的不同?
Serial 与 Parallel在 GC执行的时候都会引起 stop-the-word 他们之前主要的不同是serial收集器是默认的复制收集器,执行GC的时候只有一个线程,Parallel收集器使用多个GC线程来执行
WeakHashMap是怎么工作的?
WeakHashMap的工作与正常的Hashmap类似,但是使用弱引用作为key 意思就是当key对象没有任何引用的时候 key/value将会被回收
JVM选项,-XX +UseCompressedOops有什么作用,为什么要用
当你将你的应用从32位jvm迁移到64位jvm的时候,由于对象的指针由32位增加到了64位,因此堆内存会突然增加,差不多要翻倍,这也会对cpu缓存(容量比内存小很多)的数据产生不利的影响,因为,迁移到64位的jvm主要动机可能在于可以制定最大堆内存的大小,通过压缩OOP可以节省一定的内存。
JRE,JDK,JVM,JIT之间有什么不同
JRE(Java run-time) 是运行java引用所必须的,JDK代表 java开发工具(Java Development kit) JVM(Java Virtual Machine)用来运行java应用。JIT(Just in time Compilation)当代码执行的次数超过一定的阈值的时候,会将java字节码转换为本地代码,这样会大幅的提高java应用的性能。
如何获取 Java程序使用的内存,堆使用的百分比?
可以通过 Java.lang.Runtime类中与内存相关的方法来获取剩余的内存,总内存以及最大堆内存。通过这些方法也可以获取到堆使用的百分比以及堆内存的剩余空间。Runtime.freememory() 方法返回剩余空间的字节数,Runtime.totalMemory返回总内存的字节数,Runtime.maxMemory()返回最大内存的字节数。
Java中的编译期常量是什么,使用有什么风险?
公共静态不可变(public static final) 变量也就是我们所说的编译期常量,这里的public是可选的。实际上这些变量在编译时会被替换掉,因为编译器知道这些变量的值,并且知道这些变量在运行的时候不会改变,这种方式存在一个问题,是你使用了一个内部的或者是第三方库中的公有编译时常量,但是这个值后面被其他人改变了,但是你的客户端仍然使用老值,甚至已经部署了一个新的jar,为了避免这种情况,当你在更新依赖Jar文件的时候,确保重新编译你的程序。
集合框架相关
List Set Map 的区别
List是一个有序集合,允许元素重复。它的某些实现可以提供基于下标值的常量访问.但是这个并不是list接口实现的。要注意这点。Set是一个无序不可重复集合。Map中,可以将key序列 value序列单独的抽取出来,使用keySet()抽取key序列,将map中的所有keys生成一个Set。使用values()抽取value序列,将map中的所有values生成一个Collection。至于为什么一个key一个Collection,因为key是独一无二的,而value允许重复。 扩展:Collection没有get方法来取得特定的某个元素,只能通过literator遍历,Set和Collection拥有一模一样的接口
poll()方法和remove()方法的区别?
poll()和remove()都是从队列中取出一个元素,但是poll()方法在获取元素失败的时候会返回空,但是remove()失败的时候会抛出异常
Java中LinkedHashMap和PriorityQueue的区别是什么?
PriorityQueue保证最高或者是最低优先级的元素总是在队列头部,但是LinkedhashMap维持的顺序是元素的插入顺序,当遍历一个priorityQueue的时候,没有任何的顺序保证,但是LinkedHashMap可以保证顺序
用那两种方式来实现集合的排序?
可以使用有序集合,如TreeSet或者是TreeMap也可以通过list然后使用Collections.sort()来排序。
Java中如何打印数组?
可以使用Arrays.toString()和Arrays.deepToString()方法来打印数组
Java LinkedList是双向链表,Java中的TreeMap是采用红黑树来实现的
Java中的HashSet内部是如何工作的?
HashSet内部是采用HashMap来实现,由于Map需要Key和Value,所以所有key都要有一个默认的value,而且hashSet不允许有重复的key只允许有一个null key 这点也和hashMap一致
我们能够自己写一个容器类,然后使用for-each循环吗?
可以,可以写一个自己的容器类,如果你想使用java中的增强for循环来遍历,只需要实现iterable接口,如果实现Collection接口,默认就具有该属性。
ArrayList和HashMap的默认大小。
Java7中,ArrayList的默认大小是10,hashmap默认大小是16
private static final int DEFAULT_CAPACITY = 10;
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
10.两个不相等的对象可能有相同的hashcode,但是两个相同的对象一定具有相同的hashcode。
11.我们能在hashCode()中使用随机数字吗?
不行,因为相同对象的hashcode()值必须是相同的,参见重写java hashcode()代码
Java IO和NIO的面试题
1.在java程序中,有三个Socket,那么需要多少个线程来处理?
这里有一个问题,基本上一个Socket至少要一个线程来处理,但是也可以用多个线程来处理一个Socket,所以至少需要三个线程。
2.Java中怎么创建ByteBuffer
ByteBuffer allocate(int capacity) //创建一个指定capacity的ByteBuffer。
ByteBuffer allocateDirect(int capacity) //创建一个direct的ByteBuffer
ByteBuffer wrap(byte [] array)
ByteBuffer wrap(byte [] array, int offset, int length) //把一个byte数组或byte数组的一部分包装成ByteBuffer。
3.Java中怎么读写ByteBuffer
//从socket中读取
int bytesReaded=socketChannel.read(buffer);
//写入Socket
socketChannel.write(buffer);
//文件信道写入
FileChannel fc = new FileInputStream().getChannel();
fc.write(buff);
fc.close();
//文件信道读取
FileChannel fc = new FileOutputStream().getChannel();
fc.read( buff);
fc.flip();
4.java采用的是大端还是小端
大端:就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
小端:就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
java貌似将大小端给隐蔽了,所以我们在编程的时候没有注意到这点。
5.ByteBuffer中的字节序是什么?
public class JVMEndianTest {
public static void main(String[] args) {
int x = 0x01020304;
ByteBuffer bb = ByteBuffer.wrap(new byte[4]);
bb.asIntBuffer().put(x);
String ss_before = Arrays.toString(bb.array());
System.out.println("默认字节序 " + bb.order().toString() + "," + " 内存数据 " + ss_before);
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.asIntBuffer().put(x);
String ss_after = Arrays.toString(bb.array());
System.out.println("修改字节序 " + bb.order().toString() + "," + " 内存数据 " + ss_after);
}
}
默认字节序 BIG_ENDIAN, 内存数据 [1, 2, 3, 4]
修改字节序 LITTLE_ENDIAN, 内存数据 [4, 3, 2, 1]
9.ByteBuffer与String转换
public static Charset charset = Charset.forName("UTF-8");
public static CharsetEncoder encoder = charset.newEncoder();
public static CharsetDecoder decoder = charset.newDecoder();
public static ByteBuffer str_to_bb(String msg){
try{
return encoder.encode(CharBuffer.wrap(msg));
}catch(Exception e){e.printStackTrace();}
return null;
}
public static String bb_to_str(ByteBuffer buffer){
String data = "";
try{
int old_position = buffer.position();
data = decoder.decode(buffer).toString();
// reset buffer's position to its original so it is not altered:
buffer.position(old_position);
}catch (Exception e){
e.printStackTrace();
return "";
}
return data;
}
Java中的最佳实践
Java中编写多线程程序的时候你会遵循那些最佳实践?
- 给线程命名,这样可以帮助调试
- 最小化同步的范围,而不是将整个方法进行同步.只对关键部分进行同步。
- 如果可以,更倾向与使用volatile而不是synchronized\
- 使用更高层次的开发工具,而不是使用wait和notify来实现线程间的通信。如BlockingQueue,CountDownLatch,以及Semeaphore
- 优先使用并发集合而不是对集合进行同步
说出几点Java中使用Collections的最佳实践
- 使用正确的集合类,比如,如果不需要同步列表,使用ArrayList而不是Vector
- 优先使用并发集合,而不是对集合进行同步
- 使用接口代表访问集合,如使用List代表ArrayList。Map存储代表HashMap
- 使用迭代器来循环集合
- 使用集合的时候使用泛型
IO的最佳实践
- 使用有缓冲区的IO类而不要单独的读取字节或者是字符
- 看情况使用NIO和AIO
- 在finally块中关闭流或者使用try-with-resource语句
- 使用内存映射文件获取更快的io
JDBC最佳实践
- 使用批量的操作来插入和更新数据
- 使用PreparedStatement避免异常,并且提高性能
- 使用数据库连接池
- 通过列名来获取结果集不要使用列的下标来获取
Java中方法重载的最佳实践
主要是为了避免造成自动装箱的混乱- 不要重载这样的方法,一个方法接收int参数,而另一个方法接收Integer参数
- 不要重载参数数量一致。而只是参数顺序不同的方法
- 如果重载的参数多于五个 采用可变参数
Date,Time 以及 Calendar
1.在多线程的环境下,SimpleDateFormat是线程安全的吗?
不是,DateFormat的所有实现都不是线程安全的,因此不应该在多线程序中使用,除非是在对外线程安全的环境中使用,如将SimpleDateFormat限制在ThreadLocal中,如果不这么做,在解析或者格式化日期的时候,可能获取到一个不正确的结构,因此从日期,时间处理的所有实践来说,推荐 joda-time 库
- Java中如何格式化一个日期 如格式化ddMMyyyy的形式?
- Java中,怎么在格式化的日期中显示时区
- java中怎么计算两个日期之间的差距
- Java中如何将字符串YYYYMMDD转化为日期
下面是利用joda-time库进行日期格式化的方法,可以解决上述问题
public static void main(String[] args) {
//初始化时间
DateTime dateTime=new DateTime(2012, 12, 13, 18, 23,55);
// 年,月,日,时,分,秒,毫秒
DateTime dt3 = new DateTime(2011, 2, 13, 10, 30, 50, 333);// 2010年2月13日10点30分50秒333毫秒
//下面就是按照一点的格式输出时间
String str2 = dateTime.toString("MM/dd/yyyy hh:mm:ss.SSSa");
String str3 = dateTime.toString("dd-MM-yyyy HH:mm:ss");
String str4 = dateTime.toString("EEEE dd MMMM, yyyy HH:mm:ssa");
String str5 = dateTime.toString("MM/dd/yyyy HH:mm ZZZZ");
String str6 = dateTime.toString("MM/dd/yyyy HH:mm Z");
DateTimeFormatter format = DateTimeFormat .forPattern("yyyy-MM-dd HH:mm:ss");
//时间解析
DateTime dateTime2 = DateTime.parse("2012-12-21 23:22:45", format);
//时间格式化,输出==> 2012/12/21 23:22:45 Fri
String string_u = dateTime2.toString("yyyy/MM/dd HH:mm:ss EE");
System.out.println(string_u);
//格式化带Locale,输出==> 2012年12月21日 23:22:45 星期五
String string_c = dateTime2.toString("yyyy年MM月dd日 HH:mm:ss EE",Locale.CHINESE);
System.out.println(string_c);
DateTime dt1 = new DateTime();// 取得当前时间
// 根据指定格式,将时间字符串转换成DateTime对象,这里的格式和上面的输出格式是一样的
DateTime dt2 = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss").parseDateTime("2012-12-26 03:27:39");
//计算两个日期间隔的天数
LocalDate start=new LocalDate(2012, 12,14);
LocalDate end=new LocalDate(2013, 01, 15);
int days = Days.daysBetween(start, end).getDays();
//计算两个日期间隔的小时数,分钟数,秒数
//增加日期
DateTime dateTime1 = DateTime.parse("2012-12-03");
dateTime1 = dateTime1.plusDays(30);
dateTime1 = dateTime1.plusHours(3);
dateTime1 = dateTime1.plusMinutes(3);
dateTime1 = dateTime1.plusMonths(2);
dateTime1 = dateTime1.plusSeconds(4);
dateTime1 = dateTime1.plusWeeks(5);
dateTime1 = dateTime1.plusYears(3);
// Joda-time 各种操作.....
dateTime = dateTime.plusDays(1) // 增加天
.plusYears(1)// 增加年
.plusMonths(1)// 增加月
.plusWeeks(1)// 增加星期
.minusMillis(1)// 减分钟
.minusHours(1)// 减小时
.minusSeconds(1);// 减秒数
//判断是否闰月
DateTime dt4 = new DateTime();
org.joda.time.DateTime.Property month = dt4.monthOfYear();
System.out.println("是否闰月:" + month.isLeap());
//取得 3秒前的时间
DateTime dt5 = dateTime1.secondOfMinute().addToCopy(-3);
dateTime1.getSecondOfMinute();// 得到整分钟后,过的秒钟数
dateTime1.getSecondOfDay();// 得到整天后,过的秒钟数
dateTime1.secondOfMinute();// 得到分钟对象,例如做闰年判断等使用
// DateTime与java.util.Date对象,当前系统TimeMillis转换
DateTime dt6 = new DateTime(new Date());
Date date = dateTime1.toDate();
DateTime dt7 = new DateTime(System.currentTimeMillis());
dateTime1.getMillis();
Calendar calendar = Calendar.getInstance();
dateTime = new DateTime(calendar);
}
关于OOP和设计模式
接口是什么?为什么要使用接口而不直接使用具体类?
接口用于定义API。它定义了类必须得准寻的规则,同时,它提供了一种抽象,因为客户端只使用接口,这样就可以有多种实现,如list接口,你可以使用可以随机访问的ArrayList也可以使用方便插入和删除的LinkedList。在java8中你可以在接口声明静态的默认方法,这种方法是具体的
解释一下里式替换原则
什么情况下会违反迪米特法则?为什么会有这个问题?
适配器模式是什么?什么时候使用?
适配器模式提供对接口的转换,如果你的客户端使用某些接口,但是你有另外一些接口,你就可以写一个适配器去链接这些接口
构造器注入和setter注入那种方式更好?
每种方式都有缺点和优点.构造器注入保证所有的注入都被初始化。但是setter注入提供更好的灵活性来设置可选依赖,如果使用xml来描述依赖,Setter注入的可读写性会更强,
依赖注入和工厂模式之间有什么不同
虽然这两种模式都是将对象的创建从应用的逻辑中分离,但是依赖注入比工厂模式更加清晰,通过依赖注入,你的类就是POJO它只知道依赖而不关心他们怎么获取。使用工厂模式你的类需要通过工厂来获取依赖,因此使用DI会比使用工厂模式更加容易测试
适配器模式和装饰器模式有什么区别?
虽然适配器模式和装饰器模式的结构类似,但是每种模式的出现意图不同,适配器模式是用来桥接两个接口,而装饰模式的目的是在不修改类的情况下给类添加功能
适配器模式和代理模式有什么不同?
这个问题与前面的类似,适配器模式和代理模式区别也是在于他们的意图不同,由于适配器模式和代理模式都是封装真正执行动作的类,因此结构是一致的,但是适配器模式用于接口之间的转换,而代理模式则是增加一个额外的中间层,以便支持分配,控制,或者智能访问
什么是模板方法模式?
模板方法提供算法的框架,你可以自己去配置或者定义步骤,例如,你可以将排序栓发看作是一个模板,它定义了排序的步骤,但是具体的比较,可以使用Comparable或者其他语言中类似东西。列出算法的该要的方法就是众所周知的模板方法
什么时候使用访问者模式?
访问者模式用于解决在类的继承层次上增加操作,但是不直接与之关联,这种模式采用双派发的形式来增加中间层
什么时候使用组合模式?
组合模式使用树形结构来展示部分与整体继承关系。它允许客户端采用统一的形式来对待但和对象和对象容器,当你想要展示对象这种部分与整体的关系时采用组合模式
继承和组合之间有什么不同?
虽然两种都可以实现代码复用,但是组合比继承更灵活,用组合允许你在代码运行时选择不同的实现,用组合实现的代码也比继承测试起来更加简单。
Java中嵌套公共静态类与顶级类有什么不同?
类的内部可以有多个嵌套公共静态类,但是一个java源文件只能有一个顶级公共类,并且顶级公共类的名称与源文件的名称必须一样。
OOP 中的组合 聚合 和 关联有什么区别?
如果两个对象彼此有关系,就说他们是彼此关联的,组合和聚合是面向对象中的两种形式的关联。组合是一种比聚合更加强力的关联。组合中,一个对象是另一个对象的拥有者,而聚合是一个对象使用另一个对象。如果对象A 是由对象B 组合的则A不存在的话B一定不存在,但是如果A对象聚合了一个对象B,即使A不存在了,B也可以单独存在
提供一个符合开闭原则的设计模式的例子
开闭原则要求你的代码对扩展开放,对修改关闭。这个意思就是说,如果你想增加一个新的功能,你可以很容易的在不改变已测试过的代码的前提下增加新的代码。有好几个设计模式是基于开闭原则的,如策略模式,如果你需要一个新的策略,只需要实现接口,增加配置,不需要改变核心逻辑。一个正在工作的例子是Collection.sort()方法,这就是基于策略模式,准循开闭原则的,你不需要为新的对象修改sort方法,你需要做的仅仅是实现你自己的Comparator接口
什么时候使用享元模式?
享元模式通过共享对象来避免创建太多的对象,为了使用享元模式,你需要确保你的对象是不可变的,这样你才能安全的共享,JDK中的String池,Integer池以及Long池都是很好的使用了享元模式的例子
Java中关于XML的面试题,JDBC面试题,正则表达式,Java错误以及序列化方面的问题
- 你能写出一个正则表达式来判断一个字符串是否是一个数字吗?
Java中 受检查异常 和 不受检查异常的区别
受检查异常编译器在编译期间检查,对于这种异常,方法强制处理通过throws子句声明。其中一种情况是Exception的子类但是不是RuntimeException的子类,非受检查是RuntimeException的子类,在编译阶段不接受编译器的检查
Java中,Serializable和Externalizable的区别
Serializable 接口是一个序列化的Java类的接口,以便于他们可以在网络上传输或者可以将他们的状态保存在磁盘上,JVM内嵌的默认序列化方式成本高,脆弱而且不安全,Externalizable允许你控制整个序列化过程,指定特定的二进制格式,增加安全机制
Java中DOM和SAX解析器有什么不同?
DOM解析器将整个XML文档加载到内存来创建一颗DOM模型树,这样可以更快的查找节点和修改XML结构,而SAX解析器是一个基于时间的解析器,不会将整个XML文档加载到内存。由于这个原因DOM比SAX更快但是也要求更高的内存,不适合于解析大XML文件
JDK 1.7中的三个新特性
try-with-resource语句,这样你在使用流或者使用资源的时候就不需要手动关闭,Java会自动关闭。
Fork-Join池某种情况下实现了Java版的Map-reduce
允许Switch中有String变量
<>操作符用于类型推断,不再需要在变量声明的右边声明泛型。因此可以写出读写更强,更简洁的代码。
另一个值得一题的就是改善一场处理,可以允许在同一个catch块中捕获多个异常
JDK 1.8中引入的新特性
Lambda表达式,允许像对象一样传递匿名函数
Stream API, 充分利用现代多核CPU,可以写出很简介的代码
Date TimeAPI 有一个稳定,简单的日期和时间库可供使用
-扩展方法,现在接口中可以有静态,默认的方法。
-重复注解。你可以将相同的注解在同一类型上使用多次