【自学】Java核心技术卷1_6接口、lambda表达式、内部类、代理

  • 接口:描述类具有什么功能,并不给出每个功能的具体实现(一个类可以实现一个或多个接口,并在需要接口的地方随时使用实现了相应接口的对象)
  • lambda表达式:表示在将来某个时间点执行的代码块
  • 内部类:定义在另一个类的内部,内部类的方法可以访问包含它的外部类的域。主要用于设计具有相互协作关系的类集合
  • 代理:一种实现任意接口的对象,可以用来构建系统级的工具

6.1 接口

  • 接口中的所有方法自动地属于public,在接口中声明方法时不必提供关键字public,但是类实现接口时,必须把方法声明为public
  • 类实现接口的步骤:1)将类声明为实现(implements)给定的接口;2)定义接口中的所有方法
  • 接口不是类,没有实例,即不能用new实例化接口,可以将接口看成没有实例域的抽象类
  • 可以声明接口的变量,用于引用实现了接口的类的对象
  • 接口可以被扩展(extends),允许存在多条从具有较高通用性的接口道较高专用性的接口的链
  • 接口可以提供多重继承的大多数好处,同时还能避免多重继承的复杂性和低效性
  • 接口中可以定义常量,即接口中的域将被自动设为public static final
  • 可以为接口方法提供默认实现,用default标记方法,默认方法可以调用任何其他方法

解决默认方法冲突:

  • 一个类扩展了一个超类,且实现了一个或多个接口:超类提供了一个具体方法,则接口的同名同参的默认方法会被忽略(超类优先)
  • 一个类实现了两个(或多个)接口,一个接口提供了默认方法,另一个接口提供了同名同参(不论是否默认)的方法,则会冲突报错,这个类需要覆盖实现这两个方法解决二义性;如果这两个接口都没有为同名同参方法提供默认实现,则不存在冲突,这个类可以实现也可以不实现这个方法(不实现则为抽象类)

6.2 接口示例

  • 回调:一种程序设计模式,用于指出某个特定事件发生时应该采取的动作
  • Cloneable接口:指示一个类提供了安全的clone方法
  • 拷贝和克隆
  • clone方法是Object的protected方法,默认的克隆操作是“浅拷贝”,没有克隆对象中引用的其他对象,所以必须重新定义clone方法来建立“深拷贝”,同时克隆子对象,即使clone的默认浅拷贝能满足要求,还是要实现Cloneable接口,将clone重新定义为public,再调用super.clone(),指定返回类型。最好在重新定义clone方法是声明异常throws CloneNotSupportedException,这样就允许这个类的子类在不支持克隆时抛出异常
  • Cloneable是标记接口(tagging/marker interface),用于确保一个类实现一个或一组特定的方法,但标记接口不包含任何方法,允许在类型查询中使用instanceof(if obj instanceof Cloneable)

6.3 lambda表达式

  • lambda表达式是一个可传递的代码块,可以在以后执行一次或多次
  • Java可以通过构造一个有 包含需要传递的代码块的 方法的对象来传递代码段
  • 代码对比:传递有包含代码段方法的对象                               方法的lambda表达式
  •                                   
  • lambda表达式形式:(参数)->{表达式}
  • 即使没有参数,仍然要提供空括号,就像无参数方法
  • 如果可以推导出lambda表达式的参数类型,则可以忽略其类型
  • Comparator<String> comp=(first,second)->{first.length()-second.length();} //用lambda赋值
  • 若方法只有一个参数,而且参数类型可以推导得出,则可以省略小括号
  • ActionListener listener=event->System.out.println(“The time is”+new Date()); //event无括号
  • 无需指定lambda表达式返回类型,因为可以从上下文推导得出
  • 如果一个lambda表达式只在某些分支返回一个值,而在另外的分支不返回值,则不合法:(int x)->{if(x>=0)  return 1;}  //error

函数式接口

  • (任何)只有一个抽象方法的接口,需要这种接口的对象时(例如函数参数等),可以提供一个lambda表达式(在底层是通过实现这个接口的类的对象调用这个抽象方法时,会执行所提供的lambda表达式)

方法引用——已经有现成的方法可以完成想要传递到其他代码的某个动作

  • 遇到重载方法时,编译器会从上下文找
  • 可以在方法引用中使用this参数:this::equals <=> x->this.equals(x)
  • super::instanceMethod:以this为目标,调用给定方法的超类版本

构造器引用 Class::new

  • 将方法引用的方法名改为new即可,编译器根据上下文判断选哪一个构造器。
  • 可以用数组类型建立构造器引用:int[]::new <=> x->new int[x]
  • 将数组构造器引用传入返回Object数组的方法后,此方法可以调用这个构造器得到正确类型的数组返回

lambda表达式的作用域

  • lambda表达式可以捕获外围作用域中的变量的值,在确保这个变量的值是明确定义且不会改变的(final)。
  • lambda表达式的体与嵌套块有相同的作用域,命名冲突和覆盖规则仍然适用:在lambda表达式中声明一个与局部变量同名的参数或局部变量是不合法的
  • 在lambda表达式使用this时,是指创建这个lambda表达式的方法的this参数

处理lambda表达式

  • 定义参数包含函数式接口变量的方法,然后调用此方法时,给这个函数式接口变量提供lambda表达式,就可以通过此变量执行lambda表达式主体
  • 如果自己设计函数式接口,则可以用@FunctionalInterface注解标记这个接口:一来可以在此接口被增加多余非抽象方法是,让编译器报错,二来在javadoc页会指出此接口是函数式接口

Comparator接口

  • 比较器(comparator):实现了Comparator接口的类的实例
  • Comparator接口包含很多方便的静态方法来创建比较器
  • Comparator接口的静态方法可以用于lambda表达式或方法引用
  • 静态comparing()方法采取“key extractor”机制将类型T映射为一个可比较的类型。
  • 可以对要比较的对象应用comparing()方法,然后对返回的key进行比较,例如对Person对象的数组person按名字排序对象:

Arrays.sort(people,Comparator.comparing(Person::getName));

  • 比较器还可以与thenComparing()方法串用:

Arrays.sort(people,Comparator.comparing(Person::getName).thenComparing(Person::getFirstName));

  • 可以为comparing()和thenComparing()提取的key指定比较器(比较方法)。如指定按照提取的人名长度排序:

Arrays.sort(people,Comparator.comparing(Person::getName,(s,t)->Integer.compare(s.length(),t.length())));

  • 或者利用comparing(),thenComparing()方法的变体形式(可以避免基础类型装箱):

Arrays.sort(people,Comparator.comparingInt(p->p.getName().length()));

6.4 内部类

  • 内部类:定义在另一个类中的类
  • 局部内部类:定义在另一个类的方法中的内部类
  • 匿名内部类:只构建一个实例,没有名字的内部类
  • 静态内部类:内部类不需要引用外围类的对象时,可以声明为static(只有内部类可声明为static)

内部类特性:

内部类的特殊语法规则:

  • 对外围类的引用:OuterClass.this
  • 当内部类声明为private类时,只有外围类的方法能够构造内部类的实例对象。(只有内部类可以是private类,常规类只有包可见性或公用可见性)
  • 当内部类为public类时,外围类的方法和实例都可以构造内部类的实例对象。
  • 在外围类的作用域之外引用内部类:OuterClass.InnerClass
  • 内部类中声明的所有静态域必须是final,保证静态域的唯一性
  • 内部类不能有static方法,即使允许有静态方法,也只能访问外围类的静态域和方法
  • 内部类是一种编译器现象,与虚拟机无关。编译器会把内部类翻译成用“OuterClass$InnerClass”类名的常规类文件,而虚拟机对此一无所知。编译器为了引用外围类,生成了一个附加的实例域“this$0”

局部内部类:

  • 只在某个方法中创建某类对象
  • 局部类不能用private或public修饰,其作用域被限定在声明这个局部类的块中
  • 局部类对外界外部时间可以完全隐藏,即使外围类的方法也不能访问,除了其所在的方法块。
  • 局部类不仅可以访问其外围类,还可以访问局部变量(final的局部变量)

匿名内部类:

  • 只创建某个类的一个对象,不必为类命名
  •  
  • 因为匿名类没有类名,而构造器又必须和类名相同,所以匿名类不能有构造器,只能将构造器参数传递给超类构造器,如果匿名类实现的是接口,则没有构造参数()不过还是要提供括号

双括号初始化:invite(new ArrayList<String>() {{add(“Harry”); add(“Tony”);}}); //构建并初始化匿名数组列表传递给invite()

  • 外层{}:new ArrayList<String>(){};建立ArrayList的匿名子类;
  • 内层括号是一个对象构造块(即第四章的初始化块,在构造类对象的时候执行)

静态内部类:

  • 只有内部类可声明为static
  • 若内部类的对象是在静态方法中构造的,则必须使用静态内部类
  • 与常规内部类不同:静态内部类可以有静态域和方法。(前面提到:内部类不能有static方法,即使允许有静态方法,也只能访问外围类的静态域和方法)
  • 在接口中声明的内部类自动成为static和public类

6.5 代理

  • 代理可以在运行时创建一个实现了一组给定接口的新类,在编译时无法确定需要实现哪个接口时才有必要使用。
  • 代理类具有指定接口所需要的全部方法和Object类的全部方法(toString(),equals()等),不过不能在运行时重写这些方法,需要通过一个“调用处理器(invocation handler)”,即在调用代理对象的方法时,调用处理器的invoke()方法也被调用,并向其传递Method对象和原始的调用参数,由调用处理器给出处理调用的方式
  • 调用处理器:实现了InvocationHandler接口的类的对象
  • InvocationHandler接口只有一个方法:

代理类:

  • 所有代理类都扩展于Proxy类
  • 在运行中被创建,创建后就成了常规类
  • 代理类只有一个实例域:调用处理器。所需要的任何附加数据都必须存储在调用处理器中
  • 代理类一定是 public 和 final。如果代理类实现的所有接口都是 public,代理类就不属于个特定的包;否则,所有非公有的接口都必须属于同一个包,同时代理类也属于这个包。
  • 检测一个Class对象是否代表一个代理类:Proxy类的isProxyClass()方法

使用代理和调用处理器的示例:

  • 定义调用处理器:即定义一个实现InvocationHandler接口的类
  • 创建代理对象:Proxy类的newProxyInstance()方法
  • 使用代理对象对二分查找进行跟踪:
  • binarySearch(elements,key)按照if(elements[i].compareTo(key)<0)的方式调用:
  • 由于elements填充了代理对象,所以compareTo()调用了调用处理器TraceHandler类的invoke方法,打印出来方法名和参数,之后用包装好的Integer对象调用原始compareTo()方法
  • System.out.println(elements[result]);
  • println()方法也是调用代理对象的toString() (前面说了代理类具有Object类的toString()方法)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值