- 前言:
在遨游了一番 Java Web 的世界以后,发现了本身的一些缺失,因此就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大部份内容参照自这一篇文章,有一些本身补充的,也算是从新学习一下 Java 吧。javascript
前序文章连接:css
对于 Java 各个版本的特性,特别是 Java 8 的新知识点,咱们都应该有所了解。
前排申明和好文推荐:闪烁之狐 » Java5新特性及使用 » Java6新特性及使用 » Java7新特性及使用 » Java8新特性及使用(一) » Java8新特性及使用(二)面试
参考文章:jdk 1.5新特性算法
答:加强 for 循环:foreach 语句,foreach 简化了迭代器。sql
格式:// 加强for循环括号里写两个参数,第一个是声明一个变量,第二个就是须要迭代的容器shell
for( 元素类型 变量名 : Collection集合 & 数组 ) { … }
语法:
for ( type 变量名:集合变量名 ) { … }
数据库
注意事项:
高级for循环和传统for循环的区别:
高级for循环在使用时,必需要明确被遍历的目标。这个目标,能够是Collection集合或者数组,若是遍历Collection集合,在遍历过程当中还须要对元素进行操做,好比删除,须要使用迭代器。
若是遍历数组,还须要对数组元素进行操做,建议用传统for循环由于能够定义角标经过角标操做元素。若是只为遍历获取,能够简化成高级for循环,它的出现为了简化书写。比起普通的for循环,高级for循环还有性能优点,由于它对数组索引的边界值只计算一次(摘自《Effective Java》第46条)。
高级for循环能够遍历map集合吗?
答:原则上map集合是没法使用加强for循环来迭代的,由于加强for循环只能针对实现了Iterable接口的集合进行迭代;Iterable是jdk5中新定义的接口,就一个方法iterator方法,只有实现了Iterable接口的类,才能保证必定有iterator方法,java有这样的限定是由于加强for循环内部仍是用迭代器实现的,而实际上,咱们能够经过某种方式来使用加强for循环。
for(Object obj : map.entrySet()) { Map.Entry entry = (Entry) obj; // obj 依次表示Entry System.out.println(entry.getKey() + "=" + entry.getValue()); }
总之,for-each 循环在简洁性和预防 Bug 方面有着传统 for 循环没法比拟的优点,而且没有性能损失。应该尽量地使用 for-each 循环。遗憾的是,有三种常见的状况是没法使用 for-each 循环的:
过滤——若是须要遍历集合,并删除选定的元素,就须要使用显式地迭代器,以即可以调用它的 remove 方法。
转换——若是须要遍历列表或者数组,并取代它部分或者所有的元素值(增删、或对元素进行赋值),就须要列表迭代器或者数组索引,以便设定元素的值
平行迭代——若是须要并行地遍历多个集合,就须要显式地控制迭代器或者所因变量以便全部迭代器或者索引变量均可以获得同步前移
解析:什么意思呢?举个例子:在 JDK 1.5 以前,当咱们要为一个传递多个类型相同的参数时,咱们有两种方法解决,1.直接传递一个数组过去,2.有多少个参数就传递多少个参数。
例如:
public void printColor(String red,String green,String yellow){ } // 或者 public void printColor(String[] colors){ }
这样编写方法参数虽然可以实现咱们想要的效果,可是,这样是否是有点麻烦呢?再者,若是参数个数不肯定,咱们怎么办呢?Java JDK1.5为咱们提供的可变参数就可以完美的解决这个问题
答:
可变参数(...):用到函数的参数上,当要操做的同一个类型元素个数不肯定的时候,但是用这个方式,这个参数能够接受任意个数的同一类型的数据。
和之前接收数组不同的是:
之前定义数组类型,须要先建立一个数组对象,再将这个数组对象做为参数传递给函数。如今,直接将数组中的元素做为参数传递便可。底层实际上是将这些元素进行数组的封装,而这个封装动做,是在底层完成的,被隐藏了。因此简化了用户的书写,少了调用者定义数组的动做。
若是在参数列表中使用了可变参数,可变参数必须定义在参数列表结尾(也就是必须是最后一个参数,不然编译会失败。)。
若是要获取多个int数的和呢?可使用将多个int数封装到数组中,直接对数组求和便可。
可变参数的特色:
Public int add(int x, int... args){//也能够直接(int..args)就是说传不传均可以 Int sum = x; For(int i = 0; i<=args.lengrth;i++){ Sum+=args[i]; } return sum; }
实例:
public class VariableParameter { public static void main(String[] args) { System. out.println(add(1, 2)); System. out.println(add(1, 2, 3)); } public static int add(int x, int... args){ int sum = x; for(int i = 0; i < args.length; i++){ sum += args[i]; } return sum; } }
解析:关键字 enum
答:
问题:对象的某个属性的值不能是任意的,必须为固定的一组取值其中的某一个;
解决办法:
1)在 setGrade 方法中作判断,不符合格式要求就抛出异常;
2)直接限定用户的选择,经过自定义类模拟枚举的方式来限定用户的输入,写一个 Grade 类,私有构造函数,对外提供 5 个静态的常量表示类的实例;
3)jdk5 中新定义了枚举类型,专门用于解决此类问题;
4)枚举就是一个特殊的java类,能够定义属性、方法、构造函数、实现接口、继承类;
为何要有枚举?
问题:要定义星期几或性别的变量,该怎么定义?假设用1-7分别表示星期一到星期日,但有人可能会写成int weekday = 0;或即便使用常量方式也没法阻止意外。
枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,不然,编译器就会报错。枚举可让编译器在编译时就能够控制源程序中填写的非法值,普通变量的方式在开发阶段没法实现这一目标。
用普通类如何实现枚举功能,定义一个Weekday的类来模拟枚举功能。
一、私有的构造方法。
二、每一个元素分别用一个公有的静态成员变量表示。
能够有若干公有方法或抽象方法。采用抽象方法定义nextDay就将大量的if.else语句转移成了一个个独立的类
示例:定义一个Weekday的类来模拟枚举功能。
public class WeekDay { private WeekDay(){} public static final WeekDay SUN = new WeekDay(); public static final WeekDay MON = new WeekDay(); public WeekDay nextDay(){ if(this == SUN){ return MON ; } else{ return SUN ; } } public String toString(){ return this == SUN? "SUN":"MON" ; } } public class EnumTest { public static void main(String[] args) { WeekDay day = WeekDay.MON; System. out.println(day.nextDay()); //结果:SUN } }
使用枚举类实现
public class EnumTest { public static void main(String[] args) { WeekDay day = WeekDay.FRI; System.out.println(day); //结果:FRI System.out.println(day.name()); //结果:FRI System.out.println(day.ordinal()); //结果:5 System.out.println(WeekDay. valueOf("SUN")); //结果:SUN System.out.println(WeekDay. values().length); //结果:7 } public enum WeekDay{ SUN,MON ,TUE,WED, THI,FRI ,SAT; } }
总结: 枚举是一种特殊的类,其中的每一个元素都是该类的一个实例对象,例如能够调用WeekDay.SUN.getClass().getName 和 WeekDay.class.getName()。
注意: 最后一个枚举元素后面能够加分号,也能够不加分号。
实现带有构造方法的枚举
示例:
public class EnumTest { public static void main(String[] args) { WeekDay day = WeekDay.FRI; } public enum WeekDay{ SUN(1),MON (),TUE, WED,THI ,FRI,SAT; private WeekDay(){ System. out.println("first" ); } private WeekDay(int value){ System. out.println("second" ); } //结果: //second //first //first //first //first //first //first } }
实现带有抽象方法的枚举
定义枚举TrafficLamp,实现抽象的nextTrafficLamp方法:每一个元素分别是由枚举类的子类来生成的实例对象,这些子类采用相似内部类的方式进行定义。增长上表示时间的构造方法。
public class EnumTest { public static void main(String[] args) { TrafficLamp lamp = TrafficLamp.RED; System.out.println(lamp.nextLamp()); //结果:GREEN } public enum TrafficLamp { RED(30) { public TrafficLamp nextLamp() { return GREEN; } }, GREEN(45) { public TrafficLamp nextLamp() { return YELLOW; } }, YELLOW(5) { public TrafficLamp nextLamp() { return RED; } }; private int time; private TrafficLamp(int time) { this.time = time; } public abstract TrafficLamp nextLamp(); } }
注意:
一、枚举只有一个成员时,就能够做为一种单例的实现方式。
二、查看生成的class文件,能够看到内部类对应的class文件。
答:在 Java 中数据类型分为两种:基本数据类型、引用数据类型(对象)
自动装箱:把基本类型变成包装器类型,本质是调用包装器类型的valueOf()方法
注意:基本数据类型的数组与包装器类型数组不能互换
在 java程序中全部的数据都须要当作对象来处理,针对8种基本数据类型提供了包装类,以下:
int → Integer
byte → Byte
short → Short
long → Long
char → Character
double → Double
float → Float
boolean → Boolean
在 jdk 1.5 之前基本数据类型和包装类之间须要相互转换:
基本---引用 Integer x = new Integer(x);
引用---基本 int num = x.intValue();
1)Integer x = 1; x = x + 1;
经历了什么过程?装箱→拆箱→装箱
2)为了优化,虚拟机为包装类提供了缓冲池,Integer池的大小为 -128~127 一个字节的大小。String池:Java 为了优化字符串操做也提供了一个缓冲池;
→ 享元模式(Flyweight Pattern):享元模式的特色是,复用咱们内存中已经存在的对象,下降系统建立对象实例。
自动装箱:
Integer num1 = 12;
自动拆箱:
System.out.println(num1 + 12);
基本数据类型的对象缓存:
Integer num1 = 12; Integer num2 = 12; System.out.println(num1 == num2);//ture
Integer num3 = 129; Integer num4 = 129; System.out.println(num3 == num4);//false
Integer num5 = Integer.valueOf(12); Integer num6 = Integer.valueOf(12); System.out.println(num5 == num6);//true
示例:
public class AutoBox { public static void main(String[] args) { //装箱 Integer iObj = 3; //拆箱 System. out.println(iObj + 12); //结果:15 Integer i1 = 13; Integer i2 = 13; System. out.println(i1 == i2); //结果:true i1 = 137; i2 = 137; System. out.println(i1 == i2); //结果:false } }
注意:
若是有不少很小的对象,而且他们有相同的东西,那就能够把他们做为一个对象。
若是还有不少不一样的东西,那就能够做为外部的东西,做为参数传入。
这就是享元设计模式(flyweight)。
例如示例中的Integer对象,在-128~127范围内的Integer对象,用的频率比较高,就会做为同一个对象,所以结果为true。超出这个范围的就不是同一个对象,所以结果为false。
答:引用泛型以后,容许指定集合里元素的类型,免去了强制类型转换,而且能在编译时刻进行类型检查的好处。Parameterized Type做为参数和返回值,Generic是vararg、annotation、enumeration、collection的基石。
泛型能够带来以下的好处总结以下:
答:静态导入:导入了类中的全部静态成员,简化静态成员的书写。
import语句能够导入一个类或某个包中的全部类
import static语句导入一个类中的某个静态方法或全部静态方法
import static java.util.Collections.*; //导入了Collections类中的全部静态成员
静态导入能够导入静态方法,这样就没必要写类名而能够直接调用静态方法了。
例子:
原来的:
public class Demo12 { public static void main(String[] args) { System.out.println(Math.max(12, 15)); System. out.println(Math.abs(3-6)); } }
使用静态导入的:
import static java.lang.Math.max ; import static java.lang.Math.abs ; public class Demo12 { public static void main(String[] args) { System.out.println(max(12, 15)); System. out.println(abs(3-6)); } }
注意:
一、也能够经过import static java.lang.Math.*;导入Math类下全部的静态方法。
二、若是将javac设置为了Java5如下,那么静态导入等jdk1.5的特性都会报告错误。
答: 最主要的就是引入了 java.util.concurrent 包,这个都是须要重点掌握的。
HashMap 的替代者 ConcurrentHashMap 和 ArrayList 的替代者 CopyOnWriteArrayList 在大并发量读取时采用 java.util.concurrent 包里的一些类会让你们满意 BlockingQueue、Callable、Executor、Semaphore
答:是 Java 语言对 Bean 类属性、事件的一种缺省处理方法。例如类 A 中有属性 name , 那咱们经过 getName,setName 来获得其值或者设置新的值。经过 getName/setName 来访问name属性,这就是默认的规则。Java 中提供了一套 API 用来访问某个属性的 getter /setter 方法,经过这些 API 可使你不须要了解这个规则(但你最好仍是要搞清楚),这些 API 存放于包 java.beans 中。
通常的作法是经过类 Introspector 来获取某个对象的 BeanInfo 信息,而后经过 BeanInfo 来获取属性的描述器 (PropertyDescriptor),经过这个属性描述器就能够获取某个属性对应的 getter/setter 方法,而后咱们就能够经过反射机制来 调用这些方法。
答:
注解(Annotation)是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符,它是一种由JSR-175标准选择用来描述元数据的一种工具。Java从Java5开始引入了注解。在注解出现以前,程序的元数据只是经过java注释和javadoc,可是注解提供的功能要远远超过这些。注解不只包含了元数据,它还能够做用于程序运行过程当中、注解解释器能够经过注解决定程序的执行顺序。
好比,下面这段代码:
@Override public String toString() { return "This is String."; }
上面的代码中,我重写了toString()方法并使用了@Override注解。可是,即便咱们不使用@Override注解标记代码,程序也可以正常执行。那么,该注解表示什么?这么写有什么好处吗?事实上,@Override告诉编译器这个方法是一个重写方法(描述方法的元数据),若是父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法。若是我不当心拼写错误,例如将toString()写成了toStrring(){double r},并且我也没有使用@Override注解,那程序依然能编译运行。但运行结果会和我指望的大不相同。如今咱们了解了什么是注解,而且使用注解有助于阅读程序。
为何要引入注解?
使用注解以前(甚至在使用以后),XML被普遍的应用于描述元数据。不知什么时候开始一些应用开发人员和架构师发现XML的维护愈来愈糟糕了。他们但愿使用一些和代码紧耦合的东西,而不是像XML那样和代码是松耦合的(在某些状况下甚至是彻底分离的)代码描述。若是你在Google中搜索“XML vs. annotations”,会看到许多关于这个问题的辩论。最有趣的是XML配置其实就是为了分离代码和配置而引入的。上述两种观点可能会让你很疑惑,二者观点彷佛构成了一种循环,但各有利弊。下面咱们经过一个例子来理解这二者的区别。
假如你想为应用设置不少的常量或参数,这种状况下,XML是一个很好的选择,由于它不会同特定的代码相连。若是你想把某个方法声明为服务,那么使用注解会更好一些,由于这种状况下须要注解和方法紧密耦合起来,开发人员也必须认识到这点。
另外一个很重要的因素是注解定义了一种标准的描述元数据的方式。在这以前,开发人员一般使用他们本身的方式定义元数据。例如,使用标记接口,注释,transient关键字等等。每一个程序员按照本身的方式定义元数据,而不像注解这种标准的方式。
目前,许多框架将XML和Annotation两种方式结合使用,平衡二者之间的利弊。
参考文章(更多注解戳这里):Java注解的理解和应用
答:
ProcessBuilder
类是 Java5 在 java.lang
包中新添加的一个新类,此类用于建立操做系统进程,它提供一种启动和管理进程(也就是应用程序)的方法。在此以前,都是由 Process
类处来实现进程的控制管理。每一个 ProcessBuilder
实例管理一个进程属性集。它的 start()
方法利用这些属性建立一个新的 Process
实例。start()
方法能够从同一实例重复调用,以利用相同的或相关的属性建立新的子进程。
ProcessBuilder
是一个 final
类,有两个带参数的构造方法,你能够经过构造方法来直接建立 ProcessBuilder
的对象。而 Process
是一个抽象类,通常都经过 Runtime.exec()
和 ProcessBuilder.start()
来间接建立其实例。ProcessBuilder
为进程提供了更多的控制,例如,能够设置当前工做目录,还能够改变环境参数。而 Process
类的功能相对来讲简单的多。ProcessBuilder
类不是同步的。若是多个线程同时访问一个 ProcessBuilder
,而其中至少一个线程从结构上修改了其中一个属性,它必须保持外部同步。
若要使用 ProcessBuilder
建立一个进程,只须要建立 ProcessBuilder
的一个实例,指定该进程的名称和所需参数。要执行此程序,调用该实例上的 start()
便可。下面是一个执行打开 Windows 记事本的例子。注意它将要编辑的文件名指定为一个参数。
class PBDemo { public static void main(String args[]) { try { ProcessBuilder proc = new ProcessBuilder("notepad.exe", "testfile"); proc.start(); } catch (Exception e) { System.out.println("Error executing notepad."); } } }
参考文章:Java5新特性及使用
Formatter
类是Java5中新增的 printf-style
格式化字符串的解释器,它提供对布局和对齐的支持,提供了对数字,字符串和日期/时间数据的经常使用格式以及特定于语言环境的输出。常见的 Java 类型,如 byte
,java.math.BigDecimal
和 java.util.Calendar
都支持。 经过 java.util.Formattable
接口提供了针对任意用户类型的有限格式定制。
更详细的介绍见这里。主要使用方法的代码示例以下:
import java.io.BufferedReader; import java.io.FileReader; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 格式化测试使用的示例类. * * @author blinkfox on 2017-11-28. */ public class FormatTester { private static final Logger log = LoggerFactory.getLogger(FormatTester.class); /** * 格式化. */ private static void formatter() { StringBuilder sb = new StringBuilder(); Formatter formatter = new Formatter(sb, Locale.US); // 可从新排序输出. formatter.format("%n%4$2s %3$2s %2$2s %1$2s %n", "a", "b", "c", "d"); // -> " d c b a" formatter.format(Locale.FRANCE, "e = %+10.4f", Math.E); // -> "e = +2,7183" formatter.format("%nAmount gained or lost since last statement: $ %(,.2f", 6217.58); // -> "Amount gained or lost since last statement: $ 6,217.58" log.info("打印出格式化后的字符串:{}", formatter); formatter.close(); } /** * printf打印. */ private static void printf() { String filename = "testfile"; try (FileReader fileReader = new FileReader(filename)) { BufferedReader reader = new BufferedReader(fileReader); String line; int i = 1; while ((line = reader.readLine()) != null) { System.out.printf("Line %d: %s%n", i++, line); } } catch (Exception e) { System.err.printf("Unable to open file named '%s': %s", filename, e.getMessage()); } } /** * stringFormat使用. */ private static void stringFormat() { // 格式化日期. Calendar c = new GregorianCalendar(1995, Calendar.MAY, 23); String s = String.format("Duke's Birthday: %1$tm %1$te,%1$tY", c); // -> s == "Duke's Birthday: May 23, 1995" log.info(s); } /** * 格式化消息. */ private static void messageFormat() { String msg = "欢迎光临,当前({0})等待的业务受理的顾客有{1}位,请排号办理业务!"; MessageFormat mf = new MessageFormat(msg); String fmsg = mf.format(new Object[]{new Date(), 35}); log.info(fmsg); } /** * 格式化日期. */ private static void dateFormat() { String str = "2010-1-10 17:39:21"; SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss"); try { log.info("格式化后的日期:{}", format.format(format.parse(str))); } catch (Exception e) { log.error("日期格式化出错!", e); } } public static void main(String[] args) { formatter(); stringFormat(); messageFormat(); dateFormat(); printf(); } }
参考文章:Java5新特性及使用
java.util.Scanner
是 Java5 的新特征,主要功能是简化文本扫描,但这个类最实用的地方仍是在获取控制台输入。
(1).Scanner概述
能够从字符串(Readable
)、输入流、文件、Channel等来直接构造Scanner对象,有了Scanner了,就能够逐段(根据正则分隔式)来扫描整个文本,并对扫描后的结果作想要的处理。
Scanner
默认使用空格做为分割符来分隔文本,但容许你使用 useDelimiter(Pattern pattern)
或 useDelimiter(String pattern)
方法来指定新的分隔符。
主要API以下:
delimiter()
: 返回此 Scanner
当前正在用于匹配分隔符的 Pattern
。hasNext()
: 判断扫描器中当前扫描位置后是否还存在下一段。hasNextLine()
: 若是在此扫描器的输入中存在另外一行,则返回true。next()
: 查找并返回来自此扫描器的下一个完整标记。nextLine()
: 此扫描器执行当前行,并返回跳过的输入信息。(2).扫描控制台输入
当经过 new Scanner(System.in)
建立了一个 Scanner
实例时,控制台会一直等待输入,直到敲回车键结束,把所输入的内容传给 Scanner
,做为扫描对象。若是要获取输入的内容,则只须要调用 Scanner
的 nextLine()
方法便可。
/** * 扫描控制台输入. * * @author blinkfox 2017-11-28 */ public class ScannerTest { public static void main(String[] args) { Scanner s = new Scanner(System.in); System.out.println("请输入字符串:"); while (true) { String line = s.nextLine(); if (line.equals("exit")) break; System.out.println(">>>" + line); } } }
(3).其它示例
该示例中会从 myNumbers
文件中读取长整型 long
的数据。
Scanner sc = new Scanner(new File("myNumbers")); while (sc.hasNextLong()) { long aLong = sc.nextLong(); }
如下示例可使用除空格以外的分隔符来从一个字符串中读取几个条目:
String input = "1 fish 2 fish red fish blue fish"; Scanner s = new Scanner(input).useDelimiter("\\s*fish\\s*"); System.out.println(s.nextInt()); System.out.println(s.nextInt()); System.out.println(s.next()); System.out.println(s.next()); s.close();
将输出:
1 2 red blue
参考文章:Java5新特性及使用
StringBuilder
也是 Java5 中新增的类,主要用来代替 +
号和 StringBuffer
来更加高效的拼接字符串。StringBuffer
与 StringBuilder
都是继承于 AbstractStringBuilder
,主要的区别就是 StringBuffer
的函数上都有 synchronized
关键字,保证线程安全。
关于 StringBuilder
的使用这里就再也不详细介绍了,网上文章也有不少。总之,对于动态字符串的拼接推荐使用 StringBuilder
。静态字符串的拼接直接使用 +
号或者字符串的 concat(String str)
方法,甚至也使用 StringBuilder
亦可。
参考文章:Java5新特性及使用
关于 JDK 1.6 的新特性,了解一下就能够了...若是有兴趣深刻研究的童鞋,右转这里:Java6新特性及使用
答:
在JDK6中 ,AWT新增长了两个类:Desktop 和 SystemTray 。
前者能够用来打开系统默认浏览器浏览指定的 URL,打开系统默认邮件客户端给指定的邮箱发邮件,用默认应用程序打开或编辑文件(好比,用记事本打开以txt为后缀名的文件),用系统默认的打印机打印文档;
后者能够用来在系统托盘区建立一个托盘程序.
答:
JAXB是Java Architecture for XML Binding的缩写,能够将一个Java对象转变成为XML格式,反之亦然。
咱们把对象与关系数据库之间的映射称为ORM, 其实也能够把对象与XML之间的映射称为OXM(Object XML Mapping). 原来JAXB是Java EE的一部分,在JDK6中,SUN将其放到了Java SE中,这也是SUN的一向作法。
JDK6中自带的这个JAXB版本是2.0, 比起1.0(JSR 31)来,JAXB2(JSR 222)用JDK5的新特性Annotation来标识要做绑定的类和属性等,这就极大简化了开发的工做量。
实际上,在Java EE 5.0中,EJB和Web Services也经过Annotation来简化开发工做。另外,JAXB2在底层是用StAX(JSR 173)来处理XML文档。除了JAXB以外,咱们还能够经过XMLBeans和Castor等来实现一样的功能。
答:
StAX(JSR 173)是JDK6.0中除了DOM和SAX以外的又一种处理XML文档的API。
StAX 的来历 :在JAXP1.3(JSR 206)有两种处理XML文档的方法:DOM(Document Object Model)和SAX(Simple API for XML).
由 于JDK6.0中的JAXB2(JSR 222)和JAX-WS 2.0(JSR 224)都会用到StAX,因此Sun决定把StAX加入到JAXP家族当中来,并将JAXP的版本升级到1.4(JAXP1.4是JAXP1.3的维护版本). JDK6里面JAXP的版本就是1.4. 。
StAX是The Streaming API for XML的缩写,一种利用拉模式解析(pull-parsing)XML文档的API.StAX经过提供一种基于事件迭代器(Iterator)的API让 程序员去控制xml文档解析过程,程序遍历这个事件迭代器去处理每个解析事件,解析事件能够看作是程序拉出来的,也就是程序促使解析器产生一个解析事件,而后处理该事件,以后又促使解析器产生下一个解析事件,如此循环直到碰到文档结束符;
SAX也是基于事件处理xml文档,但倒是用推模式解析,解析器解析完整个xml文档后,才产生解析事件,而后推给程序去处理这些事件;DOM 采用的方式是将整个xml文档映射到一颗内存树,这样就能够很容易地获得父节点和子结点以及兄弟节点的数据,但若是文档很大,将会严重影响性能。
答:
如今咱们能够用JDK6 的Compiler API(JSR 199)去动态编译Java源文件,Compiler API结合反射功能就能够实现动态的产生Java代码并编译执行这些代码,有点动态语言的特征。
这个特性对于某些须要用到动态编译的应用程序至关有用,好比JSP Web Server,当咱们手动修改JSP后,是不但愿须要重启Web Server才能够看到效果的,这时候咱们就能够用Compiler API来实现动态编译JSP文件,固然,如今的JSP Web Server也是支持JSP热部署的,如今的JSP Web Server经过在运行期间经过Runtime.exec或ProcessBuilder来调用javac来编译代码,这种方式须要咱们产生另外一个进程去 作编译工做,不够优雅并且容易使代码依赖与特定的操做系统;Compiler API经过一套易用的标准的API提供了更加丰富的方式去作动态编译,并且是跨平台的。
答:
JDK6 提供了一个简单的Http Server API,据此咱们能够构建本身的嵌入式Http Server,它支持Http和Https协议,提供了HTTP1.1的部分实现,没有被实现的那部分能够经过扩展已有的Http Server API来实现,程序员必须本身实现HttpHandler接口,HttpServer会调用HttpHandler实现类的回调方法来处理客户端请求,在 这里,咱们把一个Http请求和它的响应称为一个交换,包装成HttpExchange类,HttpServer负责将HttpExchange传给 HttpHandler实现类的回调方法.
答:
插入式注解处理API(JSR 269)提供一套标准API来处理Annotations(JSR 175)
实 际上JSR 269不只仅用来处理Annotation,我以为更强大的功能是它创建了Java 语言自己的一个模型,它把method, package, constructor, type, variable, enum, annotation等Java语言元素映射为Types和Elements(二者有什么区别?), 从而将Java语言的语义映射成为对象, 咱们能够在javax.lang.model包下面能够看到这些类. 因此咱们能够利用JSR 269提供的API来构建一个功能丰富的元编程(metaprogramming)环境.
JSR 269用Annotation Processor在编译期间而不是运行期间处理Annotation, Annotation Processor至关于编译器的一个插件,因此称为插入式注解处理.若是Annotation Processor处理Annotation时(执行process方法)产生了新的Java代码,编译器会再调用一次Annotation Processor,若是第二次处理还有新代码产生,就会接着调用Annotation Processor,直到没有新代码产生为止.每执行一次process()方法被称为一个"round",这样整个Annotation processing过程能够看做是一个round的序列.
JSR 269主要被设计成为针对Tools或者容器的API. 举个例子,咱们想创建一套基于Annotation的单元测试框架(如TestNG),在测试类里面用Annotation来标识测试期间须要执行的测试方法。
JDK6 中提供了java.io.Console 类专用来访问基于字符的控制台设备. 你的程序若是要与Windows下的cmd或者Linux下的Terminal交互,就能够用Console类代劳. 但咱们不老是能获得可用的Console, 一个JVM是否有可用的Console依赖于底层平台和JVM如何被调用. 若是JVM是在交互式命令行(好比Windows的cmd)中启动的,而且输入输出没有重定向到另外的地方,那么就能够获得一个可用的Console实例.
如: ruby, groovy, javascript.
Common annotations
本来是Java EE 5.0(JSR 244)规范的一部分,如今SUN把它的一部分放到了Java SE 6.0中.随着Annotation元数据功能(JSR 175)加入到Java SE 5.0里面,不少Java 技术(好比EJB,Web Services)都会用Annotation部分代替XML文件来配置运行参数(或者说是支持声明式编程,如EJB的声明式事务), 若是这些技术为通用目的都单独定义了本身的Annotations,显然有点重复建设, 因此,为其余相关的Java技术定义一套公共的Annotation是有价值的,能够避免重复建设的同时,也保证Java SE和Java EE 各类技术的一致性。
从 JDK6 开始,JDK 目录中新增了一个名为 db
的目录。这即是 Java 6 的新成员:Java DB。这是一个纯 Java 实现、开源的数据库管理系统(DBMS),源于 Apache 软件基金会(ASF)名下的项目 Derby
。它只有 2MB 大小,对比动辄上 G 的数据库来讲可谓袖珍。但这并不妨碍 Derby 功能齐备,支持几乎大部分的数据库应用所须要的特性。JDK6.0里面带的这个Derby的版本是10.2.1.7,支持存储过程和触发器;有两种运行模式,一种是做为嵌入式数据库,另外一种是做为网络数据库。前者的数据库服务器和客户端都在同一个JVM里面运行,后者容许数据库服务器端和客户端不在同一个JVM里面,并且容许这二者在不一样的物理机器上。值得注意的是JDK6里面的这个Derby支持JDK6的新特性 JDBC 4.0
规范(JSR 221)。
在 Java SE 6 所提供的诸多新特性和改进中,值得一提的是为 Java 程序提供数据库访问机制的 JDBC 版本升级到了 4.0, 这个以 JSR-221 为代号的版本,提供了更加便利的代码编写机制及柔性,而且支持更多的数据类型。JDBC4.0 主要有如下改进和新特性。
java.sql.Driver
,而不须要再调用 class.forName
;java.sql.RowId
数据类型用来能够访问 sql rowid
;National Character Set
的支持;BLOB
和 CLOB
的支持功能;SQL/XML
和 XML
支持;Wrapper Pattern
;SQLException
加强;Connection
和 Statement
接口加强;New Scalar Funtions
;JDBC API changes
。以前已经写过一篇详细介绍 Java 7 特性的文章了,这里就直接黏了:Java 7新特性
类型判断是一我的特殊的烦恼,入下面的代码:
Map<String,List<String>> anagrams = new HashMap<String,List<String>>();
经过类型推断后变成:
Map<String,List<String>> anagrams = new HashMap<>();
注:这个<>被叫作diamond(钻石)运算符,Java 7后这个运算符从引用的声明中推断类型。
switch语句可使用原始类型或枚举类型。Java引入了另外一种类型,咱们能够在switch语句中使用:字符串类型。
说咱们有一个根据其地位来处理贸易的要求。直到如今,咱们使用if-其余语句来完成这个任务。
private voidprocessTrade(Trade t){ String status = t.getStatus(); if(status.equalsIgnoreCase(NEW)) { newTrade(t); } else if(status.equalsIgnoreCase(EXECUTE)) { executeTrade(t); } else if(status.equalsIgnoreCase(PENDING)) { pendingTrade(t); } }
这种处理字符串的方法是粗糙的。在Java中,咱们可使用加强的switch语句来改进程序,该语句以String类型做为参数。
public voidprocessTrade(Trade t) { String status = t.getStatus(); switch(status) { caseNEW: newTrade(t); break; caseEXECUTE: executeTrade(t); break; casePENDING: pendingTrade(t); break; default: break; } }
在上面的程序中,状态字段老是经过使用 String.equals() 与案例标签来进行比较。
Java中有一些资源须要手动关闭,例如Connections,Files,Input/OutStreams等。一般咱们使用 try-finally 来关闭资源:
public voidoldTry() { try{ fos= newFileOutputStream("movies.txt"); dos= newDataOutputStream(fos); dos.writeUTF("Java 7 Block Buster"); } catch(IOException e) { e.printStackTrace(); } finally{ try{ fos.close(); dos.close(); } catch(IOException e) { // log the exception } } }
然而,在Java 7中引入了另外一个很酷的特性,能够自动管理资源。它的操做也很简单,咱们所要作的就是在 try 块中申明资源以下:
try(resources_to_be_cleant){ // your code }
以上方法与旧的 try-finally 能最终写成下面的代码:
public voidnewTry() { try(FileOutputStream fos = newFileOutputStream("movies.txt"); DataOutputStream dos = newDataOutputStream(fos)) { dos.writeUTF("Java 7 Block Buster"); } catch(IOException e) { // log the exception } }
上面的代码也表明了这个特性的另外一个方面:处理多个资源。FileOutputStream 和 DataOutputStream 在try语句中一个接一个地含在语句中,每个都用分号(;)分隔符分隔开。咱们没必要手动取消或关闭流,由于当空间存在try块时,它们将自动关闭。
在后台,应该自动关闭的资源必须试验 java.lang.AutoCloseable 接口。
任何实现 AutoCloseable 接口的资源均可以做为自动资源管理的候选。AutoCloseable 是 java.io.Closeable 接口的父类,JVM会在程序退出try块后调用一个方法 close()。
数字文字绝对是对眼睛的一种考验。我相信,若是你给了一个数字,好比说,十个零,你就会像我同样数零。若是不计算从右到左的位置,识别一个文字的话,就很容易出错,并且很麻烦。Not anymore。Java在识别位置时引入了下划线。例如,您能够声明1000,以下所示:
int thousand = 1_000;
或1000000(一百万)以下:
int million = 1_000_000
请注意,这个版本中也引入了二进制文字-例如“0b1”-所以开发人员没必要再将它们转换为十六进制。
在异常处理区域有几处改进。Java引入了多个catch功能,以使用单个抓到块捕获多个异常类型。
假设您有一个方法,它抛出三个异常。在当前状态下,您将分别处理它们,以下所示:
public voidoldMultiCatch() { try{ methodThatThrowsThreeExceptions(); } catch(ExceptionOne e) { // log and deal with ExceptionOne } catch(ExceptionTwo e) { // log and deal with ExceptionTwo } catch(ExceptionThree e) { // log and deal with ExceptionThree } }
在一个catch块中逐个捕获一个连续的异常,看起来很混乱。我还看到了捕获十几个异常的代码。这是很是低效和容易出错的。Java为解决这只丑小鸭带来了新的语言变化。请参阅下面的方法oldMultiCatch方法的改进版本:
public voidnewMultiCatch() { try{ methodThatThrowsThreeExceptions(); } catch(ExceptionOne | ExceptionTwo | ExceptionThree e) { // log and deal with all Exceptions } }
多个异常经过使用 “|” 操做符在一个catch块中捕获。这样,您没必要编写数十个异常捕获。可是,若是您有许多属于不一样类型的异常,那么您也可使用“多个catch块”块。下面的代码片断说明了这一点:
public voidnewMultiMultiCatch() { try{ methodThatThrowsThreeExceptions(); } catch(ExceptionOne e) { // log and deal with ExceptionOne } catch(ExceptionTwo | ExceptionThree e) { // log and deal with ExceptionTwo and ExceptionThree } }
在上面的例子中,在和ExceptionThree属于不一样的层次结构,所以您但愿以不一样的方式处理它们,但使用一个抓到块。
那些使用Java的人可能还记得框架引发的头痛。在操做系统或多文件系统之间无缝地工做历来都不是一件容易的事情.。有些方法,例如删除或重命名,在大多数状况下都是出乎意料的。使用符号连接是另外一个问题。实质上API须要大修。
为了解决上述问题,Java引入了一个新的API,并在许多状况下引入了新的api。
在NIO2.0提出了许多加强功能。在处理多个文件系统时,它还引入了新的类来简化开发人员的生活。
Working With Path(使用路径):
新的 java.nio.file 由包和接口组成例如:Path,Paths,FileSystem,FileSystems等等。
路径只是对文件路径的简单引用。它与java.io.File等价(并具备更多的特性)。下面的代码段显示了如何获取对“临时”文件夹的路径引用:
public voidpathInfo() { Path path= Paths.get("c:\Temp\temp"); System.out.println("Number of Nodes:"+ path.getNameCount()); System.out.println("File Name:"+ path.getFileName()); System.out.println("File Root:"+ path.getRoot()); System.out.println("File Parent:"+ path.getParent()); }
最终控制台的输出将是:
Number of Nodes:2 File Name:temp.txt File Root:c: File Parent:c:Temp
删除文件或目录就像在文件中调用delete方法(注意复数)同样简单。在类公开两个删除方法,一个抛出NoSuchFileException,另外一个不抛。
下面的delete方法调用抛出NoSuchFileException,所以您必须处理它:
Files.delete(path);
Where as Files.deleteIfExists(path) does not throw exception (as expected) if the file/directory does not exist.
使用 Files.deteleIfExists(path) 则不会抛出异常。
您可使用其余实用程序方法,例如Files.copy(.)和Files.move(.)来有效地对文件系统执行操做。相似地,使用 createSymbolicLink(..) 方法使用代码建立符号连接。
文件更改通知:
JDK 7中最好的改善算是File change notifications(文件更改通知)了。这是一个长期等待的特性,它最终被刻在NIO 2.0中。WatchService API 容许您在对主题(目录或文件)进行更改时接收通知事件。
具体的建立步骤就不给了,总之它的功能就跟它的名字通常,当文件发生更改的时候,能及时做出反馈。
在一个 Java 程序中有效地使用并行内核一直是一个挑战。不多有国内开发的框架将工做分配到多个核心,而后加入它们来返回结果集。Java已经将这个特性做为Fork/Join框架结合了起来。
基本上,在把手头的任务变成了小任务,直到小任务简单到能够不进一步分手的状况下解决。这就像一个分而治之的算法.。在这个框架中须要注意的一个重要概念是,理想状况下,没有工做线程是空闲的。他们实现了一个 work-stealing 算法,在空闲的工人“偷”工做从那些工人谁是忙。
支持Fork-Join机制的核心类是 ForkJoinPool和ForkJoinTask。
什么是Fork/Join框架:
Java7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每一个小任务结果后获得大任务结果的框架。
Fork/Join的运行流程图以下:
工做窃取算法:
工做窃取(work-stealing)算法是指某个线程从其余队列里窃取任务来执行。工做窃取的运行流程图以下:
工做窃取算法的优势是充分利用线程进行并行计算,并减小了线程间的竞争,其缺点是在某些状况下仍是存在竞争,好比双端队列里只有一个任务时。而且消耗了更多的系统资源,好比建立多个线程和多个双端队列。
Fork/Join框架使用示例:
让咱们经过一个简单的需求来使用下 Fork/Join
框架,需求是:计算1 + 2 + 3 + 4
的结果。
使用 Fork/Join
框架首先要考虑到的是如何分割任务,若是咱们但愿每一个子任务最多执行两个数的相加,那么咱们设置分割的阈值是2
,因为是4
个数字相加,因此 Fork/Join
框架会把这个任务 fork
成两个子任务,子任务一负责计算1 + 2
,子任务二负责计算3 + 4
,而后再 join
两个子任务的结果。
由于是有结果的任务,因此必须继承 RecursiveTask
,实现代码以下:
import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.Future; import java.util.concurrent.RecursiveTask; /** * CountTask. * * @author blinkfox on 2018-01-03. */ public class CountTask extends RecursiveTask<Integer> { /** 阈值. */ public static final int THRESHOLD = 2; /** 计算的开始值. */ private int start; /** 计算的结束值. */ private int end; /** * 构造方法. * * @param start 计算的开始值 * @param end 计算的结束值 */ public CountTask(int start, int end) { this.start = start; this.end = end; } /** * 执行计算的方法. * * @return int型结果 */ @Override protected Integer compute() { int sum = 0; // 若是任务足够小就计算任务. if ((end - start) <= THRESHOLD) { for (int i = start; i <= end; i++) { sum += i; } } else { // 若是任务大于阈值,就分裂成两个子任务来计算. int middle = (start + end) / 2; CountTask leftTask = new CountTask(start, middle); CountTask rightTask = new CountTask(middle + 1, end); // 等待子任务执行完,并获得结果,再合并执行结果. leftTask.fork(); rightTask.fork(); sum = leftTask.join() + rightTask.join(); } return sum; } /** * main方法. * * @param args 数组参数 */ public static void main(String[] args) throws ExecutionException, InterruptedException { ForkJoinPool fkPool = new ForkJoinPool(); CountTask task = new CountTask(1, 4); Future<Integer> result = fkPool.submit(task); System.out.println("result:" + result.get()); } }
参考文章:Java7新特性及使用
这里是Java 7的新特性一览表:http://www.oschina.net/news/20119/new-features-of-java-7
关于 Java 8 中新知识点,面试官会让你说说 Java 8 你了解多少,下面分享一下我收集的 Java 8 新增的知识点的内容,前排申明引用自:Java8新特性及使用
Java 8用默认方法与静态方法这两个新概念来扩展接口的声明。与传统的接口又有些不同,它容许在已有的接口中添加新方法,而同时又保持了与旧版本代码的兼容性。
1.接口默认方法
默认方法与抽象方法不一样之处在于抽象方法必需要求实现,可是默认方法则没有这个要求。相反,每一个接口都必须提供一个所谓的默认实现,这样全部的接口实现者将会默认继承它(若是有必要的话,能够覆盖这个默认实现)。让咱们看看下面的例子:
private interface Defaulable { // Interfaces now allow default methods, the implementer may or // may not implement (override) them. default String notRequired() { return "Default implementation"; } } private static class DefaultableImpl implements Defaulable { } private static class OverridableImpl implements Defaulable { @Override public String notRequired() { return "Overridden implementation"; } }
Defaulable
接口用关键字 default
声明了一个默认方法 notRequired()
,Defaulable
接口的实现者之一 DefaultableImpl
实现了这个接口,而且让默认方法保持原样。Defaulable
接口的另外一个实现者 OverridableImpl
用本身的方法覆盖了默认方法。
1.1 多重继承的冲突说明:
因为同一个方法能够从不一样的接口引入,天然而然的会有冲突的现象,规则以下:
public interface A { default void hello() { System.out.println("Hello A"); } }
public interface B extends A { default void hello() { System.out.println("Hello B"); } }
public class C implements A, B { public static void main(String[] args) { new C().hello(); // 输出 Hello B } }
1.2 优缺点:
1.3 接口默认方法不能重载Object类的任何方法:
接口不能提供对Object类的任何方法的默认实现。简单地讲,每个java类都是Object的子类,也都继承了它类中的 equals()/hashCode()/toString()
方法,那么在类的接口上包含这些默认方法是没有意义的,它们也历来不会被编译。
在 JVM 中,默认方法的实现是很是高效的,而且经过字节码指令为方法调用提供了支持。默认方法容许继续使用现有的Java接口,而同时可以保障正常的编译过程。这方面好的例子是大量的方法被添加到 java.util.Collection
接口中去:stream()
,parallelStream()
,forEach()
,removeIf()
等。尽管默认方法很是强大,可是在使用默认方法时咱们须要当心注意一个地方:在声明一个默认方法前,请仔细思考是否是真的有必要使用默认方法。
2.接口静态方法
Java 8 带来的另外一个有趣的特性是接口能够声明(而且能够提供实现)静态方法。在接口中定义静态方法,使用 static
关键字,例如:
public interface StaticInterface { static void method() { System.out.println("这是Java8接口中的静态方法!"); } }
下面的一小段代码是上面静态方法的使用。
public class Main { public static void main(String[] args) { StaticInterface.method(); // 输出 这是Java8接口中的静态方法! } }
Java 支持一个实现类能够实现多个接口,若是多个接口中存在一样的 static
方法会怎么样呢?若是有两个接口中的静态方法如出一辙,而且一个实现类同时实现了这两个接口,此时并不会产生错误,由于Java8中只能经过接口类调用接口中的静态方法,因此对编译器来讲是能够区分的。
Lambda
表达式(也称为闭包)是整个Java 8发行版中最受期待的在Java语言层面上的改变,Lambda容许把函数做为一个方法的参数(即:行为参数化,函数做为参数传递进方法中)。
一个 Lambda
能够由用逗号分隔的参数列表、–>
符号与函数体三部分表示。
首先看看在老版本的Java中是如何排列字符串的:
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia"); Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return b.compareTo(a); } });
只须要给静态方法 Collections.sort
传入一个List对象以及一个比较器来按指定顺序排列。一般作法都是建立一个匿名的比较器对象而后将其传递给sort方法。 在Java 8 中你就不必使用这种传统的匿名对象的方式了,Java 8提供了更简洁的语法,lambda表达式:
Collections.sort(names, (String a, String b) -> {
return b.compareTo(a); });
看到了吧,代码变得更短且更具备可读性,可是实际上还能够写得更短:
Collections.sort(names, (String a, String b) -> b.compareTo(a));
对于函数体只有一行代码的,你能够去掉大括号{}以及return关键字,可是你还能够写得更短点:
Collections.sort(names, (a, b) -> b.compareTo(a));
Java编译器能够自动推导出参数类型,因此你能够不用再写一次类型。
更多 Lambda 表达式的示例在这里:Java8 lambda表达式10个示例
Lambda
表达式是如何在 Java 的类型系统中表示的呢?每个Lambda表达式都对应一个类型,一般是接口类型。而函数式接口是指仅仅只包含一个抽象方法的接口,每个该类型的Lambda表达式都会被匹配到这个抽象方法。由于默认方法不算抽象方法,因此你也能够给你的函数式接口添加默认方法。
咱们能够将Lambda表达式看成任意只包含一个抽象方法的接口类型,确保你的接口必定达到这个要求,你只须要给你的接口添加 @FunctionalInterface
注解,编译器若是发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。
示例以下:
@FunctionalInterface interface Converter<F, T> { T convert(F from); } Converter<String, Integer> converter = (from) -> Integer.valueOf(from); Integer converted = converter.convert("123"); System.out.println(converted); // 123
注意: 若是
@FunctionalInterface
若是没有指定,上面的代码也是对的。
更多参考: Java 8——Lambda表达式、Java8新特性及使用
1.概述:
在学习了Lambda表达式以后,咱们一般使用Lambda表达式来建立匿名方法。然而,有时候咱们仅仅是调用了一个已存在的方法。以下:
Arrays.sort(strArray, (s1, s2) -> s1.compareToIgnoreCase(s2));
在Java8中,咱们能够直接经过方法引用来简写Lambda表达式中已经存在的方法。
Arrays.sort(strArray, String::compareToIgnoreCase);
这种特性就叫作方法引用(Method Reference
)。
方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。方法引用提供了一种引用而不执行方法的方式,它须要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会建立函数式接口的一个实例。当Lambda表达式中只是执行一个方法调用时,不用Lambda表达式,直接经过方法引用的形式可读性更高一些。方法引用是一种更简洁易懂的Lambda表达式。
注意: 方法引用是一个Lambda表达式,其中方法引用的操做符是双冒号::。
2.分类:
方法引用的标准形式是:类名::方法名
。(注意:只须要写方法名,不须要写括号)
有如下四种形式的方法引用:
3.示例:
使用示例以下:
public class Person { String name; LocalDate birthday; public Person(String name, LocalDate birthday) { this.name = name; this.birthday = birthday; } public LocalDate getBirthday() { return birthday; } public static int compareByAge(Person a, Person b) { return a.birthday.compareTo(b.birthday); } @Override public String toString() { return this.name; } }
public class MethodReferenceTest { @Test public static void main() { Person[] pArr = new Person[] { new Person("003", LocalDate.of(2016,9,1)), new Person("001", LocalDate.of(2016,2,1)), new Person("002", LocalDate.of(2016,3,1)), new Person("004", LocalDate.of(2016,12,1)) }; // 使用匿名类 Arrays.sort(pArr, new Comparator<Person>() { @Override public int compare(Person a, Person b) { return a.getBirthday().compareTo(b.getBirthday()); } }); //使用lambda表达式 Arrays.sort(pArr, (Person a, Person b) -> { return a.getBirthday().compareTo(b.getBirthday()); }); //使用方法引用,引用的是类的静态方法 Arrays.sort(pArr, Person::compareByAge); } }
Java8添加的
Stream API(java.util.stream)
把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,由于Stream API
能够极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。使用 Steam 写出来的代码真的能让人兴奋,这里链出以前的一篇文章:Java 8——函数式数据处理(流)
流能够是无限的、有状态的,能够是顺序的,也能够是并行的。在使用流的时候,你首先须要从一些来源中获取一个流,执行一个或者多个中间操做,而后执行一个最终操做。中间操做包括filter
、map
、flatMap
、peel
、distinct
、sorted
、limit
和 substream
。终止操做包括 forEach
、toArray
、reduce
、collect
、min
、max
、count
、anyMatch
、allMatch
、noneMatch
、findFirst
和 findAny
。 java.util.stream.Collectors
是一个很是有用的实用类。该类实现了不少归约操做,例如将流转换成集合和聚合元素。
1.一些重要方法说明:
stream
: 返回数据流,集合做为其源parallelStream
: 返回并行数据流, 集合做为其源filter
: 方法用于过滤出知足条件的元素map
: 方法用于映射每一个元素对应的结果forEach
: 方法遍历该流中的每一个元素limit
: 方法用于减小流的大小sorted
: 方法用来对流中的元素进行排序anyMatch
: 是否存在任意一个元素知足条件(返回布尔值)allMatch
: 是否全部元素都知足条件(返回布尔值)noneMatch
: 是否全部元素都不知足条件(返回布尔值)collect
: 方法是终端操做,这是一般出如今管道传输操做结束标记流的结束2.一些使用示例:
2.1 Filter 过滤:
stringCollection .stream() .filter((s) -> s.startsWith("a")) .forEach(System.out::println);
2.2 Sort 排序:
stringCollection .stream() .sorted() .filter((s) -> s.startsWith("a")) .forEach(System.out::println);
2.3 Map 映射:
stringCollection .stream() .map(String::toUpperCase) .sorted((a, b) -> b.compareTo(a)) .forEach(System.out::println);
2.4 Match 匹配:
boolean anyStartsWithA = stringCollection .stream() .anyMatch((s) -> s.startsWith("a")); System.out.println(anyStartsWithA); // true boolean allStartsWithA = stringCollection .stream() .allMatch((s) -> s.startsWith("a")); System.out.println(allStartsWithA); // false boolean noneStartsWithZ = stringCollection .stream() .noneMatch((s) -> s.startsWith("z")); System.out.println(noneStartsWithZ); // true
2.5 Count 计数:
long startsWithB = stringCollection .stream() .filter((s) -> s.startsWith("b")) .count(); System.out.println(startsWithB); // 3
2.6 Reduce 规约:
Optional<String> reduced = stringCollection
.stream()
.sorted()
.reduce((s1, s2) -> s1 + "#" + s2); reduced.ifPresent(System.out::println);
到目前为止,臭名昭著的空指针异常是致使Java应用程序失败的最多见缘由。之前,为了解决空指针异常,Google公司著名的Guava
项目引入了Optional
类,Guava经过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional
类已经成为Java 8类库的一部分。
Optional
其实是个容器:它能够保存类型T的值,或者仅仅保存null。Optional
提供不少有用的方法,这样咱们就不用显式进行空值检测。
咱们下面用两个小例子来演示如何使用Optional类:一个容许为空值,一个不容许为空值。
Optional<String> fullName = Optional.ofNullable(null); System.out.println("Full Name is set? " + fullName.isPresent()); System.out.println("Full Name: " + fullName.orElseGet(() -> "[none]")); System.out.println(fullName.map(s -> "Hey " + s + "!").orElse("Hey Stranger!"));
若是Optional
类的实例为非空值的话,isPresent()
返回true
,否从返回false
。为了防止Optional为空值,orElseGet()
方法经过回调函数来产生一个默认值。map()
函数对当前Optional
的值进行转化,而后返回一个新的Optional
实例。orElse()
方法和orElseGet()
方法相似,可是orElse
接受一个默认值而不是一个回调函数。下面是这个程序的输出:
Full Name is set? false Full Name: [none] Hey Stranger!
让咱们来看看另外一个例子:
Optional<String> firstName = Optional.of("Tom"); System.out.println("First Name is set? " + firstName.isPresent()); System.out.println("First Name: " + firstName.orElseGet(() -> "[none]")); System.out.println(firstName.map(s -> "Hey " + s + "!").orElse("Hey Stranger!")); System.out.println();
下面是程序的输出:
First Name is set? true First Name: Tom Hey Tom!
Java 8 在包java.time下包含了一组全新的时间日期API。新的日期API和开源的Joda-Time库差很少,但又不彻底同样,下面的例子展现了这组新API里最重要的一些部分:
1.Clock 时钟:
Clock
类提供了访问当前日期和时间的方法,Clock是时区敏感的,能够用来取代System.currentTimeMillis()
来获取当前的微秒数。某一个特定的时间点也可使用Instant
类来表示,Instant
类也能够用来建立老的java.util.Date
对象。代码以下:
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis(); Instant instant = clock.instant(); Date legacyDate = Date.from(instant); // legacy java.util.Date
2.Timezones 时区:
在新API中时区使用ZoneId
来表示。时区能够很方便的使用静态方法of
来获取到。时区定义了到UTS时间的时间差,在Instant
时间点对象到本地日期对象之间转换的时候是极其重要的。代码以下:
System.out.println(ZoneId.getAvailableZoneIds());
// prints all available timezone ids ZoneId zone1 = ZoneId.of("Europe/Berlin"); ZoneId zone2 = ZoneId.of("Brazil/East"); System.out.println(zone1.getRules()); System.out.println(zone2.getRules()); // ZoneRules[currentStandardOffset=+01:00] // ZoneRules[currentStandardOffset=-03:00]
3.LocalTime 本地时间:
LocalTime
定义了一个没有时区信息的时间,例如 晚上10点,或者 17:30:15。下面的例子使用前面代码建立的时区建立了两个本地时间。以后比较时间并以小时和分钟为单位计算两个时间的时间差。代码以下:
LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);
System.out.println(now1.isBefore(now2)); // false long hoursBetween = ChronoUnit.HOURS.between(now1, now2); long minutesBetween = ChronoUnit.MINUTES.between(now1, now2); System.out.println(hoursBetween); // -3 System.out.println(minutesBetween); // -239
LocalTime
提供了多种工厂方法来简化对象的建立,包括解析时间字符串。代码以下:
LocalTime late = LocalTime.of(23, 59, 59); System.out.println(late); // 23:59:59 DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedTime(FormatStyle.SHORT) .withLocale(Locale.GERMAN); LocalTime leetTime = LocalTime.parse("13:37", germanFormatter); System.out.println(leetTime); // 13:37
4.LocalDate 本地日期:
LocalDate表示了一个确切的日期,好比2014-03-11。该对象值是不可变的,用起来和LocalTime基本一致。下面的例子展现了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的,操做返回的老是一个新实例。代码以下:
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS); LocalDate yesterday = tomorrow.minusDays(2); LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4); DayOfWeek dayOfWeek = independenceDay.getDayOfWeek(); System.out.println(dayOfWeek); // FRIDAY
从字符串解析一个LocalDate类型和解析LocalTime同样简单。代码以下:
DateTimeFormatter germanFormatter = DateTimeFormatter
.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.GERMAN);
LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter); System.out.println(xmas); // 2014-12-24
5.LocalDateTime 本地日期时间:
LocalDateTime
同时表示了时间和日期,至关于前两节内容合并到一个对象上了。LocalDateTime
和LocalTime
还有LocalDate
同样,都是不可变的。LocalDateTime
提供了一些能访问具体字段的方法。代码以下:
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59); DayOfWeek dayOfWeek = sylvester.getDayOfWeek(); System.out.println(dayOfWeek); // WEDNESDAY Month month = sylvester.getMonth(); System.out.println(month); // DECEMBER long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY); System.out.println(minuteOfDay); // 1439
只要附加上时区信息,就能够将其转换为一个时间点Instant
对象,Instant
时间点对象能够很容易的转换为老式的java.util.Date
。代码以下:
Instant instant = sylvester
.atZone(ZoneId.systemDefault())
.toInstant();
Date legacyDate = Date.from(instant);
System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014
格式化LocalDateTime
和格式化时间和日期同样的,除了使用预约义好的格式外,咱们也能够本身定义格式。代码以下:
DateTimeFormatter formatter =
DateTimeFormatter
.ofPattern("MMM dd, yyyy - HH:mm"); LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter); String string = formatter.format(parsed); System.out.println(string); // Nov 03, 2014 - 07:13
和java.text.NumberFormat
不同的是新版的DateTimeFormatter
是不可变的,因此它是线程安全的。
关于Java8中日期API更多的使用示例能够参考Java 8中关于日期和时间API的20个使用示例。
自从Java 5引入了注解机制,这一特性就变得很是流行而且广为使用。然而,使用注解的一个限制是相同的注解在同一位置只能声明一次,不能声明屡次。Java 8打破了这条规则,引入了重复注解机制,这样相同的注解能够在同一地方声明屡次。
重复注解机制自己必须用@Repeatable
注解。事实上,这并非语言层面上的改变,更多的是编译器的技巧,底层的原理保持不变。让咱们看一个快速入门的例子:
import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; public class RepeatingAnnotations { @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Filters { Filter[] value(); } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Repeatable(Filters.class) public @interface Filter { String value(); }; @Filter("filter1") @Filter("filter2") public interface Filterable { } public static void main(String[] args) { for(Filter filter: Filterable.class.getAnnotationsByType(Filter.class)) { System.out.println(filter.value()); } } }
正如咱们看到的,这里有个使用@Repeatable(Filters.class)
注解的注解类Filter
,Filters
仅仅是Filter
注解的数组,但Java编译器并不想让程序员意识到Filters
的存在。这样,接口Filterable
就拥有了两次Filter
(并无提到Filter
)注解。
同时,反射相关的API提供了新的函数getAnnotationsByType()
来返回重复注解的类型(请注意Filterable.class.getAnnotation(Filters.class)
经编译器处理后将会返回Filters的实例)。
Java 8扩展了注解的上下文。如今几乎能够为任何东西添加注解:局部变量、泛型类、父类与接口的实现,就连方法的异常也能添加注解。下面演示几个例子:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Collection; public class Annotations { @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) public @interface NonEmpty { } public static class Holder<@NonEmpty T> extends @NonEmpty Object { public void method() throws @NonEmpty Exception { } } @SuppressWarnings("unused") public static void main(String[] args) { final Holder<String> holder = new @NonEmpty Holder<String>(); @NonEmpty Collection<@NonEmpty String> strings = new ArrayList<>(); } }
在Java 8中,Base64编码已经成为Java类库的标准。它的使用十分简单,下面让咱们看一个例子:
import java.nio.charset.StandardCharsets; import java.util.Base64; public class Base64s { public static void main(String[] args) { final String text = "Base64 finally in Java 8!"; final String encoded = Base64.getEncoder().encodeToString(text.getBytes(StandardCharsets.UTF_8)); System.out.println(encoded); final String decoded = new String(Base64.getDecoder().decode(encoded), StandardCharsets.UTF_8); System.out.println(decoded); } }
程序在控制台上输出了编码后的字符与解码后的字符:
QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!
Base64类同时还提供了对URL、MIME友好的编码器与解码器(Base64.getUrlEncoder() / Base64.getUrlDecoder()
, Base64.getMimeEncoder() / Base64.getMimeDecoder()
)。
JavaFX
是一个强大的图形和多媒体处理工具包集合,它容许开发者来设计、建立、测试、调试和部署富客户端程序,而且和Java同样跨平台。从Java8开始,JavaFx已经内置到了JDK中。关于JavaFx更详细的文档可参考JavaFX中文文档。
Java8中,HashMap内部实现又引入了红黑树(数组+链表+红黑树),使得HashMap的整体性能相较于Java7有比较明显的提高。
区别:
引用自文章:Java 9 中的 9 个新特性、Java 9 新特性概述——IBM、【译】使用示例带你提早了解 Java 9 中的新特性
Oracle 公司(Java Library 开发者)新引进一个表明 Java Shell 的称之为 “jshell” 或者 REPL(Read Evaluate Print Loop)的新工具。该工具能够被用来执行和测试任何 Java 中的结构,如 class,interface,enum,object,statements 等。使用很是简单。
JDK 9 EA(Early Access)下载地址:https://jdk9.java.net/download/
G:\>jshell
| Welcome to JShell -- Version 9-ea
| For an introduction type: /help intro jshell> int a = 10 a ==> 10 jshell> System.out.println("a value = " + a ) a value = 10
一般,您但愿在代码中建立一个集合(例如,List 或 Set ),并直接用一些元素填充它。 实例化集合,几个 “add” 调用,使得代码重复。 Java 9,添加了几种集合工厂方法:
Set<Integer> ints = Set.of(1, 2, 3); List<String> strings = List.of("first", "second");
除了更短和更好阅读以外,这些方法也能够避免您选择特定的集合实现。 事实上,从工厂方法返回已放入数个元素的集合实现是高度优化的。这是可能的,由于它们是不可变的:在建立后,继续添加元素到这些集合会致使 “UnsupportedOperationException” 。
在 Java 8 中,咱们能够在接口中使用默认或者静态方法提供一些实现方式,可是不能建立私有方法。
为了不冗余代码和提升重用性,Oracle 公司准备在 Java SE 9 接口中引入私有方法。也就是说从 Java SE 9 开始,咱们也可以在接口类中使用 ‘private’ 关键字写私有化方法和私有化静态方法。
接口中的私有方法与 class 类中的私有方法在写法上并没有差别,如:
public interface Card{ private Long createCardID(){ // Method implementation goes here. } private static void displayCardDetails(){ // Method implementation goes here. } }
这里只给出解决的问题,仅限了解....
Java 9 的定义功能是一套全新的模块系统。当代码库愈来愈大,建立复杂,盘根错节的“意大利面条式代码”的概率呈指数级的增加。这时候就得面对两个基础的问题: 很难真正地对代码进行封装, 而系统并无对不一样部分(也就是 JAR 文件)之间的依赖关系有个明确的概念。每个公共类均可以被类路径之下任何其它的公共类所访问到, 这样就会致使无心中使用了并不想被公开访问的 API。此外,类路径自己也存在问题: 你怎么知晓全部须要的 JAR 都已经有了, 或者是否是会有重复的项呢? 模块系统把这俩个问题都给解决了。
Java SE 9 迎来一些 Process API 的改进,经过添加一些新的类和方法来优化系统级进程的管控。
Process API 中的两个新接口:
Process API 示例
ProcessHandle currentProcess = ProcessHandle.current();
System.out.println("Current Process Id: = " + currentProcess.getPid());
咱们知道,Java SE 7 引入了一个新的异常处理结构:Try-With-Resources
,来自动管理资源。这个新的声明结构主要目的是实现“Automatic Better Resource Management”(“自动资源管理”)。
Java SE 9 将对这个声明做出一些改进来避免一些冗长写法,同时提升可读性。
Java SE 7 示例
void testARM_Before_Java9() throws IOException { BufferedReader reader1 = new BufferedReader(new FileReader("journaldev.txt")); try (BufferedReader reader2 = reader1) { System.out.println(reader2.readLine()); } }
Java SE 9 示例
void testARM_Java9() throws IOException { BufferedReader reader1 = new BufferedReader(new FileReader("journaldev.txt")); try (reader1) { System.out.println(reader1.readLine()); } }
在 Java SE 9 中,Oracle 公司将改进 CompletableFuture API 来解决一些 Java SE 8 中出现的问题。这些被添加的 API 将用来支持一些延时和超时操做,实用方法和更好的子类化。
Executor exe = CompletableFuture.delayedExecutor(50L, TimeUnit.SECONDS);
这里的 delayedExecutor() 是静态实用方法,用来返回一个在指定延时时间提交任务到默认执行器的新 Executor 对象。
反应式编程的思想最近获得了普遍的流行。 在 Java 平台上有流行的反应式 库 RxJava 和 R eactor。反应式流规范的出发点是提供一个带非阻塞负压( non-blocking backpressure ) 的异步流处理规范。反应式流规范的核心接口已经添加到了 Java9 中的 java.util.concurrent.Flow 类中。
Flow 中包含了 Flow.Publisher、Flow.Subscriber、Flow.Subscription 和 F low.Processor 等 4 个核心接口。Java 9 还提供了 SubmissionPublisher 做为 Flow.Publisher 的一个实现。RxJava 2 和 Reactor 均可以很方便的 与 Flow 类的核心接口进行互操做。
长期以来,Stream API 都是 Java 标准库最好的改进之一。经过这套 API 能够在集合上创建用于转换的申明管道。在 Java 9 中它会变得更好。Stream 接口中添加了 4 个新的方法:dropWhile, takeWhile, ofNullable。还有个 iterate 方法的新重载方法,可让你提供一个 Predicate (判断条件)来指定何时结束迭代:
IntStream.iterate(1, i -> i < 100, i -> i + 1).forEach(System.out::println);
第二个参数是一个 Lambda,它会在当前 IntStream 中的元素到达 100 的时候返回 true。所以这个简单的示例是向控制台打印 1 到 99。
除了对 Stream 自己的扩展,Optional 和 Stream 之间的结合也获得了改进。如今能够经过 Optional 的新方法 stram
将一个 Optional 对象转换为一个(多是空的) Stream 对象:
Stream<Integer> s = Optional.of(1).stream();
在组合复杂的 Stream 管道时,将 Optional 转换为 Stream 很是有用。
Java 9 中有新的方式来处理 HTTP 调用。这个迟到的特性用于代替老旧的 HttpURLConnection
API,并提供对 WebSocket 和 HTTP/2 的支持。注意:新的 HttpClient API 在 Java 9 中以所谓的孵化器模块交付。也就是说,这套 API 不能保证 100% 完成。不过你能够在 Java 9 中开始使用这套 API:
HttpClient client = HttpClient.newHttpClient();
HttpRequest req =
HttpRequest.newBuilder(URI.create("http://www.google.com")) .header("User-Agent","Java") .GET() .build(); HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandler.asString());
HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandler.asString());
除了这个简单的请求/响应模型以外,HttpClient 还提供了新的 API 来处理 HTTP/2 的特性,好比流和服务端推送。
在 Java SE 9 中,Oracle 公司添加了一些新的实用方法到 java.util.Optional
类里面。这里我将使用一些简单的示例来描述其中的一个:stream 方法。
若是一个值出如今给定 Optional 对象中,stream() 方法能够返回包含该值的一个顺序 Stream 对象。不然,将返回一个空 Stream。
stream()
方法已经被添加,并用来在 Optional 对象中使用,如:
Stream<Optional> emp = getEmployee(id) Stream empStream = emp.flatMap(Optional::stream)
这里的 Optional.stream()
方法被用来转化 Employee 可选流对象 到 Employee 流中,如此咱们即可以在后续代码中使用这个结果。
咱们最后要来着重介绍的这个特性对于库的维护者而言是个特别好的消息。当一个新版本的 Java 出现的时候,你的库用户要花费数年时间才会切换到这个新的版本。这就意味着库得去向后兼容你想要支持的最老的 Java 版本 (许多状况下就是 Java 6 或者 7)。这实际上意味着将来的很长一段时间,你都不能在库中运用 Java 9 所提供的新特性。幸运的是,多版本兼容 JAR 功能能让你建立仅在特定版本的 Java 环境中运行库程序时选择使用的 class 版本:
multirelease.jar ├── META-INF │ └── versions │ └── 9 │ └── multirelease │ └── Helper.class ├── multirelease ├── Helper.class └── Main.class
在上述场景中, multirelease.jar 能够在 Java 9 中使用, 不过 Helper 这个类使用的不是顶层的 multirelease.Helper 这个 class, 而是处在“META-INF/versions/9”下面的这个。这是特别为 Java 9 准备的 class 版本,能够运用 Java 9 所提供的特性和库。同时,在早期的 Java 诸版本中使用这个 JAR 也是能运行的,由于较老版本的 Java 只会看到顶层的这个 Helper 类。
欢迎转载,转载请注明出处!转载自@我没有三颗心脏