接口
接口用来描述类应该做什么,不指定具体应该怎么做,一个类可以实现一个或多个接口
接口概念
如果你的类符合某个特定接口,我就会履行这项服务
接口中的所有方法自动是public方法,在接口中声明方法时,不必提供关键字public
接口可以有多个方法,不可以有实例字段
提供实例字段和方法实现的任务应该有实现接口的哪个类来完成,可以将接口看作没有实例字段的抽象类
让类实现一个接口:
- 将类声明为实现给定的接口,implements,java强类型语言
- 对接口中的所有方法提供定义
package com.fu.javatec.chapter06;
import java.util.Arrays;
public class Demo01 { }
class EmployeeSortTest{
public static void main(String[] args) {
Employee[] staff = new Employee[3];
staff[0] = new Employee("H_H",35000);
staff[1] = new Employee("C_C",75000);
staff[2] = new Employee("T_T",38000);
Arrays.sort(staff);
for (Employee e : staff) {
System.out.println("name=" + e.getName() + ",salary=" + e.getSalary());
}
}
}
class Employee implements Comparable<Employee>{
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
public void raiseSalary(double byPercent){
double raise = salary * byPercent / 100;
salary += raise;
}
public int compareTo(Employee other){
return Double.compare(salary,other.salary);
}
}
接口的属性
接口不是类,不能new对象,能声明变量
接口变量必须引用实现接口的类对象
用instanceof检查对象是否实现接口
可以扩展接口,让接口继承接口
接口方法自动public 字段自动public static final
有些接口只定义常量,不定义方法
每个类只能继承一个超类,可以实现多个接口
接口和抽象类
使用抽象类表示通用属性存在问题,一个类只能继承(扩展)一个类,接口不会有这种问题
默认方法
接口方法提供一个默认实现 default修饰符标记
public interface Comparable<T>{
default int compareTo(T other){return 0;}
}
这实际没有太大的用处,知道即可
默认方法重要用法: 接口演化
解决默认方法冲突
-
超类优先(类优先,超类优先原则)
-
接口冲突
public class Test implements A,B{
@Override
public void a() {
A.super.a();
}
}
interface A{default void a(){}}
interface B{default void a(){}}
接口与回调
回调是一种常见的程序设计模式,指定某个特定事件发生时应该采取的动作
例如:按下鼠标,播放▶音乐
定时器和动作监听器的具体使用
package com.fu.javatec.chapter06;
import javax.swing.*;
import java.awt.*;
import java.time.*;
import java.awt.event.*;
public class Demo02 {
}
class TimerTest{
public static void main(String[] args) {
TimePrinter listener = new TimePrinter();
Timer timer = new Timer(1000, listener);
timer.start();
JOptionPane.showMessageDialog(null,"Quit program?");
System.exit(0);
}
}
class TimePrinter implements ActionListener{
@Override
public void actionPerformed(ActionEvent event) {
System.out.println("At the tone, the time is " + Instant.ofEpochMilli(event.getWhen()));
Toolkit.getDefaultToolkit().beep();
}
}
Comparator接口
比较器
利用lambda有奇效
对象克隆
clone();
初始状态相同,之后他们就会有自己不同的状态
(浅拷贝)浅克隆对象和原对象共享子对象
(深拷贝)深克隆会克隆所有子对象
对于一个类需要确定:
- 默认的clone方法是否满足需求
- 是否可以在可变的子对象上调用clone来修补默认的clone方法
- 是否不该使用clone
实际上第三个选项时必选项,选择第一个和第二个选项,类必须:
- 实现Cloneable接口
- 重新定义clone方法,指定public访问修饰符
package com.fu.javatec.chapter06;
import java.util.Date;
import java.util.GregorianCalendar;
public class Demo03 { }
class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
Employee_ original = new Employee_("J", 50000);
original.setHireDay(2000,1,1);
Employee_ copy = original.clone();
copy.raiseSalary(10);
copy.setHireDay(2002,12,31);
System.out.println("original=" + original);
System.out.println("copy=" + copy);
}
}
class Employee_ implements Cloneable{
private String name;
private double salary;
private Date hireDay;
public Employee_(String name, double salary) {
this.name = name;
this.salary = salary;
hireDay = new Date();
}
@Override
protected 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;
}
@Override
public String toString() {
return "Employee_{" +
"name='" + name + '\'' +
", salary=" + salary +
", hireDay=" + hireDay +
'}';
}
}
Lambda表达式 λ
Lambda 表达式采用一种简洁的语法定义代码块,实现对代码块的有效处理
Lambda是一个可传递的代码块,可以执行一次或多次
像写方法一样,把代码放在{}中,包含显式的return语句
lamba表达式没有参数,仍然要提供空括号
可以推导一个lambda表达式的参数类型,可以忽略其类型(含泛型<>)
无需指定lambda表达式的返回类型,lambda表达式返回类型会由上下文推导出
package com.fu.javatec.chapter06;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;
public class Demo04 {
public static void main(String[] args) {
String [] planets = new String[]{"Mercury","Venus","Earth","Mars","Jupiter","Saturn","Uranus","Neptune"};
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));
Timer timer = new Timer(1000, event -> System.out.println("The time is " + new Date()));
timer.start();
JOptionPane.showMessageDialog(null,"Quit program?");
System.exit(0);
}
}
函数式接口
java有很多封装代码块的接口,对于只有一个抽象方法的接口,需要这种接口的对象,就可以提供一个lambda表达式,这种接口叫 函数式接口
接口可以声明非抽象方法,接口可以重新声明Object类方法,函数式接口必须有一个抽象方法
方法引用
lambda表达式设计一个方法
Timer timer = new Timer(1000,event -> System.out.println(event));
Timer timer = new Timer(1000,System.out::println);
方法引用也不是一个对象,为一个类型为函数式接口的变量赋值时会生成一个对象
用::运算符分隔方法名与对象或类名
- Object::instanceMethod
- Class::instanceMethod
- Class::staticMethod
方法引用实例 | 等价的lambda表达式 | 说明 |
---|---|---|
separator::equals | x -> separator.equals(x) | 包含一个对象和一个是实例方法的方法表达式,lambda参数作为这个方法的显示参数传入 |
String::trim | x -> x.trim() | 包含一个类和一个实例方法的方法表达式,lambda表达式会成为隐式参数 |
String::concat | (x,y) -> x.concat(y) | 有一个实例方法,有一个显示参数,第一个lambda参数会成为隐式参数,其余参数会传递到方法 |
Integer::valueOf | x -> Integer::valueOf(x) | 包含一个静态方法的方法表达式,lambda参数会传递到这个静态方法 |
Integer::sum | (x,y) -> Integer::sum(x,y) | 另一个静态方法,有两个参数 |
Integer::new | x -> new Integer(x) | 构造器引用 |
Integer[ ]::new | n -> new Integer[n] | 数组构造器引用 |
只有当lambda表达式的体只调用一个方法而不做其他操作时,才能把lambda表达式重写为方法引用
可以在方法引用中使用this参数
this::equals 等同于 x -> this.equals(x) super也可以
构造器引用
方法名为new
例如:Person :: new
有一个String参数的构造器,上下文推导出这是调用一个字符串的构造器
限制:无法构造泛型类型 T 的数组
变量作用域
lambda表达式三个部分:
- 一个代码块
- 参数
- 自由变量的值,指非参数而且不在代码中定义的变量
java中lambda表达式就是一个闭包
lambda只能捕获值不变的变量,如果更爱变量,并发执行多个动作就会不安全
处理lambda表达式
使用lambda表达式重点是 延迟执行
原因:
- 在一个单独线程中运行代码
- 多次运行代码
- 在算法的适当位置运行代码
- 发生某种情况执行代码
- 只在必要时执行代码
再谈 Comparator
Comparator接口有很多静态方法创建比较器,用于lambda表达式和方法引用
去一个键提取器函数,键函数可以返回null,产生比较的比较器
Array.sort(people,Comparator.comparing(Person::getName));
内部类
内部类是定义另一个类中的类
内部类可以对同一个包中的其他类隐藏
内部类方法可以访问定义这个类的作用域中的数据,包括原本私有的数据
内部类访问对象状态
一个内部类方法可以访问自身的数据字段,可以访问创建它的外围类对象数据字段
内部类对象有一个隐式引用,指向创建他的外部类对象,这个引用在内部类的定义中是不可见的
外围类的引用在构造器中设置,编译器会修改所有的内部类构造器,添加一个对应外围类引用的参数
package com.fu.javatec.chapter06;
import javax.swing.*;
import java.awt.*;
import java.time.*;
import java.awt.event.*;
public class Demo05 { }
class InnerClassTest{
public static void main(String[] args) {
TalkingClock clock = new TalkingClock(1000, true);
clock.start();
JOptionPane.showMessageDialog(null,"Quit program?");
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() {
TimePrinter2 listener = new TimePrinter2();
Timer timer = new Timer(interval, listener);
timer.start();
}
public class TimePrinter2 implements ActionListener {
@Override
public void actionPerformed(ActionEvent event) {
System.out.println("At the tone, the time is "+ Instant.ofEpochMilli(event.getWhen()));
if (beep) Toolkit.getDefaultToolkit().beep();
}
}
}
内部类的特殊语法规则
内部类有一个外围类的引用,我们叫他outer
内部类中声明的所有静态字段都是final,初始化一个编译时常量
内部类不能有static方法
内部类是否有用,必要,安全
内部类语法很复杂,内部类是一个编译器现象与虚拟机无关,内部类可以访问外围类私有属性,外部的其他类则不行
内部类有更大的访问权限,天生就比常规类功能更加强大
内部类访问私有数据字段,可能通过外围类所在包中增加的其他类访问那些字段
程序员不可能无意之中获得对类的访问权限,必须可以构建或修改类文件才有可能达到目的
局部内部类
声明局部内部类没有访问修饰符,局部内部类作用域限定在声明这个局部类的块中
优势:对外部世界完全隐藏,其他代码不能访问它
public void start(){
class TimePrinter implements ActionListener{
public void actionPerformed(ActionEvent event){
System.out.println("At the tone, the time is " + Instant.ofEpochMilli(event.getWhen()));
if (beep) Toolkit.getDefaultToolkit().beep();
}
}
TimePrinter listener = new TimePrinter();
timer.start();
}
由外部方法访问变量
局部类另一个优势:访问外部类字段和局部变量
匿名内部类
创建这个类的一个对象,甚至不需要为类指定名字,这个类叫匿名内部类
public void start(int interval,boolean beep){
var listener = new ActionLitener(){
public void actionPerformed(ActionEvent event){
System.out.println("At the tone, the time is " + Instant.ofEpochMilli(event.getWhen()));
if(beep)Toolkit.getDefaultToolkit().beep();
}
};
var timer = new Timer(interval , listener);
timer.start();
}
含义:创建一个类的新对象,这个类实现了ActionListener接口,需要的方法actionPerformerd中定义
匿名内部类要扩展这个类
构造器的名字必须与类名相同,匿名内部类没有类名,匿名内部类不能有构造器
只要内部类实现一个接口,就不能有任何参数,仍要有一组小括号
package com.fu.javatec.chapter06;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.time.*;
public class Demo06 {}
class AnonymousInnerClassTest{
public static void main(String[] args) {
TalkingClock1 clock = new TalkingClock1();
clock.start(1000,true);
JOptionPane.showMessageDialog(null,"Quit program?");
System.exit(0);
}
}
class TalkingClock1{
public void start(int interval,boolean beep){
ActionListener listener = new ActionListener(){
public void actionPerformed(ActionEvent event){
System.out.println("At the tone,the time is " + Instant.ofEpochMilli(event.getWhen()));
if (beep) Toolkit.getDefaultToolkit().beep();
}
};
Timer timer = new Timer(interval, listener);
timer.start();
}
}
双括号初始化
利用内部类语法,构造一个数组列表,传递给一个方法
ArrayList<String> friends = new ArrayList<String>();
friends.add("Harry");
friends.add("Tony");
invite(friends);
等价于:
invite(new ArrayList<String>(){add("Harry");add("Tony");}});
静态内部类
为了把一个类隐藏在另外一个类内部,不需要内部类有外围类对象的一个引用
把内部类声明为static,就不会有引用
静态内部类可以有静态字段和方法,常规类不行
服务加载器
采用一个服务架构的应用,用偶遇开发环境,应用服务器,其他复杂的应用
实现类可以放在任意包中,不一定是服务接口所在的包,每个实现类有一个无参构造器
代理
使用代理可以在运行时创建实现一组给定接口的新类,在编译时无法确定需要实现哪个接口时用这种代理