4.接口、lambda表达式与内部类

接口、lambda表达式与内部类

Java中,通过接口实现了多重继承的功能还能避免多重继承的复杂性和低效性

接口

概念(和抽象类之间的区别)

接口(interface),通俗的说,它是对对象行为的抽象(人类的衣食住行等一系列行为);而抽象类是对具体事物的抽象(比如一个Animal类,里面肯定有名字,种类等属性)

public interface Comparable {
    int compareTo(Object other);
}

接口中的方法自动是public方法,故不必提供关键字public;且全是抽象方法(故没有函数体)。

一个类只能继承一个父类,但可以实现多个接口

接口中一定没有实例字段!!!!

让类实现一个接口:

  1. 将类声明为实现(implements)给定的接口
  2. 对接口的所有方法提供定义
public class Employee implements Comparable {
    int compareTo(Object otherObject) {
        Employee other = (Employee) otherObject;
        return Double.compare(salary, other.salary);
    }
}

也可以使用泛型接口:

public class Employee implements Comparable<Employee> {
    int compareTo(Employee other) {
        return Double.compare(salary, other.salary);
    }
}

接口的属性

因为接口和抽象类一样,故接口也不能通过new运算符实例化

接口也能通过extends关键字进行拓展

接口中可以包含常量,字段会自动设置为**public static final**。

静态和私有方法

允许接口中增加静态方法,但不常用,通常将静态方法放在伴随类中

默认方法

**在jdk1.8中,**如果要实现非抽象的实例方法,必须使用default修饰符标记这样一个方法,并且提供一个默认实现。

解决默认方法冲突

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

  2. 接口冲突:如果一个接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型相同的方法。必须通过覆盖(Override)来解决冲突

    interface Person {
        default String getName() {return "";};
    }
    
    interface Named{
        default String getName() { return getClass.getName() + "_" + hashCode;}
    }
    
    class Student implements Person, Named {
        public String getName() { return Person.super.getName();}
    }
    

接口与回调(待写)

回调是一种常见的程序设计模式,这种模式可以指定某个特定事件发生时应该采取的动作。

Comparator接口

java.util.Comparator中定义

public interface Comparator<T> {
    int compare(T first, T second);
}

对象克隆(待写)

Cloneable接口,这个接口指示了一个类提供了一个安全的clone方法。

lambda表达式(Java8新特性)

为什么引入lambda表达式

lambda表达式,也被称为闭包,是一个可传递的代码块,可以在以后执行一次或多次;但是在Java中传递代码块并不容易,因为Java是面向对象的语言,你必须先构造一个对象,这个对象需要有一个方法包含所需的代码。

使用Lambda表达式,实际就是创建出该接口的实例对象并返回它

lambda表达式的语法

(parameters) -> expression 或 (parameters) ->{ statements; }

// 1. 不需要参数,返回值为 5  
() -> 5  
  
// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x  
  
// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  
  
// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  
  
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s)

函数式接口

Java中有很多封装代码块的接口,lambda表达式与这些接口是兼容的。对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式,这种接口称为函数式接口。

如果不使用函数式接口:

public class LengthComparator implements Comparable<String> {
    int compareTo(String first, String second) {
        return first.length() - second.length;
    }
}

public class Test {
    public static void main(String[] args) {
        Arrays.sort(array,new LenthComparator());
    }
}

如果使用了函数式接口:

public class Test {
    public static void main(String[] args) {
        Arrays.sort(array,(String first, String second) -> first.length() - second.length());
        // 这里返回了Comparable接口的实例对象
    }
}

从上面的代码例子可以看出,我们使用Lambda表达式的时候,并不关心接口名,方法名,参数名。我们只关注他的参数类型,参数个数,返回值

需要掌握的四大函数式接口

  1. Function转换式接口:有一个输入参数,有一个输出参数(类型可以相同也可以不相同)

    源码:

    @FunctionalInterface
    public interface Function<T, R> {
    
        /**
         * Applies this function to the given argument.
         *
         * @param t the function argument
         * @return the function result
         */
        R apply(T t);
    }
    
    Function<String, Integer> function  = str -> Integer.valueOf(str);
    
  2. Predicate断定式接口:有一个输入参数,返回值只能是布尔值

    源码:

    public interface Predicate<T> {
    
        /**
         * Evaluates this predicate on the given argument.
         *
         * @param t the input argument
         * @return {@code true} if the input argument matches the predicate,
         * otherwise {@code false}
         */
        boolean test(T t);
    }
    
    Predicate<String> predicate = str -> str.isEmpty();//如果str为空,返回true
    
  3. Consumer接口:有一个输入参数,没有返回值

    源码:

    public interface Consumer<T> {
    
        /**
         * Performs this operation on the given argument.
         *
         * @param t the input argument
         */
        void accept(T t);
    }
    
    Consumer<String> consumer = System.out::println;
    
  4. Supplier接口:没有输入,有一个返回值

    源码:

    public interface Supplier<T> {
    
        /**
         * Gets a result.
         *
         * @return a result
         */
        T get();
    }
    
    Supplier<String> supplier = () -> "I love you";
    

方法引用

如果函数式接口的实现恰好可以通过调用一个方法来实现,那么我们可以使用方法引用:

在学Lambda的时候,还可能会发现一种比较"奇怪"的写法,例如下面的代码:

// 方法引用写法
Consumer<String> consumer = System.out::println;
consumer.accept("Java3y");

如果按正常Lambda的写法可能是这样的:

// 普通的Lambda写法
Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("Java3y");

表达式System.out::println就是一个方法引用,它指示编译器生成一个函数式接口的实例,覆盖这个接口的抽象方法来调用给定的方法

构造器引用

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

变量作用域

  1. 在lambda表达式中引用的变量不能在外部改变

  2. 在lambda表达式中只能引用变量不能改变变量

  3. 在lambda表达式中不能声明与局部变量同名的参数或局部变量

  4. 在lambda表达式中使用this关键字是指创建这个lambda表达式的方法的this参数:

    public class Application {
        public void init {
            ActionListener listener = event -> {
                System.out.println(this.toString)
            }
        }
    }
    

    表达式this.toString会调用Application对象的toString方法

处理lambda表达式

JDK提供的常用的函数式接口

名称一元接口说明二元接口说明
一般函数Function一元函数,抽象apply方法BiFunction二元函数,抽象apply方法
算子函数(输入输出同类型)UnaryOperator一元算子,抽象apply方法BinaryOperator二元算子,抽象apply方法
谓词函数(输出布尔值)Predicate一元谓词,抽象test方法BiPredicate二元谓词,抽象test方法
消费者(无返回值)Consumer一元消费者函数,抽象accept方法BiConsumer二元消费者函数,抽象accept方法
供应者(无参数,只有返回值)Supplier供应者函数,抽象get方法
  • 表格中的一元接口表示只有一个入参,二元接口表示有两个入参
  • 如果你设计你自己的接口,其中只有一个抽象方法,可以用@FunctionalInterface注解来标记这个接口

内部类

定义在另一个类中的类

  • 内部类可以对同一个包其他类隐藏
  • 内部类方法可以访问定义这个类的作用域中的数据,包括原本私有的数据

使用内部类访问对象状态

成员内部类

class Outer {
   class Inner {
   }
}

外部类引用

Outer.this

在外部类中新建一个对象

外部类名.内部类名(这里可以用var替代) 对象名 = new 外部类名.new 内部类名();

内部类中所有静态字段必须是final

非静态内部类中不能有静态方法

内部类是否必要

实际上内部类在开发中使用较少

局部内部类

局部内部类——就是定义在一个方法或者一个作用域里面的类
特点:主要是作用域发生了变化,只能在自身所在方法和属性中被使用,对外部世界完全隐藏

匿名内部类

一个没有名字的类,是内部类的简化写法

假如只想创建这个类的一个对象,可以使用匿名内部类。

var listener = new ActionListener() {
    public void actionPerformed(ActionEvent event) {
        ...
    }
};

上例中,创建了一个类的新对象,这个类实现了ActionListener接口

由于构造器的名字必须和类名相同,匿名内部类没有名字,故匿名内部类不能有构造器

Java程序员习惯用匿名内部类实现事件监听器和其他回调。

静态内部类

我们所知道static是不能用来修饰类的,但是成员内部类可以看做外部类中的一个成员,所以可以用static修饰,这种用static修饰的内部类我们称作静态内部类,也称作嵌套内部类.
特点:不能使用外部类的非static成员变量和成员方法

仅仅为了隐藏一个类,并不需要内部类有对外部类的对象的引用

在接口中声明的内部类自动是static和public

典例!!!

public class StaticInnerClassTest {
    public static void main(String[] args) {
        var values = new double[20];
        for (int i = 0; i < values.length; i++) {
            values[i] = 100 * Math.random();
            ArrayAlg.Pair p = ArrayAlg.minmax(values);
            System.out.println("min = " + p.getFirst());
            System.out.println("max = " + p.getSecond());
        }
    }
}

class ArrayAlg {
    // Pair类中没有引用任何外部类的对象
    public static class Pair {
        private double first;
        private double second;
        
        public Pair(double first, double second) {
            this.first = first;
            this.second = second;
        }

        public double getFirst() {
            return first;
        }

        public double getSecond() {
            return second;
        }
    }

    public static Pair minmax(double[] values) {
        double min = Double.POSITIVE_INFINITY;
        double max = Double.NEGATIVE_INFINITY;

        for (double v :
                values) {
            if (min > v) min = v;
            if (max < v) max = v;
        }
        return new Pair(min, max); // 在静态方法中构造了一个内部类对象
    }
}

服务加载器

代理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值