java 8 的新特性
lambada表达式
Lambda 表达式,也可称为闭包,它是 Java 8 发布的最重要新特性。Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中) 。使用 Lambda 表达式可以使代码变的更加简洁紧凑。
一个简单的例子
本例是新建一个线程,不使用lambda表达式的写法为 :
public void testThread(){
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello, I am a thread!");
}
}).start();
}
使用lambda表达式的写法为:
public void testThread()
new Thread(()-> System.out.println("hello, I am a thread!")).start();
}
lambda语法
| 类型 | 格式 | 备注 |
|---|---|---|
| 多参数 | (s1,s2) -> {表达式...} | 参数类型可以省略,两边的括号不能省略。如果表达式只有一行,那么表达式两边的花括号可以省略。 |
| 一个参数 | s -> {表达式...} | 参数类型可以省略,两边的可以括号省略。如果表达式只有一行,那么表达式两边的花括号可以省略。 |
| 没有参数(如上例) | () -> {表达式...} | 括号不能省略。如果表达式只有一行,那么表达式两边的花括号可以省略。 |
如需进一步的了解,可以参看博客:www.cnblogs.com/kangkaii/p/…
JDK 提供的常用函数式接口
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。 以下是部分Java 8 中新增的函数式接口,它们都在java.util.function 中:
| Name | Type | Description |
|---|---|---|
| Predicate | Predicate< T > | 接收T对象并返回boolean |
| Function | Function< T, R > | 接收T对象,返回R对象 |
| Supplier | Supplier< T > | 提供T对象(例如工厂),不接收值 |
| Consumer | Consumer< T > | 接收T对象,不返回值 |
| UnaryOperator | UnaryOperator | 接收T对象,返回T对象 |
| BinaryOperator | BinaryOperator | 接收两个T对象,返回T对象 |
通过一个例子看看它们的应用:
public class Mytest {
//对于数组的操作的函数, 函数本身并没有“功能”,仅仅应当于一种“规范”
public static Integer arrayOperate(Integer[] ints, Function<Integer[], Integer> func) {
return func.apply(ints);
}
public static void main(String[] args){
Integer[] v = {1, 2, 3, 4, 5};
//求和
int sum = Mytest.arrayOperate(v, (s) -> {
int total = 0;
for(int i=0; i<s.length; i++){
total += s[i];
}
return total;
});
System.out.println(sum);
//计算奇数的个数
int count = Mytest.arrayOperate(v, (s) -> {
int num = 0;
for(int i=0; i<s.length; i++){
if (s[i]%2 == 1) num++;
}
return num;
});
System.out.println(count);
}
}
如需更多的了解,可以参考博客:www.runoob.com/java/java8-…
方法引用
在Java 8中,会使用Lambda表达式创建匿名方法,但是有时候,我们的Lambda表达式可能仅仅调用一个已存在的方法,而不做任何其它事,对于这种情况,通过一个方法名字来引用这个已存在的方法会更加清晰,Java 8的方法引用允许我们这样做。
方法引用是一个更加紧凑、易读的Lambda表达式,注意方法引用是一个Lambda表达式,其中方法引用的操作符是双冒号" :: "。
先看一个简单的例子:
public static void main(String[] args) {
List<String> slist = Arrays.asList("Tom", "Jack", "Monica", "Alpha");
//slist.sort((s1,s2) -> s1.compareTo(s2));
slist.sort(String::compareTo);
for(String s: slist)
{
System.out.println(s);
}
}
在本例中,String::compareTo 等同于 (s1,s2) -> s1.compareTo(s2) 。
方法引用的标准形式是:类名::方法名。(注意:只需要写方法名,不需要写括号)有以下四种形式的方法引用:
| 类型 | 示例 |
|---|---|
| 静态方法引用 | ClassName::methodName |
| 实例上的实例方法引用 | instanceReference::methodName |
| 超类上的实例方法引用 | super::methodName |
| 类型上的实例方法引用 | ClassName::methodName |
| 构造方法引用 | Class::new |
| 数组构造方法引用 | TypeName[]::new |
这里不便一一枚举详解,可以参考博客:www.cnblogs.com/xiaoxi/p/70…
【主要摘自】 www.cnblogs.com/andywithu/p…
Stream API
集合是Java中用途十分广泛的一个集合,正是因为它对于数据的处理的优势,几乎任何一个Java应用程序都会设计对Java集合的制造和处理。然而,传统的Java集合仅仅是对Java集合内部的数据进行简单的添加、删除等操作,而且处理的数据类型有限。当涉及到复杂的业务逻辑处理时,需要借助大量的迭代才能完成需求,操作繁琐,工作量大。
Java 8 中的 Stream 是对集合(Collection) 对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。所以说,Java 8 中首次出现的 java.util.stream 是一个函数式语言+多核时代综合影响的产物。
一个简单的例子
现在,通过一个简单的例子来看一下对集合进行操作时,Stream API 和传统方法的区别。
先定义一个Student类:
public class Student {
private int number;//学号
private String name;//姓名
private boolean sex;//性别 : true--女, false--男
private float height;//身高
public Student(int number, String name, boolean sex, float height) {
this.number = number;
this.name = name;
this.sex = sex;
this.height = height;
}
...... //getter and setter
@Override
public String toString() {
return "Student [number=" + number + ", name=" + name + ", sex=" + sex + ", height=" + height + "]";
}
}
新建一个Student的列表:
List<Student> list = new ArrayList<>();
Random rand = new Random();
list.add(new Student(1, "A", true, 150+rand.nextInt(40)));
list.add(new Student(2, "B", false, 150+rand.nextInt(40)));
list.add(new Student(3, "C", true, 150+rand.nextInt(40)));
list.add(new Student(4, "D", false, 150+rand.nextInt(40)));
list.add(new Student(5, "E", true, 150+rand.nextInt(40)));
list.add(new Student(6, "F", false, 150+rand.nextInt(40)));
list.add(new Student(7, "G", true, 150+rand.nextInt(40)));
list.add(new Student(8, "H", false, 150+rand.nextInt(40)));
list.add(new Student(9, "L", true, 150+rand.nextInt(40)));
list.add(new Student(10, "M", false, 150+rand.nextInt(40)));
对Student列表进行以下的操作:
- 找出身高在170以上的女生;
- 将满足上述条件的列表按学生身高进行排序;
- 将列表中的学生信息打印出来。
传统的方式:
//1.找出高于170的女生
List<Student> list01 = new ArrayList<>();
for(Student stu : list)
{
if(true == stu.getSex() && stu.getHeight() > 170){
list01.add(stu);
}
}
//2.按身高进行排序
list01.sort(new Comparator<Student>(){
@Override
public int compare(Student o1, Student o2) {
return (int) (o1.getHeight() - o1.getHeight());
}
});
//3.打印出来
for(Student stu : list01){
System.out.println(stu);
}
Stream API:
list.stream()
.filter(s -> true==s.getSex() && s.getHeight()>170) //1.找出高于170的女生
.sorted((o1,o2) -> (int)(o1.getHeight()-o2.getHeight())) //2.按身高进行排序
.forEach(s -> System.out.println(s)); //3.打印出来
这对Stream API 只是做了简单的介绍,更多详情请看博客:
当然,本也节参考过这些博客。
默认方法
在 java 8 之前,接口与其实现类之间的耦合度太高(tightly coupled),当需要为一个接口添加方法时,所有的实现类都必须随之修改。默认方法解决了这个问题,它可以为接口添加新的方法,而不会破坏已有的接口的实现。这在 lambda 表达式作为 java 8 语言的重要特性而出现之际,为升级旧接口且保持向后兼容(backward compatibility)提供了途径。
请看下面这个简单的例子:
interface InterfaceA {
void go();//抽象方法
default void foo() { //默认方法
System.out.println("I am foo, a default mothod ,in interface");
}
}
class ClassA implements InterfaceA { //默认方法可以不用覆写(但也可以覆写)
@Override
public void go() {
// TODO Auto-generated method stub
}
// @Override
// public void foo() {
// System.out.println("I am foo, a default mothod ,in instance class");
// }
}
public class Test {
public static void main(String[] args) {
new ClassA().foo(); //接口的默认方法,可以由实现类直接调用
}
}
如本例所示,在接口中添加一个新的方法,其实现类可以自由的选择要不要覆写,而 Java 8 之前是需要强制覆写。
关于默认方法的更多内容,可以参考博客:blog.csdn.net/u010003835/… ,应阐明, 本小节也有参考。
重复注解
在Java 8 之前,同一个注解是不能在一个位置上重复使用的, 这样不能通过编译。请看例子:
//自定义一个注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
@MyAnnotation("hello")
@MyAnnotation("world")//重复了,会报错
class Test {
public static void main(String[] args) {
}
}
如果一定要在这个类上多次使用注解,按照以前的做法,我们需要定义一个注解容器。
//自定义一个注解容器
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotationContainer {
MyAnnotation[] value();
}
@MyAnnotationContainer({@MyAnnotation("hello"), @MyAnnotation("world")})
class Test {
public static void main(String[] args) {
}
}
这样实在不怎么优雅!
而在 Java 8 中提供了一个元注解@Repeatable,可以对用户隐藏掉注解容器,需要在@MyAnnotation注解的定义上加上这个注解,并指定相应的注解容器,如 :
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyAnnotationContainer.class) //指定注解容器
public @interface MyAnnotation {
String value();
}
@MyAnnotation("hello")
@MyAnnotation("world") //可以重复使用了
class Test {
public static void main(String[] args) {
}
}
【参考】 blog.csdn.net/TimHeath/ar…
注解增强
Java 8 扩展了注解可以使用的范围,现在我们几乎可以在所有的地方:局部变量、泛型、超类和接口实现...... 具体表现为:ElementType新增了两个枚举值:TYPE_PARAMETER 和 TYPE_USE。对应的含义:
TYPE_PARAMETER
表示该注解能使用在自定义类型参数(参数的自定义类型可以是javaBean或者枚举等)的声明语句中。请看示例:
@Target(ElementType.TYPE_PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface TypeParameterAnnotation {
}
public class TypeParameterClass<@TypeParameterAnnotation T> {
public <@TypeParameterAnnotation U> T foo(T t) {
return null;
}
}
TYPE_USE
表示该注解能使用在使用类型的任意语句中。请看示例:
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TypeUseAnnotation {
}
public class Test {
public static @TypeUseAnnotation class TypeUseClass<@TypeUseAnnotation T> extends @TypeUseAnnotation Object {
public void foo(@TypeUseAnnotation T t) throws @TypeUseAnnotation Exception {
}
}
@SuppressWarnings({ "rawtypes", "unused", "resource" })
public static void main(String[] args) throws Exception {
TypeUseClass<@TypeUseAnnotation String> typeUseClass = new @TypeUseAnnotation TypeUseClass<>();
typeUseClass.foo("");
List<@TypeUseAnnotation Comparable> list1 = new ArrayList<>();
List<? extends Comparable> list2 = new ArrayList<@TypeUseAnnotation Comparable>();
@TypeUseAnnotation String text = (@TypeUseAnnotation String)new Object();
java.util. @TypeUseAnnotation Scanner console = new java.util.@TypeUseAnnotation Scanner(System.in);
}
}
方法参数反射
在 Java 8 之前,代码编译为 .class 文件后,方法参数的类型是固定的,但参数名称却丢失了,这和动态语言严重依赖参数名称形成了鲜明对比。现在,Java 8开始在class文件中保留参数名,给反射带来了极大的便利。请看示例:
//定义一个类,有两个方法
public class Person {
public void say(String str, int num)
{
}
public void work(Double salary)
{
}
}
public static void main(String[] args) {
for (Method m : Person.class.getDeclaredMethods()) {
String returnValue = m.getReturnType().getName();//获取返回值
String methodName = m.getName(); //获取方法名
Map<String, String> paraMap = new LinkedHashMap<>();
for (Parameter p : m.getParameters()) {
String paraType = p.getType().getName(); //获取参数类型
String paraName = p.getName(); // 重点: 获取参数名
paraMap.put(paraName, paraType);
}
System.out.println("------------------------------");
System.out.println("returnValue: " + returnValue);
System.out.println("methodName: " + methodName);
paraMap.forEach((k, v)->System.out.println(" parameter: " + k + "--" + v ));
}
}
打印的结果:
------------------------------
returnValue: void
methodName: say
parameter: str--java.lang.String
parameter: num--int
------------------------------
returnValue: void
methodName: work
parameter: salary--java.lang.Double
遗憾的是,保留参数名这一选项由**编译开关javac -parameters**打开,默认是关闭的。具体集成开发环境打开方法请自行查阅。
【摘自】 blog.csdn.net/qq_32331073…
Optional类
Optional 的简介
Optional 类的引入很好的解决空指针异常。在 java 8 之前,调用一个方法得到了返回值后,不能直接将其去调用别的方法,因为其可能为 null,所以要先进行判断。
Java 8 引入了一个新的 Optional 类。Optional 类的 Javadoc 描述如下:
这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。 具体的,可以参考一篇很好的博客:
一个简单的例子
这是一个来自博客:www.importnew.com/26066.html 的一个很漂亮的例子,与大家分享一下:
在 Java 8 之前,若要判断调用一个方法得到了返回值是否为 null ,我们必须依赖 if 语句:
public static String getChampionName(Competition comp) throws IllegalArgumentException {
if (comp != null) {
CompResult result = comp.getResult();
if (result != null) {
User champion = result.getChampion();
if (champion != null) {
return champion.getName();
}
}
}
throw new IllegalArgumentException("The value of param comp isn't available.");
}
由于种种原因(比如:比赛还没有产生冠军、方法的非正常调用、某个方法的实现里埋藏的“大礼包”等等),我们并不能开心的一路comp.getResult().getChampion().getName()到底。而其他语言比如 kotlin ,就提供了在语法层面的操作符加持:comp?.getResult()?.getChampion()?.getName()。所以讲到底在Java里我们怎么办?
让我们看看经过Optional加持过后,这些代码会变成什么样子:
public static String getChampionName(Competition comp) throws IllegalArgumentException {
return Optional.ofNullable(comp)
.map(c->c.getResult())
.map(r->r.getChampion())
.map(u->u.getName())
.orElseThrow(()->new IllegalArgumentException("The value of param comp isn't available."));
}
这样的语法好不好看见仁见智,但是Optional给了我们一种优雅的 Java 风格的方法来解决 null 的安全问题。
【参考】
Date Time API
为什么需要新的 Date Time API
在Java 8之前中,日期和时间相关的类诸多问题:
- Java中日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义;
- java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计;
- 对于时间、时间戳、格式化以及解析,并没有一些明确定义的类。对于格式化和解析的需求,我们有java.text.DateFormat抽象类,但通常情况下,SimpleDateFormat类被用于此类需求;
- 所有的日期类都是可变的,因此他们都不是线程安全的,这是Java日期类最大的问题之一;
- 日期类并不提供国际化,没有时区支持,虽然引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
在现有的日期和日历类中定义的方法还存在一些其他的问题,但以上问题已经很清晰地表明:Java需要一个健壮的日期/时间类。
新的 Date Time API 的优点
Java 8 Date Time API 的目标是克服旧的日期/时间API实现中所有的缺陷,新的日期/时间API的一些设计原则如下:
- 不变性:新的日期/时间API中,所有的类都是不可变的,这种设计有利于并发编程;
- 关注点分离:新的API将人可读的日期时间和机器时间(unix timestamp)明确分离,它为日期(Date)、时间(Time)、日期时间(DateTime)、时间戳(unix timestamp)以及时区定义了不同的类;
- 清晰:在所有的类中,方法都被明确定义用以完成相同的行为。举个例子,要拿到当前实例我们可以使用now()方法,在所有的类中都定义了 format() 和 parse() 方法,而不是像以前那样专门有一个独立的类。为了更好的处理问题,所有的类都使用了工厂模式和策略模式,一旦你使用了其中某个类的方法,与其他类协同工作并不困难;
- 实用操作:所有新的日期/时间API类都实现了一系列方法用以完成通用的任务,如:加、减、格式化、解析、从日期/时间中提取单独部分等操作;
- 可扩展性:新的日期/时间API是工作在ISO-8601日历系统上的,但我们也可以将其应用在非IOS的日历上。
新的 Date Time API 的包
- java.time:这是新的Java日期/时间API的基础包,所有的主要基础类都是这个包的一部分,如:LocalDate, LocalTime, LocalDateTime, Instant, Period, Duration等等。所有这些类都是不可变的和线程安全的;
- java.time.chrono:这个包为非ISO的日历系统定义了一些泛化的API,我们可以扩展AbstractChronology类来创建自己的日历系统;
- java.time.format:这个包包含能够格式化和解析日期时间对象的类,在绝大多数情况下,我们不应该直接使用它们,因为java.time包中相应的类已经提供了格式化和解析的方法;
- java.time.temporal:这个包包含一些时态对象,我们可以用其找出关于日期/时间对象的某个特定日期或时间,比如说,可以找到某月的第一天或最后一天。你可以非常容易地认出这些方法,因为它们都具有“withXXX”的格式;
- java.time.zone:这个包包含支持不同时区以及相关规则的类。
结语
很遗憾,在这里没有给出示例。新的 Date Time API 的类型过多,由于篇幅的原因,无法一一列举。这里推介一篇非常好的blog:
在此声明,本节的内容也是出自这篇blog。
java.util 和 java.lang 的增强
并行数组排序(java.util.Arrays.parallelSort(T[] a))
将数组拆分成多个子数组,多线程进行排序,然后归并,只有当数组长度大于 8192 时才进行并行排序。
运行以下测试代码(运行的示例,不一定要读懂,目的是说明parallelSort使用场合):
测试环境(硬件):
处理器:Intel Core i5-6200U
核心数:4 核
RAM: 8.00 GB
public class MyTest {
final int UPPER_LIMIT = 0xffffff; //数组容量的上限: 16,777,215
final int ROUNDS = 20; //重复计算的次数(多次计算取平均值)
final int INCREMENT = 2; //数组容量的增量,取INCREMENT的幂
final int INIT_SIZE = 1024; //数组的初始容量
@Test
public void test() {
ArrayList<Integer> list = new ArrayList<Integer>();
// 测试数组大小从INIT_SIZE开始,每次增加INCREMENT倍,直到超过UPPER_LIMIT.
for (int capacity = INIT_SIZE; capacity <= UPPER_LIMIT; capacity *= INCREMENT) {
int priorCapacity = (capacity/INCREMENT)<INIT_SIZE ? 0 : capacity/INCREMENT;
for (int i = priorCapacity; i < capacity; i++) list.add(i);
// sumTimeOfParallelSort: parallelSort经过ROUNDS次排序所耗费的总时间
double sumTimeOfParallelSort = 0;
// sumTimeOfSort: sort经过ROUNDS次排序所耗费的总时间
double sumTimeOfSort = 0;
for (int i = 0; i < ROUNDS; i++) {
// 每次排序都先打乱顺序
Collections.shuffle(list);
Integer[] arr1 = list.toArray(new Integer[capacity]);
Integer[] arr2 = arr1.clone();
sumTimeOfParallelSort += counter(arr1, true);
sumTimeOfSort += counter(arr2, false);
}
output(capacity, sumTimeOfParallelSort / ROUNDS, sumTimeOfSort
/ ROUNDS);
}
}
/**
* 用于计算排序花费的时间
*
* @param arr
* 要排序的数组
* @param useParallelSort
* true:使用parallelSort;false:使用sort
* @return 返回花费的时间
*/
private double counter(Integer[] arr, boolean useParallelSort) {
long begin, end;
begin = System.nanoTime();
if (useParallelSort) {
Arrays.parallelSort(arr);
} else {
Arrays.sort(arr);
}
end = System.nanoTime();
return BigDecimal.valueOf(end - begin, 9).doubleValue();
}
/**
*
* @param capacity
* 当前数组容量
* @param avgTimeOfParallelSort
* parallelSort花费的平均时间
* @param avgTimeOfSort
* sort花费的平均时间
*/
private void output(int capacity, double avgTimeOfParallelSort,
double avgTimeOfSort) {
System.out
.println("==================================================");
System.out.println("Capacity:" + capacity);
System.out.println("ParallelSort:" + avgTimeOfParallelSort);
System.out.println("Sort:" + avgTimeOfSort);
System.out.println("ParallelSort - Sort:" + (avgTimeOfParallelSort-avgTimeOfSort));
System.out.println("Winner is:"
+ (avgTimeOfParallelSort < avgTimeOfSort ? "ParallelSort"
: "Sort"));
System.out
.println("==================================================");
}
}
结果统计表:
| Array capacity | ParallelSort time(s) | Sort time(s) | Sort - ParallelSort (s) | Winner |
|---|---|---|---|---|
| 1024 | 4.3118880000000005E-4 | 4.578128E-4 | 2.662399999999994E-5 | ParallelSort |
| 2048 | 5.576312499999999E-4 | 4.7123139999999993E-4 | -8.639984999999999E-5 | Sort |
| 4096 | 0.0010973852999999999 | 9.039348000000003E-4 | -1.934504999999996E-4 | Sort |
| 8192 | 0.0023074957999999995 | 0.0021431013 | -1.6439449999999974E-4 | Sort |
| 16384 | 0.002989884150000000 | 0.0040638242500000005 | 0.0010739400999999998 | ParallelSort |
| 32768 | 0.0054426598 | 0.008933193950000001 | 0.003490534150000001 | ParallelSort |
| 65536 | 0.01075987965 | 0.01854429615 | 0.0077844165 | ParallelSort |
| 131072 | 0.024299616849999994 | 0.04371892010000001 | 0.019419303250000016 | ParallelSort |
| 262144 | 0.058445194449999995 | 0.1132567723 | 0.05481157785000001 | ParallelSort |
| 524288 | 0.16812678224999997 | 0.29016267934999995 | 0.12203589709999998 | ParallelSort |
| 1048576 | 0.3692876339500001 | 0.6477613255999999 | 0.2784736916499998 | ParallelSort |
| 2097152 | 0.8475651018499999 | 1.36344326825 | 0.5158781664000001 | ParallelSort |
| 4194304 | 1.6877532316500006 | 3.0629741062499996 | 1.375220874599999 | ParallelSort |
由此可见,只有当数组的容量大于8192之后,并列排序才有优势,而且这种优势随着数组容量的增加而上升。
【摘自】 blog.csdn.net/lc0817/arti…
支持标准的Base64编解码
Java 8 之前,要实现Base64编码,要么借助sun.misc.BASE64Encoder,或者apache commons-codec,再或者Guava、JAXB的DatatypeConverter。现在,官方已经把Base64进行了重新优化,放到 java.util.Base64 包里,比之前的都要简单优雅,且性能卓越,无需引用第三方包。
java.util.Base64工具类提供了一套静态方法获取下面三种Base64编解码器:
Basic编码
Basic编码是标准的Base64编码,用于处理常规的需求:输出的内容不添加换行符,而且输出的内容由字母加数字组成。示例如下:
// encode
String asB64 = Base64.getEncoder().encodeToString("some string".getBytes("utf-8"));
System.out.println(asB64); // 输出为: c29tZSBzdHJpbmc=
// decode
byte[] asBytes = Base64.getDecoder().decode("c29tZSBzdHJpbmc=");
System.out.println(new String(asBytes, "utf-8")); // 输出为: some string
URL编码
由于URL对反斜线“/”有特殊的意义,因此URL编码需要替换掉它,使用下划线替换。示例如下:
String basicEncoded = Base64.getEncoder().encodeToString("subjects?abcd".getBytes("utf-8"));
System.out.println("Using Basic Alphabet: " + basicEncoded);
String urlEncoded = Base64.getUrlEncoder().encodeToString("subjects?abcd".getBytes("utf-8"));
System.out.println("Using URL Alphabet: " + urlEncoded);
输出为:
Using Basic Alphabet: c3ViamVjdHM/YWJjZA==
Using URL Alphabet: c3ViamVjdHM_YWJjZA==
MIME编码
MIME编码器会使用基本的字母数字产生BASE64输出,而且对MIME格式友好:每一行输出不超过76个字符,而且每行以“\r\n”符结束。示例如下:
StringBuilder sb = new StringBuilder();
for (int t = 0; t < 10; ++t) {
sb.append(UUID.randomUUID().toString());
}
byte[] toEncode = sb.toString().getBytes("utf-8");
String mimeEncoded = Base64.getMimeEncoder().encodeToString(toEncode);
System.out.println(mimeEncoded);
输出为:
YzY2ZGMzZmEtMjg0NS00YTVmLThjNzMtNGYyYjBkNjM0YzI3M2ZhZmYyYjgtMThlNC00ZTBjLTk3
MGQtMWNhYzkzNjVkNjViZmRkNmI0OGUtMWFkNC00OTA4LTg5YWMtZTU3ZWM4NTU1YTNiYzg1YWRh
ZTItYTA1OC00YWY3LWFmOWUtYjdmZjVmNmFmMDU2YjY1MWE0N2UtN2ZmMy00Njc5LTlhNTQtMjIy
ZjhlMzVkODE2MGYyNmRiNDQtM2RmZS00OGU1LWJmZGYtMDMwYTgxMmQzN2U1NzRiNjlhNzAtYmRm
Ny00ODRiLWE3MWMtYzcxOTQyNTg5YTIxMmQ5ZDFlMmUtODAzZi00NmY5LWFiNWUtNTgxZjg5MTQy
MDNiYTYwOWUyNTEtZTMxNS00ZGY2LTkxZTctZDdiYzlkZjhjNGIxYjIzN2U1NDQtYzQ0Yy00Y2I5
LTg3NzAtYzhmYjUwOTg4YWY1
【摘自】 blog.csdn.net/chszs/artic…
支持无符号运算
Java 8 为整型包装类增加支持无符号运算的方法。
为 Integer、Long 新增如下静态方法:
| Integer | Long |
|---|---|
| long toUnsignedLong(int x) | |
| String toUnsignedString(int x) | String UnsignedString(long i) |
| String toUnsignedString(int x, int radix) | String UnsignedString(long i, int radix) |
| int parseUnsignedInt(String s) | long parseUnsignedLong(String s) |
| int parseUnsignedInt(String s, int radix) | long parseUnsignedLong(String s, int radix) |
| int compareUnsigned(int x, int y) | int compareUnsigned(long x, long y) |
| int divideUnsigned(int dividend, int divisor) | long divideUnsigned(long dividend, long divisor) |
| int remainderUnsigned(int dividend, int divisor) | long remainderUnsigned(long dividend, long divisor) |
java8 还为 Byte、Short 新增如下的静态方法:
| Byte | Short |
|---|---|
| int toUnsignedInt(byte x) | int toUnsignedInt(short x) |
| long toUnsignedLong(byte x) | long toUnsignedLong(short x) |
java.util.concurrent 包的增强
ConcurrentHashMap 的增强
ConcurrentHashMap 在 java 8 中进行了巨大改动。它摒弃了Segment(锁段) 的概念,而是使用CAS来实现并发保护。它沿用了与它同时期的 HashMap 版本的思想,底层依然由 “数组” + 链表 + 红黑树 的方式思想,但是为了做到并发,又增加了很多辅助的类,例如TreeBin,Traverser等对象内部类。在这里不作重点讨论,可以参考其他博客:www.importnew.com/22007.html
不仅如此,为了回应新增的 Stream API 和 Lambda表达式 ,ConcurrentHashMap 新增三十多种方法。包括forEach系列(forEach,forEachKey, forEachValue, forEachEntry)、search系列(search, searchKeys, searchValues, searchEntries)、reduce系列(reduce, reduceToDouble, reduceToLong)以及mappingCount 、newKeySet等方法。请看示例:
forEach系列
public static void main(String[] args) {
final ConcurrentHashMap<String, Integer> count = new ConcurrentHashMap<>();
count.put("a", 97);
count.put("b", 98);
count.put("c", 99);
count.forEach((k, v)->System.out.println(k + ": " + v));
count.forEachKey(0, s -> System.out.println(s));
count.forEachValue(0, s -> System.out.println(s));
count.forEachEntry(0, s -> System.out.println(s));
}
search系列
public static void main(String[] args) {
final ConcurrentHashMap<String, Integer> count = new ConcurrentHashMap<>();
count.put("a", 1);
count.put("ab", 2);
count.put("abc", 3);
count.put("abcd", 4);
Object obj = count.search(0, (k, v) -> v%2==0 ? k:null);
Boolean boolK = count.searchKeys(0, k -> 3==k.length() ? true:null);
Boolean boolV = count.searchValues(0, v -> v>4 ? true:null);
Object entry = count.searchEntries(0, e -> e.getKey().length()==e.getValue() ? e:null);
System.out.println(obj);
System.out.println(boolK);
System.out.println(boolV);
System.out.println(entry);
}
atomic 包下新增了类以支持可伸缩可更新的变量
LongAccumulator
在初始化这个类的时候,需要提供一个累加函数,该累加函数应该是无副作用的(side-effect-free), 因为当与其他线程的竞争而失败时,函数操作将会重复进行。 在累加函数中,将当前的值作为第一个参数,将“更新值”作为第二个参数。线程内部或跨线程的累积的顺序是不能保证的,也不能依赖于此类,因此该类只适用于累积的顺序无关的函数。
在多线程的情况下,面对一个用于汇总统计(collecting statistics),而非细粒度(fine-grained)同步控制的公共值时,这个类会比 AtomicLong 的表现更好。在高竞争的情况下,这个类的性能(throughput)会明显的高于AtomicLong,但是以更高的内存消耗为代价。
示例:
public static void main(String[] args) {
//初始化LongAccumulator时,需要 “累加函数” 和 “初始值”
LongAccumulator la = new LongAccumulator((current, update) -> current/2 + update/2, 1024);
la.accumulate(512);
la.accumulate(256);
la.accumulate(128);
la.accumulate(64);
la.accumulate(32);
la.accumulate(16);
// la.reset();
la.accumulate(8);
la.accumulate(4);
la.accumulate(2);
System.out.println(la.get());
}
LongAdder
LongAdder其实是LongAccumulator的一个特例,调用LongAdder相当使用下面的方式调用LongAccumulator。
LongAccumulator accumulator = new LongAccumulator((current, update)->current+update, 0L);
相比于LongAccumulator,LongAdder的初始值默认为0,并默认累加规则为相加。示例:
public static void main(String[] args) {
LongAdder la = new LongAdder();
la.add(100);
la.add(100);
la.add(100);
//la.decrement();
//la.reset();
System.out.println(la.longValue());
}
DoubleAccumulator & DoubleAdder
DoubleAccumulator 、 DoubleAdder 的用法和 LongAccumulator、LongAdder 的用法大同小异,这里就不详细列举了。
【参考】 docs.oracle.com/javase/8/do…
ForkJoinPool 类新增了方法以支持 common pool
新增的 commonPool() 是一个静态的方法,适合于大多数的应用。common pool 可用于任何没有其他特殊的池的ForkJoinTask。正常情况下,使用 common pool 能减少资源的消耗(在不使用 common pool 的情况下,ForkJoinPool 的线程在不使用时会被缓慢地回收,并在随后的使用时恢复)。
示例:
public class SumTask extends RecursiveTask<Integer> {
static final int THRESHOLD = 50;
private Integer[] numbers;
private int from;
private int to;
public SumTask(Integer[] numbers, int from, int to) {
this.numbers = numbers;
this.from = from;
this.to = to;
}
@Override
protected Integer compute() {
// 当需要计算的数字小于THRESHOLD时,直接计算结果
if (to - from < THRESHOLD) {
int total = 0;
for (int i = from; i <= to; i++) {
total += numbers[i];
}
return total;
// 否则,把任务一分为二,递归计算
} else {
int middle = (from + to) / 2;
SumTask taskLeft = new SumTask(numbers, from, middle);
SumTask taskRight = new SumTask(numbers, middle + 1, to);
taskLeft.fork();
taskRight.fork();
return taskLeft.join() + taskRight.join();
}
}
}
public class Test {
public static void main(String args[]) throws InterruptedException, ExecutionException{
final int TOTAL = 1000;
ForkJoinPool commonpool = ForkJoinPool.commonPool();//重点:返回公共池实例
Integer[] numbers = new Integer[TOTAL];
for(int i=0; i<TOTAL; i++){
numbers[i] = Integer.valueOf(i+1);
}
SumTask task = new SumTask(numbers, 0, numbers.length-1);
int result = commonpool.submit(task).get();
System.out.println("result: " + result);
}
}
【参考】 docs.oracle.com/javase/8/do…
新增了 java.util.concurrent.locks.StampedLock 类
StampedLock的简介
为控制读/写访问提供了一个基于性能的锁(capability-based lock),且有三种模式可供选择。StampedLock的状态由版本和模式构成。锁获取方法(Lock acquisition methods )返回一个标志(stamp),这个标志代表并控制对锁状态的访问。而“try”版本的锁获取方法也许会返回特殊值——零,代表对锁状态获取的失败。锁的释放和转换方法需要标志(stamp)作为参数,如果没有得到相应的锁状态就会失败。三种模式分别是:
-
Writing(写模式)
writeLock()方法可能会因独占的的访问(exclusive access)而阻塞,并最终返回一个标志,这个标志能由方法unlockWrite(long)释放掉相应的锁。也提供了不定时和定时的tryWriteLock方法。当锁由写模式(write mode)持有时,所有的读操作将无法获得该锁,所有的乐观读的验证( optimistic read validations)将会失败。 -
Reading(读模式)
readLock()方法可能会因独占的的访问(exclusive access)而阻塞,并最终返回一个标志,这个标志能由方法unlockRead(long)释放掉相应的锁。也提供了不定时和定时的tryReadLock方法。 -
Optimistic Reading(乐观读模式)
仅当锁此时没有由写模式持有时,
tryOptimisticRead()方法返回一个非零的标志。自从获取一个给定的标志(tryOptimisticRead()返回的非零标志)起,如果锁没有被写模式获得,则validate(long)方法的返回值为真(true)。这个模式可以看作一个非常弱的读锁版本 ,写操作能随时破坏它。对于那些短小且只读的代码段(short read-only code segments),乐观模式的使用常常能减少竞争和提高效率。然而,乐观模式本质上是脆弱的,乐观读部分应该仅仅读取字段(fields),并将它们保存在局部变量中,以便在验证之后使用。在乐观模式下,字段的读取极其可能不一致, 仅当你对数据表示非常熟悉,足以检查一致性,且(或)重复地调用
validate()方法,否则不应该使用。例如,当第一次读取对象或者数组引用,并之后访问它的其中一个字段、元素或方法时,通常需要这些步骤。
一个简单的例子
封装一个线程安全的文件读写通道。基于StampedLock,读操作使用乐观读模式。
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.locks.StampedLock;
/**
* 线程安全的文件读写通道,基于lock,读操作使用乐观锁
*
* @author Tucson
*/
public class TextRWChannel {
private final StampedLock lock = new StampedLock();
private final int BUF_LIMIT = 1024;
private Path path;
private String charset = "utf-8";
/**
* 构造函数
*
* @param path
* 文件路径
* @throws IOException
*/
public TextRWChannel(Path path) throws IOException {
this.path = path;
}
/**
* 构造函数
*
* @param path
* 文件路径
* @param charset
* 编码方式
* @throws IOException
*/
public TextRWChannel(Path path, String charset) throws IOException {
this.path = path;
this.charset = charset;
}
/**
* 写方法
*
* @param text
* 要进行写操作的文本
* @param position
* 写的开始位置
* @throws IOException
*/
public void write(String text, long position) throws IOException {
try (FileChannel writeChannel = FileChannel.open(path, StandardOpenOption.WRITE)) {
long writeStamp = lock.writeLock(); // 写锁
try {
writeText(writeChannel, text, charset, position);
} finally {
lock.unlockWrite(writeStamp);// 释放写锁
}
}
}
/**
* 文件读操作,使用乐观锁
*
* @param postion
* 读的开始位置
* @return 读取的文本
* @throws IOException
*/
public String read(long position) throws IOException {
try (FileChannel readChannel = FileChannel.open(path, StandardOpenOption.READ)) {
long stamp = lock.tryOptimisticRead(); // 1、先使用乐观读锁
String text = this.readText(readChannel, charset, position);// 2、直接读文件(在没有加锁的情况下)
if (!lock.validate(stamp)) { // 3、判断读文件期间有没有别的线程进行写操作
long readStamp = lock.readLock();// 4、若有,加“读锁”
try {
//System.out.println("******** the file has been changed ********");
text = this.readText(readChannel, charset, position);// 5、重读
} finally {
lock.unlockRead(readStamp);// 6、释放读锁
}
}
return text;
}
}
private String readText(FileChannel channel, String charset, long position) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(BUF_LIMIT);
StringBuilder sBuilder = new StringBuilder();
channel.position(position);
while (-1 != channel.read(buffer)) {
buffer.flip();
String str = new String(buffer.array(), buffer.position(), buffer.limit(), charset);
sBuilder.append(str);
buffer.clear();
}
return sBuilder.toString();
}
private void writeText(FileChannel channel, String text, String charset, long position) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(BUF_LIMIT);
byte[] bytes = text.getBytes(charset);
channel.position(position);
int count = bytes.length / BUF_LIMIT;
for (int i = 0; i < count; i++) {
buffer.put(bytes, BUF_LIMIT * i, BUF_LIMIT * (i + 1));
buffer.flip();
channel.write(buffer);
buffer.clear();
}
buffer.put(bytes, count*BUF_LIMIT, bytes.length);
buffer.flip();
channel.write(buffer);
buffer.clear();
}
}
测试代码:
public class Test {
static TextRWChannel channel = null;
public static void main(String[] args) throws IOException {
Path path = Paths.get("E:\test.txt");
String text = "使用 Lambda 表达式可以使代码变的更加简洁紧凑。";
channel = new TextRWChannel(path, "GBK");
for (int i = 0; i < 100; i++) {
new Thread(() -> {
try {
channel.read(0);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
if (i % 5 == 0) {
new Thread(() -> {
try {
channel.write(text, 0);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
}
}