java-接口、lambda表达式

简介: 接口(interface)接口是一个不含任何成员变量的抽象类接口中所有方法必须为public接口中不能定义成员变量,但是可以定义为一个常量(类型为public static final)不能用new来实例化一个接口,但是允许定义一个接口变量接口变量必须引用实现了该接口的类对象同一个类可以同时实现多个接口接口可以定义default方法,该方法提供了一个默认的实现,实现该接口时可以按需重写该方法接口中的默认方法可以调用其他任意方法java-SE8后允许接口定义静态方法public class InterfaceTest implements Test{ public

接口(interface)

接口是一个不含任何成员变量的抽象类

  • 接口中所有方法必须为public
  • 接口中不能定义成员变量,但是可以定义为一个常量(类型为public static final)
  • 不能用new来实例化一个接口,但是允许定义一个接口变量
  • 接口变量必须引用实现了该接口的类对象
  • 同一个类可以同时实现多个接口
  • 接口可以定义default方法,该方法提供了一个默认的实现,实现该接口时可以按需重写该方法
  • 接口中的默认方法可以调用其他任意方法
  • java-SE8后允许接口定义静态方法

public class InterfaceTest implements Test{
    public static void main(String[] args) {
        InterfaceTest test = new InterfaceTest();
        // 调用接口的默认方法
        test.getMessage();
        System.out.println(InterfaceTest.info);
        // 调用接口的静态方法
        System.out.println(Test.staticMethod());
    }
    @Override
    public void getHint() {
        System.out.println("call getHint");
    }
}
interface Test {
    // 常量
    String info = "test interface";
    // 默认方法
    default void getMessage() {
        getHint();
        System.out.println("test message");
    }
    // 一般方法
    void getHint();
  // 静态方法
    static String staticMethod() {
        return "this is static method";
    }
}

默认方法冲突

1.超类优先:如果超类提供了一个具体方法, 同名而且有相同参数类型的默认方法会被忽略

假设Test接口的默认方法getMessageTest1类的方法getMessage重名

只实现Test接口时不需要重写默认方法getMessage

public class InterfaceTest implements Test{
    public static void main(String[] args) {
        InterfaceTest test = new InterfaceTest();
        // 调用接口的默认方法
        test.getMessage();
        System.out.println(InterfaceTest.info);
        // 调用接口的静态方法
        System.out.println(Test.staticMethod());
        System.out.println();
    }
    @Override
    public void getHint() {
    }
}
interface Test {
    // 常量
    String info = "test interface";
    // 默认方法
    default void getMessage() {
        getHint();
        System.out.println("Test: message");
    }
    // 一般方法
    void getHint();
    static String staticMethod() {
        return "Test: this is static method";
    }
}
class Test1 {
    void getMessage() {
        System.out.println("Test1: message");
    }
}

如果InterfaceTest同时继承了Test1类并实现了Test接口,那么从Test接口的默认方法会被忽略,必须进行重写

public class InterfaceTest extends Test1 implements Test{
    public static void main(String[] args) {
        InterfaceTest test = new InterfaceTest();
        // 调用接口的默认方法
        test.getMessage();
        System.out.println(InterfaceTest.info);
        // 调用接口的静态方法
        System.out.println(Test.staticMethod());
        System.out.println();
    }
    @Override
    public void getMessage() {
        Test.super.getMessage();
    }
    @Override
    public void getHint() {
    }
}
interface Test {
    // 常量
    String info = "test interface";
    // 默认方法
    default void getMessage() {
        getHint();
        System.out.println("Test: message");
    }
    // 一般方法
    void getHint();
    static String staticMethod() {
        return "Test: this is static method";
    }
}
class Test1 {
    void getMessage() {
        System.out.println("Test1: message");
    }
}

2.接口冲突:如果一个类同时实现了多个接口,并且这些接口中包含相同的默认方法

实现类必须重写重名方法,手动选择使用某个接口的默认方法

这里假设同时实现了TestTest1接口,两个接口都有getMessage()

public class InterfaceTest implements Test, Test1 {
    public static void main(String[] args) {
        InterfaceTest test = new InterfaceTest();
        // 调用接口的默认方法
        test.getMessage();
    }
    @Override
    public void getMessage() {
        // 使用Test1的getMessage
        // Test1.super.getMessage();
        // 或者使用Test的getMessage
        Test.super.getMessage();
    }
    @Override
    public void getHint() {
    }
}
interface Test {
    // 常量
    String info = "test interface";
    // 默认方法
    default void getMessage() {
        getHint();
        System.out.println("Test: message");
    }
    // 一般方法
    void getHint();
    static String staticMethod() {
        return "Test: this is static method";
    }
}
interface Test1 {
    default void getMessage() {
        System.out.println("Test1: message");
    }
}

有这样一种情况,此时Test1接口的getMessage不是默认方法,InterfaceTest并不会默认使用Test的默认方法。必须重写getMessage方法(可以指定使用 Test 的默认方法)

也就是说: 如果至少有一个接口提供了一个同名的默认实现, 编译器就会报告错误, 而程序员就必须解决这个二义性。

public class InterfaceTest implements Test, Test1 {
    public static void main(String[] args) {
        InterfaceTest test = new InterfaceTest();
        // 调用接口的默认方法
        test.getMessage();
    }
    @Override
    public void getMessage() {
        Test.super.getMessage();
    }
    @Override
    public void getHint() {
    }
}
interface Test {
    // 常量
    String info = "test interface";
    // 默认方法
    default void getMessage() {
        getHint();
        System.out.println("Test: message");
    }
    // 一般方法
    void getHint();
    static String staticMethod() {
        return "Test: this is static method";
    }
}
interface Test1 {
    void getMessage();
}

Comparator 接口

String类实现了Comparable<String>接口,其中的String.compareTo()实现了按字典序排序

需求: 将字符串按照长度从小到大排序,如果长度相等,则按字典序排序

这里需要重新实现comparator接口的compare方法

public  class InterfaceTest {
    public static void main(String[] args) {
        String[] strs = {"Happy", "new", "year"};
        Comparator<String> cmp = new LengthComparator();
        for (int i = 0; i < strs.length - 1; i++) {
            if (cmp.compare(strs[i],strs[i+1]) > 0) {
                String tmp = strs[i];
                strs[i]=strs[i+1];
                strs[i+1] = tmp;
            }
        }
        System.out.println(Arrays.toString(strs));
    }
}
class LengthComparator implements Comparator<String> {
    // 返回正值的时候说明需要交换位置
    @Override
    public int compare(String o1, String o2) {
        // 长度相等,按字典序排序
        if (o1.length() == o2.length()) {
            return o1.compareTo(o2);
        }
        // 按长度从小到大排序
        return o1.length() - o2.length();
    }
}

简写方式:

import java.util.Arrays;
import java.util.Comparator;
public  class InterfaceTest {
    public static void main(String[] args) {
        String[] strs = {"Happy", "new", "year"};
        Arrays.sort(strs, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                if (o1.length() == o2.length()) {
                    return o1.compareTo(o2);
                }
                return o1.length() - o2.length();
            }
        });
        System.out.println(Arrays.toString(strs));
    }
}

Cloneable 接口

Object.clone()默认的克隆操作是浅拷贝,并没有克隆对象中引用的其他对象。 浅拷贝会有什么影响吗?如果原对象和浅克隆对象共享的子对象是不可变的(如 String 类),那么这种共享就是安全的。或者在对象的生命期中,子对象一直包含不变的常量,没有更改器方法会改变它,也没有方法会生成它的引用,这种情况下同样是安全的。

下面的图给出了一个浅拷贝的例子,Date是一个可变类

注意

一般而言,子对象都是可变的,所以需要重写clone方法来实现对象的深拷贝

要使用Object.clone()方法(浅拷贝)或者重写来实现深拷贝方法,都必须完成下面的两步:

  • 让所在类实现Cloneable接口
  • clone()方法的权限改为public(这样这个方法在所有类中都可以调用)

public class Main  {
    public static void main(String[]args) throws CloneNotSupportedException {
        Test t = new Test();
        Test other = (Test) t.clone();
        System.out.println(t.hashCode());
        System.out.println(other.hashCode());
    }
}
class Test implements Cloneable{
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

让图6.2的实现深拷贝

package CloneTest;
import java.util.Date;
import java.util.GregorianCalendar;
public class CloneTest
{
    public static void main(String[] args)
    {
        try
        {
            Employee original = new Employee("John Q. Public", 50000);
            original.setHireDay(2000, 1, 1);
            Employee copy = original.clone();
            copy.raiseSalary(100);
            copy.setHireDay(2022, 11, 17);
            System.out.println("original=" + original);
            System.out.println("copy=" + copy);
        }
        catch (CloneNotSupportedException e)
        {
            e.printStackTrace();
        }
    }
}
class Employee implements Cloneable
{
    private String name;
    private double salary;
    private Date hireDay;
    public Employee(String n, double s)
    {
        name = n;
        salary = s;
        hireDay = new Date();
    }
    public Employee clone() throws CloneNotSupportedException
    {
        // 先调用一次浅拷贝
        // Object.clone()返回的是Object对象
        // call Object.clone()
        Employee cloned = (Employee) super.clone();
        // 对可变对象进行克隆
        // clone mutable fields
        cloned.hireDay = (Date) hireDay.clone();
        return cloned;
    }
    public void setHireDay(int year, int month, int day)
    {
        Date newHireDay = new GregorianCalendar(year, month - 1, day).getTime();
        // Example of instance field mutation
        hireDay.setTime(newHireDay.getTime());
    }
    public void raiseSalary(double byPercent)
    {
        double raise = salary * byPercent / 100;
        salary += raise;
    }
    public String toString()
    {
        return "Employee[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay + "]";
    }
}

Object 类的 clone()方法为什么是protected修饰的

protected修饰clone方法,主要是为了让子类去重写它,实现深拷贝,以防在其他任何地方随意调用后修改了对象的属性对原来的对象造成影响。

lambda 表达式

lambda表达式形式:(参数)->一个表达式或{代码块}

  • 无需指定表达式的返回类型,可通过上下文进行推断
  • 代码块需要显式的指定返回值
  • 即使没有参数也要提供一个空括号()
  • 如果可以推导出lambda表达式的参数类型,可以忽略其类型

package LambdaTest;
import java.util.*;
public class LambdaTest {
    public static void main(String[] args) {
        Runnable hello = () -> {
            System.out.println("hello");
        };
        hello.run();
        String[] strs = new String[] {"Hello","Year","New"};
        // 按字符串长度升序排序
        Comparator<String> stringComparator = (first, second) -> {
            if (first.length() < second.length()) return -1;
            else if (first.length() > second.length()) return 1;
            else return 0;
        };
        Arrays.sort(strs,stringComparator);
        System.out.println(Arrays.toString(strs));
        // 按字符串长度降序排序
        Comparator<String> stringComparator1 = (first,second) -> second.length() - first.length();
        Arrays.sort(strs,stringComparator1);
        System.out.println(Arrays.toString(strs));
    }
}

函数式接口

  • 函数式接口只有一个抽象方法
  • 接口重写了 Object 的公共方法也不算入内

技巧

Comparator就是一个函数式接口,接口之前有@FunctionalInterface注解

最好把 lambda 表达式看作是一个函数而不是一个对象,另外lambda表达式可以传递到函数式接口。

注意

Object不是一个函数式接口,不能将lambda表达式赋给类型为Object变量

Predicate 接口

Predicate函数式接口的主要作用就是提供一个test方法,接受一个参数返回一个布尔类型,Predicate接口在stream api中进行一些判断的时候非常常用。

引用

Java8-6-Predicate 接口详解

方法引用(method refrence)

在 Java 8 中,我们会使用lambda表达式创建匿名方法,但是有时候,我们的lambda表达式可能仅仅调用一个已存在的方法,而不做任何其它事,对于这种情况,通过一个方法名字来引用这个已存在的方法会更加清晰,Java 8 的方法引用允许我们这样做。方法引用是一个更加紧凑,易读的lambda表达式,注意方法引用是一个lambda表达式,其中方法引用的操作符是双冒号"::"

lamda 表达式的代码可以改写为方法引用的形式

public class LambdaTest {
    public static void main(String[] args) {
        Runnable hello = () -> {
            System.out.println("hello");
        };
        hello.run();
        String[] strs = new String[] {"Hello","Year","New"};
        // 按字符串长度升序排序
        Arrays.sort(strs, LambdaTest::compare2);
        System.out.println(Arrays.toString(strs));
        // 按字符串长度降序排序
        Arrays.sort(strs, LambdaTest::compare);
        System.out.println(Arrays.toString(strs));
    }
    private static int compare(String first, String second) {
        return second.length() - first.length();
    }
    private static int compare2(String first, String second) {
        if (first.length() < second.length()) return -1;
        else if (first.length() > second.length()) return 1;
        else return 0;
    }

方法引用主要有以下三种形式:

  1. object::instanceMethod 对象 + 实例方法
  2. Class::staticMethod 类名+ 静态方法
  3. Class::instanceMethod 类名 + 实例方法

// 第一种
System.out::println // 等价于x -> System.out.println(x)
// 第二种
Math::pow // 等价于(x,y)-> Math.pow(x,y)
// 第三种,第一个参数将作为object
String::compareToIgnoreCase // 等价于(x, y) -> x.compareToIgnoreCase(y)

对于第三种方法,显然不能用类调用实例方法,这样编译都会报错!

当一个对象调用一个方法,方法的参数中包含一个函数式接口,该函数式接口的第一个参数类型是这个对象的类,那么这个函数式接口可用方法引用代替,并且替换用的方法可以不包含函数式接口的第一个参数

@FunctionalInterface
interface TestInterface<T> {
    String handleString(T a, String b);
}
class TestClass {
    private final String oneString;
    TestClass(String oneString) {
        this.oneString = oneString;
    }
    public String concatString(String a) {
        return this.oneString + a;
    }
    public String startHandleString(TestInterface<TestClass> testInterface, String str) {
        return testInterface.handleString(this, str);
    }
}
public class Test {
    public static void main(String[] args) {
        TestClass testClass = new TestClass("abc");
        String result = testClass.startHandleString(TestClass::concatString, "123");
        System.out.println(result);
        //相当于以下效果
        TestClass testClass2 = new TestClass("abc");
        TestInterface testInterface = (a, b) -> testClass2.concatString(b);
        String result2 = testInterface.handleString(testClass2, "123");
        System.out.println(result2);
    }
}

引用

Java8 之方法引用

Java 方法引用

构造方法引用

1.构造器引用

语法形式:className::new

构造函数本质上是静态方法,只是方法名字比较特殊,使用的是new 关键字

public class ConstructorRefDemo {
    public static void main(String[] args) {
        // 利用MyClass的构造方法实现了MyFunc接口
        MyInterface myInterface = MyClass :: new;
        MyClass mc = myInterface.func(100);
        System.out.println("val in mc is: " + mc.getValue());
    }
}
interface MyInterface {
    MyClass func(int n);
}
class MyClass {
    private final int val;
    MyClass(int v) {
        val = v;
    }
    public int getValue() {
        return val;
    }
}

还有一个更难理解的例子

package interfaceTest;
import java.util.Arrays;
import java.util.List;
@FunctionalInterface
interface Supplier<T>
{
  T get();
}
class Car
{
  public static Car create(final Supplier<Car> supplier)
  {
    return supplier.get();
  }
  public static void collide(final Car car)
  {
    System.out.println("静态方法形式调用 " + car.toString());
  }
  public void follow(final Car another)
  {
    System.out.println("对象方法形式调用 " + another.toString());
  }
  public void repair()
  {
    System.out.println("任意对象方法引用 " + this.toString());
  }
  @Override
  public String toString()
  {
    return "just a Car " + this.hashCode();
  }
}
public class CRacer
{
  public static void main(String[] args)
  {
    //构造器引用:它的语法是Class::new
    Car car0 = Car.create(Car::new);
    Car car1 = Car.create(Car::new);
    System.out.println(car0.hashCode());
    System.out.println(car1.hashCode());
    final List<Car> cars = Arrays.asList(car0,car1);
    //静态方法引用:Class::static_method
    cars.forEach(Car::collide);
    //特定类的任意对象的方法引用Class::method
    cars.forEach(Car::repair);
    cars.forEach(System.out::println);
    final Car police = Car.create(Car::new);
    System.out.println(police.hashCode());
    cars.forEach(police::follow);
  }
}

2.构造数组引用

语法形式:Typename[]::new

import java.util.Arrays;
import java.util.function.Function;
public  class InterfaceTest {
    public static void main(String[] args) {
        Function<Integer, String[]> func1 = length -> new String[length];
        String[] arr1 = func1.apply(5);
        for (int i = 0; i < arr1.length; i++) {
            arr1[i] = "arr1_" + i;
        }
        System.out.println(Arrays.toString(arr1));
        Function<Integer, String[]> func2 = String[]::new;
        String[] arr2 = func2.apply(10);
        for (int i = 0; i < arr2.length; i++) {
            arr2[i] = "arr2 _" + i;
        }
        System.out.println(Arrays.toString(arr2));
    }
}

自由变量和 this 关键字

自由变量的值,这是指非参数而且不在代码中定义的变量。

package interfaceTest;
import javax.swing.*;
import java.awt.event.ActionListener;
public  class InterfaceTest {
    public static void main(String[] args) throws InterruptedException {
        countDown("Hello",1000);
        Thread.sleep(2000);
        System.exit(0);
    }
    public static void countDown(String message,int delay) {
        ActionListener listener = event -> {
            System.out.println(message);
        };
        new Timer(delay,listener).start();
    }
}

比如countDown中的message就是自由变量,也可以说messagelambda表达式捕获的。

注意:lambda表达式中只能引用值不会改变的变量。因为在lambda表达式中改变变量,并发执行多个操作时可能会不安全

lambda表达式捕获的变量也称为最终变量,也就是该变量在初始化后不会被赋新值。

在一个lambda表达式中使用this关键字时,是指创建这个lambda表达式的方法的this参数。this关键字的含义不会随lambda表达式而改变。

import javax.swing.*;
import java.awt.event.ActionListener;
public  class InterfaceTest {
    public static void main(String[] args) throws InterruptedException {
        new Application().init();
        Thread.sleep(2000);
        System.exit(0);
    }
}
class Application {
    public void init() {
        ActionListener listener = e -> {
            // 调用的是Application的toString()方法
            System.out.println(this.toString());
        };
        new Timer(1000,listener).start();
    }
    @Override
    public String toString() {
        return "hello";
    }
}

Runnable 接口和 IntConsumer 接口

Runnable: 无参数,返回值类型为void,抽象方法名为run

IntConsumer: 接受参数类型为int,返回值类型为void,抽象方法名为accept

import java.util.function.IntConsumer;
public  class InterfaceTest {
    public static void main(String[] args)  {
        repeat(10,() -> System.out.println("hello"));
        repeatAndNumber(10,i -> System.out.println("hello_" + i));
    }
    public static void repeat(int n, Runnable runnable) {
        for (int i = 0; i < n; i++) {
            runnable.run();
        }
    }
    public static void repeatAndNumber(int n, IntConsumer consumer) {
        for (int i = 0; i < n; i++) {
            consumer.accept(i);
        }
    }
}
相关文章
|
4天前
|
数据采集 JSON Java
利用Java获取京东SKU接口指南
本文介绍如何使用Java通过京东API获取商品SKU信息。首先,需注册京东开放平台账号并创建应用以获取AppKey和AppSecret。接着,查阅API文档了解调用方法。明确商品ID后,构建请求参数并通过HTTP客户端发送请求。最后,解析返回的JSON数据提取SKU信息。注意遵守API调用频率限制及数据保护法规。此方法适用于电商平台及其他数据获取场景。
|
9天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
40 6
|
23天前
|
Java API 开发者
Java中的Lambda表达式与Stream API的协同作用
在本文中,我们将探讨Java 8引入的Lambda表达式和Stream API如何改变我们处理集合和数组的方式。Lambda表达式提供了一种简洁的方法来表达代码块,而Stream API则允许我们对数据流进行高级操作,如过滤、映射和归约。通过结合使用这两种技术,我们可以以声明式的方式编写更简洁、更易于理解和维护的代码。本文将介绍Lambda表达式和Stream API的基本概念,并通过示例展示它们在实际项目中的应用。
|
25天前
|
Java API 开发者
Java中的Lambda表达式:简洁代码的利器####
本文探讨了Java中Lambda表达式的概念、用途及其在简化代码和提高开发效率方面的显著作用。通过具体实例,展示了Lambda表达式如何在Java 8及更高版本中替代传统的匿名内部类,使代码更加简洁易读。文章还简要介绍了Lambda表达式的语法和常见用法,帮助开发者更好地理解和应用这一强大的工具。 ####
|
27天前
|
并行计算 Java 编译器
深入理解Java中的Lambda表达式
在Java 8中引入的Lambda表达式,不仅简化了代码编写,还提升了代码可读性。本文将带你探索Lambda表达式背后的逻辑与原理,通过实例展示如何高效利用这一特性优化你的程序。
|
26天前
|
Java API
Java中内置的函数式接口
Java中内置的函数式接口
23 2
|
22天前
|
安全 Java API
Java中的Lambda表达式:简化代码的现代魔法
在Java 8的发布中,Lambda表达式的引入无疑是一场编程范式的革命。它不仅让代码变得更加简洁,还使得函数式编程在Java中成为可能。本文将深入探讨Lambda表达式如何改变我们编写和维护Java代码的方式,以及它是如何提升我们编码效率的。
|
25天前
|
安全 Java API
Java中的Lambda表达式与Stream API的高效结合####
探索Java编程中Lambda表达式与Stream API如何携手并进,提升数据处理效率,实现代码简洁性与功能性的双重飞跃。 ####
26 0
|
Java
Java接口和抽象类
Java接口和抽象类
92 0
|
4月前
|
设计模式 Java
【惊天揭秘】Java编程绝技大曝光:接口、抽象类、静态类与非静态类的神秘面纱终被揭开!
【8月更文挑战第22天】Java支持面向对象编程,通过接口、抽象类、静态类(如枚举与工具类)及普通类实现设计原则。接口定义行为规范,允许多重继承;抽象类含未实现的抽象方法,需子类完成;静态类常为工具类,提供静态方法;普通类则实例化对象。恰当运用这些结构能提升程序质量。
42 2