函数式编程与面向对象编程的区别: 函数式编程将程序代码看做数学中的函数, 函数本身是另一个函数的函数或返回值, 即高阶函数.



Lambda 表达式



示例: 通过匿名类实现计算两个int值的功能



public class HelloWorld
{
  public static Calculate calculate(char opt)
  {
    Calculate result;
    if(opt == '+')
    {
      // 匿名类实现Calculate接口
      result = new Calculate() {
      // 实现加法运算
      @Override
        public int calculateInt(int a, int b) {
          return a + b;
        }
      };
    }else
    {
      result = new Calculate()
      {
        // 实现减法运算
        @Override
        public int calculateInt(int a, int b)
        {
          return a -b;
        }
      };
    }
 
    return result;
  }
 
  public static void main(String[] args)
  {
    int n1 = 10;
    int n2 = 5;
    Calculate f1 = HelloWorld.calculate('+');
    Calculate f2 = HelloWorld.calculate('-');
    System.out.println(f1.calculateInt(n1, n2));
    System.out.println(f2.calculateInt(n1, n2));
  }
}



上例中通过匿名类实现 calculateInt 方法. 现在通过 Lambda 表达式将该方法的 if-else 部分修改为:



if(opt == '+')
{
  // Lambda 表达式
  result = (int a, int b) ->
  {
    return a+b;
  };
}else
{
  // Lambda 表达式
  result = (int a, int b) ->
  {
    return a - b;
  };
}



Lambda 表达式是一个匿名函数 (方法) 代码块, 可以作为表达式、方法参数和方法返回值. 其标准语法形式为:



(参数列表) ->
{
  // Lambda 表达式
}



函数式接口



Lambda 表达式实现的接口不是普通的接口, 是函数式接口, 这种接口只能有一个方法. 为防止在函数式接口中声明多个抽象方法, Java 8 提供了一个声明函数式接口的注解 “@FunctionalInterface”.



Lambda 表达式是一个匿名方法的代码块, 它实现的是在函数接口中声明的方法, 返回的是该接口的一个实例.



Lambda 表达式简化形式
省略参数形式



Lambda 表达式可以根据上下文环境推断出参数类型. 上例中的 if-else 可以修改为:



if(opt == '+')
{
  result = (a, b) ->
  {
    return a+b;
  };
}else
{
  result = (a, b) ->
  {
    return a - b;
  };
}



省略参数小括号



Lambda 表达式中参数只有一个时, 可以省略参数小括号.



将接口 Calculable 中的 calculateInt 方法修改为:



int calculateInt(int a);



上例中的 if-else 可以修改为:



if(opt == "square")
{
  result = a ->
  {
    return a * a;
  };
}



省略 return 和大括号



Lambda 表达式体中只有一条语句时, 可以省略 return 和大括号.



继续上例中的 if-else 可以修改为:



if(opt == "square")
{
  result = a -> a * a;
}



作为参数使用 Lambda 表达式



Lambda 表达式常见用途之一是作为参数传递给方法. 这需要声明参数类型为函数式接口类型.



public class HelloWorld
{
  public void display(Calculate c, int a)
  {
    System.out.println(c.squareInt(a));
  }
  
  public static void main(String[] args)
  {
    int n = 12;
    HelloWorld h = new HelloWorld();
    // 传入 Lambda 表达式作为参数
    h.display(x -> x * x, n);
  }
}
 
// 定义接口
interface Calculate
{
  // 计算两个int的值
  int squareInt(int a);
}



访问变量



Lambda 表达式可以访问所在外层作用域内定义的变量, 包括成员变量和局部变量.



访问成员变量



public class HelloWorld
{
  private int value = 10;
  private static int staticValue = 5;
 
  public static Calculate add()
  {
    Calculate result = (int a, int b) ->
    {
      // add是静态方法, 不能访问非静态变量, 只能访问静态变量
      staticValue++;
      int c = a + b + staticValue;
      return c;
    };
 
    return result;
  }
 
  public Calculate sub()
  {
    Calculate result = (int a, int b) ->
    {
      staticValue++;
      this.value++; //如果不与局部变量冲突, 可以省略this
      int c = a - b - staticValue - this.value;
      return c;
    };
 
    return result;
  }
}
 
// 定义接口
interface Calculate
{
  int calculateInt(int a, int b);
}



捕获局部变量



Lambda 表达式访问作用域外层的局部变量时, 会发生 “捕获变量” 情况. Lambda 表达式捕获变量时, 会将变量当成 final 的, 无论该变量是否被 final 修饰.



方法引用



Java 8 之后增加了双冒号 “::” 运算符, 该运算符用于 “方法引用” , 注意不是调用方法. “方法引用” 虽然没有直接使用 Lambda 表达式, 但也与 Lambda 表达式有关, 与函数式接口有关.



方法引用分为: 静态方法的方法引用和实例方法的方法引用. 语法形式如下:



类型名:: 静态方法 // 静态方法的方法引用
类型名:: 实例方法 // 实例方法的方法引用



被引用方法的参数列表和返回值类型, 必须与函数式接口方法的参数列表和返回值类型一致.



public class LambdaDemo
{
  // 声明被引用的静态方法
  public static int add(int a, int b)
  {
    return a + b;
  }
  // 声明被引用的实例方法
  public int sub(int a, int b)
  {
    return a - b;
  }
  // 声明使用函数式接口实例为参数的方法
  public static void display(Calculable c, int n1, int n2)
  {
    System.out.println(c.calculateInt(n1, n2));
  }
  public static void main(String[] args)
  {
    int n1 = 10;
    int n2 = 5;
    // 引用静态方法
    display(LambdaDemo::add, n1, n2);
    LambdaDemo ld = new LambdaDemo();
    // 引用实例方法
    display(ld::sub, n1, n2);
  }
}
 
interface Calculable
{
  int calculateInt(int a, int b);
}



方法引用就是使用其他类的方法代替了 Lambda 表达式, 使引用的方法起到 Lambda 表达式的作用.



异常处理



Java 中异常封装成为类 Exception, 此外, 还有 Throwable 和 Error 类. 异常类继承层次如图:



java设置cell函数 java calculate函数_代码块



异常基类 Throwable 有几个常用方法:



String getMessage(): 获得发生异常的详细信息.



void printStackTrace(): 打印异常堆栈跟踪信息.



String toString(): 获得异常对象的描述.



Throwable 有两个子类 Error 和 Exception.



Error



Error 是程序无法恢复的严重错误, 只能让程序终止.



Exception



Exception 是程序可以恢复的异常. 该类可以分为: 受检查异常和运行时异常.



受检查异常



编译器会检查这类异常是否进行了处理, 即要么捕获 (try-catch 语句), 要么抛出 (通过在方法后声明 throws), 否则会发生变异错误.



运行时异常



编译器不检查这类异常是否进行了处理. 但由于没有进行异常处理, 一旦运行时异常发生就会导致程序终止.



对运行时异常不采用抛出或捕获处理方式, 而是应该提前预判, 防止发生这种异常.



捕获异常



当前方法有能力解决时, 则捕获异常进行处理; 没有能力解决, 则抛给上层调用方法处理. 上层调用方法也无力解决时, 继续抛给它的上层调用方法. 如果所有方法都没有处理该异常, JVM 会终止程序运行.



try-catch 语句



语法格式:



try
{
  // 可能发生异常的语句
}catch(Throwable e)
{
  // 异常处理
}



try 代码块中包含可能发生异常的代码语句. 每个 try 代码块可以伴随一个或多个 catch 代码块, 用于处理 try 代码块中可能发生的异常.



多个异常类之间存在父子关系时, 捕获异常顺序与 catch 代码块的顺序有关. 一般先捕获子类, 后捕获父类, 否则子类捕获不到.



多重捕获



Java 7 推出了多重捕获 (multi-catch) 技术, 在 catch 中多重捕获异常用 “|” 运算符连接.



try
{
  ...
}catch(IOException | ParseException e)
{
  ...
}



释放资源



有时在 try-catch 语句中会占用一些非 Java 资源. 为了确保这些资源可以释放, 可以使用 finally 代码块或 Java 7 之后提供自动资源管理技术.



finally 代码块



try-catch 语句后面还可以跟一个 finally 代码块:



try
{
  ...
}catch(Throwable e)
{
  ...
}fianlly
{
  ...
}



无论是否发生异常, finally 代码块都会执行.



自动资源管理



Java 7 之后提供了自动资源管理技术. 自动资源管理是在 try 语句上的扩展, 语法如下:



try(声明或初始化资源语句)
{
  ...
}catch(Throwable e)
{
  ...
}



在 try 语句后面追加声明或初始化资源语句, 可以有多条语句, 多条语句间使用分号 “;” 分隔.



throws 与声明方法抛出异常



方法后面声明抛出异常使用 “throws” 关键字. 多个异常间使用逗号 (,) 分隔.



自定义异常类



实现自定义异常类需要继承 Exception 类或其子类. 如果自定义运行时异常类需要继承 RuntimeException 类或其子类.



自定义异常类主要是提供两个构造方法:



public class MyException extends Exception
{
  public MyException{}
 
  public MyException(String message)
  {
    super(message);
  }
}



throw 与显式抛出异常



throws 用于方法后声明抛出异常, throw 关键字用来人工引发异常.



throw new Exception("业务逻辑异常");