JavaSE 第十六章 Java8新特性

文章目录

    • 16.1 函数式(Functional)接口
      • 16.1.1 函数式接口概述
      • 16.1.2 自定义函数式接口
    • 16.2 Lambda表达式
      • 16.2.1 概述
      • 16.2.2 示例
      • 16.2.3 Lambda表达式的语法
    • 16.3 方法引用与构造器引用
      • 16.3.1 方法引用
      • 16.3.2 构造器引用
    • 16.4 Stream API
      • 16.4.1 Stream说明
      • 16.4.2 什么是Stream
      • 16.4.3 Stream操作的三个步骤
      • 16.4.4 创建Stream流的四种方式
        • 通过集合创建
        • 通过数组创建
        • 使用Stream.of()方法创建
        • 创建无限流
      • 16.4.5 Stream的中间操作
        • 筛选与切片
        • 映射
        • 排序
        • 中止操作
          • 匹配与查找
          • 归约
    • 16.5 Optional类

16.1 函数式(Functional)接口

16.1.1 函数式接口概述

  • 只包含一个抽象方法的接口,称为函数式接口

  • 可以通过Lambda表达式来创建该接口的对象。(若 Lambda 表达式 抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。

  • 我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。

  • 在java.util.function包下定义了Java 8 的丰富的函数式接口

  • Java从诞生日起就是一直倡导“一切皆对象”,在Java里面面向对象(OOP) 编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,Java不 得不做出调整以便支持更加广泛的技术要求,也即java不但可以支持OOP还 可以支持OOF(面向函数编程)

  • 在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的 对象类型——函数式接口。

  • 简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是 Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口 的实例,那么该对象就可以用Lambda表达式来表示。

  • 以前用匿名实现类表示的现在都可以用Lambda表达式来写。

Runnable就是典型的函数式接口。接口上放带有@FunctionalInterface注解,接口中又只有一个抽象方法。

JavaSE 第十六章 Java8新特性_第1张图片

16.1.2 自定义函数式接口

@FunctionalInterface
public interface InterA {
    void show() ;
}
@FunctionalInterface
public interface InterB {
    int show() ;
}

16.2 Lambda表达式

16.2.1 概述

Lambda是一个匿名函数,我们可以把 Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。

16.2.2 示例

  • 匿名内部类到Lambda的转换:
    JavaSE 第十六章 Java8新特性_第2张图片

JavaSE 第十六章 Java8新特性_第3张图片

  • Lambda表达式是在Java8中引入的一种新的语法元素和操作符。这个操作符为 -> ,该操作符被称为Lambda操作符或箭头操作符。它将Lambda分为两部分:
    • 左侧:是Lambda表达式需要的参数列表
    • 右侧:指定了Lambda体,是抽象方法的实现逻辑,也就是Lambda表达式要执行的功能

16.2.3 Lambda表达式的语法

  • 语法格式一:无参,无返回值
public class Demo1 {
    public static void main(String[] args) {
        InterA a = () -> System.out.println("无参无返回值的Lambda表达式") ;
        a.show();
    }
}

@FunctionalInterface
interface InterA {
    void show() ;
}
  • 语法格式二:一个参数,没有返回值
public class Demo2 {
    public static void main(String[] args) {
        // Lambda表达式
        // InterB b = (int num) -> System.out.println(num) ;
        // 参数的数据类型可以省略,编译器可以推断得出,称为”类型推断“参数的类型和包括,当Lambda表达式只需要一个参数时,参数的小括号可以省略,
        // InterB b = (num) -> System.out.print(num) ;
         InterB b = num -> System.out.println(num) ;

        b.show(10);
    }
}

@FunctionalInterface
interface InterB {
    public abstract void show(int num) ;
}
  • 格式三:Lambda 需要两个或以上的参数,多条执行语句,并且有返回值
public class Demo3 {
    public static void main(String[] args) {
        InterC c = ((s1, s2) -> {
            System.out.println("两个及以上参数,有返回值");
            return s1 + s2 ;
        }) ;

        System.out.println(c.show("Hello", " World"));
    }
}

@FunctionalInterface
interface InterC {
    String show(String s1 , String s2) ;
}
  • 格式四:当Lambda有返回值,且只有一条语句时
public class Demo4 {
    public static void main(String[] args) {
        InterD d = (a , b) -> Integer.max(a , b) ;
        System.out.println(d.max(5, 7));
    }
}

@FunctionalInterface
interface InterD {
    int max(int a , int b) ;
}
  • 类型推断
    • Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac
      根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”。

16.3 方法引用与构造器引用

16.3.1 方法引用

  • 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用
  • 方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向 一个方法,可以认为是Lambda表达式的一个语法糖。
  • 要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致 
  • 格式:使用操作符 “::” 将类(或对象) 与 方法名分隔开来。
  • 如下三种主要使用情况:
    • 对象::实例方法名
    • 类::静态方法名
    • 类::实例方法名

如:
InterB b = num -> System.out.println(num) ; ——> InterB b = System.out::println ;
InterD d = (a , b) -> Integer.max(a , b) ; ——> InterD d = Integer::max;
InterD d = ((a, b) -> a.equals(b)); ——> InterD d = String::equals;

  • 当函数式接口方法的第一个参数是需要引用方法的调用者,并且第二个参数是需要引用方法的参数(或无参数)时:类名::方法名

16.3.2 构造器引用

  • 格式:类名::new

  • 与函数式接口相结合,自动与函数式接口中方法兼容。

  • 可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致。且方法的返回值即为构造器对应类的对象。

示例:

public class Demo5 {
    public static void main(String[] args) {
        /*Inter1 inter2 = new Inter1() {
            @Override
            public Date getTime() {
                return new Date();
            }
        } ;*/
        
        // Inter1 inter1 = () -> new Date();
        
        Inter1 time = Date::new ;
        
        System.out.println(time.getTime());
    }
}

@FunctionalInterface
interface Inter1 {
    Date getTime() ;
}
  • 数组引用格式:type[]::new

示例:

public class Demo6 {
    public static void main(String[] args) {
        /*Inter2 inter2 = new Inter2() {
            @Override
            public int[] show(int length) {
                return new int[length];
            }
        } ;*/

        // Inter2 inter2 = length -> new int[length] ;

        Inter2 inter2 = int[]::new;
    }
}

@FunctionalInterface
interface Inter2 {
    int[] show(int length) ;
}

16.4 Stream API

16.4.1 Stream说明

  • Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这 是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
  • Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用 Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。 也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

16.4.2 什么是Stream

Stream 是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。 集合讲的是数据,Stream讲的是计算

  • Stream本身不会存储数据
  • Stream不会改变源对象,每次对源对象进行操作都会返回一个新的Stream流
  • Stream操作是延迟执行的,这意味着Stream会等到需要结果时才执行

16.4.3 Stream操作的三个步骤

  • 1、创建Stream
    • 一个数据源,如通过集合、数组获取一个Stream流
  • 2、中间操作
    • 一个中间操作链,使用Stream类中的方法对数据进行处理
  • 3、中止操作
    • 一旦执行中止操作,Stream流就会执行中间操作链,并产生结果。

16.4.4 创建Stream流的四种方式

通过集合创建

  • default Stream stream() : 返回一个顺序流
  • default Stream parallelStream() : 返回一个并行流
  • 示例
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list , "张三" , "李四" , "王五" , "赵六") ;

// 使用集合创建Stream流对象
Stream<String> stream = list.stream();

通过数组创建

  • Arrays 的静态方法stream()可以获取数组流

    • static Stream stream(T[] array): 返回一个流
    • public static IntStream stream(int[] array)
    • public static LongStream stream(long[] array)
    • public static DoubleStream stream(double[] array
  • 示例
public class Demo2 {
    public static void main(String[] args) {
        int[] arr = {1 , 5 , 3 , 2 , 4} ;
        long[] arr1 = {1 , 5 , 3 , 2 , 4} ;
        double[] arr2 = {1 , 5 , 3 , 2 , 4} ;
        IntStream stream = Arrays.stream(arr);
        LongStream stream1 = Arrays.stream(arr1);
        DoubleStream stream2 = Arrays.stream(arr2);
    }
}

使用Stream.of()方法创建

  • 调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的同种数据类型的参数。

  • public static Stream of(T... values) : 返回一个流

示例:

public class Demo3 {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
        Stream<String> stream1 = Stream.of("张三", "李四", "王五", "赵六");
    }
}

创建无限流

  • 使用静态方法 Stream.iterate()Stream.generate(), 创建无限流。
  • 迭代
    • public static Stream iterate(final T seed, final UnaryOperator f)
  • 生成
    • public static Stream generate(Supplier s)

示例:

public class Demo4 {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.iterate(0, x -> x + 2); // 0 2 4 6 8 ... 18
        stream.limit(10).forEach(System.out::println);

        // 生成
        Stream<Double> stream1 = Stream.generate(Math::random); // 10random生成的随机数个随机数
        stream1.limit(10).forEach(System.out::println);
    }
}

16.4.5 Stream的中间操作

  • 多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理。而在终止操作时一次性全 部处理,称为“惰性求值”。

筛选与切片

  • filter(Predicate p) :接收 Lambda , 从流中排除某些元素
  • distinct() :筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
  • limit(long maxSize) :截断流,使其元素不超过给定数量
  • skip(long n) :跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一 个空流。与 limit(n) 互补
  • filter(Predicate p)
public class FilterDemo {
    public static void main(String[] args) {
        int[] arr = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10} ;
        IntStream stream = Arrays.stream(arr);

        /*stream.filter(new IntPredicate() {
            @Override
            public boolean test(int value) {
                return value > 5;
            }
        }) ;*/
        // 调用filter()方法,筛选出大于5的元素
        IntStream stream1 = stream.filter(value -> value > 5);
        stream1.forEach(System.out::println);
    }
}
  • distinct()
public class DistinctDemo {
    public static void main(String[] args) {
        int[] arr = {1 , 2 , 3 , 4 , 5 , 1 , 2 , 3 , 4 , 5} ;
        IntStream stream = Arrays.stream(arr);
        // 使用distinct()方法去除Stream流中的重复元素
        stream.distinct().forEach(System.out::println);
    }
}
  • limit(long maxSize)
public class LimitDemo {
    public static void main(String[] args) {
        int[] arr = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10} ;
        IntStream stream = Arrays.stream(arr);

        // 调用limit()方法
        stream.limit(3).forEach(System.out::println);
    }
}
  • skip(long n)
public class SkipDemo {
    public static void main(String[] args) {
        int[] arr = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10} ;
        IntStream stream = Arrays.stream(arr);
        
        stream.skip(5).forEach(System.out::println);
    }
}

映射

  • map(Function f) :接收一个函数作为参数,该函数会被应用到每个元 素上,并将其映射成一个新的元素。
  • mapToDouble(ToDoubleFunction f): 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的DoubleStream。
  • mapToInt(ToIntFunction f): 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 IntStream。
  • mapToLong(ToLongFunction f) :接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 LongStream。
  • flatMap(Function f): 接收一个函数作为参数,将流中的每个值都换成另 一个流,然后把所有流连接成一个流
  • map(Function f)
public class MapDemo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list , "张三" , "李四" , "王五" , "赵六") ;

        /*list.stream().map(new Function() {
            @Override
            public Student apply(String s) {
                return new Student(s);
            }
        }) ;*/

        // 将String类型转为Student类型
        // 简化的写法,构造器引用并遍历
        list.stream().map(Student::new).forEach(System.out::println); ;
    }
}
  • mapToInt(ToIntFunction f)
public class MapToIntDemo {
    public static void main(String[] args) {
        String[] arr = {"12" , "5" , "8" , "3" , "88" , "99"} ;

        Stream<String> stream = Arrays.stream(arr);
        // 调用mapToInt()方法,将String类型转为int类型
        /*stream.mapToInt(new ToIntFunction() {
            @Override
            public int applyAsInt(String value) {
                return Integer.parseInt(value);
            }
        }).forEach(System.out::println); */

        // 方法引用方式
        stream.mapToInt(Integer::parseInt)
                .forEach(System.out::println);
    }
}

排序

  • sorted() 产生一个新流,其中按自然顺序排序
  • sorted(Comparator com) 产生一个新流,其中按比较器顺序排序
public class SortedDemo1 {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("7", "5", "8", "3", "1", "9");

        // 调用sorted()方法进行排序,排序规则为String默认规则
        stream.sorted().forEach(System.out::println);
    }
}
public class SortedDemo2 {
    public static void main(String[] args) {
        String[] arr = {"7", "5", "8", "3", "1", "9"} ;

        Arrays.stream(arr).sorted(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return Integer.parseInt(o2) - Integer.parseInt(o1) ;
            }
        }).forEach(System.out::println);
        
        // lambda表达式写法
        Arrays.stream(arr)
                .sorted((o1 , o2) -> Integer.parseInt(o2) - Integer.parseInt(o1))
                .forEach(System.out::println);

    }
}

中止操作

匹配与查找
  • allMatch(Predicate p): 检查是否匹配所有元素
  • anyMatch(Predicate p): 检查是否至少匹配一个元素
  • noneMatch(Predicate p): 检查是否没有匹配所有元素
  • findFirst(): 返回第一个元素
  • findAny(): 返回当前流中的任意元素
  • count() :返回流中元素总数
  • max(Comparator c) :返回流中最大值
  • min(Comparator c) :返回流中最小值
  • forEach(Consumer c) :内部迭代(使用 Collection 接口需要用户去做迭代, 称为外部迭代。相反,Stream API 使用内部迭 代——它帮你把迭代做了)
  • allMatch(Predicate p)
public class AllMatchDemo {
    public static void main(String[] args) {
        // 创建Stream流对象
        Stream<Integer> stream = Stream.of(2, 2, 1, 2, 2);
        Stream<Integer> stream1 = Stream.of(2, 2, 2, 2, 2);

        // 如果流中的所有元素都为2,则返回true
        boolean allMatch = stream.allMatch(new Predicate<Integer>() {
            @Override
            public boolean test(Integer integer) {
                return integer == 2;
            }
        });

        System.out.println(allMatch);   // false

        boolean allMatch1 = stream1.allMatch(num -> num == 2);
        System.out.println(allMatch1);  // true
    }
}
  • anyMatch(Predicate p)
public class AnyMatchDemo {
    public static void main(String[] args) {
        // 创建Stream流
        Stream<Integer> stream = Stream.of(5, 1, 4, 3, 7);

        /*boolean anyMatch = stream.anyMatch(new Predicate() {
            @Override
            public boolean test(Integer integer) {
                return integer == 3;
            }
        });*/

        boolean anyMatch = stream.anyMatch(num -> num == 3);

        System.out.println(anyMatch);   // true
    }
}
  • noneMatch(Predicate p)
public class NoneMatchDemo {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
        Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5);

        // 检查流中是否所有的元素都不匹配
        boolean noneMatch = stream.noneMatch(num -> num > 6);
        System.out.println(noneMatch);  // true

        boolean noneMatch1 = stream1.noneMatch(num -> num < 2);
        System.out.println(noneMatch1); // false
    }
}
  • findFirst() findAny()
public class FindFirstDemo {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(7, 2, 3, 4, 5);
        Stream<Integer> stream1 = Stream.of(7, 2, 3, 4, 5);

        // 返回第一个元素
        Optional<Integer> first = stream.findFirst();
        System.out.println(first);

        // 返回任意一个元素
        Optional<Integer> any = stream1.findAny();
        System.out.println(any);
    }
}
  • count()

  • max(Comparator c)

  • min(Comparator c)

public class Demo5 {
    public static void main(String[] args) {
        Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5);
        Stream<Integer> stream2 = Stream.of(1, 2, 3, 4, 5);
        Stream<Integer> stream3 = Stream.of(1, 2, 3, 4, 5);

        // 获取流中元素个数
        long count = stream1.count();
        System.out.println(count);

        // 获取流中最大值
        Optional<Integer> max = stream2.max(Comparator.comparingInt(o -> o));
        System.out.println(max);

        // 获取流中最小值
        Optional<Integer> min = stream3.min(Comparator.comparingInt(o -> o));
        System.out.println(min);
    }
}
  • forEach(Consumer c)

public class ForEachDemo {
    public static void main(String[] args) {
        Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5);
        Stream<Integer> stream2 = Stream.of(1, 2, 3, 4, 5);
        Stream<Integer> stream3 = Stream.of(1, 2, 3, 4, 5);

        stream1.forEach(integer -> System.out.println(integer + 1)); // 2 3 4 5 6

        stream2.forEach(System.out::println);

        stream3.forEach(integer -> System.out.println(integer = 0));
    }
}

归约
  • reduce(T iden, BinaryOperator b): 可以将流中元素反复结合起来,得到一 个值。返回 T
  • reduce(BinaryOperator b): 可以将流中元素反复结合起来,得到一 个值。返回 Optional
public class ReduceDemo {
    public static void main(String[] args) {
        Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5);
        Stream<Integer> stream2 = Stream.of(1, 2, 3, 4, 5);

        // 求流中元素的和
        Integer reduce = stream1.reduce(10, (integer, integer2) -> integer + integer2);
        System.out.println(reduce); // 25 = 10 + (1 + 2 + 3 + 4 + 5)

        Optional<Integer> sum = stream2.reduce(Integer::sum);
        System.out.println(sum);    // 15
    }
}
  • collect(Collector c): 将流转换为其他形式。接收一个 Collector 接口的实现,用于给Stream中元素做汇总的方法

Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、 Map)。

  • toList : 把流中元素收集到List
    • List emps= list.stream().collect(Collectors.toList());
  • toSet : 把流中元素收集到Set
    • Set emps= list.stream().collect(Collectors.toSet());
  • toCollection : 把流中元素收集到创建的集合
    • Collection emps =list.stream().collect(Collectors.toCollection(ArrayList::new));
public class CollectDemo1 {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
        Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5);
        Stream<Integer> stream2 = Stream.of(1, 2, 3, 4, 5);

        // 将流中数据收集到List集合中
        List<Integer> list = stream.collect(Collectors.toList());
        System.out.println(list);

        // 将流中数据收集到set集合
        Set<Integer> set = stream1.collect(Collectors.toSet());
        System.out.println(set);

        // 将流中数据收集到Collection集合
        ArrayList<Integer> arrayList = stream2.collect(Collectors.toCollection(ArrayList::new));
        System.out.println(arrayList);
        
    }
}

16.5 Optional类

  • Optional 类(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不 存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。 
  • Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在 则isPresent()方法会返回true,调用get()方法会返回该对象。
  • 创建Optional类对象的方法:
    • Optional.of(T t) : 创建一个 Optional 实例,t必须非空;
    • Optional.empty() : 创建一个空的 Optional 实例
    • Optional.ofNullable(T t):t可以为null
  • 判断Optional容器中是否包含对象:
    • boolean isPresent() : 判断是否包含对象
    • void ifPresent(Consumer consumer) :如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它。
  • 获取Optional容器的对象:
    • T get(): 如果调用对象包含值,返回该值,否则抛异常
    • T orElse(T other) :如果有值则将其返回,否则返回指定的other对象。
    • T orElseGet(Supplier other) :如果有值则将其返回,否则返回由Supplier接口实现提供的对象。
    • T orElseThrow(Supplier exceptionSupplier) :如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。
public class OptionalDemo {
    public static void main(String[] args) {
        // 创建Optional类的实例
        Optional<Integer> o = Optional.of(1);

        // 判断Optional实例中是否包含对象(类似于非空校验)
        boolean present = o.isPresent();
        System.out.println(present);

        // 如果存在就执行Consumer接口中实现的代码
        o.ifPresent(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        });
        // lambda表达式 , 方法引用
        // o.ifPresent(System.out::println);

        // 获取对象值
        System.out.println(o.get());

        // 有值则返回值,无值返回0
        Integer integer = o.orElse(0);
        System.out.println(integer);

        // 有值则返回值,无值则执行Supplier接口中的代码
        Integer integer1 = o.orElseGet(new Supplier<Integer>() {
            @Override
            public Integer get() {
                return 0;
            }
        });

        System.out.println(integer1);

    }
}

你可能感兴趣的:(java,java)