Java核心技术——第四章:接口、lambda表达式

  • 接口(interface):这种技术主要用来描述类具有什么功能,而并不给出每个功能的具体实现。
  • lambda表达式:是一种表示可以在将来某个时间点执行的代码块的简介方法。
  • 内部类(inner class):捏不累定义在另外一个类的内部,其中的方法可以访问包含它们的外部类的域,主要用于设计具有相互协作关系的类集合。

接口

接口概念

接口不是类,而是对类的一组需求描述,比如:Arrays类中的sort方法可以对对象数组进行排序,但要满足一个前提:对象所属的类必须实现了Comparable接口。

接口代码:

public interface Comparable<T>
{
	int compareTo(T other);
}

任何实现Comparable接口的类都需要包含compareTo方法。

  • 接口中的所有方法都自动的属于public,不必提供关键字
  • 接口可以包含多个方法
  • 接口中可以定义常量,变量
  • 接口中绝不能含有实例域,不能构造接口对象
  • 可以在接口中提供简单方法,但这些方法不能引用实例域——接口中没有实例
  • 提供实例域和方法实现的任务应该由实现接口的那个类完成

为了让类实现一个接口,需要以下步骤

  1. 将类声明为实现给定的接口
class Employee implements Comparable
  1. 对接口中的所有方法进行定义

这里的Employee类需要提供compareTo方法,假设根据雇员薪水进行比较
在Employee类中:

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

这里使用了静态方法Double.compare,如果第一个参数小于第二个参数,返回一个负值,如果二者相等返回0, 否则返回一个正值。

注意:声明接口时,CompareTo方法没有声明public,是因为所有方法默认时public,但实现接口时,必须把方法声明public,否则编译器将认为这个方法的访问属性是包可见(默认访问属性),编译器会给出提供更严格访问权限的警告信息

接口和抽象类

Java不支持多继承,每个类只能扩展一个类,假设Employee类已经扩展了person类,就不能再扩展第二个类了,但每个类可以实现多个接口,接口可以提供多重继承的大多数好处,同时可以避免多种继承的复杂性和低效性。

默认方法

可以为接口方法提供一个默认实现,必须用default修饰符标记这样一个方法。在有些情况下有用。

解决默认方法冲突

如果在一个接口中将一个方法定义为默认方法,然后又在超类或另一个接口定义了同样的方法:

  1. 超类优先,如果超类提供了一个具体方法,同名而且有相同参数类型的默认方法会被忽略
  2. 接口冲突,如果一个超接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型相同的方法,必须覆盖这个方法来解决冲突。

对象克隆

Cloneable接口,这个接口提供了安全的clone方法,如果希望copy是一个新对象,它的初始状态与original相同,但之后各自会有自己的不同的状态,这种情况就可以使用clone方法。

clone方法是Object的一个protected方法,不能直接调用这个方法,原因:Object类对于要拷贝的对象一无所知,只能逐个区域进行拷贝,如果对象中的数据域都是数值或其他基本数据类型, 拷贝没有问题,但是如果对象包含子对象的引用,拷贝域就会得到相同子对象的另一个引用, 这样原对象和克隆对象会共享一些信息。

浅拷贝

默认的克隆操作没有克隆对象引用的其他对象,如果原对象和浅克隆对象共享的子对象是不可变的,这种共享就是安全的;如果子对象属于一个不可变类,如String,或者在生命周期中,子对象一直包含不变的常量,没有更改器去更改它,这种情况也是安全的。
但是,子对象通常是可变的,必须重新定义clone方法来建立一个深拷贝,同时克隆所有子对象,对于每个类,需要确定

  1. 默认的clone方法是否满足要求
  2. 是否可以在可变的子对象上调用clone来修补默认的clone方法
  3. 是否不该使用clone
  4. 实现Cloneable接口
  5. 重新定义clone方法,并指定public修饰符

子类只能调用受保护的clone方法来克隆它的对象,必须重新定义clone为public才能允许所有方法克隆对象。Cloneable接口的出现与接口的使用没有关系,它是Java提供的一组标记接口之一,只是作为一个标记,Cloneable接口的用途就是确保一个类实现一个或一组特定的方法,标记接口不包含任何方法,唯一的作用就是允许在类型查询中使用instanceof;

即使clone的默认实现(浅拷贝)能满足要求,还是需要实现Cloneable接口,将clone重新定义为public,在调用super.clone()

class Employee implements Cloneable
{
	//提高访问权限到public
	public Employee clone() throw CloneNotSupportException
	{
	//改变返回值类型
		return (Employee)super.clone();
	}
}

深拷贝

只将方法变为public还不够,要建立深拷贝,需要拷贝可变的实例域。
如果在一个对象上调用clone,但这个对象没有实现Cloneable接口,Object类的clone方法就会抛出一个CloneNotSupportedException。

lambda表达式

传入代码来检查一个字符串是否比另一个字符串短
first.length() - second.length()
first和second都是字符串,还需指定它们的类型

(String first, String secong)
	-> first.length() - second.length()

这就是lambda表达式。
参数、箭头(->)、表达式,如果代码要完成的计算无法放在表达式中,就可以像写方法一样放在{}中,并包含return语句

(String first, String second) ->
{
	if(first.length() < second.length()) return -1;
	else if (first.length() > second.length()) return 1;
	else return 0;
}
  • 即使表达式没有参数,也要提供括号,就像无参构造方法一样
    () -> {for (int i = 100; i>=0; i--) System.out.println(i); }
  • 如果可以推导出表达式的参数类型,就可以忽略其类型
  • 如果方法只有一个参数,而且这个参数的类型可以推导出,就可省略小括号
  • 无需指定表达式的返回类型,lambda表达式的返回值会根据上下文推导得出

函数式接口

对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式,这种接口称为函数式接口。

为什么函数接口必须有一个抽象方法,不是接口中的所有方法都是抽象的吗?
接口完全可能重新声明Object类的方法,如toString、clone,这些声明可能会使方法变的不再是抽象的,Java SE8中,接口可以声明非抽象方法。

方法引用

有时可能已经有现成的方法完成你想要传递给其他代码的某个动作,如:加入希望只要出现一个定时器事件就打印这个事件对象
Timer t = new Timer(1000, event -> System.out.println(event))
如果直接将println方法传递到Timer构造器就更好了
Timer t = new Timer(1000, System.out::println)
System.out::println 是一个方法引用,等价于表达式x -> System.out.println(x)

例二:加入想要对字符串排序而不考虑大小写,可有传递以下方法表达式
Arrays.sort(strings, String::compareToIgnoreCase)

用::操作符分隔方法名与对象或类名,主要有三种情况

  1. object::instanceMethod
  2. Class::staticMethod
  3. Class::instanceMethod
    前两种情况,方法引用等价于提供方法参数的lambda表达式
    例如:System.out::println 等价于 x -> System.out.println(x)
    Math::pow 等价于 (x,y) ->Math.pow(x,y)
    第三种情况, 第一个参数会成为方法的目标,
    例如:String::compareToIgnoreCase 等价于 (x, y) -> x.compareToIgnoreCase(y)

可以在方法引用中使用this参数, this::quals 等同于 x -> this.equals(x), 使用super也是合法的,
super::instanceMethod
使用this作为目标,会调用给定方法的超类版本。
具体双冒号的理解:双冒号的理解

构造器引用

构造器引用和方法引用很相似, 只不过方法名为new, 例如:Person:: new是Person构造器的一个引用, 具体引用哪个构造器取决于上下文。
可以用数组类型建立一个构造器引用, 例如:int[]::new是一个构造器引用,他有一个参数,即数组的长度,等价与lambda表达式 x -> new int[x]

处理lambda表达式

lambda表达式的重点就是延迟执行, 希望以后再执行的原因很多,如

  • 在一个单独额线程中执行代码
  • 多次运行代码
  • 在算法的合适位置运行代码
  • 某种情况时运行代码
  • 只有必要时才运行代码

内部类

定义在另一个内部的类,使用内部类的原因

  • 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。
  • 内部类可以对同一个包中的其他类隐藏起来。
  • 想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。
    编译器会把内部类翻译成用$分隔外部类名与内部类名的常规文件, 如TalkingClock类内部的TimdePrinter类会被翻译成文件TalkingClock $ TimdePrinter

局部内部类

如果一个内部类只在一个方法中创建这个类型的对象时使用了一次,可以在方法中定义一个局部类,局部类不能用public或private声明,它的作用域被限定在生命这个局部类的块中。局部内部类有一个优势:对外部完全隐藏,即使外围类中的其他代码也不能访问。除了创建的方法以外没有任何方法知道这个类的存在。
局部内部类还有一个优点:它不仅能访问包含它们的外部类,还可以访问局部变量,不过这些局部变量必须是final,一旦被赋值就不会改变。

匿名内部类

将局部内部类的使用在深入一步,假如只创建这个类的一个对象,就不必命名了,称为匿名内部类。

静态内部类

有时使用一个内部类只为了把类隐藏在另外一个类的内部,并不需要内部类引用外围类对象,因此可以将内部类声明为static,以便取消产生的引用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值