java核心技术卷1::接口、lambda表达式和内部类

接口:

 主要用来描述类具有什么功能,而并不给出每个功能的具体实现。
 一个类可以实现一个或多个接口,并在需要接口的地方,随时使用实现了相应接口的对象。

lambda表达式:

 这是一种表示可以在将来某个时间段执行的代码块的简洁方法。
 使用lambda表达式,可以用一种精巧而简介的方式表示使用回调或变量行为的代码。

内部类:

 内部类定义在另外一个类的内部,其中的方法可以访问包含它们的外部类的域。
 内部类主要用于设计具有相互协作关系的类集合。  

代理:

 是一种实现任意接口的对象。
 是一种非常专业的构造工具,可以用来构建系统级工具。

接口

类实现接口步骤:

  • 将类声明为实现给定的接口。

  • 对接口中的所有方法进行定义。

要将类声明为实现某个接口,需要使用关键字 implement

class Employee implements

接口的特性:

  • 接口不是类,不能使用 new 运算符实例化一个接口

    x = new Comparable(...);//ERROR

  • 尽管不能构造接口的对象,却能声明接口的变量

    Comparable x;//OK

  • 接口变量必须引用实现了接口的类对象

    x = new (...)

  • 可以使用 instance 检查一个对象是否实现了某个特定的接口

    if (anObject instanceof Comparable) {...}

  • 允许存在多条从具有较高通用性的接口到较高专用性的接口的链,以拓展接口

    public interface extends {...}

  • 接口中不能包含实例域或静态方法,但却可以包含常量和简单方法(非实例)

  • 接口中的所有方法自动地属于public

  • 接口中的域将自动设为 public static final

  • 一个类可以实现多个接口


接口与抽象类

接口可以提供多重继承的大多数好处,同时还能避免多重继承的复杂性和低效性。


默认方法

可以为接口方法提供一个默认实现。用 default修饰符标记。

可以将所有方法设置为默认方法,然后在实现类里,只实现自己想实现的方法,不需要实现所有的方法。

默认方法的一个重要用法是 “接口演化”。

接口更新后,若增加了新的接口方法,则以前的实现类将都需要进行修改,默认方法则可以避免实现类未实现新功能报错。

默认方法冲突解决办法

对于接口与接口,接口与父类冲突产生的默认方法二义性问题,解决办法为:

  • 超类优先

  • 接口冲突

    • 需要选择借用关键字选择合适的冲突方法。否则两个接口冲突的情况下,如果至少有一个接口提供了实现,编译器将会报错。

    • 如果两个接口都没有为共享方法提供默认实现,那么就不存在冲突。

    • 实现类可以有两种选择:实现这个方法,或干脆不实现。后者当且仅当这个类也是抽象类。


接口与回调

回调模式可以指出某个特定事件发生时应该采取的动作。


lambda 表达式

lambda 表达式是一个可传递的代码块,可以再以后执行一次或多次。

lambda 语法

参数,箭头以及一个表达式

(ElemType...args) -> {...; return...;}

这样就可以像写方法一样,将代码放在{}中,并包含显式的 return 函数

  • 如果可以推导出参数类型,则可以忽略其类型

    Comparator comp = (first, second)->first.length() - second.length();

  • 如果只有一个参数,甚至可以省略小括号

    ActionListener listener = event->System.out.println("The time is " + new Date());

  • 无需指定 lambda 表达式的返回类型

  • lambda 可以没有返回值,但如果有,必须所有情况都有返回值。


函数式接口

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

这些对象和类的管理完全取决于具体实现,与使用传统的内联类相比,这样可能要高效得多。


方法引用

当已经有现成的方法可以完成想要传递到其他代码的某个操作,可以使用方法引用。

Timer t = new Timer(1000, event->System.out.println(event))

等价于:Timer t = new Timer(1000, System.out::println)

即用::操作符分隔方法名与对象或类名。

  • object::instanceMethod

  • Class::staticMethod

  • 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 参数,或 super参数

this::equals <=> x->this.equals(x)

super::instanceMethod


构造器引用

与方法引用类似,方法名为new。

Person::new是 Person 构造器的一个引用。至于引用哪个构造器,这取决于上下文。

ArrayList<String> names = ...;
Stream<Person> stream = names.stream().map(Person::new);
List<Person> people = stream.collect(Collectors.toList());

如果存在多个 Person 构造器,编辑器会选择一个 String 参数的构造器,因为它从上下文推导出这是在对一个字符串调用构造器。

Person[] people = stream.toArray(Person[]::new);

这样就可以创建一个Person对象数组,toArray 方法调用这个构造器来得到一个正确类型的数组,然后填充这个数组并返回。


变量作用域

lambda 表达式可以捕获外围作用域中变量的值,而且只能引用值不会改变的变量。如果这个值可能在外部改变,也是不合法的。

故 lambda 表达式捕获的变量必须实际上是最终变量,这个变量初始化之后就不会再为它赋予新值。

lambda 表达式的体与嵌套快有相同的作用域,声明一个与局部变量同名的参数或局部变量是不合法的。

当 lambda表达式使用 this 关键字时,是指创建这个 lambda 表达式的方法的this参数。


处理 lambda 表达式

使用lambda表达式的重点是延迟执行。之所以希望以后再执行代码,有很多原因:

  • 在一个单独的线程中运行代码

  • 多次运行代码

  • 在算法的适当位置运行代码

  • 发生某种事件时执行代码

  • 只在必要时才运行代码

常用的函数式接口

函数式接口 参数类型 返回类型 抽象方法名 描述
Runnable void run 作为无参数或返回值的动作运行
Supplier T get 提供一个T类型的值
Consumer T void accept 处理一个T类型的值
BiConsumer T,U void accept 处理T和U类型的值
Function T R apply 有一个T类型参数的函数
BiFunction T,U R apply 有T和U类型参数的函数
UnaryOperator T T apply 类型T上的一元操作符
BinaryOperator T,T T applu 类型T上的二元操作符
Predicate T boolean test 布尔值函数
BiPredicate T,U boolean test 有两个参数的布尔值函数

内部类

内部类是定义在另一个类中的类。

使用内部类的原因:

  • 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。

  • 内部类可以对同一个包中的其他类隐藏起来。

  • 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较快捷。

内部类数据域只能被外围类(具有访问这些数据域的合理需要)中的方法访问,而不会暴露给其他的代码。

内部类的对象有一个隐式引用,它引用了实例化内部对象的外围类对象。通过这个指针,可以访问外围类对象的全部状态。


使用内部类访问对象状态

内部类的对象总有一个隐式引用,它指向了创建它的外部类对象。这个引用在内部类的定义中是不可见的。

外围类的引用在构造器中设置。编译器修改了所有的内部类的构造器,添加一个外围类引用的参数。


内部类的特殊语法规则

OuterClass.this表示外围类引用

outerObject.new InnerClass (construction parameters)编写内部对象构造器

OuterClass.InnerClass表示内部类引用

  • 内部类中声明的所有静态域都必须是 final。

  • 内部类不能有 static 方法。也可以允许有静态方法,但只能访问外围类的静态域和方法。


局部内部类

当遇到创建这个类型的对象只使用一次的情况下,可以在方法中定义局部类。

public ... <Method>() {
     
	class <Class>(...) {
     
		pubilc ... <Method>(...) {
     
			...
		}
	}
	...	
}

局部类不能用 public 或 private 访问说明符进行声明。它的作用域被限定在声明这个局部类的块中。他可以对外部世界完全隐蔽起来。除了外部块,没有任何方法知道他的存在。


由外部方法访问变量

局部类不仅能够访问包含它们的外部类,还可以访问局部变量。但是这些局部变量必须为final。这是为了防止同时在多个线程中执行内部类中的代码发生的并发更新导致的竞态条件。

它减少了需要显式编写的实例域,从而使得内部类更加简单

若出现需要更新的局部变量,可以使用一个长度为1的数据


匿名内部类

假如只创建这个类的一个对象,就不必命名了,这种类被称为匿名局部类。

语法格式;

new SuperType(construction parameters) // 接口或类
	{
     
    	inner class methods and data;
	}

由于匿名函数没有类名,故不能有构造器,但可以将构造器参数传递给超类构造器。尤其是在内部类实现接口时,不能有任何构造参数。

new InterfaceType()
	{
     
		methods and data
	}

使用匿名内部类的解决方案比较简短、更切实际、更易于理解。但在回调时依旧建议使用 lambda 表达式。

用匿名内部类创建传递给方法的一次性对象

method(new class(){ {...}})

双重内层括号,外层建立匿名子类,内层则是一个对象构造快。


静态内部类

不需要内部类引用外围类对象时,将内部类声明为 static,以取消产生的引用。


代理

利用代理可以在运行时创建一个实现了一组给定接口的新类。只有在编译时无法确定需要实现哪个接口时才有必要使用。

何时使用代理

代理类可以在运行时创建全新的类,这样的代理类能够实现指定的接口。

代理类方法:

  • 指定接口所需要的全部方法。

  • Object 类中的全部方法。

然而,不能在运行时定义这些方法的新代码,而是要提供一个调用处理器。调用处理器是实现了 InvocationHandler 接口的类对象。该接口只有一个方法:

Object invoke(Object proxy, Method method, Object[] args)

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


创建代理对象

需要使用 Proxy 类的 newProxyInstance 方法。该方法有三个参数:

  • 一个类加载器。用 null 表示使用默认的类加载器,加载系统类或者外部类。

  • 一个 Class 对象数组,每个元素都是需要实现的接口。

  • 一个调用处理器。

使用代理的原因:

  • 路由对远程服务器的方法调用

  • 在程序运行期间,将用户接口事件与动作关联起来。

  • 为调试,跟踪方法调用。


代理类的特性

代理类是在程序运行过程中创建的。然而,一旦被创建,就变成了常规类。

  • 所有的代理类都扩展与 Proxy 类。一个代理类只有一个实例域 – 调用处理器,它定义在 Proxy 的超类中,所需要的任何附加数据都必须存储在调用处理器中。

  • 所有的代理类都覆盖了 Object 类中的方法 toString、equals 和 hashCode。这些方法仅仅调用了调用处理器的 invoke。Object 类中的其他方法没有被重新定义。

  • 对于特定的类加载器和预设的一组接口来说,只能有一个代理类。如果使用同一个类加载器和接口数组调用两次 newProxyInstance 方法的话,那么只能得到同一个类的两个对象,也可以利用 getProxyClass 方法获取这个类:

Class proxyClass = Proxy.getProxyClass(null, interfaces);

代理类一定是 public 和 final。如果代理类实现的所有接口都是 public,代理类就不属于某个特定的包;否则,所有非公有的接口都必须属于同一个包,同时,代理类也属于这个包。

你可能感兴趣的:(java爆肝学习期)