文章目录

  • 第六章 接口、lambda表达式与内部类
    • ==接口==
      • 接口的概念
      • 接口的属性
      • 接口与抽象类
      • 静态和私有方法
      • 默认方法
      • 解决默认方法冲突
      • 接口与回调
      • `Comparator`接口
      • 对象克隆
    • ==`lambda`表达式==
      • ==函数式接口==
      • 方法引用
      • 构造器引用
      • 变量作用域
      • 处理`lambda`表达式
      • 再谈`Comparator`类
    • 内部类
      • 使用内部类访问对象的状态
      • 局部内部类
      • 由外部方法访问变量
      • 匿名内部类
      • 静态内部类
    • 代理
      • 何时使用代理
      • 创建代理对象
      • 代理的特性

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

  • 接口: 描述类应该做什么, 不指定如何做
  • lambda表达式: 表示使用回调或者可变行为的代码

接口

接口的概念

  • 接口: 不是类, 而是对希望符合这个接口的类的一组需求

  • Arrays类中的sort方法对对象数组进行排序, 要求对象所属的内必须实现Comparable接口

public interface Comparable
{int compareTo(Object other);
}
//java5后
public interface Comparable<T>
{int compareTo(T other);
}
  • 接口中的所有方法自动为public
  • 接口中可以包含多个方法, 但是接口不会有实例字段
  • 让一个类实现一个接口:
    1. 将类声明为实现给定的接口
    2. 对接口中的所有方法提供定义
  • 将类声明为实现为某个接口, 使用关键字implements
class Employee implements Comparable
  • Java API建议equals, compareTo方法兼容
  • 例外:x = BigDecimal("1.0"); y = BigDecimal("1.00"); x.equals(y)//false x.compareTo(y) == 0
package chapter6_interface_lambda_innerClass.interfaces;public class Employee implements Comparable<Employee>{private String name;private double salary;public Employee(String name, double salary) {this.name = name;this.salary = salary;}public double getSalary() {return salary;}public String getName() {return name;}public int compareTo(Employee other) {return Double.compare(salary, other.salary);}
}package chapter6_interface_lambda_innerClass.interfaces;import java.util.Arrays;public class EmployeeSortTest {public static void main(String[] args) {var staff = new Employee[3];staff[0] = new Employee("Oukunnan", 25598);staff[1] = new Employee("Ovfdunnan", 18);staff[2] = new Employee("dsukunnan", 98);Arrays.sort(staff);for (Employee employee : staff) {System.out.println(employee.getName() + "  salary=" + employee.getSalary());}}
}

接口的属性

  • 接口不是类, 不能用new运算符实例化一个接口
  • 能够声明接口变量: Comparable x ; //OK
  • 接口变量必须引用实现了这个接口的类对象 x = new Employee(...);
  • instanceof: 1. 检查一个对象是否属于某个特定的类 2. 一个对象是否实现了某个特定的接口
if (anObject instanceof Comparable){...};
  • 与建立类的继承层次类似, 可以扩展接口
public interface Moveable{...}
public interface Powered extends Moveable{..}
  • 接口不能包含实例字段但是可以包含常量
  • 接口的方法总是public, 接口的字段总是public static final, 都可以省略, 建议省略
  • 每个类只有一个超类, 却可以实现多个接口, 有点像C++的多重继承

接口与抽象类

  • 抽象类问题: 每个类只能扩展一个类
  • java可以扩展一个基类并且派生多个接口

静态和私有方法

  • 标准库中成对出现的接口和实用工具类: Collection/Collections, Path/paths
  • 允许在接口中增加静态方法, 一般做法是放在伴随类中
  • java9中接口中的方法可以是private

默认方法

  • 接口方法提供一个默认实现, default修饰符标记
public interface Comparable<T>
{default int CompareTo(T other) {return 0;}
}
  • 如果迭代器是只读的就不用实现remove方法

    public interface Iterator<E>
    {boolean hasNext();E next();default void remove(){throw new UnsupprotedOperationException("remove");}
    }
    
  • 另一个作用是接口演化, 实现源代码兼容


解决默认方法冲突

  • 超类优先
  • 同时实现的两个接口中由完全同名并且参数类型相同的方法, 要求这个类实现该方法覆盖接口的方法
  • class Student extends Person implements Named{...}只会考虑超类方法, 类优先原则

接口与回调

  • 回调: 指定某个特定事件发生时应该采取的动作
package chapter6_interface_lambda_innerClass.timer;import java.awt.event.*;
import java.awt.*;
import java.time.*;
import javax.swing.*;public class TimerTest {public static void main(String[] args) {var listener = new TimePrinter();var timer = new Timer(1000, listener);//每隔1000ms(1s)响铃并且打应输出timer.start();JOptionPane.showMessageDialog(null, "Quit Program?");//展示消息框System.exit(0);}
}class TimePrinter implements ActionListener {public void actionPerformed(ActionEvent event) {//ActionEvent 事件参数,提供事件的相关信息              //getWhen的得到纪元以来的毫秒数, 利用函数转换成可读时间System.out.println("At this tone, the time is " + Instant.ofEpochMilli(event.getWhen()));Toolkit.getDefaultToolkit().beep(); //调用默认工具箱响铃}
}

Comparator接口

  • 对于对象数组进行排序, 前提是这些对象是实现了Comparable接口类的实例
  • 如果按照长度而不是字典顺序对于字符串进行排序, 使用Arrays.sort的另一种版本, 一个数组和比较器作为参数
  • 比较器实现Comparator接口
public interface Comparator<T>
{int compare(T first, T second);
}class LengthComparator implements Comparator<String>
{public int compare(String first, String second){return first.length() - second.length();}
}调用
Arrays.sort(words, new LengthComparator());

对象克隆

  • 拷贝: 一个包含对象引用的变量建立副本时,原变量和副本都是对同一个对象的引用, 任何一个对象的引用都会改变另一个变量
  • 克隆: 希望变量是一个新的对象, 初始状态和原变量相同, 之后会有各自不同的状态
  • 默认的克隆操作是浅拷贝: 逐个字段拷贝, 对于数值和其他基本类型克隆, 但是对于包含对象引用的子对象也会共享一些信息
  • 如果原对象和浅克隆对象共享的子对象是不可变的, 那么浅拷贝的共享安全
  • 深拷贝: 子对象可变的, 必须重新定义clone方法建立深拷贝, 克隆所有对象
  • Cloneable: 标记接口, 不包含任何方法(一般的接口确保一个类实现一组特定的方法), 作用:允许类型查询中使用instanceof
if(obj instanceof Cloneable) ...
  • 所有数组类型都有一个公共的clone方法, 不是受保护的
int[] a = {1, 2, 3};
int[] b = a.clone();
b[0] = 5;// a[0] == 1;
package chapter6_interface_lambda_innerClass.clone;public class CloneTest {public static void main(String[] args) throws CloneNotSupportedException{var original = new Employee("Ou Kunnan", 52000);original.setHireDay(2001, 12, 26);Employee copy = original.clone();System.out.println(copy); // 自动调用toString方法,相当于copy.toString()copy.raiseSalary(10);copy.setHireDay(2002, 12, 26);System.out.println("original: " + original);System.out.println("copy: " + copy);}
}package chapter6_interface_lambda_innerClass.clone;import java.util.Date;
import java.util.GregorianCalendar;public class Employee implements Cloneable{private double salary;private String name;private Date hireDay;public Employee(String name, double salary) {this.name = name;this.salary = salary;hireDay = new Date();}public Employee clone() throws CloneNotSupportedException {Employee cloned = (Employee) super.clone();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();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 + "]";}
}

lambda表达式

  • lambda表达式就是一个代码块,以及必须传入的代码变量规范

  • 形式: 参数, ->, 一个表达式

  • (String first, String second)->{if(first.length()  < second.length()) return -1;else return 1;
    }
    
  • lambda没有参数,`()-> {…};

  • 如果可以推导出lambda表达式参数类型, 可以忽略其类型

Comparator<String> comp = (first, second) -> first.length() - second.length();
  • 只有一个参数并且类型可以推到, 可以省略小括号

  • 无需指定返回类型

package chapter6_interface_lambda_innerClass.lambda;import java.util.*;import javax.swing.*;
import javax.swing.Timer;public class lambdaTest {public static void main(String[] args) {var planets = new String[]{"Mercury", "Venus", "Earth", "Mars","Jupiter", "Saturn", "Uranus", "Neptune"};System.out.println(Arrays.toString(planets));System.out.println("Sorted in dictionary order:");Arrays.sort(planets);System.out.println(Arrays.toString(planets));System.out.println("Sorted by length:");Arrays.sort(planets, (first, second) -> first.length() - second.length());//第二个参数应该是比较器System.out.println(Arrays.toString(planets));var timer = new Timer(1000, event -> System.out.println("The time is " + new Date()));timer.start();JOptionPane.showMessageDialog(null, "quit?");System.exit(0);}
}

函数式接口

  • 函数式接口: 对于只有一个抽象方法的接口,需要这种接口对象时, 可以提供一个lambda表达式
  • lambda表达式可以转换为接口
var timer = new Timer(1000, event->{System.out.println("At this tone, the time is " + Instant.ofEpochMilli(event.getWhen()));Toolkit.getDefaultToolkit().beep(); })
  • ArrayList类的removeIf方法参数是Predicate
public interface Predicate<T>
{boolean test(T t);
}
//删除列表所有null值
list.removeIf(e -> e == null);
  • supplier没有参数, 调用时会生成T类型的值, 用于实现计算
public interface Supplier<T>
{T get();
}LocalDate hireDay = Objects.requireNonNullOrElse(day, new LocalDate(1970, 1, 1));//通过使用供应者supplier, 延迟该计算 , 只有day==null时候才调用供应者(构造默认的LocalDate)
LocalDate hireDay = Objects.requireNonNullOrElse(day, () -> new LocalDate(1970, 1, 1));

方法引用

  • var timer = new Timer(1000, event->System.out.println(event));
    var timer = new Timer(1000, System.out::println);
    
  • System.out::println是一个方法引用, 它指示编译器生成一个函数式接口的实例,覆盖这个接口的抽象方法来调用给定的方法

  • 上面的例子, 会生成一个ActionListener, 他的actionPerformed(ActionEvent e)方法要调用System.out.println(e)

  • 方法引用不是对象, 为一个类型为函数式接口的变量赋值时会生成一个对象

  • 方法引用示例与等价的lambda表达式见P248

  • 当lambda表达式的体只调用一个方法而不做其他操作的时候才能把方法引用重写为方法引用


构造器引用

  • Person::new就是Person构造器的一个引用
  • int[]::new是一个构造器引用, 他有一个参数,即数组的长度, 等价于x->new int[x]

变量作用域

  • lambda表达式三个部分: 代码块, 参数, 自由变量的值
  • 可以把一个lambda表达式转换为一个包含方法的对象, 自由变量的值会复制到这个对象的实例变量中
  • lambda表达式是闭包的
  • lambda表达式可以捕获外围作用域中变量的值, 确保值是明确定义的(事实最终变量, 初始化后不会改变)

处理lambda表达式

  • lambda表达式重点是延迟执行

  • Runnable作为无参数或返回值的动作运行, action.run()会调用lambda表达式主体

    package test;import java.util.function.IntConsumer;public class lambda {public static void main(String[] args){repeat(10, ()->System.out.println("hello, world"));repeat(10, (i)->System.out.println("Countdown:" + (9-i)));}public static void repeat(int n, Runnable action){for(int i = 0; i < n; i++) action.run();}public static void repeat(int n, IntConsumer action) {for(int i = 0; i < n; i++)action.accept(i);}
    }
    

再谈Comparator

  • P255

内部类

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

使用内部类访问对象的状态

  • 一个内部类方法可以访问自身的数据字段,也可以访问创建它的外围类对象的数据字段
  • 内部类对象总有一个隐式引用指向创建它的外部类对象
  • 这个引用在构造器中设置, 编译器会修改所有内部类的构造器,添加一个对应外围类引用的参数
以下这个例子生成的无参数构造器如下
public TimePrinter(TalkingClock clock){outer = clock;}
package chapter6_interface_lambda_innerClass.innerClass;
import java.awt.*;
import java.awt.event.*;
import java.time.*;import javax.swing.*;public class innerClassTest {public static void main(String[] args) {var clock = new TalkingClock(1000, true);clock.start();JOptionPane.showMessageDialog(null, "Quit?");System.exit(0);}
}class TalkingClock{private int interval;private boolean beep;public TalkingClock(int interval, boolean beep) {this.interval = interval;this.beep = beep;}public void start() {var listener = new TimerPrinter();var timer = new Timer(interval, listener);timer.start();}public class TimerPrinter implements ActionListener {public void actionPerformed(ActionEvent event) {System.out.println("At this tone, the time is " + Instant.ofEpochMilli(event.getWhen()));if (beep) {Toolkit.getDefaultToolkit().beep();}}}}

局部内部类

  • TimePrinter名字只出现了一次,在start方法中创建这个类型对象时使用了一次.可以在一个方法中局部定义这个类

    public void start() {class TimerPrinter implements ActionListener {public void actionPerformed(ActionEvent event) {System.out.println("At this tone, the time is " + Instant.ofEpochMilli(event.getWhen()));if (beep) {Toolkit.getDefaultToolkit().beep();}}}var listener = new TimerPrinter();var timer = new Timer(interval, listener);timer.start();
    }
    
  • 局部内部类声明时候不能有访问修饰符public, private

  • 优势: 对外部完全隐藏,除了start代码


由外部方法访问变量

  • 局部内部类不仅能够访问外部类字段,还可以访问局部变量(事实最终变量)

  • public void start(int interval, boolean beep) {class TimerPrinter implements ActionListener {public void actionPerformed(ActionEvent event) {System.out.println("At this tone, the time is " + Instant.ofEpochMilli(event.getWhen()));if (beep) {Toolkit.getDefaultToolkit().beep();}}}var listener = new TimerPrinter();var timer = new Timer(interval, listener);timer.start();
    }
    

匿名内部类

  • 匿名内部类不需要为类指定名字

  • 以下代码: 创建了一个类的新对象,这个类实现了ActionListener接口, 需要实现的方法{}中定义

    public void start(int interval, boolean beep) {var listener = new ActionListener();{public void actionPerformed(ActionEvent event) {System.out.println("At this tone, the time is " + Instant.ofEpochMilli(event.getWhen()));if (beep) {Toolkit.getDefaultToolkit().beep();}}};var timer = new Timer(interval, listener);timer.start();
    }//用lambda表达式
    public void start(int interval boolean beep)
    {var timer = new Timer(interval, event->{System.out.println("At this tone, the time is " + Instant.ofEpochMilli(event.getWhen()));if (beep) Toolkit.getDefaultToolkit().beep();})
    }
    
  • 语法如下

new SuperType(construction parameters)
{innner class methods and data
}

SuperType可以是接口,内部类就要实现这个接口;如果是一个类,内部类就要扩展这个类

  • 构造器名字必须和类名相同,匿名内部类没有类名所以没有构造器
  • 构造参数要传递给超类构造器
  • 注意: 构造一个类的新对象和构造一个扩展了那个类的匿名内部类的对象之间的差别
vae queen = new Person("Marry");
var count = new Person("bjcs"){...};
  • 匿名内部类不能有构造器但是可以提供一个对象的

  • 双括号初始化:

  • var f  = new ArrayList<String>();
    f.add("Harry");
    f.add("Alice");
    invite(f);
    --->
    invite(new ArrayList<String>)(){{add("Harry"); add("Alice");}}
    //外层括号建立了一个匿名子类,内层括号是一个初始化块
    
  • 得到匿名内部类的外部类类名不能直接getClass,这个方法带调用this.getClass(), 静态方法没有隐式参数

new Object(){}.getClass().getEnclosingClass()

new Object()建立Object的匿名子类的一个匿名对象,getEnclosingClass则得到其外围类,也就是包含这个静态方法的类


静态内部类

  • 只要内部类不需要访问外围类对象,就应该使用静态内部类
  • 接口中声明的内部类自动为public, static
package chapter6_interface_lambda_innerClass.staticInnerClass;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{public static class Pair {/* 一个静态的内部类 */private double first;private double second;/*** Constructs a pair from two floating-point numbers** @param f the first number* @param s the second number*/public Pair(double f, double s) {first = f;second = s;}/*** Returns the first number of the pair** @return the first number*/public double getFirst() {return first;}/*** Returns the second number of the pair** @return the second number*/public double getSecond() {return second;}}/*** Computes both the minimum and the maximum of an array* @param values an array of floating-point numbers* @return a pair whose first element is the minimum and whose second element* is the maximum*/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);}
}

代理

何时使用代理

  • 代理类在运行时闯将全新的类,这样代理类可以实现你指定的接口
  • 代理类包含的方法: 指定接口所需要的全部方法, Object类中的全部方法(equals, toStirng等)

创建代理对象

  • 需要使用Proxy类的newProxyInstance方法, 有三个参数

    • 一个类加载器(这里指定系统类加载器)
    • 一个Class对象数组,每个元素对应需要实现的各个接口
    • 一个调用处理器
  • 代理作用: 将方法调用路由到远程服务器;在运行中的程序将用户界面事件与动作关联起来;为了调试,跟踪方法使用
package chapter6_interface_lambda_innerClass.proxy;import java.lang.reflect.*;
import java.util.*;public class ProxyTest
{public static void main(String[] args) {var elements = new Object[1000];for (int i = 0; i < elements.length; i++) {Integer value = i + 1;var handler = new TraceHandler(value);Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Comparable.class}, handler);elements[i] = proxy;}Integer key = new Random().nextInt(elements.length) + 1;int result = Arrays.binarySearch(elements, key);if(result >= 0) System.out.println(elements[result]);}
}class TraceHandler implements InvocationHandler {//打应输出方法名和参数,并且调用该方法private Object target;public TraceHandler(Object t) {target = t;}public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {System.out.print(target);System.out.print("." + m.getName() + "(");if (args != null) {for (int i = 0; i < args.length; i++) {System.out.print(args[i]);if(i < args.length - 1) System.out.print(", ");}}System.out.println(")");return m.invoke(target, args);}
}

代理的特性

  • 代理是在运行过程中创建的,一旦创建就变成了常规类
  • 代理类都是扩展Proxy, 一个代理类只有一个实例字段即调用处理器,在超类Proxy中定义
  • 所有的代理类都要覆盖toString, hasCode, equals方法, 这些方法只是在调用处理器上调用invoke.

CoreJava 笔记总结-第六章 接口、lambda表达式与内部类相关推荐

  1. 【Java】 第六章 接口、lambda 表达式与内部类 Java核心技术卷1基础知识原书第10版 读书笔记

    第六章 接口.lambda表达式与内部类 6. 接口.lambda 表达式与内部类 6.1 接口 6.1.1 接口概念 6.1.2 接口的特性 6.1.3 接口与抽象类 6.1.4 静态方法 6.1. ...

  2. Java 核心技术卷1 --第六章 接口、lambda表达式和内部类

    吧Github代码链接: https://github.com/deyou123/corejava.git 第六章 接口.lambda表达式和内部类 6.1 接口 6.1.1 接口概念 接口不是类,而 ...

  3. CoreJava 笔记总结-第三章 Java的基本程序设计结构

    CoreJava 笔记总结 文章目录 CoreJava 笔记总结 第三章 Java的基本程序设计结构 数据类型 1. 整型 2. 浮点类型 3. char类型 4. boolean类型 变量与常量 1 ...

  4. 系统架构师学习笔记_第六章(下)_连载

    系统架构师学习笔记_第六章(下)_连载 6.3 基于 UML 的软件开发过程 6.3.1  开发过程概述 UML 是独立于软件开发过程的,能够在几乎任何一种软件开发过程中使用.迭代的渐进式软件开发过程 ...

  5. 强化学习(RLAI)读书笔记第十六章Applications and Case Studies(不含alphago)

    强化学习(RLAI)读书笔记第十六章Applications and Case Studies(不含alphago) 16.1 TD-Gammon 16.2 Samuel's Checkers Pla ...

  6. 强化学习(RLAI)读书笔记第十六章Applications and Case Studies(alphago)

    强化学习(RLAI)读书笔记第十六章Applications and Case Studies(alphago) 16.6 Mastering the Game of Go 16.6.1 AlphaG ...

  7. 机器学习理论《统计学习方法》学习笔记:第六章 逻辑斯谛回归与最大熵模型

    机器学习理论<统计学习方法>学习笔记:第六章 逻辑斯谛回归与最大熵模型 6 逻辑斯谛回归与最大熵模型 6.1 逻辑斯谛回归模型 6.1.1 逻辑斯谛分布 6.1.2 二项逻辑斯蒂回归模型 ...

  8. [转]《精通Javascript》笔记:第六章(事件)

    <精通Javascript>笔记:第六章(事件) Published by sansan at 11:41 am under 前端|Front-End 事件模型:捕获和冒泡 通过oneve ...

  9. [go学习笔记.第十六章.TCP编程] 3.项目-海量用户即时通讯系统-redis介入,用户登录,注册

    1.实现功能-完成用户登录 在redis手动添加测试用户,并画出示意图以及说明注意事项(后续通过程序注册用户) 如:输入用户名和密码,如果在redis中存在并正确,则登录,否则退出系统,并给出相应提示 ...

最新文章

  1. springboot 事务手动回滚_来,讲讲Spring事务有哪些坑?
  2. 深入了解JavaScript对象(2)--函数、对象
  3. 关于双机热备,你该知道那些问题?
  4. LZW算法PHP实现方法 lzw_decompress php
  5. 《MacTalk•人生元编程》导读
  6. Spring整合junit4实现对方法的测试
  7. python列表字典如何提取_怎么提取字典里面的列表里面的字典的value
  8. Centos 利用yum源安装 nginx 1.20.1
  9. Jquery有哪些选择器
  10. e-006 matlab,基于MATLAB进行潮流计算
  11. Flink-keySet方法
  12. HTTP协议···(一)
  13. Netty源代码学习——EventLoopGroup原理:NioEventLoopGroup分析
  14. Xen的调度分析 (五) ——关于RTDS调度算法简介
  15. 在计算机领域黑箱,计算机模拟电学黑箱
  16. vue element 的el-checkbox-group默认全部选中
  17. qpython教程_qpython教程
  18. 稀疏数组练习demo 数据结构和算法
  19. 从专升本到互联网大厂-我的2021
  20. php实现自动续费功能,如何关闭wps自动续费

热门文章

  1. Android之category
  2. c语言 case语句用法,switch ... case语句的用法[组图]
  3. google+stackoverflow_哪些开发问题最让程序员“头秃”?我们分析了Stack Overflow的11000个问题...
  4. char添加一个字符_给你五十行代码把图片变成字符画!程序:太多了,一半都用不完...
  5. axure如何导出原件_axure导出_axure怎么导出流程图
  6. go kegg_GO,KEGG富集分析工具——DAVID
  7. python总线 rabbitmq_python - 操作RabbitMQ
  8. 请问:如何写出没有BUG的代码?
  9. java当前时间推前三个月_获取当前时间的前三个月 java
  10. python gdb coredump_Linux段错误及GDB Coredump调试方法