侧边栏壁纸
  • 累计撰写 57 篇文章
  • 累计创建 10 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

6、接口、lambda 表达式与内部类

yilee
2023-04-04 / 0 评论 / 0 点赞 / 49 阅读 / 0 字
温馨提示:
本文最后更新于2024-05-31,若内容或图片失效,请留言反馈。 部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

六、接口、lambda 表达式与内部类

​ 接口(interface)技术主要用来描述类具有什么功能,而并不给出每个功能的具体实现使用 lambda 表达式, 可以用一种精巧而简洁的方式表示使用回调或变量行为的代码。内部类技术主要用于设计具有相互协作关系的类集合。代理(proxy)是一种实现任意接口的对象代理是一种非常专业的构造工具,它可以用来构建系统级的工具。

6.1 接口

实现

  • 将类声明为实现给定的接口。
  • 对接口中的所有方法进行定义。
  • 要将类声明为实现某个接口, 需要使用关键字implements

特征

  • 允许在接口中增加静态方法
  • 可以为接口方法提供一个默认实现使用关键词(default

解决默认方法冲突

  • 超类优先。如果超类提供了一个具体方法, 同名而且有相同参数类型的默认方法会被忽略。
  • 接口冲突。如果一个超接口提供了一个默认方法, 另一个接口提供了一个同名而且参数类型(不论是否是默认参数)相同的方法, 必须覆盖这个方法来解决冲突

6.2 接口示例

  1. 接口与抽象类

    • 接口不是类,尤其不能使用new 运算符实例化一个接口,但能声明接口的变量
    • 每个类只能继承一个抽象类,但是每个类可以像下面这样实现多个接
  2. Comparator 接口

  3. 对象克隆

    克隆条件:

    • 默认的clone 方法是否满足要求(实现Cloneable 接口
    • 是否可以在可变的子对象上调用clone 来修补默认的clone 方法(重新定义clone 方
    • 是否不该使用clone

    注意

    Cloneable 接口是Java 提供的一组标记接口之一。(有些程序员称之为记号接口( mark interface))。Comparable 等接口的通常用途是确保一个类实现一个或一组特定的方法。标记接口不包含任何方法; 它唯一的作用就是允许在类型查询中使用 instanceof: if (obj instanceof Cloneable) ... 建议你自己的程序中不要使用标记接口。

6.3 lambda表达式

  1. 表达式形 (均无需指定lambda 表达式的返回类型)

    • 参数, 箭头(->) 以及一个表达式。如果代码要完成的计算无法放在一个表达式中,就可以像写方法一样,把这些代码放在{丨中,并包含显式的return 语句。

      (String first, String second) ->{
          if (first.length() < second.length()) return -1;
          else if (first.length() > second.length()) return 1;
          else return 0;
      }
      
    • lambda 表达式没有参数, 仍然要提供空括号

      () -> { for (int i = 100; i >= 0;i--) System.out.println(i); }
      
    • 若可以推导出一个lambda 表达式的参数类型,则可以忽略其类型

      Comparator<String> comp = (first, second) -> first.length() - second.length();
      
    • 方法只有一参数, 而且这个参数的类型可以推导得出,那么甚至还可以省略小括号

      ActionListener listener = event -> System.out.println(new Date());
      

    注意

    • lambda 表达式可以传递到函数式接口

    • 如果一个lambda 表达式只在某些分支返回一个值, 而在另外一些分支不返回值,这是不合法的。例如,(int x) -> { if (x >= 0) return 1; } 就不合法。

  2. 方法引用

    • 若有现成的方法可以完成你想要传递到其他代码的某个动作,则一下俩个代码等价

      Timer t = new Timer(1000, event -> System.out.println(event));
      // 另一个
      Timer t = new Timer(1000, System.out::println);
      
    • :: 操作符分隔方法名与对象或类名

      • object::instanceMethod (列如:super::instanceMethod, super为超类)

      • Class::staticMethod

      • Class::instanceMethod

    前2 种情况中, 方法引用等价于提供方法参数的lambda 表达式,第1 个参数会成为方法的目标

  3. 构造器引用

    • 构造器引用与方法引用很类似,只不过方法名为new

    • 可以用数组类型建立构造器引用。例如, int[]::new 是一个构造器引用, 它有一个参数:即数组的长度。这等价于lambda 表达式 x-> new int[x]

    • 假设我们需要一个Person 对象数组。Stream 接口有一个toArray 方法可以返回Object 数组:

      ArrayList<String> names = ...;
      Stream<Person> stream = names.stream().map(Person::new);
      Object[] people = stream.toArray();
      
    • 用户希望得到一个Person 引用数组,而不是Object 引用数组。流库利用构造器引用解决了这个问题。可以把Person[]::new 传入 toArray 方法:

      ArrayList<String> names = ...;
      Stream<Person> stream = names.stream().map(Person::new);
      Person[] people = stream.toArray(Person::new);
      

    lambda 表达式可以捕获外围作用域中变量的值。在Java 中, 要确保所捕获的值是明确定义的,这里有一个重要的限制。在lambda 表达式中, 只能引用值不会改变的变量。

    public static void countDown(int start, int delay){
    ActionListener listener = event -> {
    start; 	// Error: Can't mutate captured variable
    System.out.println(start);
    };
    new Timer(delay, listener).start();
    }
    

    lambda 表达式中捕获的变量必须实际上是最终变量( effectively final )。实际上的最终变量是指, 这个变量初始化之后就不会再为它赋新值。在这里,text 总是指示同一个 String对象,所以捕获这个变量是合法的。不过,i 的值会改变,因此不能捕获

    public static void repeat(String text , int count){
    for (int i = 1; i <= count; i ++){
    ActionListener listener = event -> {
       System.out.println(i + ": " + text) ;
    };
    new Timer(1000, listener) .start();
    }
    }
    
  4. lambda 表达式使用

    使用lambda 表达式的重点是延迟执行

    • 在一个单独的线程中运行代码;

    • 多次运行代码;

    • 在算法的适当位置运行代码(例如, 排序中的比较操作);

    • 发生某种情况时执行代码(如, 点击了一个按钮, 数据到达, 等等);

    • 只在必要时才运行代码。

  5. lambda 表达式常用函数式接口

    a410919acaf649ecad24d3df9dd60c25

  6. lambda 表达式基本类型函数式接口

    1c20e688eb65480bb8f9482d12bd6e48

    ​ 大多数标准函数式接口都提供了非抽象方法来生成或合并函数。例如, Predicate.isEqual(a) 等同于a::equals, 不过如果a 为null 也能正常工作。已经提供了默认方法and、or 和negate 来合并谓词。例如Predicate.isEqual(a).or(Predicate.isEqual(b)) 就等同于 x -> a.equals(x) || b.equals(x)

    ​ 如果设计你自己的接口,其中只有一个抽象方法, 可以用@FunctionalInterface 注解来标记这个接口,但并不是必须使用注解。这样做有两个优点。如果你无意中增加了另一个非抽象方法, 编译器会产生一个错误消息。另外javadoc 页里会指出你的接口是一个函数式接口。

  7. Comparator 接口包含很多方便的静态方法来创建比较器。

  8. 静态comparing 方法取一个“ 键提取器” 函数, 它将类型T 映射为一个可比较的类型(如String )。对要比较的对象应用这个函数, 然后对返回的键完成比较。例如, 假设有一个Person 对象数组,可以如下按名字对这些对象排序:

    Arrays.sort(people, Comparator.comparing(Person::getName));
    

    还可以把比较器与thenComparing 方法串起来,如果两个人的姓相同, 就会使用第二个比较器。

    Arrays.sort(people, Comparator.comparing(Person::getlastName).thenComparing(Pe rson::getFirstName));
    

    这些方法有很多变体形式。可以为comparing 和thenComparing 方法提取的键指定一个比较器。例如,可以如下根据人名长度完成排序:

    Arrays.sort(people, Comparator.comparing(Person::getName, (s, t) -> Integer.compare(s.1ength() , t.length())))
    

    如果键函数可以返回null, 可能就要用到nullsFirst 和nullsLast 适配器。

    Comparator.comparing(Person::getMiddleName, Comparator.nullsFirst(naturalOrder()))
    // naturalOrder()	升序
    // reverseOrder()	降序
    // naturalOrder().reversed()	降序
    

6.4 内部类

  1. 内部类

    原因

    1. 内部类方法可以访问该类定义所在的作用域中的数据, 包括私有的数据。
    2. 内部类可以对同一个包中的其他类隐藏起来。
    3. 当想要定义一个回调函数且不想编写大量代码时,使用匿名(anonymous) 内部类比较便捷。

    内部类

    1. 内部类中声明的所有静态域都必须是final。原因很简单,我们希望一个静态域只有一个实例, 不过对于每个外部对象, 会分别有一个单独的内部类实例。如果这个域不是final , 它可能就不是唯一的。
    2. 内部类不能有static 方法。Java 语言规范对这个限制没有做任何解释。也可以允许有静态方法, 但只能访问外围类的静态域和方法。显然,Java 设计者认为相对于这种复杂性来说, 它带来的好处有些得不偿失。
  2. 匿名类

    ​ 建立一个与超类大体类似(但不完全相同)的匿名子类通常会很方便。不过, 对于equals 方法要特别当心。第5 章中, 我们曾建议equals 方法最好使用以下测试,但是对匿名子类做这个测试时会失败。

    if (getClass() != other.getClass()) return false;
    
  3. 静态类

    使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象。为此,可以将内部类声明为static, 以便取消产生的引用。

    1. 只有内部类可以声明为static。静态内部类的对象除了没有对生成它的外围类对象的引用特权外, 与其他所冇内部类完全一样。

    2. 在内部类不需要访问外围类对象的时候, 应该使用静态内部类。有些程序员用嵌套类(nested class ) 表示静态内部类。

    3. 与常规内部类不同, 静态内部类可以有静态域和方法。

    4. 声明在接口中的内部类自动成为static 和public 类。

6.5 代理

  1. 代理对象(代理类是在程序运行过程中创建的

    • 代理类可以在运行时创建全新的类。这样的代理类能够实现指定的接口。
    • 代理类具有指定接口所需要的全部方法、Object 类中的全部方法。

    java.Iang.reflect.InvocationHandler

    • Object invoke(Object proxy, Method method, 0bject[] args)

      定义了代理对象调用方法时希望执行的动作。

    java.Iang.reflect.Proxy

    • static Class getProxyClass(Cl assLoader loader, Class… interfaces)
      返回实现指定接口的代理类。

    • static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)

      构造实现指定接口的代理类的一个新实例。所有方法会调用给定处理器对象的invoke 方法。

    • static boolean isProxyClass(Class<?> cl)

      如果cl 是一个代理类则返回true。

API注释

6 . 1.1 接口概念

java.lang.Comparable 1.0

int compareTo(T other)
// 用这个对象与other 进行比较。如果这个对象小于other 则返回负值; 如果相等则返回0; 否则返回正值。

java.util.Arrays 1.2

static void sort(Object[] a )
// 使用mergesort 算法对数组a 中的元素进行排序。要求数组中的元素必须属于实现了Comparable 接口的类, 并且元素之间必须是可比较的。

java.lang.lnteger 1.0

static int compare(int x , int y) 7
// 如果x < y 返回一个负整数;如果x 和y 相等,则返回0; 否则返回一个负整数。

java.lang.Double 1.0

static int compare(double x , double y) 1.4
// 如果x < y 返回一个负数;如果x 和y 相等则返回0; 否则返回一个负数

6.5.3 代理类的特性

java.Iang.reflect.InvocationHandler 1.3

Object invoke(Object proxy, Method method, Object[] args)
// 定义了代理对象调用方法时希望执行的动作。

java.Iang.reflect.Proxy 1.3

static Class< ?> getProxyClass(ClassLoader loader, Class< ?>... interfaces)
// 返回实现指定接口的代理类。
static Object newProxyInstance(ClassLoader loader, Class< ?>[] interfaces, InvocationHandler handler)
// 构造实现指定接口的代理类的一个新实例。所有方法会调用给定处理器对象的invoke 方法。
static boolean isProxyClass(Class< ?> cl)
// 如果cl 是一个代理类则返回true。
0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区