接口
接口(interface)技术,主要用来描述类具有什么功能,而并不给出每个功能的具体实现。一个类可以实现(implement)一个或多个接口,并在需要接口的地方,随时使用实现了相应接口的对象。
接口可以定义常量,但决不能含有实例域,提供实例域和方法实现的任务应该由实现接口的那个类来完成。在接口中的所有方法都自动地是public,在实现接口时,必须把方法声明为public。为了让类实现一个接口,通常需要下面两个步骤:
- 将类声明为实现给定的接口,使用关键字implements;
对接口中的所有方法进行定义;
1234567891011//接口public interface Comparable{int compareTo(Object other);}//实现class Employee implements Comparable{public int compareTo(Object otherObject){Employee other = (Employee) otherObject;return Double.compare(salary, other.salary);}}java.util.Arrays;
static void sort(Object[] a)
使用mergesort算法对数组a中的元素进行排序,要求数组中的元素必须实现Comparable接口的类,并且元素之间必须是可比较的。
接口的特性
接口不是类,不能使用new运算符实例化一个接口,但可以声明接口的变量。接口变量必须引用实现了接口的类对象。可以使用instanceof检查一个对象是否实现某个特定的接口。
接口也可以被扩展,从而从通用接口变为专用接口。接口中的方法自动被设置为public,接口中的域被自动设为public static final。
类实现多个接口时,使用逗号将实现的各个接口分隔开。
接口与抽象类
由于Java不支持多重继承,因此每个类只能扩展于一个类,但是可以实现多个接口。
默认方法
可以为接口提供一个默认实现,必须用default修饰符标记这样一个方法:
解决默认方法冲突
若在接口中定义默认方法,并在超类或另一接口中定义同样的方法,处理方法如下:
- 超类优先:若超类提供一个具体方法,同名且参数类型相同的默认方法会被忽略;
- 接口冲突:若超接口提供了一个默认方法,另一个接口提供同名且参数类型相同的方法,子类必须覆盖这个方法来解决冲突;
对象克隆
Cloneable接口是Java提供的一组标记接口之一,不包含任何方法,唯一作用就是允许在类型查询中使用instanceof。不同于Comparable等接口通常用于确保一个类实现一个或一组特定的方法。
即使clone的默认(浅拷贝)实现能够满足要求,还是需要实现Cloneable接口,将clone重新定义为public,再调用super.clone()。若要建立深拷贝,需要克隆对象中可变的实例域。
lambda表达式
lambda表达式是一个代码块,以及必须传入代码的变量规范:
lambda表达式形式:参数,箭头(->)以及一个表达式,可以将表达式代码放在{}中,并包含显式的return语句;
12345(String first, String second)->{if(first.length() < second.length()) return -1;else if(first.length() > second.length()) return 1;else return 0;}即时lambda表达式没有参数,仍然要提供空括号:
1()->{for(int i=0; i<100; i++) System.out.println(i);}如果可以推导出lambda表达式的参数类型,可以忽略其类型:
12Comparator<String> comp =(first, second)->first.length()-second.length();如果方法只有一个参数,且参数类型可推导,可以省略小括号:
12ActionListener listener = event->System.out.println("The time is" + new Date());lambda表达式的返回类型不需指定,可以从上下文中推导得出;
- lambda表达式必须在每个分支上都返回一个值;
函数式接口
对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式,这种接口称为函数式接口。
方法引用
使用现成的方法完成想要传递到其他代码的某个动作,用::操作符分隔方法名与对象或类名,等同于lambda表达式的情况;
- object::instanceMethod
- Class::staticMethod
- Class::instanceMethod123456789101112class Greeter{public void greet(){System.out.println("Hello, world!");}}class TimeGreeter extends Greeter{public void greet(){Time t = new Timer(1000, super::greet);t.start();}}
变量作用域
lambda表示式有3个部分:
- 一个代码块;
- 参数;
- 自由变量的值,指非参数而且不在代码中定义的变量;
lambda表达式可以捕获外围作用域中的变量值,且只能引用值不会改变的变量,即在代码块内部或外部都不能改变,这种变量称为最终变量,即初始化之后就不会再为它赋新值。
在lambda表达式中声明一个与局部变量同名的参数或局部变量是不合法的。在lambda表达式中使用this关键字时,时指创建这个lambda表达式的方法的this参数。
处理lambda表达式
使用lambda表达式的重点是延迟执行,希望延迟执行的原因如:
- 在一个单独的线程中运行代码;
- 多次运行代码;
- 在算法的适当位置运行代码(如,排序中的比较操作);
- 发生某种情况时执行代码(如,点击了一个按钮,数据到达,等)
- 只在必要时才运行代码;1234567public interface IntConsumer{void accept(int value);}public static void repeat(int n, IntConsumer action){for(int i=0; i<n; i++) action.accept(i);}repeat(10, i->System.out.println("Countdown: " + (9-i)));
上述代码中,i表示输入参数,在repeat中,每次执行action.accept(i)时才会调用lambda表达式,从而传入对应的i值。
内部类
内部类是定义在另一个类中的类。使用内部类的原因:
- 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据;
- 内部类可以对同一个包中的其他类隐藏起来;
- 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。
使用内部类访问对象状态
内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域。内部类的对象总有一个隐式引用,指向创建它的外部类对象。外围类的引用在构造器中设置,编译器修改了所有内部类的构造器,添加一个外围类引用的参数。只有内部类可以是私有类,而常规类只可以具有包可见性,或公有可见性。
内部类的特殊语法规则
在外围类内引用内部类可表示为OuterClass.this,在外围类外引用内部类可表示为OuterClass.InnerClass。
内部类中声明的所有静态域都必须是final,因为对于每个外部对象,会分别有一个单独的内部类实例,如果这个域不是final的,它可能就不唯一。内部类不能有static方法。
内部类是否有用、必要和安全
内部类是一个编译器现象,编译器将内部类翻译为用$分隔外部类名与内部类名的常规类文件,而与虚拟机无关。
局部内部类
可以在一个方法中定义局部类,不能使用public或private访问说明符声明局部类,它的作用域被限定在声明这个局部类的块中。
由外部方法访问变量
局部类不仅能够访问包含它们的外部类,还能访问局部变量,但是这些局部变量必须事实上为final。
匿名内部类
通用语法格式为:
静态内部类
将内部类声明为static,可以取消产生的引用,从而使内部类不能引用外围类对象。与常规内部类不同,静态内部类可以有静态域和方法。声明在接口中的内部类自动称为static和public类。
代理
====日后再看一遍====