栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > .Net

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

.Net 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

吧Github代码链接: https://github.com/deyou123/corejava.git

第六章 接口、lambda表达式和内部类 6.1 接口 6.1.1 接口概念
  • 接口不是类,而是对类的一组需求描述。
  • ex: Comparable接口
public interface Comparable{
int compareTo(Object other);
}
public interface Comparable{
int compareTo(T other);
}

不带参数,compareTo是Object类型

  • 接口中所有方法自动地属于public,不必提供关键字public

  • 接口中可能包含多个方法。

  • 接口中还可以定义常量。

  • 接口不饿能提供哪些功能:

    • 接口中绝对不能含有实例域
    • Java SE8 之前不能在接口中实现方法;现在已经可以在接口中提供简单方法了;这些方法不能引用实例域。
  • 可以将接口看成是没有实例域的抽象类。

  • 为了让类实现一个接口,需要两个步骤
    1、将类声明为实现给定的接口
    2、对接口中的所有方法进行定义。

  • implements:
    class Employee implements Comparable

public int compareTo(Object otherObj){
Employee other = (Employee)otherObj;
return Double.compare(salary,otherObj.salary);
}
  • 在实现接口时,必须把方法声明为public;否则编译器会提出访问权限的警告信息。
6.1.2 接口的特性
  • 接口不是类,不能new实例
  • 可以生命接口变量;必须引用实现了接口的类对象
  • 可以使用instanceof检查一个对象是否实现了某个特定的接口
    if(obj instanceof Comparable){…}
  • 接口可以被扩展;允许存在多条从具有较高通用性的接口到较高专用性接口的链。
public interface Moveable{
	void move(double x, double y);
}
public interface Powered extends Moveable
{
	double milesPerGallon();
}
  • 接口可以包含常量;接口中的域都将被自动设为 public static final
public interface Powered extends Moveable{
	double milesPerGallon();
	double SPEED_LIMIT = 95;
}
  • Cloneable接口,clone方法可以创建类对象的一个拷贝。
6.1.3 接口与抽象类
  • 每个类只能扩展于一个类
6.1.4 静态方法
  • Java SE8 中,允许存在接口中增加静态方法;只是有违将接口作为抽象规范的初衷。
  • 通常的做法是将静态方法放在伴随类中;ex成对出现的接口和使用工具类,Collection/Collections 或 Path/Paths
  • ex
public interface Path
{
	public static Path get(String first,String... more){
	return FileSystems.getDefault().getPath(first, more);
	}
}
6.1.5 默认方法
  • 可以为接口方法提供一个默认实现;default修饰符
  • 实现接口的程序员只需要为他们真正关系的事件覆盖相应的监听器。
6.1.6 默认方法冲突
  • 接口中一个默认方法,父类或者另一个接口中定义了同样的方法
    1、父类优先,其他默认方法会被忽略。
    2、接口冲突,必须覆盖这个方法来解决冲突。
6.2 接口示例 6.2.1 接口与回调

回调:是一种常见的设计模式;可以指出某个特定事件发生时应该采取的动作;
ex:可以指出按下鼠标或者选择某个菜单项时应该采取什么行动。

public class TimerTest
{  
   public static void main(String[] args)
   {  
      ActionListener listener = new TimePrinter();

      // construct a timer that calls the listener
      // once every 10 seconds
      Timer t = new Timer(1000, listener);
      t.start();

      JOptionPane.showMessageDialog(null, "Quit program?");
      System.exit(0);
   }
}

class TimePrinter implements ActionListener
{  
   public void actionPerformed(ActionEvent event)
   {  
      System.out.println("At the tone, the time is " + new Date());
      Toolkit.getDefaultToolkit().beep();
   }
}
javax.swing.JOptionPane 1.2
static void showMessageDialog(Component parent, Objct message);显示一个包含一条消息和OK按钮的对话框,在parent或屏幕中间。

javax.swing.Timer
Timer(int interval, ActionListenner listener);构造计时器,每个interval通告listenner一次。
void start(); 启动计时器;定时器将调用监听器的actionPerformed
void stop(); 停止计时器;

java.awt.Toolkit 1.0
static Toolkit getDefaultToolkit();:获得默认工具箱
void beep(); 发出铃响。
6.2.2 Comparator接口 6.2.3 对象克隆
  • 默认的克隆操作是 浅拷贝,不会克隆对象中的引用(String、对象等)。

  • 对于每一个类,需要确定:
    1、默认的clone方法是否满足要求;
    2、是否可以在可变的子对象上调用clone来修补默认的clone方法;
    3、是否不该使用clone

  • 对于1、2,类必须:
    1、实现Cloneable接口;
    2、重新定义clone方法,并指定public访问修饰符。

  • Cloneable接口(标记接口:tagging interface)的出现与接口的正常使用并没有关系;只是作为一个标记,指示类设计者了解克隆过程。

  • 用途是确保一个类实现一个或一组特定的方法。

深拷贝例子:

class Employee implements Cloneable{
...
	public Employee clone(0 throws CloneNotSupportedException{
	Employee cloned = (Employee)super.clone();
	cloned.hireDay = (Date) hirDay.clone();
	return cloned;
}
}
  • try-catch适用于final类; 保留throws说明符
  • 当心子类的克隆;不能保证clone满足子类的需求;
  • 所有的数组类型都有一个public的clone方法,可以用这个方法建立一个新数组。
int[] luckyNumbers = {...}
int[] cloned = luckyNumbers.clone();
cloned[5] = 12;
6.3 lambda表达式 6.3.1 为什么引入lambda表达式
  • lambda表达式是一个可传递的代码块;可以在以后执行一次或多次。
  • ex:
Class Worker implements ActionListener{
	public void actionPerformed(ActionEvent event){//do some work;
	}
}
class LengthComparator implements Comparator{
	public int compare(String first,String second){
		return first.length() - second.length();}
}

Arrays.sort(strings, new LengthComparator());
6.3.2 lambda表达式语法

+ex:

(String first, String second)-> first.length() - second.length();
  • 参数,箭头(->)以及一个表达式。
(String first, String second)->
{
	if(first.length() < second.length())return -1;
	else if(first.length() > second.length()) reutrn 1;
	return 0;
}
  • 即使lambda表达式没有参数,仍然要提供空括号:
()->{for(int i = 0; i < 100; i++)System.out.println(i);}
  • 如果可以推导出lambda表达式的参数类型,则可以忽略其类型。
Comparator comp = (first,second)->first.length() - second.length();

first和second都是字符串

  • 如果只有一个参数,并且这个参数类型可以推导得出,可以省略小括号:
ActionListener listener = event->System.out.println("The time is " + new Date());
  • 无需只想lambda表达式的返回类型;lambda表达式的返回类型总是会由上下文推导得出。
(String first,Stirng second)-> first.length()-second.length();
注释
  • 如果lambda表达式只是在某些分支返回一个值,而在另外一个分支不返回值,这是不合法的。
(int x) -> {if(x >= 0) return 1;}//不合法
6.3.3 函数式接口

函数式接口:对于只有一个抽象方法的接口;可以提供一个lambda表达式。

Arrays.sort(words,(first,second)->first.length()-second.length());
  • 在顶层,Arrays.sort会接受实现了Comparator< String >的某个对象;调用compare方法执行这个lambda表达式的体。
  • 可能要高效得多;
  • 最好把lambda表达式看作是一个函数,而不是一个对象。
  • 要接受lambda表达式可以传递到函数式接口。
  • lambda表达式可以转换为接口。
Timer t = new Timer(1000, event->
{
	System.out.println("At the time is" + new Date());
	Toolkit.getDefaultToolkit().beep();
});
  • lambada表达式所能做的也只是能转换为函数值接口。
  • java.util.funciton包中定义了很多分厂通用的函数式接口。
  • Bifunction 描述了参数类型为T和U而且返回类型为R的函数;可以把我们的字符串比较lambda表达式保存在这个类型的变量中:
Bifucntion comp = 
(first,second)->first.length() - second.length();
  • 有用的接口Predicate:
public interface Predicate
{
	boolean test(T t);
}

ArrayList类有一个removelf方法,参数就是一个Predicate;这个接口专门用来传递lambda表达式:
list.removelf(e -> e == null);

6.3.4 方法引用

有时,可能已经有现成的方法可以完成你想要传递到其他代码的某个动作;
例如:希望只要出现一个定时器事件就打印这个事件对象:
Timer t = new Timer(1000, event->System.out.println(event));
但是如果把println方法传递到Timer构造器更好:
Timer t = new Timer(1000, System.out:: println);

表达式System.out::println是一个方法引用(method reference),它等价于lambda表达式 x->System.out.println(x);

再来一个例子:字符串排序
Arrays.sort(strings, String:: compareToIgnoreCase);

要用::操作符分割方法名与对象或类型

  • object::instacneMethod

  • Class::staticMethod

  • Class::instanceMethod

  • 第二种情况,方法引用等价于提供方法参数lambda表达式。

    • System.out::println => x-> System.out.println(x);
    • Math::pow => (x,y)-> Math.pow(x,y);
  • 第三种情况,第1个参数会成为方法的目标。

    • String::compareToIgnoreCase => (x,y)->x.compareToIgnoreCase(y);
  • 如果有多个同名的重载方法,编译器就会从上下文中找出你指的哪一个方法。

  • 可以在方法引用中使用this参数;this::equals => x->this.equals(x).

  • 使用super也是合法的: super::instanceMethod

6.3.5 构造器引用
  • 构造器引用和方法引用很类似,只不过方法名为new。
  • Persion::new 是Persion构造器的一个引用,用哪一个构造器取决于上下文。
  • 假设有一个字符串列表,可以把它转换成一个Persion对象数组,为此要在各个字符串上调用构造器:
ArrayList names = ...;
Stream stream = names.stream().map(Persion::new);
List people = stream.collect(Collectors.toList());
  • map方法会为哥哥列表元素调用Person(String)构造器;如果有多个构造器,编译器会选择有一个String参数的构造器。
  • 数组类型建立构造器引用:int[]:: new =》 x->new int[x].
  • 无法构造泛型类型T的数组。表达式new T[n] 会改为 new Object[n]。
  • 流库利用构造器引用,可以把Person[]::new传入toArray方法:
    Person[] people = stream.toArray(Person::new);
    toArray方法调用这个构造器来得到一个正确类型的数组;然后填充这个数组并返回。
6.3.6 变量作用域

希望能够在lambda表达式中访问外围方法或是类中的变量。

public static void repeatMessage(String text,int delay){
	ActionListener listener = event->{
	System.out.println(text);
	Toolkit.getDefaultToolkit().beep();
	};
	new Timer(delay,listener).start();
}

repeatMessage("Hello", 1000);
  • text变量是repeatMessage方法的一个参数变量。
  • lambda表达式的代码可能会在repeatMessage调用返回很久以后才运行,而那时这个参数变量已经不存在了。

lambda表达式的3个部分:

  • 一个代码块;
  • 参数;
  • 自由变量的值,这是指非参数而且不在代码中定义的变量。

lambda表达式捕获captured变量text;自由变量的值会复制到这个对象的实例变量中。

注释

lambda表达式就是闭包(closure);

  • lambda表达式可以捕获外围作用域中变量的值。
  • 要确保所捕获的值是明确定义的;之恶能引用值不会改变给的变量;
public static void countDown(int start,int delay){
ActionListener listener = event->{
start--; //Error: can't mutate captured variable
Sout(start);
};
}

这个限制的原因:如果在lambda表达式中改变变量,并发执行多个动作时就会不安全。

  • lambda表达式不能引用在外部可能会改变的变量。
for(int i = 0; i < 10; i++){
	ActionListener listener = event->{
	sout(i + " " );//Error
}
}
  • lambda 表达式中捕获的变量必须实际上是最终变量;这个变量初始化之后就不会再为他赋新值。
  • lambda表达式的体与嵌套块有相同的作用域;在lambda表达式中声明与一个局部变量同名的参数或局部变量是不合法的。
  • lambda表达式中同样也不能有同名的局部变量
  • 在一个lambda表达式中使用this关键字时,是指创建这个lambda表达式的方法this参数。
public class Applicaiton{
	public void init(){
		ActionListener listener = event->
		{
			sout(this.toString());
		}
	}
}

表达式this.toString() 会调用Application对象的toString方法;而不是ActionListener实例的方法。

6.3.7 处理lambda表达式

重点是延迟执行(deferred execution);

  • 在一个单独的线程中运行代码:
  • 多次运行代码:
  • 在算法的适当位置运行代码(排序比较操作)
  • 发生某种情况时执行代码 (点击事件)
  • 指在必要时才运行代码。

ex:重复一个动作n次;将这个动作和重复次数传递到一个repeat方法:
repeat(10, ()-> sout("…"));

  • 要接受这个lambda表达式,需要选择一个函数值接口。
public static void repeat(int n, Runnable action){
	for(int i = 0; i < n; i++)aciton.run();
}

调用action.run()时会执行这个lambda表达式。

  • 我们希望这个动作他出现在那一次迭代中;需要选择一个合适的函数值接口:包含一个方法,有一个int参数而且返回类型为void;
public interface IntConsumer{
	void accept(int value);
}

repaet改进版:

public static void repeat(int n, intConsumer action){
	for(int i = 0; isout("CountDown: " + (9 - i));

基本类型int、long、double的34个可能的规范;使用这些特殊规范来减少自动装箱。

提示
  • 最好使用上两表中的接口;假设要编写一个方法来处理满足某个特定条件的文件;对此有一个遗留接口 java.io.FileFilter,最好使用标准的Predicate

  • 大多数标准函数式接口都提供了非抽象方法来生成或合并函数。

    • ex:Predicate.isEqual(a) => a::equals,a为null也能正常工作。
    • 提供了默认方法and、or和negate来合并谓词。Predicate.isEqual(a).or(Predicate.isEqual(b)) 等同 x->a.equals(x) || b.equals(x);
  • 如果设计只有一个抽象方法的接口;可以用@FunctionalInterface注解来标记这个接口。

  • 如果无意中增加了另一个非抽象方法,编译器会产生错误消息。

  • javadoc页里会指出你的接口是一个函数式接口。

6.3.8 再谈 Comparator

  • Comparator接口包含很多方便的静态方法来创建比较器;这些方法可以用于lambda表达式或方法引用。

  • 静态comparing方法去一个”键提取器“函数,将类型T映射为一个可比较的类型。
    Arrays.sort(people,Comparator.comparing(Person::getName));

  • 可以把比较器与thenComparing方法串起来
    Arrays.sort(people,
    Comparator.comparing(Person:: getLastName).
    thenComparing(Person::getFirstName);

  • 根据名字长度排序:
    Arrays.sort(people, Comparator.comparing(Person::getName,
    (s, t)->Integer.compare(s.length(), t.length()));

  • 避免int、long、double的装箱
    Arrays.sort(people, Comparator.comparingInt(p->p.getName().length()));

  • 如果键函数可以返回null,要用到适配器:nullsFirst、nullsLast;可以修改现有的比较器,遇到null值时不会抛出异常,而是将这个值标记为小于或大于正常值。
    ex: Comparator.comparing(Person::getMiddleName(),Comparator.nullFirst(…).

  • nullFirst方法需要一个比较器,这里是比较两个字符串的比较器;

  • Arrays.sort(people, comparing(Person::getMiddleName,nullsFirst(naturalOrder())));

6.4 内部类

内部类:是定义在另一个类中的类
使用内部类:

  • 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。
  • 内部类可以对同一个包中的其他类隐藏起来
  • 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。
6.4.1 使用内部类访问对象状态
  • 一个方法可以调用这个方法的对象数据域。
  • 内部类及可以访问自身的数据域,也可以访问创建它的外围类对象的数据域。
  • 内部类的默认构造
public TimePrinter(TalkingClock clock){
	outer = clock;
}
6.4.2 内部类的特殊语法规则
  • 内部类有一个外围类的引用outer: OuterClass.this; ex: TalkingClock.this.beep

  • 编写内部对象的构造器:outerObject.new InnerClass(construction parameters);
    ex: ActionListener listener = this.new TimePrinter();

  • 外围类的作用域之外,可以这样引用内部类:OuterClass.InnerClass

  • 内部类中生命的所有静态域都必须是final;我们希望一个静态域只有一个实例。

6.4.3 内部类是否有用、必要和安全 6.4.4 局部内部类

可以在方法中定义局部类。

public void start(){
	class TimePrinter implements ActionListener{
	public void actionPerformed(Action event)
	{
	...
}
}
}
  • 局部类不能用public或private访问说明符进行声明
  • 他的作用域被限定在声明这个局部类块中。
  • 可以对外部世界完全的隐藏起来
6.4.5 由外部方法访问变量
  • 局部类不仅能够访问包含他们的外部类,还可以访问局部变量;局部变量为final。
6.4.6 匿名内部类
  • 匿名内部类: 加入之创建这个类的一个对象,就不必命名了;
public void start(int interval, boolean beep)
{
	ActionListener listener = new ActionListener(){
	public void actionPerformed(ActionEvent event){
	sout...
	...
}
};
	Timer t = new Timer(interval,listener);	
	t.start();
}
  • 创建一个ActionListener接口类的新对象,需要实现的方法actionPerformed定义在括号{}内。
new SuperTyep(construction parameters)
{
	inner class methods and data;
}
  • superType可以是ActionListener这样的接口,内部类实现这个接口;SuperType也可以是一个类,内部类扩展他。
  • 匿名内部类不能由构造器。
  • 将构造器参数传递给超类构造器
  • 内部是实现接口的时候,不能有任何构造参数
    new interfaceType(){
    methods and data
    }
  • 如果构造参数的闭小括号后面跟一个开大括号,正在定义的就是匿名内部类。

例子。。。

6.4.7 静态内部类
  • 又是只需要把一个类隐藏在另一个类的内部,并不需要内部类引用外围类对象。
  • 可以将内部类声明为static,以便取消产生的引用。
  • 只有内部类可以声明为static;
  • 静态内部类的对象除了没有对生成他的外围类对象的引用特权外,与其他所有内部类完全一样。
6.5 代理
  • 利用代理可以再运行时创建一个实现了一组给定接口的新类。
  • 只有在编译时无法确定需要实现那个接口时才有必要使用。
  • 对于系统程序设计人员来说,地阿里带来的灵活性十分重要;不感兴趣的可以直接跳过。
6.5.1 何时使用代理
  • 指定接口所要的全部方法
  • OBject类中的全部方法

调用处理器是是吸纳了InvocationHandler接口的类对象;有一个方法:
Object invoke(Object proxy,Method method,Object[] args);

  • 无论何时调用代理对象的方法,调用处理器的invoke方法都会被调用,并向其传递Method对象和原始的调用参数。
  • 调用处理器必须给出处理调用的方法。
6.5.2 创建代理对象

使用Proxy类的newProxyInstancee方法创建代理对象,三个参数:

  • 一个类加载器。
  • 一个Clsss对象数组,每个元素都是需要实现的接口
  • 一个调用处理器

使用代理处于很多原因:

  • 路由对远程服务器的方法调用。
  • 再程序运行期间,将用户接口事件和动作关联起来
  • 为调试、跟踪方法调用

再
例子。。。

6.5.3 代理类的特性
  • 代理类是在程序运行过程中创建的;创建后变成常规类,和其它类没区别。
  • 所有代理类都扩展于Proxy类;一个代理类只有一个实例域–调用处理器;他定义在Proxy的超类中。
  • 所有的代理类都覆盖了Object类中的方法:toString、equals、hashCode;代理方法仅仅调用了调用处理器的invoke。
  • 没有定义代理器的名字;SUn是¥Proxy开头的类名。
  • 对于特定的类加载器和预设的一组接口来说,只能有一个代理类;
  • 代理类一定是public和final;
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/984279.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号