首页 > 编程语言 > 详细

java核心技术卷1基础知识 第六章接口 lambda表达式 内部类

时间:2021-04-01 23:33:04      阅读:31      评论:0      收藏:0      [点我收藏+]
# 第六章 接口、lambda表达式与内部类
## 6.1 接口
### 6.1.1 概念
接口不是类,而是对希望符合这个接口的类的一组需求\
Arrays类中的sort方法承诺可以对对象数组进行排序,但是对象所属的类必须实现Comparable接口。\
接口代码如下
```
public interface Comparable
{
    int compareTo(Object other);
}
```
 接口中所有方法自动都是public 不必手动声明\
 接口不会有实例字段,也不会有实例(可粗略划分为没有实例字段的抽象类)\

为了让类实现接口,需要下面两个步骤
1. 将类声明为实现给定接口(implements)
2. 对接口中的所有方法提供定义
```
class Employee implements Comparable<Employee>
public int compareTo(Employee other)
{
    return Double.compare(salary,other.salary);//静态的Double.compare方法,<则返回负数,>则返回正数,否则返回0
}
```
因为Manager扩展了Employee,而Employee实现的是Comparable```<Employee>```,而不是Comparable```<Manager>```。如果Manager要实现compareTo,就必须做好准备比较Employee和Manager

如果不同子类中的比较有不同含义,就不应该比较不同类,compareTo方法应该进行检测
```
if(getClass()!=other.getClass()) throw new ClassCastException();
```
如果存在一个比较子类的通用算法,可以在超类设置一个compareTo方法,并设置为final。
### EmployeeSortTest.java
```
package javaSX.interfaces;

import java.util.*;

public class EmployeeSortTest {
    public static void main(String[] args) {
        var staff = new Employee[3];
        staff[0] = new Employee("harry", 10000);
        staff[1] = new Employee("harr", 2000);
        staff[2] = new Employee("har", 3000);

        Arrays.sort(staff);
        for (Employee e : staff) {
            System.out.println("name=" + e.getName() + ", salary=" + e.getSalary());
        }

    }
}

```
### Employee.java
```
package javaSX.interfaces;

public class Employee implements Comparable<Employee> {
    private double salary;
    private String name;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;

    }

    public String getName() {
        return name;
    }

    public double getSalary() {
        return this.salary;
    }

    public void raiseSalary(double percent) {
        double raise = this.salary * percent / 100;
        this.salary += raise;
    }

    public int compareTo(Employee other) {
        return Double.compare(salary, other.salary);
    }
}

```

### 6.1.2 接口的属性
接口虽然不能实例化,但能够声明接口的变量,接口变量必须引用实现了这个接口的类的对象
```
x=new Comparable(...);//error
Comparable x;//Ok
x=new Employee(...)//Employee implements Comparable
```
接口可以extends接口,也可以包含常量\
接口中的方法都是public 接口的字段都是public static final\
类实现多个接口中间用逗号隔开

### 6.1.3 接口与抽象类
抽象类与接口的最大不同在于,抽象类是类,类不能够多重继承
### 6.1.5 默认方法
默认方法即在接口的方法加关键字default,默认方法可以调用其他方法
```
public interface Collection
{
    int size();
    default boolean isEmpty(){
        return size()==0;
    }
}
```
默认方法的重要用法在于“接口演化”,以Collection接口为例,Bag类实现它
```
public class Bag implements Collection
```
后来又为这个接口添加了一个stream方法,假设该方法不是默认方法,那么Bag类将不能通过编译,因为它没有实现新方法。若该新方法是默认方法,则在一个Bag实例上调用stream方法将调用Collection.stream方法
### 6.1.6 解决默认方法冲突
先在一个接口声明一个默认方法,又在超类或另一个接口定义同样的方法。冲突规则如下:
1. 超类优先 如果超类提供了一个具体方法,同名且具有相同类型参数的默认方法会被忽略。
2. 接口冲突,程序员需要二选一,即使一个是默认方法一个不是,如果两个方法都非默认(没有具体实现),则不存在冲突。类可以实现方法,也可以不实现(抽象类)
```
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();
    }
}

```
### 6.1.7 接口与回调
回调指触发某个事件应该采取的动作
### TimerTest.java
```
package javaSX.timer;

import java.awt.*;
import java.awt.event.*;
import java.time.*;
import javax.swing.*;

public class TimerTest {
    public static void main(String[] args) {
        var listener = new TimerPrinter();
        var timer = new Timer(1000, listener);
        timer.start();
        JOptionPane.showMessageDialog(null, "Quit Program?");
        System.exit(0);
    }
}

class TimerPrinter implements ActionListener {
    public void actionPerformed(ActionEvent event) {
        System.out.println("Tone time is:" + Instant.ofEpochMilli(event.getWhen()));
        Toolkit.getDefaultToolkit().beep();
    }
}

```
### 6.1.8 Comparator接口
现在假设我们希望按长度递增的顺序对字符串进行排序,而不是按字典顺序进行排序。\
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();
    }
}
var comp=new LengthComparator();
String test={"Peter","Mary","Bob"};
Arrays.sort(test,comp);
```
数组变为{"Bob","Mary","Peter"}
### 6.1.9 对象克隆
```
var ori =new Employee("Mary",6000);
Employee copy=ori;
copy.raiseSalary(1000);//ori 一样会提高,因为原变量和副本都是同一个对象的引用
```
使用clone可以进行浅拷贝,像salary这种数值或基本类型字段,拷贝没有问题。但如果对象包含子对象的引用,拷贝字段就会得到相同子对象的另一个引用。这样一来,原对象和克隆对象依然会共享一些信息。
```
Employee copy=ori.clone();
copy.raiseSalary(1000)
```
Cloneable接口没有指定clone方法,只作为一个标记接口(标记接口不包含任何方法,只允许在类型查询中使用instanceof)。对象对于克隆很固执,如果一个对象请求克隆,但是没有实现这个接口,就会生成一个检查型异常。下面为深拷贝
```
class Employee implements Cloneable
{
    public Employee clone() throws CloneNotSupportException
    {
        Employee cloned=(Employee)super.clone();
        cloned.hireDay=(Date)hireDay.clone();
        return cloned
    }
}
```
如果在一个对象上调用clone,但这个对象的类并没有实现Cloneable接口,Object类的clone方法就会抛出CloneNotSupportException

所有数组类型都有一个公共的clone方法,而非受保护的,可用来建立不会对原始数组造成影响的副本
```
int[] num={1,2,3,4};
int[] copy=num.clone();
copy[2]=12;//不会改变num数组
```
### CloneTest.java
```
package javaSX.clone;

public class CloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        var original = new Employee("John", 6000);
        original.setHireDay(2000, 1, 2);
        Employee copy = original.clone();
        copy.raiseSalary(10);
        copy.setHireDay(2002, 12, 11);
        System.out.println("original=" + original);
        System.out.println("copy=" + copy);
    }
}

```
### Employee.java
```
package javaSX.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 String getName() {
        return name;
    }

    public double getSalary() {
        return this.salary;
    }

    public void raiseSalary(double percent) {
        double raise = this.salary * percent / 100;
        this.salary += raise;
    }

    public void setHireDay(int year, int month, int day) {
        Date newHireDay = new GregorianCalendar(year, month - 1, day).getTime();
        hireDay.setTime(newHireDay.getTime());
    }

    public String toString() {
        return "Employee[name=" + name + ", salary=" + salary + ", hireDay=" + hireDay + "]";
    }
}

```
## 6.2 lambda表达式
### 6.2.1 为什么引入lambda
lambda是一个可传递的代码块 
### 6.2.2 lambda语法
为实现比较字符串长短而不是首字母
```
(String first,String second)
    -> first.length() - second.length();
```
也可以加入{}
```
(String first,String second)-> 
    {
        if(first.length()==second.length()) return 1;
        else return 0;
    }
```
### 6.2.3 函数式接口
对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式,这种接口称为函数式接口。

下面展示如何转换为函数式接口,考虑Arrays.sort()方法。他的第二个参数需要一个Comparator实例,Comparator就是只有一个方法的接口。所以可以提供一个lambda表达式。
```
Arrays.sort(words,
    (String first,String second) -> first.length()-second.length());
```
Arrays.sort方法会接收实现了```Comparator<String>```的某个类的对象。在这个对象上,调用compare方法会执行这个lambda方法表达式的体。最好把lambda表达式看做一个函数,而不是对象。

lambda表达式可以转换为接口
```
var timer=new Timer(1000, event ->//event参数省略了类型(编译器根据上下文推导) 并且因为只有一个参数所以省略了括号
    {
        System.out.println("At the tone, the time is"
            +Instant.ofEpochMilli(event.getWhen()));
        Toolkit.getDefaultToolkit().beep();
    });
```
### 6.2.4 方法引用 
有时,lambda涉及一个方法。假如你希望只要出现一个定时器时间就打印这个事件对象。
```
var timer=new Timer(1000, event->System.out.println(event));
```
也可以直接把println方法传递到Timer构造器
```
var Timer=new Timer(1000, System.out::println);
```
表达式System.out::println是一个方法引用,他指示编译器生成一个函数式接口的实例,覆盖这个接口的抽象方法来调用给定的方法。在这个例子中,会生成一个ActionListener,它的actionPerformed(ActionEvent e)方法要调用System.out.println(e);

要想使用::运算符分割方法名与对象或类名,主要分3种情况
1. object::instanceMethod

    方法引用等价于向方法传递参数的lambda表达式,对于System.out::println,对象是System.out,所以方法表达式等价于x->System.out.println(x)
2. Class::instanceMethod

    第一个参数会成为方法的隐式参数。例如String::compareToIgnoreCase等同于(x,y)->x.compareToIgnoreCase(y)
3. Class::staticMethod

    所有参数都传递到静态方法,Math::pow等价于(x,y)->Math.pow(x,y)

只有当lambda表达式的体只调用一个方法而不做其他操作时,才能把lambda表达式重写为方法引用。
```
s->s.length==0//这里是一个方法调用,但还有一个比较,不能使用方法引用
```
可以在方法引用中使用this参数,例如this::equals等同于x->this.equals(x),使用super也是合法的。
### 6.2.5 构造器方法引用
```
Person[] people=stream.toArray(Person[]::new);
```
toArray方法调用这个构造器来得到一个有正确类型的数组,然后填充并返回这个数组
### 6.2.6 变量作用域
若希望能够在lambda表达式中访问外围方法或类中的变量,lambda会捕获外围变量,但只能捕获值不会改变的变量,变量在初始化后就不会赋新值,

## 6.3 内部类
内部类是定义在一个类中的类,内部类可以对本包的其他类隐藏,内部类方法可以访问定义这个类的作用域中的数据,包括私有数据。
```
public class TalkingClock
{
    private int interval;
    private boolean beep;
    public TalkingClock(int interval, boolean bepp){...}
    public void start()
    public class TimerPrinter implementsActionListener
    {
        public void actionPerformed(ActionEvent event)
        {
            System.out.println("Time is"+Instant.ofEpochMilli(event.getWhen()));
            if (beep) Tookit.getDefaultToolkit().beep();
        }
    }
}
```
每个TalkingClock不意味着都有一个Timeprinter实例字段,TimePrinter对象是由TalikingClock类的方法构造的。内部类声明的所有静态字段必须是final
### 6.3.4 局部内部类
可以在一个方法中局部定义一个类。声明局部类不能有访问说明符(public...)局部类只能在声明的方法里使用。
### 6.3.5 由外部方法访问变量
与其他内部类相比,局部内部类不仅能访问外部类字段,还可以访问局部变量(一经赋值不能改变的)。
### 6.3.6 匿名内部类
假如只想创建匿名内部类的一个对象,可以使用匿名内部类
### 6.3.7 静态内部类
当使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类有外围类对象的一个引用,可以将内部类声明为static



java核心技术卷1基础知识 第六章接口 lambda表达式 内部类

原文:https://www.cnblogs.com/suijiguocheng/p/14608251.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!