java基础学习之jdk8新特性(六)

可变参数

➢当在Java中调用一个方法时, 必须严格的按照方法定义的变量进行参数传递,但是在开发中有可能会出现这样-种情况: 不确定要传递的参数个数。例如:现在要求设计一个方法, 此方法可以实现若千个整型变量的相加操作。最早的时候为了解决这个问题,往往需要将多个参数封装为数组才可以满足要求。

最初的解决方案

public class Main {
    public static void main(String args[]) throws Exception {
        System.out.println(add(new int[]{1,2,3}));
    }
    public static int add(int [] data){
        int sum = 0;
        for(int i = 0; i < data.length; i++){
            sum += data[i];
        }
        return sum;
    }
}
结果:6

使用可变参数

public class Main {
    public static void main(String args[]) throws Exception {
        System.out.println(add(1,2,3));
    }
    //“...”代表接收任意个int类型的参数
    public static int add(int ... data){
        int sum = 0;
        for(int i = 0; i < data.length; i++){
            sum += data[i];
        }
        return sum;
    }
}
结果:6

如果参数列表中有2个及以上参数,可变参数放最后且可变参数最多只能有一个

public static void is(int c,int... a){
        //方法内容
    }

foreach循环

➢foreach是一种加强型的for循环操作, 主要是可以简化数组或集合数据的输出操作。以下是使用格式:
在这里插入图片描述

public class Main {
    public static void main(String args[]) throws Exception {
        int[] ints = {1, 2, 3};
        for(int i : ints){
            System.out.print(i+"、");
        }
    }
}
结果:123

泛型

➢在面向对象的开发中,利用对象的多态性可以解决方法参数的统一-问题, 但是随之而来也会带来有一个新的问题: “向 下转型会存在类转换异常(ClassCastException) ",所以向下转型的操作并不是安全的,那么为了解决这样的问题,从JDK 1.5开始提供有泛型技术,而本节将为读者分析泛型技术的产生原因以及相关定义。

泛型的引出

➢现在假设要开发一个GIS系统(地理信息系统、Geographic Information System),则肯定需要一个可以描述坐标的类 (Point) ,同时在这个类里面要求保存有以下几种类型的坐标:
保存数字:x=10、y = 20;
保存小数: x= 10.2、y = 20.3;
保存字符串: x=东经20度、y =北纬15度。
➢现在这个Point类设计的关键就在于x与y这两个变量的数据类型选择上。必须有一种数据类型可以保存这三 类数据,那么首先想到的一定是Object类型,因为此时会存在有如下的转换关系:
➢int数据类型: int自动装箱为Integer, Integer向上转型为Object;
double数据类型: double自动装箱为Double,Double向 上转型为Object;
➢String数据类型: 直接向上转型为Object。

package com.company;
class Point{
    private Object x;   //设置坐标
    private Object y;   //设置坐标
    public Object getX() {
        return x;
    }
    public void setX(Object x) {
        this.x = x;
    }
    public Object getY() {
        return y;
    }
    public void setY(Object y) {
        this.y = y;
    }
}
public class Main {
    public static void main(String args[]) {
        //设置坐标
        Point p = new Point();
        p.setX(10);
        p.setY(20);
        //获取坐标
        int x =(Integer)p.getX();
        int y = (Integer)p.getY();
        System.out.println("x坐标:"+x+"y坐标:"+y);
    }
}
结果:x坐标:10y坐标:20

上述程序在调用setter方法设置坐标时,所有数据类型都发生了向上转型,而在取得数据时都发生了向下转型。表面看起来时没有什么问题,但是会带来一个严重后果:一旦设置的内容出现错误,在程序编译时是无法检查出来的。

错误的程序

public class Main {
    public static void main(String args[]) {
        //设置坐标
        Point p = new Point();
        p.setX("北纬10");
        p.setY(20);
        //获取坐标
        String x =(String)p.getX();
        String y = (String) p.getY();
        System.out.println("x坐标:"+x+"y坐标:"+y);
    }
}
结果:Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
	at com.company.Main.main(Main.java:26)

原本打算设置的坐标数据是字符串数据,但是在设置数据时出现了错误,将Y坐标数据设置成了20而不是字符串。由于int型可以通过自动装箱使用Integer再向上转型Object接收,所以在程序编译时不会有任何语法错误。而在程序执行过程中需要将Y坐标数据取出,并且以String的形式进行强制向下转型,这时运行中程序就会报异常了,这样就会为开发带来很大的困扰,如果可以在编译时就能够排查出来就比较合理了。所以就新增了泛型技术,其意义在于:类属性或方法的参数在定义数据类型时,可以直接使用一个标记进行占位,在具体使用时才设置其对应的实际数据类型,这样当设置的数据类型出现错误后,就可以在程序编译时监测出来。

使用泛型修改

package com.company;
class Point<T>{
    private T x;   //设置坐标
    private T y;   //设置坐标
    public T getX() {
        return x;
    }
    public void setX(T x) {
        this.x = x;
    }
    public T getY() {
        return y;
    }
    public void setY(T y) {
        this.y = y;
    }
}
public class Main {
    public static void main(String args[]) {
        //设置坐标
        Point<String> p = new Point<>();	//设置泛型类型为String
        p.setX("北纬10");
        p.setY(20);	//此行代码会出现警告,此处需要字符串
        //获取坐标
        String x =p.getX();		//取出数据,不需要强制转换
        String y = p.getY();	//取出数据,不需要强制转换
        System.out.println("x坐标:"+x+"y坐标:"+y);
    }
}

使用泛型后,所有类中的属性的类型都是动态设置的,且所有使用泛型标记的方法参数类型也都发生改变,这样就相当于避免了向下转型的问题,从而解决了类对象转换的安全隐患。需要特别说明的时,要想使用泛型,那么它能够采用的类型只能够是类,即不能是基本类型,只能是引用类型。

定义多个泛型

class Point<P,R>{
	public R fun(P p){
		return null;
	}
}

如果不设置泛型会怎样?
答:为了保证设计的合理性,如果不设置泛型会使用Object类型。

通配符

➢利用泛型技术虽然解决了向下转型所带来的安全隐患问题,但同时又会产生一个新的问题: 即便是同一个类,但是由于设置泛型类型的不同,那么其对象表示的含义也是不同,是不能够直接进行引用操作的,例如:现在有如下一个类。

class Message<T>{
    private T msg;
    public T getMsg() {
        return msg;
    }
    public void setMsg(T msg) {
        this.msg = msg;
    }
}

如果定义“Message< String>”和“Message< Integer>” 虽然都是Message类的对象,但是这两个对象之间是不能够进行直接的引用传递操作,那么这样就会在方法的参数传递上造成新的问题,此时就可以利用通配符“ ?”来进行描述。

上面代码参照上面的
public class Main {
    public static void main(String args[]) {
        Message<String> m = new Message<>();
        m.setMsg("aaa");
        fun(m);
    }
    public static void fun(Message<String> temp){
        System.out.println(temp.getMsg());
    }
}

这个程序的主要功能是实现一个Message类对象的引用传递,而在定义fun()方法时也设置了接收的参数“Message< String> temp”,这样就表示只要泛型类型为String的Message对象都可以接收。但是一旦这样定义,fun()方法就不能再接收泛型类型非String的了。
一个错误的示例

public class Main {
    public static void main(String args[]) {
        Message<Integer> m = new Message<>();
        m.setMsg(30);
        fun(m);		//程序错误,因为泛型类型为String
    }
    public static void fun(Message<String> temp){
        System.out.println(temp.getMsg());
    }
}

使用通配符“ ?”来解决参数转递问题

public class Main {
    public static void main(String args[]) {
        Message<Integer> m = new Message<>();
        Message<String> n = new Message<>();
        m.setMsg(10);
        n.setMsg("aaa");
        fun(m);
        fun(n);
    }
    public static void fun(Message<?> temp){
        System.out.println(temp.getMsg());
    }
}
结果:10
	  aaa

提问:能否使用Object作为泛型类型,或者不设置类型?
回答:使用通配符“ ?”的意义在于可以接收类对象,但是不能修改对象属性。
首先需要解释一个核心问题:在明确设置一个类为泛型类型时没有继承的概念范畴,也就是说虽然Object类与String类在类定义关系中属于父类与子类的关系,但换到泛型中“Message< Object>”与“Message< String>”就属于两个完全独立的概念。
如果在定义fun()方法时不设置泛型类型,也就可以实现任意泛型类型对象的接收,但此时就会出现一个问题:如果不指派具体的泛型类型,则默认Object类型,也就是说方法里面可以随意修改属性内容。观察如下代码

public class Main {
    public static void main(String args[]) {
        Message<Integer> m = new Message<>();
        Message<String> n = new Message<>();
        m.setMsg(10);
        n.setMsg("aaa");
        fun(m);
        fun(n);
    }
    public static void fun(Message temp){
        //随意修改属性内容
        temp.setMsg("修改属性内容");
        System.out.println(temp.getMsg());
    }
}
结果:修改属性内容
	 修改属性内容

上述程序随意修改对象内容是不严谨的,必须使用通配符“ ?”来制约这种任意修改数据问题的操作。所以“ ?”设置的泛型类型只表示可以取出,但是不能设置,一旦设置了内容,程序编译就会出现错误提示。

泛型的上限与下限


“? extends类”:设置泛型上限,可以在声明上和方法参数上使用;
➢? extends Number:意味着可以设置Number或者是Number的子类(Integer、 Double、 … ;
➢”? super类”:设置泛型下限,方法参数.上使用;
➢? super String:意味着只能够设置String或者是它的父类0bject。

设置泛型的上限

package com.company;
class Message<T extends Number>{	//设置泛型的上限,只能是Number或Number子类
    private T msg;
    public T getMsg() {
        return msg;
    }
    public void setMsg(T msg) {
        this.msg = msg;
    }
}
public class Main {
    public static void main(String args[]) {
        Message<Integer> m = new Message<>();	//Integer是Number子类
        m.setMsg(10);
        fun(m);		//引用传递
    }
    public static void fun(Message<? extends Number> temp){	//设置泛型的上限,只能是Number或Number子类
        System.out.println(temp.getMsg());
    }
}
结果:10

设置泛型的下限

package com.company;
class Message<T>{
    private T msg;
    public T getMsg() {
        return msg;
    }
    public void setMsg(T msg) {
        this.msg = msg;
    }
}
public class Main {
    public static void main(String args[]) {
        Message<String> m = new Message<>();
        m.setMsg("hello");
        fun(m);
    }
    public static void fun(Message<? super String> temp){	//设置了泛型的下限,只能是String或者是它的父类0bject
        System.out.println(temp.getMsg());
    }
}
结果:hello

泛型接口

定义泛型接口

/** 
* 定义泛型接口,由于类与接口命名标准相同,为了区分出类与接口,在接口前面加上字母“I”,例如:IMessage
* 如果定义抽象类,则可以在前面加上Abstract,例如:AbstractMessage
*/
interface IMessage<T>{	//定义泛型接口
	public void print(T t);
}

在子类继续设置泛型标记

package com.company;
interface IMessage<T>{
    public void print(T t);
}
class MessageImpl<T> implements IMessage<T>{
    @Override
    public void print(T t) {
        System.out.println(t);
    }
}
public class Main {
    public static void main(String args[]) {
        IMessage<String> m = new MessageImpl<>();
        m.print("hello");
    }
}
结果:hello

本程序在定义IMeaasge接口子类时继续设置了泛型,在实例化接口子类对象时所设置的泛型类型也就是IMessage接口使用的泛型类型。

在子类不设置泛型,而为父接口明确定义一个泛型类型

package com.company;
interface IMessage<T>{
    public void print(T t);
}
class MessageImpl implements IMessage<String>{
    @Override
    public void print(St
    ring t) {
        System.out.println(t);
    }
}
public class Main {
    public static void main(String args[]) {
        IMessage<String> m = new MessageImpl();
        m.print("hello");
    }
}
结果:hello

泛型方法

对于泛型除了可以定义在类上外,还可以在方法上进行定义,而在方法上定义泛型时,这个方法不一定非要在泛型类中定义。

public 与 返回值中间< T >非常重要,可以理解为声明此方法为泛型方法

泛型方法定义

public class Main {
    public static void main(String args[]) {
        System.out.println(("hello"));
    }
    public static <T> T fun(T t){
        return t;
    }
}
结果:hello

调用泛型方法

public class study {
    public static void main(String args[]) {
        List<java.lang.String> list = new ArrayList<>();
        list.add("huahua");
        list.add("fanfan");
        list.add("maomao");
        //调用toArray(T[] a)泛型方法
        String[] array = list.toArray(new String[list.size()]);
    }
}

枚举

➢枚举是JDK 1.5之后增加的一个主要新功能,利用枚举可以简化多例设计模式(一 个类只能够产生固定几个实例化对象)的定义,同时在Java中的枚举也可以像普通类那样定义属性、构造方法、实现接口等

认识枚举

枚举主要用于定义一组可以使用的类对象,这样在使用时只能通过固定的几个对象来进行类的操作。
从JDK1.5开始,专门提供了一个新的关键字:enum,利用该关键字就可以定义枚举类型。
定义枚举类

enum Color{
    RED,GREEN,BLUE;
}
public class Main {
    public static void main(String args[]) {
        System.out.println(Color.RED);	//默认内容就是对象的名称
    }
}
结果:RED

枚举属于简化的多例设计模式

class Color{
    private String title;
    private static final Color RED = new Color("红色");
    private static final Color GREEN = new Color("绿色");
    private static final Color BLUE = new Color("蓝色");
    public Color(String title) {
        this.title = title;
    }
    public static Color getInstance(int chr){
        switch (chr){
            case 1:
                return RED;
            case 2:
                return GREEN;
            case 3:
                return BLUE;
            default:
                return null;
        }
    }
    @Override
    public String toString() {
        return "Color{" +
                "title='" + title + '\'' +
                '}';
    }
}
public class Main {
    public static void main(String args[]) {
        Color red = Color.getInstance(1);
        System.out.println(red);
    }
}
结果:Color{title='红色'}

Enum类
➢枚举只需要使用enum关键字就可以定义,但是严格来讲,枚举只是类结构的加强而已。因为在Java中使用enum定义的枚举类就相当于默认继承了java.lang.Enum类,此类定义如下:
public abstract class Enum<E extends Enum>
extends Object
implements Comparable, Serializable

Enum类定义的方法:
NO. | 方法|类型| 描述|
-------- | ----- |----| —| —| —
1 |protected Enum(String name , int ordinal) | 构造|传递枚举对象的名称和序号
2 | pubilc final int ordinal() | 普通| 取得当前枚举对象的序号
3 | public final String name () |普通 | 取得当前枚举对象的名称

enum Color{
    RED,GREEN,BLUE;
}
public class Main {
    public static void main(String args[]) {
        Color red = Color.RED;
        System.out.println("当前枚举对象序号:"+red.ordinal());
        System.out.println("当前枚举对象名称:"+red.name());
    }
}
结果:当前枚举对象序号:0
	 当前枚举对象名称:RED

枚举类除了可以继承Enum抽象类提供的方法外,还定义了一个values()方法,这个方法会将枚举类中的全部对象以对象数组的形式返回。
返回枚举中的全部内容

enum Color{
    RED,GREEN,BLUE;
}
public class Main {
    public static void main(String args[]) {
        for(Color c : Color.values()){
            System.out.println(c.ordinal()+"--"+c.name());
        }
    }
}
结果:
0--RED
1--GREEN
2--BLUE

枚举中定义其他结构

➢按照之前所理解,枚举就属于多例设计模式,那么既然是多例设计模式,对于类之中就肯定有多种组成,包括属性、方法、构造方法,在枚举之中也同样可以定义以上的内容,但是此处需要注意两点问题:
➢枚举之中定义的构造方法不能够使用public声明,如果没有无参构造,请手工调用构造传递参数;
➢枚举对象必须要放在首行,随后才可以定义属性、构造、普通方法等结构。
扩充枚举功能

enum Color{
    RED("红色"),GREEN("绿色"),BLUE("蓝色");
    private String title;
    private Color(String title){
        this.title = title;
    }
    @Override
    public String toString() {
        return this.title;
    }
}
public class Main {
    public static void main(String args[]) {
        for(Color c : Color.values()){
            System.out.print(c+"、");
        }
    }
}
结果:红色、绿色、蓝色、

枚举实现接口

interface IMessage{
    String getTitle();
}
enum Color implements IMessage{
    RED("红色"),GREEN("绿色"),BLUE("蓝色");
    private String title;
    private Color(String title){
        this.title = title;
    }
    @Override
    public String toString() {
        return this.title;
    }
    @Override
    public String getTitle() {	//其实没啥特别的,跟以前类一样实现方法就行
        return this.title;
    }
}
public class Main {
    public static void main(String args[]) {
        IMessage red = Color.RED;
        System.out.println(red.getTitle());
    }
}
结果:红色

枚举的实际作用

枚举最大的作用就是限定一个类的对象的产生格式,并且其要比多例设计模式更加简单

在Switch中使用枚举

enum Color{
    RED("红色"),GREEN("绿色"),BLUE("蓝色");
    private String title;
    private Color(String title){
        this.title = title;
    }
    @Override
    public String toString() {
        return this.title;
    }
}
public class Main {
    public static void main(String args[]) {
        Color red = Color.RED;
        switch (red){
            case RED:
                System.out.println("红色");
                break;
            case GREEN:
                System.out.println("绿色");
                break;
            case BLUE:
                System.out.println("蓝色");
                break;
        }
    }
}
结果:红色

在类设计结构中使用枚举

enum Sex{
    MAN("男人"),WOMAN("女人");
    private String title;
    private Sex(String title){
        this.title = title;
    }
    @Override
    public String toString() {
        return this.title;
    }
}
class Member{
    private String name;
    private int age;
    private Sex sex;
    public Member(String name,int age,Sex sex){
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "Member{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex=" + sex +
                '}';
    }
}
public class Main {
    public static void main(String args[]) {
        System.out.println(new Member("小红",15,Sex.WOMAN));
    }
}
结果:Member{name='小红', age=15, sex=女人}

Annotation(注解)

准确的覆写:@Override

➢当进行方法覆写的时候,为了保证子类所覆写的方法的确是父类中定义过的方法,就可以加上"@Override" 注解,这样一-旦用户覆写方法时出现了 错误,可以在编译时直接检查出来。

声明过期操作:@Deprecated

class Member{
    @Deprecated		//声明此方法是过期方法,尽量不适用
    public void fun(){}
}
public class Main {
    public static void main(String args[]) {
        Member member = new Member();
        member.fun();	//有横线
    }
}

压制警告:@SuppressWarnings

暂无

Lambda表达式

➢Lambda表达式是JDK 1.8中引入的重要技术特征。所谓的Lamda表达式指的是应用在“SAM”(Single Abstract Method,含有一个抽象方法的接口) 环境下的一-种简化定义形式,可以于解决匿名内部类的定义复杂问题。
关于@FunctionalInterface注解的使用:
在Lambda表达式中已经明确要求是在接口上进行的一种操作,并且接口中只运行定义一个抽象方法。但是在一个项目开发中往往会定义大量的接口,而为了分辨出Lambda表达式使用的接口,可以在接口上使用“@FunctionalInterface”注解声明,这样就表示此为函数式接口,里面只允许定义一个抽象方法

什么是函数式接口?

➢接口中只有一个抽象方法的接口,称为函数式接口,可以用@FunctionalInterface修饰一下,这里需要注意的是:未使用 @FunctionalInterfaces注解的接口未必就不是函数式接口,一个接口是不是函数式接口的条件只有一条,即接口中只有一个抽象方法的接口(Object类中的方法不算)。而使用@FunctionalInterface注解修饰了的接口就一定是函数式接口,添加@FunctionalInterface注解可以帮助我们检查是否是函数式接口。
JDK中常见的函数式接口有:

1 @FunctionalInterface
2 public interface Runnable {
3     void run();
4 }
1 @FunctionalInterface
2 public interface Callable<V> {
3     V call() throws Exception;
4 }

Lambda表达式入门操作

interface IMessage{
    void print();
}
public class Main {
    public static void main(String args[]) {
        fun(() -> System.out.println("Lambda表达式"));
    }
    public static void fun(IMessage msg){
        msg.print();
    }
}
结果:Lambda表达式

下面是传统的写法(使用匿名内部类),观察下区别

interface IMessage{
    void print();
}
public class Main {
    public static void main(String args[]) {
        fun(new IMessage(){
            @Override
            public void print() {
                System.out.println("Lambda表达式");
            }
        });
    }
    public static void fun(IMessage msg){
        msg.print();
    }
}
结果:Lambda表达式

Lambda表达式使用形式
➢方法主体为1个表达式: (params) -> expression;
➢方法主体为1行执行代码: (params) -> statement;
➢方法主体需要多行代码: (params) -> { statements}.
编写多行语句

@FunctionalInterface		//用来检测该接口是否只有一个抽象方法
interface IMessage{
    void print();
}
public class Main {
    public static void main(String args[]) {
        fun(() -> {
            System.out.println("多行语句");
            System.out.println("多行语句");
        });
    }
    public static void fun(IMessage msg){
        msg.print();
    }
}
结果:多行语句
	 多行语句

定义有参数有返回值的方法

@FunctionalInterface
interface IMessage{
    int add(int x,int y);
}
public class Main {
    public static void main(String args[]) {
        fun((a,b)->{
            return a+b;
        });
    }
    public static void fun(IMessage msg){
        msg.add(5,6);
    }
}
结果:11

传递可变参数

@FunctionalInterface
interface IMessage{
    int add(int ...arg);
    static int sum(int ...arg){
        int sum = 0;
        for(int temp : arg){
            sum += temp;
        }
        return sum;
    };
}
public class Main {
    public static void main(String args[]) {
        fun((int ...parms) -> IMessage.sum(parms));
    }
    public static void fun(IMessage msg){
        msg.add(5,6);
    }
}
结果:11

lambda表达式若访问了局部变量,则局部变量必须是final的,若是局部变量没有加final关键字,系统会自动添加,此后在修改该局部变量,会报错

interface IMessage{
    int add(int ...agr);
    static int sum(int ...arg){
        int sum = 0;
        for(int temp : arg){
            sum += temp;
        }
        return sum;
    }
}
public class b {
    public static void main(String[] args) {
        //测试final
        int i = 5;
        fun(agr -> IMessage.sum(agr)-i); //这里编译会报错,因为局部变量i已经系统自动加上了final关键字
        i = 6;
    }

    public static void fun(IMessage args) {
        args.add(5,6,7);
    }
}

方法的引用

➢我们用Lambda表达式来实现匿名方法。但有些情况下,我们用Lambda表达式仅仅是调用一些已经存在的方法,除了调用动作外,没有其他任何多余的动作,在这种情况下,我们倾向于通过方法名来调用它,而Lambda表达式可以帮助我们实现这一要求,它使得Lambda在调用那些已经拥有方法名的方法的代码更简洁、更容易理解。方法引用可以理解为Lambda表达式的另外一种表现形式。

➢引用静态方法:类名称:: static方法名称;
➢引用某个对象的方法:实例化对象::普通方法;
➢引用特定类型的方法:特定类::普通方法;
➢引用构造方法:类名称:: new。

在这里插入图片描述

Lambda表达式写法(引用静态方法)


public class b {
    public static void main(String[] args) {

        IMessage iMessage = agr -> String.valueOf(agr);
        
    }
}

引用静态方法

@FunctionalInterface
interface IMessage<P,R>{
    R zhuanhuan(P p);
}
public class Main {
    public static void main(String args[]) {
        //其实就是将String.valueOf()方法变为了IMessage接口中的zhuanhuan方法,换了个名称而已
        IMessage<Integer,String> msg = String::valueOf;
        String s = msg.zhuanhuan(1000);
        System.out.println(s);
    }
}
结果:1000

引用普通方法

@FunctionalInterface
interface IMessage<R>{
    R zhuanhuan();
}
public class Main {
    public static void main(String args[]) {
        //其实就是将toUpperCase()方法变为了IMessage接口中的zhuanhuan方法
        IMessage<String> msg = "aaa"::toUpperCase;
        String s = msg.zhuanhuan();
        System.out.println(s);
    }
}
结果:AAA

引用特定类的方法

若Lambda参数列表中的第一个参数是实例方法的参数调用者,而第二个参数是实例方法的参数时,可以使用对象方法引用。

@FunctionalInterface
interface IMessage<R>{
    int zhuanhuan(R r1,R r2);
}
public class Main {
    public static void main(String args[]) {
        IMessage<String> msg = String::compareTo;
        //传递调用的参数,形式为:"a".compareTo("b")
        int s = msg.zhuanhuan("a","b");
        System.out.println(s);
    }
}
结果:-1

引用构造方法

@FunctionalInterface
interface IMessage<R>{
    R create(String title);
}
class Book{
    private String title;
    public Book(String title){
        this.title = title;
    }
    @Override
    public String toString() {
        return "Book{" +
                "title='" + title + '\'' +
                '}';
    }
}
public class Main {
    public static void main(String args[]) {
        IMessage<Book> msg = Book::new;
        Book book = msg.create("aa");
        System.out.println(book);
    }
}
结果:Book{title='aa'}

内建函数式接口

➢在方法引用的操作过程之中,读者可以发现,不管如何进行操作,对于可能出现的函数式接口的方法也最多只会有四类:有参数有返回值、有参数无返回值、无参数有返回值、判断真假。所以为了简化开发者的定义以及操作的统一, 从JDK 1.8开始提供有一个新的开发包: java.util.function, 并且在这个包中提供有以下四个核心的函数式接口: Function、 Consumer. Supplier、 Predicate。

使用功能型函数式接口——接收参数并返回结果

public class Main {
    public static void main(String args[]) {
        Function<String,Boolean> fun = "##aa"::startsWith;
        System.out.println(fun.apply("#"));
    }
}
结果:true

消费型接口

public class Main {
    public static void main(String args[]) {
        Consumer<String> fun = System.out::println;
        fun.accept("##");
    }
}
结果:##

供给型接口

public class Main {
    public static void main(String args[]) {
        Supplier<String> fun = "aaa"::toUpperCase;
        System.out.println(fun.get());
    }
}
结果:AAA

断言型接口

public class Main {
    public static void main(String args[]) {
        Predicate<String> fun = "abc"::equalsIgnoreCase;
        System.out.println(fun.test("ABC"));
    }
}
结果:true

Stream API

Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

Stream的操作步骤

在这里插入图片描述

Stream有如下三个操作步骤:

一、创建Stream

从一个数据源,如集合、数组中获取流。

public class Demo1 {
    public static void main ( String[] args ) {
        //创建stream流,通过Arrays.stream
        int[] arr = {1, 2, 3};
        IntStream stream = Arrays.stream(arr);
        Person[] personStr = {new Person(18, "xiaoliu"), new Person(18, "xiaojing")};
        Stream<Person> personStream = Arrays.stream(personStr);
        // 通过stream.of
        Stream<Integer> integerStream = Stream.of(1, 2, 3, 4);
        // 通过集合创建流
        List<String> stringList = Arrays.asList("123", "456", "789");
        // 创建普通流
        Stream<String> stringStream = stringList.stream();
        // 创建并行流
        Stream<String> parallelStream = stringList.parallelStream();

    }
}

二、中间操作

一个操作的中间链,对数据源的数据进行操作。

三、终止操作

一个终止操作,执行中间操作链,并产生结果。

要注意的是,对流的操作完成后需要进行关闭操作(或者用JAVA7的try-with-resources)。

举个简单的例子:

假设有一个Person类和一个Person列表,现在有两个需求:1)找到年龄大于18岁的人并输出;2)找出所有中国人的数量。

@Data
class Person{
    private String name;
    private Integer age;
    private String country;
    private char sex;

    public Person(String name, Integer age, String country, char sex) {
        this.name = name;
        this.age = age;
        this.country = country;
        this.sex = sex;
    }

}
public class b {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<>();
        personList.add(new Person("欧阳雪",18,"中国",'F'));
        personList.add(new Person("Tom",24,"美国",'M'));
        personList.add(new Person("Harley",22,"英国",'F'));
        personList.add(new Person("向天笑",20,"中国",'M'));
        personList.add(new Person("李康",22,"中国",'M'));
        personList.add(new Person("小梅",20,"中国",'F'));
        personList.add(new Person("何雪",21,"中国",'F'));
        personList.add(new Person("李康",22,"中国",'M'));

        personList.stream().filter((p) -> p.getAge() > 18).forEach((System.out::println));
        System.out.println("-------------------");
        long count = personList.stream().filter((p) -> p.getCountry().equals("中国")).count();
        System.out.println("中国人数量:"+count);
    }
}
结果:Person(name=Tom, age=24, country=美国, sex=M)
	Person(name=Harley, age=22, country=英国, sex=F)
	Person(name=向天笑, age=20, country=中国, sex=M)
	Person(name=李康, age=22, country=中国, sex=M)
	Person(name=小梅, age=20, country=中国, sex=F)
	Person(name=何雪, age=21, country=中国, sex=F)
	Person(name=李康, age=22, country=中国, sex=M)
-------------------
中国人数量:6

在这个例子中,personList.stream()是创建流,filter()属于中间操作,forEach、count()是终止操作。

Stream中间操作–筛选与切片

filter:接收Lambda,从流中排除某些操作;
limit:截断流,使其元素不超过给定对象
skip(n):跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补
distinct:筛选,通过流所生成元素的hashCode()和equals()去除重复元素。

filter举例

// 筛选出集合中数字大于4的元素
List<Integer> collect = integerList.stream().filter(x -> x > 4).collect(Collectors.toList());

limit举例

截短流,返回前 n 个元素的流
从stream流中取前2个

personList.stream().limit(2).filter((p) -> p.getSex() == 'F').forEach((System.out::println));

skip举例

跳过流,返回一个扔掉了前 n 个元素的流
从stream流中跳过2个,从第3个开始取

personList.stream().skip(2).filter((p) -> p.getSex() == 'F').forEach((System.out::println));

distinct举例

返还一个去重流

personList.stream().distinct().filter((p) -> p.getSex() == 'M').forEach((System.out::println));

Stream中间操作–映射

map–接收Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
flatMap–接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流

map举例

List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
integers.stream().map((p) -> p*p).forEach((System.out::println));
结果:1491625

Stream中间操作–排序

sorted()–自然排序(Comparable)
sorted(Comparator com)–定制排序(Comparator)

自然排序举例

无需参数直接使用sorted()就行,前提是这个对象可以自然排序

List<Integer> integers = Arrays.asList(1,2,3,9,7);
integers.stream().sorted().forEach((System.out::print));
结果:12379

定制排序举例

按照年龄升序排列(ps:升序为从小到大排列)

personList.stream().sorted((p1,p2) -> {
            return p1.getAge().compareTo(p2.getAge());
        }).forEach((System.out::println));
        
结果:Person(name=欧阳雪, age=18, country=中国, sex=F)
Person(name=向天笑, age=20, country=中国, sex=M)
Person(name=小梅, age=20, country=中国, sex=F)
Person(name=何雪, age=21, country=中国, sex=F)
Person(name=Harley, age=22, country=英国, sex=F)
Person(name=李康, age=22, country=中国, sex=M)
Person(name=李康, age=22, country=中国, sex=M)
Person(name=Tom, age=24, country=美国, sex=M)

Stream终止操作–归约

reduce()方法传入的对象是BinaryOperator接口,它定义了一个apply()方法,负责把上次累加的结果和本次的元素 进行运算,并返回累加的结果

List<Integer> integers = Arrays.asList(1,2,3,9,7);
Integer reduce = integers.stream().reduce(0, (sum, t) -> sum + t);
System.out.println(reduce);
结果:22

Stream终止操作–收集

收集为List

List<String> collect = personList.stream().map((p) -> p.getName()).distinct().collect(Collectors.toList());
System.out.println(collect);
结果:[欧阳雪, Tom, Harley, 向天笑, 李康, 小梅, 何雪]

收集为Map

1、指定key-value,value是对象中的某个属性值。

Map<Integer,String> userMap1 = userList.stream().collect(Collectors.toMap(User::getId,User::getName));

2、指定key-value,value是对象本身,User->User 是一个返回本身的lambda表达式

Map<Integer,User> userMap2 = userList.stream().collect(Collectors.toMap(User::getId,User->User));

3、指定key-value,value是对象本身,Function.identity()是简洁写法,也是返回对象本身

Map<Integer,User> userMap3 = userList.stream().collect(Collectors.toMap(User::getId, Function.identity()));

4、指定key-value,value是对象本身,Function.identity()是简洁写法,也是返回对象本身,key 冲突的解决办法,这里选择第二个key覆盖第一个key。

Map<Integer,User> userMap4 = userList.stream().collect(Collectors.toMap(User::getId, Function.identity(),(key1,key2)->key2));

5、拼接key
Map<String, Parts> partsMap = synList.stream().collect(Collectors.toMap(k -> k.getOe()+k.getOeId()+k.getPartGroupId()+k.getStdPartId()+k.getBrandCode(), part -> part));

Optional

从 Java 8 引入的一个很有趣的特性是 Optional 类。Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException) —— 每个 Java 程序员都非常了解的异常。

我们从一个简单的用例开始。在 Java 8 之前,任何访问对象方法或属性的调用都可能导致 NullPointerException:

String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();

在这个小示例中,如果我们需要确保不触发异常,就得在访问每一个值之前对其进行明确地检查:

if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        Country country = address.getCountry();
        if (country != null) {
            String isocode = country.getIsocode();
            if (isocode != null) {
                isocode = isocode.toUpperCase();
            }
        }
    }
}

为了简化这个过程,我们来看看用 Optional 类是怎么做的。从创建和验证实例,到使用其不同的方法,并与其它返回相同类型的方法相结合,下面是见证 Optional 奇迹的时刻。

创建 Optional 实例

重申一下,这个类型的对象可能包含值,也可能为空。你可以使用同名方法创建一个空的 Optional。

@Test(expected = NoSuchElementException.class)
public void whenCreateEmptyOptional_thenNull() {
    Optional<User> emptyOpt = Optional.empty();
    emptyOpt.get();
}

毫不奇怪,尝试访问 emptyOpt 变量的值会导致 NoSuchElementException。
你可以使用 of() 和 ofNullable() 方法创建包含值的 Optional。两个方法的不同之处在于如果你把 null 值作为参数传递进去,of() 方法会抛出 NullPointerException:

@Test(expected = NullPointerException.class)
public void whenCreateOfEmptyOptional_thenNullPointerException() {
    Optional<User> opt = Optional.of(user);
}

你看,我们并没有完全摆脱 NullPointerException。因此,你应该明确对象不为 null 的时候使用 of()。
如果对象即可能是 null 也可能是非 null,你就应该使用 ofNullable() 方法:

Optional<User> opt = Optional.ofNullable(user);

访问 Optional 对象的值

从 Optional 实例中取回实际值对象的方法之一是使用 get() 方法:

@Test
public void whenCreateOfNullableOptional_thenOk() {
    String name = "John";
    Optional<String> opt = Optional.ofNullable(name);
 
    assertEquals("John", opt.get());
}

不过,你看到了,这个方法会在值为 null 的时候抛出异常。要避免异常,你可以选择首先验证是否有值:

@Test
public void whenCheckIfPresent_thenOk() {
    User user = new User("john@gmail.com", "1234");
    Optional<User> opt = Optional.ofNullable(user);
    assertTrue(opt.isPresent());
 
    assertEquals(user.getEmail(), opt.get().getEmail());
}

检查是否有值的另一个选择是 ifPresent() 方法。该方法除了执行检查,还接受一个Consumer(消费者) 参数,如果对象不是空的,就对执行传入的 Lambda 表达式:

opt.ifPresent( u -> assertEquals(user.getEmail(), u.getEmail()));

这个例子中,只有 user 用户不为 null 的时候才会执行断言。

接下来,我们来看看提供空值的方法。

返回默认值

Optional 类提供了 API 用以返回对象值,或者在对象为空的时候返回默认值。

这里你可以使用的第一个方法是 orElse(),它的工作方式非常直接,如果有值则返回该值,否则返回传递给它的参数值:

@Test
public void whenEmptyValue_thenReturnDefault() {
    User user = null;
    User user2 = new User("anna@gmail.com", "1234");
    User result = Optional.ofNullable(user).orElse(user2);
 
    assertEquals(user2.getEmail(), result.getEmail());
}

这里 user 对象是空的,所以返回了作为默认值的 user2。

如果对象的初始值不是 null,那么默认值会被忽略:

@Test
public void whenValueNotNull_thenIgnoreDefault() {
    User user = new User("john@gmail.com","1234");
    User user2 = new User("anna@gmail.com", "1234");
    User result = Optional.ofNullable(user).orElse(user2);
 
    assertEquals("john@gmail.com", result.getEmail());
}

第二个同类型的 API 是 orElseGet() —— 其行为略有不同。这个方法会在有值的时候返回值,如果没有值,它会执行作为参数传入的 Supplier(供应者) 函数式接口,并将返回其执行结果:

User result = Optional.ofNullable(user).orElseGet( () -> user2);

orElse() 和 orElseGet() 的不同之处

乍一看,这两种方法似乎起着同样的作用。然而事实并非如此。我们创建一些示例来突出二者行为上的异同。

我们先来看看对象为空时他们的行为:

@Test
public void givenEmptyValue_whenCompare_thenOk() {
    User user = null
    logger.debug("Using orElse");
    User result = Optional.ofNullable(user).orElse(createNewUser());
    logger.debug("Using orElseGet");
    User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
}
 
private User createNewUser() {
    logger.debug("Creating New User");
    return new User("extra@gmail.com", "1234");
}

上面的代码中,两种方法都调用了 createNewUser() 方法,这个方法会记录一个消息并返回 User 对象。

代码输出如下:

Using orElse
Creating New User
Using orElseGet
Creating New User

由此可见,当对象为空而返回默认对象时,行为并无差异。

我们接下来看一个类似的示例,但这里 Optional 不为空:

@Test
public void givenPresentValue_whenCompare_thenOk() {
    User user = new User("john@gmail.com", "1234");
    logger.info("Using orElse");
    User result = Optional.ofNullable(user).orElse(createNewUser());
    logger.info("Using orElseGet");
    User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
}

这次的输出:

Using orElse
Creating New User
Using orElseGet

这个示例中,两个 Optional 对象都包含非空值,两个方法都会返回对应的非空值。不过,orElse() 方法仍然创建了 User 对象。与之相反,orElseGet() 方法不创建 User 对象。

在执行较密集的调用时,比如调用 Web 服务或数据查询,这个差异会对性能产生重大影响。

返回异常

除了 orElse() 和 orElseGet() 方法,Optional 还定义了 orElseThrow() API —— 它会在对象为空的时候抛出异常,而不是返回备选的值:

@Test(expected = IllegalArgumentException.class)
public void whenThrowException_thenOk() {
    User result = Optional.ofNullable(user)
      .orElseThrow( () -> new IllegalArgumentException());
}

这里,如果 user 值为 null,会抛出 IllegalArgumentException。

这个方法让我们有更丰富的语义,可以决定抛出什么样的异常,而不总是抛出 NullPointerException。

现在我们已经很好地理解了如何使用 Optional,我们来看看其它可以对 Optional 值进行转换和过滤的方法。

转换值

有很多种方法可以转换 Optional 的值。我们从 map() 和 flatMap() 方法开始。

先来看一个使用 map() API 的例子:

@Test
public void whenMap_thenOk() {
    User user = new User("anna@gmail.com", "1234");
    String email = Optional.ofNullable(user)
      .map(u -> u.getEmail()).orElse("default@gmail.com");
 
    assertEquals(email, user.getEmail());
}

map() 对值应用(调用)作为参数的函数,然后将返回的值包装在 Optional 中。这就使对返回值进行链试调用的操作成为可能 —— 这里的下一环就是 orElse()。

相比这下,flatMap() 也需要函数作为参数,并对值调用这个函数,然后直接返回结果。

下面的操作中,我们给 User 类添加了一个方法,用来返回 Optional:

public class User {    
    private String position;
 
    public Optional<String> getPosition() {
        return Optional.ofNullable(position);
    }
 
    //...
}

既然 getter 方法返回 String 值的 Optional,你可以在对 User 的 Optional 对象调用 flatMap() 时,用它作为参数。其返回的值是解除包装的 String 值:

@Test
public void whenFlatMap_thenOk() {
    User user = new User("anna@gmail.com", "1234");
    user.setPosition("Developer");
    String position = Optional.ofNullable(user)
      .flatMap(u -> u.getPosition()).orElse("default");
 
    assertEquals(position, user.getPosition().get());
}

过滤值

除了转换值之外,Optional 类也提供了按条件“过滤”值的方法。

filter() 接受一个 Predicate 参数,返回测试结果为 true 的值。如果测试结果为 false,会返回一个空的 Optional。

来看一个根据基本的电子邮箱验证来决定接受或拒绝 User(用户) 的示例:

@Test
public void whenFilter_thenOk() {
    User user = new User("anna@gmail.com", "1234");
    Optional<User> result = Optional.ofNullable(user)
      .filter(u -> u.getEmail() != null && u.getEmail().contains("@"));
 
    assertTrue(result.isPresent());
}

如果通过过滤器测试,result 对象会包含非空值。

Optional 类的链式方法

为了更充分的使用 Optional,你可以链接组合其大部分方法,因为它们都返回相同类似的对象。

我们使用 Optional 重写最早介绍的示例。

首先,重构类,使其 getter 方法返回 Optional 引用:

public class User {
    private Address address;
 
    public Optional<Address> getAddress() {
        return Optional.ofNullable(address);
    }
 
    // ...
}
public class Address {
    private Country country;
 
    public Optional<Country> getCountry() {
        return Optional.ofNullable(country);
    }
 
    // ...
}

上面的嵌套结构可以用下面的图来表示:
在这里插入图片描述

@Test
public void whenChaining_thenOk() {
    User user = new User("anna@gmail.com", "1234");
 
    String result = Optional.ofNullable(user)
      .flatMap(u -> u.getAddress())
      .flatMap(a -> a.getCountry())
      .map(c -> c.getIsocode())
      .orElse("default");
 
    assertEquals(result, "default");
}

上面的代码可以通过方法引用进一步缩减:

String result = Optional.ofNullable(user)
  .flatMap(User::getAddress)
  .flatMap(Address::getCountry)
  .map(Country::getIsocode)
  .orElse("default");

结果现在的代码看起来比之前采用条件分支的冗长代码简洁多了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值