【笔记】Java版本更新历史(ing)
[笔记系列文章说明]: 该类型的文章是笔者学习过程中整理的学习笔记.
历史版本特性
JDK Version 1.0
开发代号为Oak(橡树),于1996-01-23发行.
JDK Version 1.1
于1997-02-19发行.
引入的新特性包括:
引入JDBC(Java DataBase Connectivity);
支持内部类;
引入Java Bean;
引入RMI(Remote Method Invocation)
引入反射(仅用于内省)
J2SE Version 1.2
开发代号为Playground(操场),于1998-12-08发行.
引入的新特性包括:
引入集合框架;
对字符串常量做内存映射;
引入JIT(Just In Time)编译器
引入对打包的Java文件进行数字签名;
引入控制授权访问系统资源的策略工具;
引入JFC(Java Foundation Classes), 包括Swing 1.0, 拖放和Java2D类库;
引入Java插件;
在JDBC中引入可滚动结果集,BLOB,CLOB,批量更新和用户自定义类型;
在Applet中添加声音支持.
J2SE Version 1.3
开发代号为Kestrel(红隼),于2000-05-08发行.
引入的新特性包括:
引入Java Sound API;
jar文件索引;
对Java的各个方面都做了大量优化和增强.
J2SE Version 1.4
开发代号Merlin(隼),于2004-02-06(首次在JCP下发行).
引入的新特性包括:
XML处理;
Java打印服务;
引入Logging API;
引入Java Web Start;
引入JDBC 3.0 API;
引入断言;
引入Preferences API;
引入链式异常处理;
支持IPV6;
支持正则表达式;
引入Imgae I/O API.
Java Version SE 5.0
开发代号为Tiger(老虎),于2004-09-30发行.
引入的新特性包括:
引入泛型;
增强循环,可以使用迭代方式;
自动装箱与自动拆箱;
类型安全的枚举;
可变参数;
静态引入;
元数据(注解);
引入Instrumentation
Java Version SE 6
开发代号为Mustang(野马),于2006-12-11发行.
引入的新特性包括:
支持脚本语言
引入JDBC 4.0API;
引入Java Compiler API;
可插拔注解;
增加对Native PKI(Public Key Infrastructure), Java GSS(Generic Security Service),Kerberos和LDAP(Lightweight Directory Access Protocol)支持
继承Web Services;
Java Version SE 7
开发代号是Dolphin(海豚),于2011-07-28发行.
引入的新特性包括:
switch语句块中允许以字符串作为分支条件;
在创建泛型对象时应用类型推断;
在一个语句块中捕获多种异常;
支持动态语言;
支持try-with-resources(在一个语句块中捕获多种异常)();
<参考: http://blog.csdn.net/hengyunabc/article/details/18459463>
引入Java NIO.2开发包;
数值类型可以用二进制字符串表示,并且可以在字符串表示中添加下划线;
钻石型语法(在创建泛型对象时应用类型推断);
null值得自动处理.
Java Version SE 8
引入Lambda 表达式;
管道和流;
新的日期和时间 API;
默认的方法;
类型注解;
Nashorn javascript引擎;
并行累加器;
并行操作
内存错误移除
TLS SNI 服务器名称标识(Server Name Identification)
- Lambda Expressions
- Pipelines and Streams
- Date and Time API
- Default Methods
- Type Annotations
- Nashhorn JavaScript Engine
- Concurrent Accumulators
- Parallel operations
- PermGen Error Removed
- TLS SNI
特性详细内容
Java 5
- 泛型(Generics):
引入泛型之后,允许指定集合里元素的类型,免去了强制类型转换,并且能在编译时刻进行类型检查的好处.
Parameterized Type作为范围和返回值,Generic是vararg,annotation,enumeration,collection的基石.
- A, 类型安全
抛弃List,Map,使用List
- B, 方法参数和返回值加上了Type
抛弃List,Map,使用List
- C, 不需要类型转换
List
String str = list.get(1);
- D, 类型通配符”?”
假设一个打印List
代码:
1 | public void printList(List<?> list,PrintStream out)throws IOException{ |
如果通配符?让我们的参数类型过于广泛,我们可以把List>、Iterator> 修改为
List<? Extends Number>、Iterator<? Extends Number>限制一下它。
枚举类型 Enumeration:
自动装箱拆箱( 自动类型包装和解包 ) autoboxing & unboxing:
简单地说是类型自动转换.
自动装包: 基本类型自动转为包装类(int —Integer)
自动拆包: 包装类自动转为基本类型(Integer —int)
- 可变参数varargs(varargs number of arguments)
参数类型相同时, 把重载函数合并到一起了.
如:
1 | public void test(object... objs){ |
- Annotations 它是Java中的metadata(注释)
- A, tiger(代号)终于定义的三种标准annotation
a, Override
指出某个method覆盖了superclass的method当你要覆盖的方法名拼写错时编译不通过
b, Deprecated
指出某个method或element类型的使用是被阻止的,子类将不能覆盖该方法
c, SupressWarnings
关闭class, method, field, variable初始化的编译器警告, 比如: List没有使用Generic, 则@SuppressWarnings(“unchecked”)去掉编译期警告.
- B, 自定义annotation
public @interface Marked{}
- C, meta-annotation
或者说annotation的annotation
四种标准的meta-annotation全部定义在java.lang.annotation包中:
a, Target
指定所定义的annotation可以用在哪些程序单元上
如果Target没有指定, 则表示该annotation可以使用在任意程序单元上
代码:
1 |
|
b, Retention
指出Java编译期如何对待annotation
annotation可以被编译期丢掉,或者在编译过的class文件中
在annotation被保留时,他也指定是否会在JVM加载class时读取该annotation
代码:
1 | @Retention(RetentionPolicy.SOURCE) // Annotation会被编译期丢弃 |
c,Documented
指出被定义的annotation被视为所熟悉的程序单元的公开API之一
被@Documented标注的annotation会在javadoc中显示,这在annotation对它标注的元素被客户端使用时有影响时起作用
d,Inherited
该meta-annotation应用于目标为class的annotation类型上, 被此annotattion标注的class会自动继承父类的annotation
D. Annotation的反射
我们发现java.lang.Class有许多与Annotation的反射相关的方法,如getAnnotations, isAnnotationpresent 我们可以利用Annotation反射来做许多事情, 比如自定义Annotation来做Model对象验证
代码:
1 | @Retention(RetentionPolicy.RUNTIME) |
使用@RejectEmpty和@AcceptInt标注我们的Model的field, 然后利用反射来做Model验证
新的迭代语句 (for(int n:numbers))
静态导入( import satic )
新的格式化方法( java.util.Formatter )
formatter.format(‘“Remaining account balance:$%.2f”,balance’);新的线程模型和并发库Thread Framework
HashMap的替代者ConcurrentHashMap和ArrayList的替代者CopyOnWriteArrayList 在大并发量读取时采用java.util.concurrent包里的一些类会让大家满意BlockingQueue、Callable、Executor、Semaphore…
Java 6
- 引入了一个支持脚本引擎的新框架
- UI的增强
- 对WebService支持的增强(JAX-WS2.0和JAXB2.0)
- 一系列新的安全相关的增强
- JDBC4.0
- Compiler API
- 通用的Annotations支持
Java 7
- switch中可以使用字串了
1 | String s = "test"; |
运用List
tempList = new ArrayList<>(); 即泛型实例化类型自动推断 语法上支持集合,而不一定是数组
final List
- 新增一些获取环境信息的工具方法
1 | File System.getJavaIoTempDir() // IO临时文件夹 |
- Boolean类型反转, 空指针安全, 参与位运算
1 | Boolean Booleans.negate(Boolean booleanObj) |
- 两个char间的equals
1 | boolean Character.equalsIgnoreCase(char ch1, char ch2) |
- 安全的加减乘除
1 | int Math.safeToInt(long value) |
- map集合支持并发请求, 且可以写成Map map = {name:”xxx”,age:18};
Java 8
java8的新特性逐一列出, 并将使用简单的代码示例来知道你如何使用默认接口方法, lambda表达式, 方法引用以及多重Annotation, 之后你将会学到最新的API上的改进,比如流, 函数式接口 Map以及全新的日期API
一, 接口的默认方法
Java 8允许我们给接口添加一个非抽象的方法实现, 只需要使用default关键字即可, 这个特征又叫做扩展方法, 示例如下:
1 | interface Formula{ |
Formula接口在拥有calculate方法之外同时还定义了sqrt方法,实现了Formula接口的子类只需要实现一个calculate方法,默认方法sqrt将在子类上可以直接使用
1 | Formula formula = new Formula() { |
文中的formula被实现为一个匿名类的实例,该代码非常容易理解,6行代码实现了计算 sqrt(a * 100)。在下一节中,我们将会看到实现单方法接口的更简单的做法。译者注: 在Java中只有单继承,如果要让一个类赋予新的特性,通常是使用接口来实现,在C++中支持多继承,允许一个子类同时具有多个父类的接口与功能,在其他 语言中,让一个类同时具有其他的可复用代码的方法叫做mixin。新的Java 8 的这个特新在编译器实现的角度上来说更加接近Scala的trait。 在C#中也有名为扩展方法的概念,允许给已存在的类型扩展方法,和Java 8的这个在语义上有差别。
二, Lambda表达式
首先看看在老版本的Java中是如何排列字符串的:
1 | List<String> names = Arrays.adList("peter","anna","mike","xenia"); |
只需要给静态方法 Collections.sort 传入一个List对象以及一个比较器来按指定顺序排列。通常做法都是创建一个匿名的比较器对象然后将其传递给sort方法。在Java 8 中你就没必要使用这种传统的匿名对象的方式了,Java 8提供了更简洁的语法,lambda表达式:
1 | Collections.sort(names, (String a, String b) -> { |
对于函数体只有一行代码的,你可以去掉大括号{}以及return关键字, 还可以更短
1 | Collections.sort(names, (String a, String b) -> b.compareTo(a)); |
更短
1 | Collections.sort(names, (a, b) -> b.compareTo(a)); |
Java编译器可以自动推导出参数类型,所以你可以不用再写一次类型。接下来我们看看lambda表达式还能作出什么更方便的东西来:
三, 函数式接口
Lambda表达式是如何在java的类型系统中表示的呢?每一个lambda表达式都对应一个类型,通常是接口类型。而“函数式接口”是指仅仅只 包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。因为 默认方法 不算抽象方法,所以你也可以给你的函数式接口添加默认方法。
我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface 注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的
1 | @FunctionalInterface |
需要注意如果@FunctionalInterface如果没有指定,上面的代码也是对的。
译者注 将lambda表达式映射到一个单方法的接口上,这种做法在Java 8之前就有别的语言实现,比如Rhino JavaScript解 释器,如果一个函数参数接收一个单方法的接口而你传递的是一个function,Rhino 解释器会自动做一个单接口的实例到function的适配器,典型的应用场景有 org.w3c.dom.events.EventTarget 的addEventListener 第二个参数 EventListener。
四, 方法与构造函数引用
前一节中的代码还可以通过静态方法引用来表示:
1 | Converter<String, Integer> converter = Integer::valueOf; |
Java 8 允许你使用 :: 关键字来传递方法或者构造函数引用,上面的代码展示了如何引用一个静态方法,我们也可以引用一个对象的方法:
1 | converter = something::startsWith; |
接下来看看构造函数是如何使用::关键字来引用的,首先我们定义一个包含多个构造函数的简单类:
1 | class Person { |
接下来我们指定一个用来创建Person对象的对象工厂接口:
1 | interface PersonFactory<P extends Person> { |
这里我们使用构造函数引用来将他们关联起来,而不是实现一个完整的工厂:
1 | PersonFactory<Person> personFactory = Person::new; |
我们只需要使用 Person::new 来获取Person类构造函数的引用,Java编译器会自动根据PersonFactory.create方法的签名来选择合适的构造函数。
五, Lambda作用域
在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。
六, 访问局部变量
我们可以直接在lambda表达式中访问外层的局部变量:
1 | final int num =1; |
但是和匿名对象不同的是,这里的变量num可以不用声明为final,该代码同样正确:
1 | int num = 1; |
不过这里的num必须不可被后面的代码修改(即隐性的具有final的语义),例如下面的就无法编译:
错误代码:
1 | int num = 1; |
在lambda表达式中试图修改num同样是不允许的。
七, 访问对象字段与静态变量
和本地变量不同的是,lambda内部对于实例的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的:
1 | class Lambda4 { |
八、访问接口的默认方法
Lambda表达式中是无法访问到default修饰的接口方法(java新特性的接口方法)的,以下代码将无法编译:
错误代码:
1 | Formula formula = (a) -> sqrt( a * 100); |
点拨: lambda的使用是建立在函数式接口的基础上, 函数式接口就是只需要实现一个方法的接口,其他的方法可以使用default,或者静态方法实现具体实现
为什么用lambda, 因为书写方便,简洁, 不需要写接口的实现类,然后创建对象,也不需要用匿名类写一堆代码.
JDK 1.8 API包含了很多内建的函数式接口,在老Java中常用到的比如Comparator或者Runnable接口,这些接口都增加了@FunctionalInterface注解以便能用在lambda上。
Java 8 API同样还提供了很多全新的函数式接口来让工作更加方便,有一些接口是来自Google Guava库里的,即便你对这些很熟悉了,还是有必要看看这些是如何扩展到lambda上使用的。
Predicate接口
Predicate 接口只有一个参数,返回boolean类型。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非):
1 | Predicate<String> predicate = (s) -> s.length() > 0; |
Function 接口
Function 接口有一个参数并且返回一个结果,并附带了一些可以和其他函数组合的默认方法(compose, andThen):
1 | Function 接口 |
Supplier 接口
Supplier 接口返回一个任意范型的值,和Function接口不同的是该接口没有任何参数
1 | Supplier<Person> personSupplier = Person::new; |
Consumer 接口
Consumer 接口表示执行在单个参数上的操作。
1 | Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName); |
Comparator 接口
Comparator 是老Java中的经典接口, Java 8在此之上添加了多种默认方法:
1 | Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName); |
Optional 接口
Optional 不是函数是接口,这是个用来防止NullPointerException异常的辅助类型,这是下一届中将要用到的重要概念,现在先简单的看看这个接口能干什么:
Optional 被定义为一个简单的容器,其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是偶尔却可能返回了null,而在Java 8中,不推荐你返回null而是返回Optional。
1 | Optional<String> optional = Optional.of("bam"); |
Stream 接口
java.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。 Stream 的创建需要指定一个数据源,比如 java.util.Collection的子类,List或者Set, Map不支持。Stream的操作可以串行执行或者并行执行。
首先看看Stream是怎么用,首先创建实例代码的用到的数据List:
1 | List<String> stringCollection = new ArrayList<>(); |
Java 8扩展了集合类,可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个Stream。下面几节将详细解释常用的Stream操作:
Filter 过滤
过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如 forEach)。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他 Stream操作。
1 | stringCollection |
Sort 排序
排序是一个中间操作,返回的是排序好后的Stream。如果你不指定一个自定义的Comparator则会使用默认排序。
1 | stringCollection |
需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据stringCollection是不会被修改的:
1 | System.out.println(stringCollection); |
Map 映射
中间操作map会将元素根据指定的Function接口来依次将元素转成另外的对象,下面的示例展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。
1 | stringCollection |
Match 匹配
Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作,并返回一个boolean类型的值。
1 | boolean anyStartsWithA = |
Count 计数
计数是一个最终操作,返回Stream中元素的个数,返回值类型是long。
1 | long startsWithB = |
Reduce 规约
这是一个最终操作,允许通过指定的函数来讲stream中的多个元素规约为一个元素,规越后的结果是通过Optional接口表示的:
1 | Optional<String> reduced = |
并行Streams
前面提到过Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。
下面的例子展示了是如何通过并行Stream来提升性能:
首先我们创建一个没有重复元素的大表:
1 | int max = 1000000; |
然后我们计算一下排序这个Stream要耗时多久,
串行排序:
1 | long t0 = System.nanoTime(); |
// 串行耗时: 899 ms
并行排序:
1 | long t0 = System.nanoTime(); |
// 并行排序耗时: 472 ms
上面两个代码几乎是一样的,但是并行版的快了50%之多,唯一需要做的改动就是将stream()改为parallelStream()。
Map
前面提到过,Map类型不支持stream,不过Map提供了一些新的有用的方法来处理一些日常任务。
1 | Map<Integer, String> map = new HashMap<>(); |
map.forEach((id, val) -> System.out.println(val));
以上代码很容易理解, putIfAbsent 不需要我们做额外的存在性检查,而forEach则接收一个Consumer接口来对map里的每一个键值对进行操作。
下面的例子展示了map上的其他有用的函数:
1 | map.computeIfPresent(3, (num, val) -> val + num); |
接下来展示如何在Map里删除一个键值全都匹配的项:
1 | map.remove(3, "val3"); |
另外一个有用的方法:
1 | map.getOrDefault(42, "not found"); // not found |
对Map的元素做合并也变得很容易了:
map.merge(9, “val9”, (value, newValue) -> value.concat(newValue));
map.get(9); // val9
map.merge(9, “concat”, (value, newValue) -> value.concat(newValue));
map.get(9); // val9concat
Merge做的事情是如果键名不存在则插入,否则则对原键对应的值做合并操作并重新插入到map中。
九、Date API
Java 8 在包java.time下包含了一组全新的时间日期API。新的日期API和开源的Joda-Time库差不多,但又不完全一样,下面的例子展示了这组新API里最重要的一些部分:
Clock 时钟
Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数。某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建老的java.util.Date对象。
1 | Clock clock = Clock.systemDefaultZone(); |
Timezones 时区
在新API中时区使用ZoneId来表示。时区可以很方便的使用静态方法of来获取到。 时区定义了到UTS时间的时间差,在Instant时间点对象到本地日期对象之间转换的时候是极其重要的。
1 | System.out.println(ZoneId.getAvailableZoneIds()); |
LocalTime 本地时间
LocalTime 定义了一个没有时区信息的时间,例如 晚上10点,或者 17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差:
1 | LocalTime now1 = LocalTime.now(zone1); |
LocalTime 提供了多种工厂方法来简化对象的创建,包括解析时间字符串。
1 | LocalTime late = LocalTime.of(23, 59, 59); |
LocalDate 本地日期
LocalDate 表示了一个确切的日期,比如 2014-03-11。该对象值是不可变的,用起来和LocalTime基本一致。下面的例子展示了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。
1 | LocalDate today = LocalDate.now(); |
从字符串解析一个LocalDate类型和解析LocalTime一样简单:
1 | DateTimeFormatter germanFormatter = |
LocalDateTime 本地日期时间
LocalDateTime 同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime和LocalTime还有LocalDate一样,都是不可变的。LocalDateTime提供了一些能访问具体字段的方法
1 | LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59); |
只要附加上时区信息,就可以将其转换为一个时间点Instant对象,Instant时间点对象可以很容易的转换为老式的java.util.Date。
1 | Instant instant = sylvester |
格式化LocalDateTime和格式化时间和日期一样的,除了使用预定义好的格式外,我们也可以自己定义格式:
1 | DateTimeFormatter formatter = |
和java.text.NumberFormat不一样的是新版的DateTimeFormatter是不可变的,所以它是线程安全的。
关于时间日期格式的详细信息:http://download.java.net/jdk8/docs/api/java/time/format/DateTimeFormatter.html
十、Annotation 注解
在Java 8中支持多重注解了,先看个例子来理解一下是什么意思。
首先定义一个包装类Hints注解用来放置一组具体的Hint注解:
1 | @interface Hints { |
Java 8允许我们把同一个类型的注解使用多次,只需要给该注解标注一下@Repeatable即可。 例 1: 使用包装类当容器来存多个注解(老方法)
1 | @Hints({@Hint("hint1"), @Hint("hint2")}) |
例 2:使用多重注解(新方法)
1 | @Hint("hint1") |
第二个例子里java编译器会隐性的帮你定义好@Hints注解,了解这一点有助于你用反射来获取这些信息:
1 | Hint hint = Person.class.getAnnotation(Hint.class); |
即便我们没有在Person类上定义@Hints注解,我们还是可以通过 getAnnotation(Hints.class) 来获取 @Hints注解,更加方便的方法是使用 getAnnotationsByType 可以直接获取到所有的@Hint注解。
另外Java 8的注解还增加到两种新的target上了:
1 | @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) |
十一, lambda与集合类批处理操作
原来:
1 | for(Object o: list) { // 外部迭代 |
现在:
1 | list.forEach(o -> {System.out.println(o);}); //forEach函数实现内部迭代 |
或者
1 | list.forEach(o -> System.out.println(o)); //forEach函数实现内部迭代 |
【笔记】Java版本更新历史(ing)
https://lvxingzhi.github.io/2016/05/02/48-[笔记]Java版本更新历史(ing)/