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);
        }
    }
}
相关文章
|
5天前
|
Java
Java——抽象类和接口
抽象类是一种不能被实例化的类,至少包含一个抽象方法(无实现体的方法),常用于定义一组相关类的共同特征,并强制子类实现特定方法。抽象方法不能被 `static` 或 `final` 修饰,且必须被重写。 接口则是一个完全抽象的类,用于规范类的行为。接口使用 `interface` 关键字定义,不能实例化,并且类与接口之间是实现关系。 内部类是在一个类内定义的类,分为成员内部类、静态内部类、局部内部类和匿名内部类。成员内部类可被修饰符修饰,静态内部类只能访问外部类的静态成员,局部内部类定义在方法内,匿名内部类则隐藏了名字,直接通过 `new` 关键字定义并实现接口或继承类。
12 5
Java——抽象类和接口
|
5天前
|
Java
Java——接口的使用实例
Comparable接口用于自定义类的对象比较。通过实现此接口并重写`compareTo`方法,可以定义自定义类型的比较规则。 接下来介绍了Comparator接口,它提供了一种更灵活的比较方式。通过实现Comparator接口并重写`compare`方法,可以根据不同属性定义不同的比较规则。例如,定义一个`BrandComparator`类来比较汽车的品牌。 最后,介绍了Cloneable接口,用于实现对象的克隆。实现该接口并重写`clone`方法后,可以创建对象的浅拷贝或深拷贝。浅拷贝仅复制对象本身,深拷贝则会递归复制所有成员变量。
12 4
Java——接口的使用实例
|
1天前
|
Java
Java 正则表达式高级用法
Java 中的正则表达式是强大的文本处理工具,用于搜索、匹配、替换和分割字符串。`java.util.regex` 包提供了 `Pattern` 和 `Matcher` 类来高效处理正则表达式。本文介绍了高级用法,包括使用 `Pattern` 和 `Matcher` 进行匹配、断言(如正向和负向前瞻/后顾)、捕获组与命名组、替换操作、分割字符串、修饰符(如忽略大小写和多行模式)及 Unicode 支持。通过这些功能,可以高效地处理复杂文本数据。
|
11天前
|
Java 数据库连接 数据库
Java服务提供接口(SPI)的设计与应用剖析
Java SPI提供了一种优雅的服务扩展和动态加载机制,使得Java应用程序可以轻松地扩展功能和替换组件。通过合理的设计与应用,SPI可以大大增强Java应用的灵活性和可扩展性。
43 18
|
2天前
|
Java 程序员 API
Java中的Lambda表达式:简化代码的秘密武器
在Java 8中引入的Lambda表达式是一种强大的编程工具,它可以显著简化代码,提高可读性。本文将介绍Lambda表达式的基本概念、优势以及在实际开发中的应用。通过具体示例,您将了解如何使用Lambda表达式来简化集合操作、线程编程和函数式编程。让我们一起探索这一革命性的特性,看看它是如何改变Java编程方式的。
15 4
|
2天前
|
Java 开发者
探索Java中的Lambda表达式:简化你的代码
【8月更文挑战第49天】在Java 8的发布中,Lambda表达式无疑是最令人兴奋的新特性之一。它不仅为Java开发者提供了一种更加简洁、灵活的编程方式,而且还极大地提高了代码的可读性和开发效率。本文将通过实际代码示例,展示如何利用Lambda表达式优化和重构Java代码,让你的编程之旅更加轻松愉快。
|
5天前
|
Java 开发者
探索Java中的Lambda表达式:简化代码,提升效率
【9月更文挑战第14天】本文旨在揭示Java 8中引入的Lambda表达式如何革新了我们编写和管理代码的方式。通过简洁明了的语言和直观的代码示例,我们将一起走进Lambda表达式的世界,了解其基本概念、语法结构以及在实际编程中的应用。文章不仅会展示Lambda表达式的魅力所在,还会指导读者如何在日常工作中有效利用这一特性,以提高编码效率和程序可读性。
|
9天前
|
Java 开发者
Java的接口详解
Java接口是编程中的一种重要特性,用于定义方法签名而不提供具体实现,作为类之间的契约,使不同类能以统一方式交互。接口使用`interface`关键字定义,可包含方法声明和常量。类通过`implements`关键字实现接口,并可同时实现多个接口,解决多重继承问题。接口中的方法默认为抽象方法,变量默认为`public static final`。Java 8引入了默认方法和静态方法,增强接口功能。接口广泛应用于回调机制和多态性实现,有助于构建更灵活和可维护的代码结构。
|
11天前
|
并行计算 Java 开发者
探索Java中的Lambda表达式:简化代码,提升效率
Lambda表达式在Java 8中引入,旨在简化集合操作和并行计算。本文将通过浅显易懂的语言,带你了解Lambda表达式的基本概念、语法结构,并通过实例展示如何在Java项目中应用Lambda表达式来优化代码,提高开发效率。我们将一起探讨这一现代编程工具如何改变我们的Java编码方式,并思考它对程序设计哲学的影响。