Java特性总结

本文详细解析了Java1.7和1.8版本中新增的关键特性,包括字符串支持的switch语句、泛型实例化推断、try-with-resources语句、集合增强、lambda表达式、函数式接口、接口默认方法、方法引用、Stream API、Optional类和新的Time类。深入探讨了这些特性的应用场景和编码实践,特别强调了lambda表达式和Stream API在提高代码效率和可读性方面的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

    本文仅用作个人知识回顾所用。最近在走查公司的项目代码,发现就连开发商都开始大面积的使用lambda函数来简化代码编写,顿时觉得自己已经落伍了。刚好最近在研究新的开发框架,那么就趁这次机会先梳理一下Java 1.6之后的特性(公司刚刚从1.6升级到1.8)。

 

Java 1.7特性

      由于Java 1.8并未移除1.7版本的特性,所以在研究1.8版本前,还是先看看1.7提供了哪些新的内容(仅列举我认为工作可能用到的内容,下同):

1. switch开放对字符串的支持:

String hs = "healthStatus";     
    switch (hs) {   
        case "stomachache":   
            System.out.println("胃痛");   
            break;   
        case "headache":   
            System.out.println("头痛");   
            break;   
        default:   
            System.out.println("挺好的");   
    } 

2. 泛型实例化推断:

ArrayList<String> al1 = new ArrayList<String>();   
ArrayList<String> al2 = new ArrayList<>();  

3. 单个catch捕获多个异常:

catch (IOException ex) {
    logger.error(ex);
    throw new MyException(ex.getMessage());
catch (SQLException ex) {
    logger.error(ex);
    throw new MyException(ex.getMessage());
}catch (Exception ex) {
    logger.error(ex);
    throw new MyException(ex.getMessage());
}

catch(IOException | SQLException | Exception ex){
    logger.error(ex);
    throw new MyException(ex.getMessage());
}

这种方法一般用于对异常的相同处理,在实际工作中考虑到生产问题排查不建议这么使用,或者说当针对某一种异常生产处理方法一致的情况(比如遇到A类异常均需要重启服务),可以这么写。

4.try-with-resource语句

public String readFirstLingFromFile(String path) throws IOException { 
    BufferedReader br = null;  
    
    try {  
        br = new BufferedReader(new FileReader(path));  
        return br.readLine();  
    } catch (IOException e) {  
        e.printStackTrace();  
    } finally {  
        if (br != null)  br.close();  
    }  

    return null;  
}  

public String readFirstLineFromFile(String path) throws IOException {  
    try (BufferedReader br = new BufferedReader(new FileReader(path))) {  
        return br.readLine();  
    }  
}

    try (Resource resource = new Resource(); 
         Resource2 resource2 = new Resource2()) {        
        resource.sayHello();
            resource2.sayhello();
        } catch (Exception e) {
            e.printStackTrace();
        } 

如果有多个资源,则在括号中用';'(分号)隔开。

在JDK1.7之前如果rd.readLine()与rd.close()都抛出异常则只会抛出finally块中的异常,不会抛出rd.readLine()中的异常, 这样经常会导致得到的异常信息不是调用程序想要得到的。

在JDK1.7及以后采用了try-with-resource机制,如果在try-with-resource声明中抛出异常(如文件无法打开或无法关闭)的同时rd.readLine()也抛出异常,则只会抛出rd.readLine()的异常。

5. 对Collection的增强

List<String> list=["item"]; 
String item=list[0]; 

Set<String> set={"item"}; 

Map<String,Integer> map={"key":1}; 
int value=map["key"]; 

这种用法在解析或传递json串比较有用,可以简化代码。

以上为工作中可能用到的Java 1.7特性。

Java 1.8特性

      该版本是Java自Java 5(发布于2004年)之后的最重要的版本。这个版本包含语言、编译器、库、工具和JVM等方面的十多个新特性。下面我们看看1.8里增加的实用特性:

1. lambda表达式

      1.8新特性中最出名,使用最广泛的应该是lambda表达式了。Lambda表达式(也称为闭包),它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理。

lambda 表达式的语法格式如下:

(parameters) -> expression或(parameters) ->{statements; }

以下是lambda表达式的重要特征:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
public class TestJava8 {
    public static void main(String args[]) {
        TestJava8 tester = new TestJava8 ();

        MathOperation addition = (int a, int b) -> a + b;
        MathOperation subtraction = (a, b) -> a - b;
        MathOperation multiplication = (int a, int b) -> {return a * b;};        
        MathOperation division = (int a, int b) -> a / b;

        System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
        System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
        System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
        System.out.println("10 / 5 = " + tester.operate(10, 5, division));
        
        GreetingService gs1 = message -> System.out.println("Hello " + message);
        GreetingService gs2 = (message) -> System.out.println("Hello " + message);
        gs1.sayMessage("Runoob");
        gs2.sayMessage("Google");
    }

    interface MathOperation {
        int operation(int a, int b);
    }
    interface GreetingService {
        void sayMessage(String message);
    }

    private int operate(int a, int b, MathOperation mathOperation) {
        return mathOperation.operation(a, b);
    }
}

使用lambda表达式需要注意的内容:

  • lambda执行体内如果使用了外部变量会认为其是final类型,若对其进行了二次赋值,IDE会报语法错误。
  • lambda执行体内不允许定义与外部变量同名的变量
  • lambda执行体内如果需要有返回值:若只有一行语句则无需显示返回;若为多行逻辑,则需显示返回结果
Arrays.asList( "a", "b", "d" ).sort(( e1, e2 ) -> e1.compareTo( e2 ));

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
    int result = e1.compareTo( e2 );
	System.out.println("");
	return result;
});

      使用Lambda可以写出更简洁,更灵活的代码,本质上讲看多了就不会有可读性障碍,然后提高了编码效率。实际的工作中lambda表达式主要用于替代匿名函数,主要使用场景也多见于线程、比较器。

2. 函数式接口

       Lambda的设计者们为了让现有的功能与Lambda表达式良好兼容,考虑了很多方法,于是产生了函数式接口这个概念:有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。这样的接口可以隐式转换为Lambda表达式。

JDK 1.8之前已有的函数式接口:

  • java.lang.Runnable
  • ava.util.concurrent.Callable
  •  java.security.PrivilegedAction
  • java.util.Comparator
  • java.io.FileFilter
  • java.nio.file.PathMatcher
  • java.lang.reflect.InvocationHandler
  • java.beans.PropertyChangeListener
  • java.awt.event.ActionListener
  • javax.swing.event.ChangeListener

JDK 1.8 新增加的函数接口:

  •  java.util.function

从1.8版本起,Java提供了四个内置的核心函数式接口:

1. Consumer<T>:消费型接口(void accept(T t))

//Consumer<T> 消费型接口
public void happy(double money, Consumer<Double> con){
    con.accept(money);
}

2. Supplier<T>:供给型接口(T get())

//Supplier<T> 供给型接口
//需求:产生一些整数,并放入集合中
public List<Integer> getNumList(int num, Supplier<Integer> sup){
    List<Integer> list = new ArrayList<>();

    for (int i = 0; i < num; i++){
        Integer n = sup.get();
        list.add(n);
    }

    return list;
}

public void test(){
    List<Integer> numList = getNumList(10, () -> (int)(Math.random() * 100));

    for (Integer num : numList){
        System.out.println(num);
    }
}

3. Function<T, R>:函数型接口(R apply(T t))

//Function<T, R> 函数型接口
//需求:用于处理字符串
public String strHandler(String str, Function<String, String> fun){
    return fun.apply(str);
}

public void test(){
    String newStr = strHandler("\t\t\t learn java 8", (str) -> str.trim());
    System.out.println(newStr);

    String subStr = strHandler("thisisalongstring", (str) -> str.substring(0, 4));
    System.out.println(subStr);
}

4. Predicate<T>:断言型接口(boolean test(T t))

//Predicate<T> 断言型接口
//需求:将满足条件的字符串,放入集合中去
public List<String> filterStr(List<String> list, Predicate<String> pre){
    List<String> strList = new ArrayList<>();

    for (String str : list){
        if (pre.test(str)){
            strList.add(str);
        }
    }
    return strList;
}

public void test(){
    List<String> list = Arrays.asList("java1.8", "learningjava", "no", "1.8");
    List<String> strList = filterStr(list, (s) -> s.length() > 3);

    for (String str : strList){
        System.out.println(str);
    }
}

3. 接口默认方法

Java 1.8使用两个新概念扩展了接口的含义:默认方法和静态方法。默认方法使得开发者可以在 不破坏二进制兼容性的前提下,往现存接口中添加新的方法,即不强制那些实现了该接口的类也同时实现这个新加的方法。简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。(只需在方法名前面加个default关键字即可实现默认方法。)

下面这个例子讲了默认(静态)方法以及其与继承方法的执行顺序。

public class TestJava8 {
	 public static void main(String args[]) {
		 Vehicle vehicle = new Car();
		 vehicle.print();
	 }
}

interface Vehicle {
	default void print() {
		System.out.println("我是一辆车!");
	}

	static void blowHorn() {
		System.out.println("按喇叭!!!");
	}
}

interface FourWheeler {
	default void print() {
		System.out.println("我是一辆四轮车!");
	}
}

class thing {
	
	thing(){
		System.out.println("constructor:thing");
	}
	
	{
		System.out.println("block:thing");
	}
	
	static {
		System.out.println("static:thing");
	}
}

class metal extends thing{
	metal(){
		System.out.println("constructor:metal");
	}
	
	{
		System.out.println("block:metal");
	}
	
	static {
		System.out.println("static:metal");
	}
}

class Car extends metal implements Vehicle, FourWheeler {
	public void print() {
		Vehicle.super.print();
		FourWheeler.super.print();
		Vehicle.blowHorn();
		System.out.println("我是一辆汽车!");
	}
}

输出结果为:

static:thing
static:metal
block:thing
constructor:thing
block:metal
constructor:metal
我是一辆车!
我是一辆四轮车!
按喇叭!!!
我是一辆汽车!

4. 方法引用

方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。当Lambda表达式中只是执行一个方法调用时,不用Lambda表达式,直接通过方法引用的形式可读性更高一些。方法引用是一种更简洁易懂的Lambda表达式。

注意方法引用是一个Lambda表达式,其中方法引用的操作符是双冒号"::"。

方法引用共分为四类:
1.类名::静态方法名
2.对象::实例方法名
3.类名::实例方法名 
4.类名::new

public class TestJava8 {
	 public static void main(String args[]) {
		 Student student1 = new Student("zhangsan",60);
		 Student student2 = new Student("lisi",70);
		 Student student3 = new Student("wangwu",80);
		 Student student4 = new Student("zhaoliu",90);
		 List<Student> students = Arrays.asList(student1,student2,student3,student4);
		 
		 System.out.println("lambda comparator");
		 students.sort((o1, o2) -> o1.getScore() - o2.getScore());
		 students.forEach(student -> System.out.println(student.getScore()));

		 System.out.println("Class static method comparator");
		 students.sort(Student::compareStudentByScore);
		 students.forEach(student -> System.out.println(student.getScore()));

		 System.out.println("Instance method comparator");
		 StudentComparator studentComparator = new StudentComparator();
		 students.sort(studentComparator::compareStudentByScore);
		 students.forEach(student -> System.out.println(student.getScore()));

		 System.out.println("Class method comparator");
		 students.sort(Student::compareByScore);
		 students.forEach(student -> System.out.println(student.getScore()));
		 
		 //Student必须有无参构造器
		 Supplier<Student> supplier = Student::new;
	 }
}

class Student {
    private String name;
    private int score;

    public Student(){

    }

    public Student(String name,int score){
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    public static int compareStudentByScore(Student student1,Student student2){
        return student1.getScore() - student2.getScore();
    }
    
    public int compareByScore(Student student){
        return this.getScore() - student.getScore();
    }
}

class StudentComparator {
    public int compareStudentByScore(Student student1,Student student2){
        return student2.getScore() - student1.getScore();
    }
}

@FunctionalInterface
interface Supplier<T> {
	T get();
}

5. Stream

Java 1.8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。

Java1.7版本如果使用需要代码来实现聚合操作:

List<Transaction> groceryTransactions = new Arraylist<>();

for(Transaction t: transactions){
    if(t.getType() == Transaction.GROCERY){
        groceryTransactions.add(t);
    }
}

Collections.sort(groceryTransactions, new Comparator(){
    public int compare(Transaction t1, Transaction t2){
        return t2.getValue().compareTo(t1.getValue());
    }
});

List<Integer> transactionIds = new ArrayList<>();

for(Transaction t: groceryTransactions)  transactionsIds.add(t.getId());

使用Stream进行编码:

List<Integer> transactionsIds = transactions
                                  .parallelStream()
                                  .filter(t -> t.getType() == Transaction.GROCERY)
                                  .sorted(comparing(Transaction::getValue).reversed())
                                  .map(Transaction::getId)
                                  .collect(toList());

1)基本特性

(1)Stream 不是集合元素,它不是数据结构并不保存数据。它是有关算法和计算的,它更像一个高级版本的 Iterator:单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。

(2)Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。

(3)Stream的操作是lazy的(惰性),直到最终操作被初始化才会执行全部的流操作。

2)流的获取

流的获取方法大概可分为5类

(1)Collection 和 Array

  • Collection.stream()
  • Collection.parallelStream()
  • Arrays.stream(T array)

(2)静态工厂

  • Stream.of()、Stream.empty()
  • Stream.generate()
  • Stream.iterate()
  • Stream.concat()
  • IntStream.range()    等

(3)文件流

  • Files.lines(path)、Files.lines(path, charSet)、Files.walk()
  • BufferedReader.lines()    等

(4)直接创建

  • Spliterator

(5)jdk相关

  • Random.ints()
  • BitSet.stream()
  • Pattern.splitAsStream(java.lang.CharSequence)
  • JarFile.stream()

3)流的操作

流的操作类型分为两种:

  • Intermediate:一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。
  • Terminal:一个流只能有一个 terminal 操作,当这个操作执行完成后,流就被耗光。如果需要继续操作,则需再次获取流。Terminal 操作的执行,才会真正开始流的遍历,获得最终结果或side effect。

通过流对集合内的全部元素进行处理时,是依次对每个元素进行全部流操作,即一次遍历完成全部内容。

Stream.of("hello","world")
      .peek(s -> System.out.println(s.toUpperCase()))
      .filter(s -> s.length() > 0).peek(s -> System.out.println(s.toLowerCase()))
      .forEach(s -> System.out.println(s.substring(0, 3)));

输出结果为:

HELLO
hello
hel
WORLD
world
wor

Stream的部分流操作又被认为具有 short-circuiting特性

  • 对于一个 intermediate 操作,如果它接受的是一个无限大(infinite/unbounded)的 Stream,但返回一个有限的新 Stream。
  • 对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。

当操作一个无限大的 Stream,而又希望在有限时间内完成操作,则在管道内拥有一个 short-circuiting 操作是必要非充分条件。

具体分类如下:

(1)Intermediate(中间操作):

  • map (mapToInt, flatMap 等):转换元素的值
  • filter:将结果为false的元素过滤掉
  • distinct:去重
  • sorted:排序(可以使用比较器)
  • peek:主要目的是用调试,通过该方法可以看到流中的数据经过每个处理点时的状态。
  • skip:跳过前n个元素
  • parallel:获取并行流
  • sequential:获取串行流
  • unordered:去除流操作中的有序化约束:默认情况下,从有序集合、生成器、迭代器产生的流或者通过调用Stream.sorted产生的流都是有序流,有序流在并行处理时会在处理完成之后恢复原顺序。unordered()方法可以解除有序流的顺序限制,更好地发挥并行处理的性能优势,例如distinct将保存任意一个唯一元素而不是第一个,limit将保留任意n个元素而不是前n。
  • limit:保留n个元素

(2)Terminal(最终操作):

  • min
  • max
  • count
  • findFirst:返回第一个元素
  • findAny:返回任意元素
  • anyMatch:任意元素匹配
  • allMatch:全部元素匹配
  • noneMatch:没有元素匹配
  • reduce
  • forEach
  • forEachOrdered
  • toArray
  • collect
  • iterator

(3)Short-circuiting(截断型操作):上述操作中anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit属于截断型操作。

4)部分操作示例

(1)map/flatMap

把 input Stream 的每一个元素,映射成 output Stream 的另外一个元素。

 

//全部转换成大写
List<String> output = wordList.stream().map(String::toUpperCase)
                              .collect(Collectors.toList());

//计算平方数
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
List<Integer> squareNums = nums.stream().map(n -> n * n).collect(Collectors.toList());

//使用flatmap对集合进行拆包
Stream<List<Integer>> inputStream = Stream.of(Arrays.asList(1),Arrays.asList(2, 3),
                                              Arrays.asList(4, 5, 6));

Stream<Integer> outputStream = inputStream.flatMap((childList) -> childList.stream());

(2)filter

filter 对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。

Integer[] sixNums = {1, 2, 3, 4, 5, 6};

Integer[] evens = Stream.of(sixNums).filter(n -> n%2 == 0).toArray(Integer[]::new);

List<String> output = reader.lines().flatMap(line -> Stream.of(line.split(REGEXP)))
                                    .filter(word -> word.length() > 0)
                                    .collect(Collectors.toList());

(3)forEach

forEach 方法接收一个 Lambda 表达式,然后在 Stream 的每一个元素上执行该表达式。

//Before java8
for (Person p : roster) {
    if (p.getGender() == Person.Sex.MALE)    System.out.println(p.getName());
}

//java 8 stream
roster.stream().filter(p -> p.getGender() == Person.Sex.MALE)
               .forEach(p -> System.out.println(p.getName()));



(4)findFirst

这是一个 termimal 兼 short-circuiting 操作,它总是返回 Stream 的第一个元素,或者空。这里比较重点的是它的返回值类型:Optional。Stream 中的 findAny、max/min、reduce 等方法等返回 Optional 值。还有例如 IntStream.average() 返回 OptionalDouble 等等。

(5)reduce

这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相当于

    Integer sum = integers.reduce(0, (a, b) -> a+b);   或  Integer sum = integers.reduce(0, Integer::sum);

也有没有起始值的情况,这时会把 Stream 的前面两个元素组合起来,返回的是 Optional。

清单 15. reduce 的用例

// 字符串连接,concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);

// 求最小值,minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0)
                        .reduce(Double.MAX_VALUE, Double::min);

// 求和,sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);

// 求和,sumValue = 10, 无起始值
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();

// 过滤,字符串连接,concat = "ace"
concat = Stream.of("a", "B", "c", "D", "e", "F")
               .filter(x -> x.compareTo("Z") > 0)
               .reduce("", String::concat);

上面代码例如第一个示例的 reduce(),第一个参数(空白字符)即为起始值,第二个参数(String::concat)为 BinaryOperator。这类有起始值的 reduce() 都返回具体的对象。而对于第四个示例没有起始值的 reduce(),由于可能没有足够的元素,返回的是 Optional,请留意这个区别。

(6)limit/skip

limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素(它是由一个叫 subStream 的方法改名而来)。

public void testLimitAndSkip() {
    List<Person> personList= new ArrayList();
    for (int i = 1; i <= 100; i++) {
        Person person = new Person(i, "name" + i);
        personList.add(person);
    }

    List<String> strList = personList.stream()
                                     .map(Person::getName).limit(10)
                                     .skip(3).collect(Collectors.toList());
    System.out.println(strList);
}

class Person {
    private int no;
    private int age;
    private String name;

    public Person (int no, String name) {
        this.no = no;
        this.name = name;
        this.age = 0;
    }
    
    public Person (int no, int age, String name) {
        this.no = no;
        this.name = name;
        this.age = age;
    }

    public int getAge(){
        return this.age;
    }

    public String getName() {
        return this.name;
    }
    
    public int getNo() {
    	return this.no;
    }
}

 

输出结果为:

name1
name2
name3
name4
name5
name6
name7
name8
name9
name10
[name4, name5, name6, name7, name8, name9, name10]

 

这是一个有 10,000 个元素的 Stream,但在 short-circuiting 操作 limit 和 skip 的作用下,管道中 map 操作指定的 getName() 方法的执行次数为 limit 所限定的 10 次,而最终返回结果在跳过前 3 个元素后只有后面 7 个返回。

有一种情况是 limit/skip 无法达到 short-circuiting 目的的,就是把它们放在 Stream 的排序操作后,原因跟 sorted 这个 intermediate 操作有关:此时系统并不知道 Stream 排序后的次序如何,所以 sorted 中的操作看上去就像完全没有被 limit 或者 skip 一样。limit 和 skip 对 sorted 后的运行次数无影响:

List<Person> personList = new ArrayList();

for (int i = 1; i <= 10; i++) {
    Person person = new Person(i, "name" + i);
    personList.add(person);
}

List<Person> resultList = personList.stream()
                                .sorted((p1, p2) ->p1.getName().compareTo(p2.getName()))
                                .limit(2).collect(Collectors.toList());
System.out.println("" + resultList.get(0).getName()+resultList.get(1).getName());

输出结果为:

name2
name1
name3
name2
name4
name3
name5
name4
name6
name5
name7
name6
name8
name7
name9
name8
name10
name9
name10
name5
name10
name3
name10
name2
name10
name1
name1
name10
name1name10

显然执行了不止2次。

(7)sorted

对 Stream 的排序通过 sorted 进行,它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间。

List<Person> personList = new ArrayList();

for (int i = 1; i <= 10; i++) {
    Person person = new Person(i, "name" + i);
    personList.add(person);
}

List<Person> resultList = personList.stream().filter(p -> p.getNo() < 5)
	                .limit(2).sorted((p1, p2) ->p1.getName()
                        .compareTo(p2.getName())).collect(Collectors.toList());

System.out.println("" + resultList.get(0).getName()+resultList.get(1).getName());

结果会简单很多:

name1name2

(8) min/max/distinct

min 和 max 的功能也可以通过对 Stream 元素先排序,再 findFirst 来实现,但前者的性能会更好,为 O(n),而 sorted 的成本是 O(n log n)。同时它们作为特殊的 reduce 方法被独立出来也是因为求最大最小值是很常见的操作。

BufferedReader br = null;

try(br = new BufferedReader(new FileReader("c:\\test.txt"));){
    //找出最长一行的长度
    int longestLine = br.lines().mapToInt(String::length).max().getAsInt();
    System.out.println(longestLine);
   
     //找出全文的单词,转小写,并排序
    List<String> words = br.lines().flatMap(line -> Stream.of(line.split(" ")))
                       .filter(word -> word.length() > 0).map(String::toLowerCase)
                       .distinct().sorted().collect(Collectors.toList());
    System.out.println(words);
}


 (9) Match

Stream 有三个 match 方法,从语义上说:

  • allMatch:Stream 中全部元素符合传入的 predicate,返回 true
  • anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true
  • noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true

它们都不是要遍历全部元素才能返回结果。例如 allMatch 只要一个元素不满足条件,就 skip 剩下的所有元素,返回 false。

List<Person> persons = new ArrayList();
persons.add(new Person(1, 11, "name" + 1));
persons.add(new Person(2, 21, "name" + 2));
persons.add(new Person(3, 31, "name" + 3));
persons.add(new Person(4, 41, "name" + 4));
persons.add(new Person(5, 51, "name" + 5));

boolean isYoung = persons.stream()
                         .peek(p -> System.out.println(p.getName() + ":" + p.getAge()))
                         .allMatch(p -> p.getAge() < 30);
System.out.println("All young? " + isYoung);

boolean isThereAnyChild = persons.stream()
                          .peek(p -> System.out.println(p.getName() + ":" + p.getAge()))
                          .anyMatch(p -> p.getAge() < 18);
System.out.println("Any child? " + isThereAnyChild);

输出结果:

name1:11
name2:21
name3:31
All young? false
name1:11
Any child? true

(10) Collectors

java.util.stream.Collectors 类的主要作用就是辅助进行各类有用的 reduction 操作,例如转变输出为 Collection,把 Stream 元素进行归组。

//使用groupingby按照年龄进行分组
Map<Integer, List<Person>> personGroups = Stream.generate(new PersonSupplier())                                                            
                             .limit(100).collect(Collectors.groupingBy(Person::getAge));

Iterator it = personGroups.entrySet().iterator();
while (it.hasNext()) {
    Map.Entry<Integer, List<Person>> persons = (Map.Entry) it.next();
    System.out.println("Age " + persons.getKey() + " = " + persons.getValue().size());
}

//使用partitioningBy按照类别进行自定义分组
Map<Boolean, List<Person>> children = Stream.generate(new PersonSupplier()).limit(100)
                              .collect(Collectors.partitioningBy(p -> p.getAge() < 18));

System.out.println("Children number: " + children.get(true).size());
System.out.println("Adult number: " + children.get(false).size());

6. Optional类

Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。同时提供了多种方法来进行逻辑处理:orElse(如果值不存在则返回默认值)、ifPresent(如果值存在则执行)。

//Before Java 1.8
if (text != null) {
    System.out.println(text);
}

return if (text != null) ? text.length() : -1;


//Java 1.8
Optional.ofNullable(text).ifPresent(System.out::println);

return Optional.ofNullable(text).map(String::length).orElse(-1);

7. Time类

Java 1.8中提供了一个全新的Time类来处理时间问题,解决以往java.util和java.sql存在相同方法的问题。

// 获取当前的日期时间
LocalDateTime currentTime = LocalDateTime.now();
System.out.println("当前时间: " + currentTime);
		 
LocalDate date1 = currentTime.toLocalDate();
System.out.println("date1: " + date1);
		 
Month month = currentTime.getMonth();
int day = currentTime.getDayOfMonth();
int seconds = currentTime.getSecond();
System.out.println("月: " + month + ", 日: " + day + ", 秒: " + seconds);

//修改当前时间对应的日期
LocalDateTime date2 = currentTime.withDayOfMonth(1).withMonth(1).withYear(9102);
System.out.println("date2: " + date2);

//生成一个日期
LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
System.out.println("date3: " + date3);
//生成一个时间
LocalTime date4 = LocalTime.of(22, 15);
System.out.println("date4: " + date4);

//字符串转时间
LocalTime date5 = LocalTime.parse("20:15:30");
System.out.println("date5: " + date5);

以上为Java 1.7和1.8(Java7、8)中新增特性中我比较关注的内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值