使用Java8实现观察者模式的方法(上)

观察者(Observer)模式又名发布-订阅(Publish/Subscribe)模式,是四人组(GoF,即 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides)在1994合著的《设计模式:可复用面向对象软件的基础》中提出的(详见书中293-313页)。尽管这种模式已经有相当长的历史,它仍然广泛适用于各种场景,甚至成为了标准Java库的一个组成部分。目前虽然已经有大量关于观察者模式的文章,但它们都专注于在 Java 中的实现,却忽视了开发者在Java中使用观察者模式时遇到的各种问题。

本文的写作初衷就是为了填补这一空白:本文主要介绍通过使用 Java8 架构实现观察者模式,并在此基础上进一步探讨关于经典模式的复杂问题,包括匿名内部类、lambda 表达式、线程安全以及非平凡耗时长的观察者实现。本文内容虽然并不全面,很多这种模式所涉及的复杂问题,远不是一篇文章就能说清的。但是读完本文,读者能了解什么是观察者模式,它在Java中的通用性以及如何处理在 Java 中实现观察者模式时的一些常见问题。

观察者模式

根据 GoF 提出的经典定义,观察者模式的主旨是:

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

什么意思呢?很多软件应用中,对象之间的状态都是互相依赖的。例如,如果一个应用专注于数值数据加工,这个数据也许会通过图形用户界面(GUI)的表格或图表来展现或者两者同时使用,也就是说,当底层数据更新时,相应的 GUI 组件也要更新。问题的关键在于如何做到底层数据更新时 GUI 组件也随之更新,同时尽量减小 GUI 组件和底层数据的耦合度。

一种简单且不可扩展的解决方案是给管理这些底层数据的对象该表格和图像 GUI 组件的引用,使得对象可以在底层数据变化时能够通知 GUI 组件。显然,对于处理有更多 GUI 组件的复杂应用,这个简单的解决方案很快显示出其不足。例如,有20个 GUI 组件都依赖于底层数据,那么管理底层数据的对象就需要维护指向这20个组件的引用。随着依赖于相关数据的对象数量的增加,数据管理和对象之间的耦合度也变得难以控制。

另一个更好的解决方案是允许对象注册获取感兴趣数据更新的权限,当数据变化时,数据管理器就会通知这些对象。通俗地说就是,让感兴趣的数据对象告诉管理器:“当数据变化时请通知我”。此外,这些对象不仅可以注册获取更新通知,也可以取消注册,保证数据管理器在数据变化时不再通知该对象。在 GoF 的原始定义中,注册获取更新的对象叫作“观察者”(observer),对应的数据管理器叫作“目标”(Subject),观察者感兴趣的数据叫作“目标状态”,注册过程叫“添加”(attach),撤销观察的过程叫“移除”(detach)。前文已经提到观察者模式又叫发布-订阅模式,可以理解为客户订阅关于目标的观察者,当目标状态更新时,目标把这些更新发布给订阅者(这种设计模式扩展为通用架构,称为发布——订阅架构)。这些概念可以用下面的类图表示:

具体观察者(ConcereteObserver)用来接收更新的状态变化,同时将指向具体主题(ConcereteSubject)的引用传递给它的构造函数。这为具体观察者提供了指向具体主题的引用,在状态变化时可由此获得更新。简单来说,具体观察者会被告知主题更新,同时用其构造函数中的引用来获取具体主题的状态,最后将这些检索状态对象存储在具体观察者的观察状态(observerState)属性下。这一过程如下面的序列图所示:

经典模式的专业化

尽管观察者模式是通用的,但也有很多专业化的模式,最常见是以下两种:

为State对象提供一个参数,传给观察者调用的Update方法。在经典模式下,当观察者被通知Subject状态发生变化后,会直接从Subject获得其更新后状态。这要求观察者保存指向获取状态的对象引用。这样就形成了一个循环引用,ConcreteSubject的引用指向其观察者列表,ConcreteObserver的引用指向能获得主题状态的ConcreteSubject。除了获得更新的状态,观察者和其注册监听的Subject间并没有联系,观察者关心的是State对象,而非Subject本身。也就是说,很多情况下都将ConcreteObserver和ConcreteSubject强行联系一起,相反,当ConcreteSubject调用Update函数时,将State对象传递给ConcreteObserver,二者就无需关联。ConcreteObserver和State对象之间关联减小了观察者和State之间的依赖程度(关联和依赖的更多区别请参见Martin Fowler's的文章)。

将Subject抽象类和ConcreteSubject合并到一个 singleSubject类中。多数情况下,Subject使用抽象类并不会提升程序的灵活性和可扩展性,因此,将这一抽象类和具体类合并简化了设计。

这两个专业化的模式组合后,其简化类图如下:

在这些专业化的模式中,静态类结构大大简化,类之间的相互作用也得以简化。此时的序列图如下:

专业化模式另一特点是删除了 ConcreteObserver 的成员变量 observerState。有时候具体观察者并不需要保存Subject的最新状态,而只需要监测状态更新时 Subject 的状态。例如,如果观察者将成员变量的值更新到标准输出上,就可以删除 observerState,这样一来就删除了ConcreteObserver和State类之间的关联。

更常见的命名规则

经典模式甚至是前文提到的专业化模式都用的是attach,detach和observer等术语,而Java实现中很多都是用的不同的词典,包括register,unregister,listener等。值得一提的是State是listener需要监测变化的所有对象的统称,状态对象的具体名称需要看观察者模式用到的场景。例如,在listener监听事件发生场景下的观察者模式,已注册的listener将会在事件发生时收到通知,此时的状态对象就是event,也就是事件是否发生。

平时实际应用中目标的命名很少包含Subject。例如,创建一个关于动物园的应用,注册多个监听器用于观察Zoo类,并在新动物进入动物园时收到通知。该案例中的目标是Zoo类,为了和所给问题域保持术语一致,将不会用到Subject这样的词汇,也就是说Zoo类不会命名为ZooSubject。

监听器的命名一般都会跟着Listener后缀,例如前文提到的监测新动物加入的监听器会命名为AnimalAddedListener。类似的,register,、unregister和notify等函数命名常会以其对应的监听器名作后缀,例如AnimalAddedListener的register、unregister、notify函数会被命名为registerAnimalAddedListener、 unregisterAnimalAddedListener和notifyAnimalAddedListeners,需要注意的是notify函数名的s,因为notify函数处理的是多个而非单一监听器。

这种命名方式会显得冗长,而且通常一个subject会注册多个类型的监听器,如前面提到的动物园的例子,Zoo内除了注册监听动物新增的监听器,还需注册监听动物减少监听器,此时就会有两种register函数:(registerAnimalAddedListener和 registerAnimalRemovedListener,这种方式处理,监听器的类型作为一个限定符,表示其应观察者的类型。另一解决方案是创建一个registerListener函数然后重载,但是方案一能更方便的知道哪个监听器正在监听,重载是比较小众的做法。

另一惯用语法是用on前缀而不是update,例如update函数命名为onAnimalAdded而不是updateAnimalAdded。这种情况在监听器获得一个序列的通知时更常见,如向list中新增一个动物,但很少用于更新一个单独的数据,比如动物的名字。

接下来本文将使用Java的符号规则,虽然符号规则不会改变系统的真实设计和实现,但是使用其他开发者都熟悉的术语是很重要的开发准则,因此要熟悉上文描述的Java中的观察者模式符号规则。下文将在Java8环境下用一个简单例子来阐述上述概念。

一个简单的实例

还是前面提到的动物园的例子,使用Java8的API接口实现一个简单的系统,说明观察者模式的基本原理。问题描述为:

创建一个系统zoo,允许用户监听和撤销监听添加新对象animal的状态,另外再创建一个具体监听器,负责输出新增动物的name。

根据前面对观察者模式的学习知道实现这样的应用需要创建4个类,具体是:

Zoo类:即模式中的主题,负责存储动物园中的所有动物,并在新动物加入时通知所有已注册的监听器。

Animal类:代表动物对象。

AnimalAddedListener类:即观察者接口。

PrintNameAnimalAddedListener:具体的观察者类,负责输出新增动物的name。

首先我们创建一个Animal类,它是一个包含name成员变量、构造函数、getter和setter方法的简单Java对象,代码如下:

public class Animal {
private String name;
public Animal (String name) {
this.name = name;
}
public String getName () {
return this.name;
}
public void setName (String name) {
this.name = name;
}
}

用这个类代表动物对象,接下来就可以创建AnimalAddedListener接口了:

public interface AnimalAddedListener {
public void onAnimalAdded (Animal animal);
}

前面两个类很简单,就不再详细介绍,接下来创建Zoo类:

public class Zoo {
private List<Animal> animals = new ArrayList<>();
private List<AnimalAddedListener> listeners = new ArrayList<>();
public void addAnimal (Animal animal) {
// Add the animal to the list of animals
this.animals.add(animal);
// Notify the list of registered listeners
this.notifyAnimalAddedListeners(animal);
}
public void registerAnimalAddedListener (AnimalAddedListener listener) {
// Add the listener to the list of registered listeners
this.listeners.add(listener);
}
public void unregisterAnimalAddedListener (AnimalAddedListener listener) {
// Remove the listener from the list of the registered listeners
this.listeners.remove(listener);
}
protected void notifyAnimalAddedListeners (Animal animal) {
// Notify each of the listeners in the list of registered listeners
this.listeners.forEach(listener -> listener.updateAnimalAdded(animal));
}
}

这个类比前面两个都复杂,其包含两个list,一个用来存储动物园中所有动物,另一个用来存储所有的监听器,鉴于animals和listener集合存储的对象都很简单,本文选择了ArrayList来存储。存储监听器的具体数据结构要视问题而定,比如对于这里的动物园问题,如果监听器有优先级,那就应该选择其他的数据结构,或者重写监听器的register算法。

注册和移除的实现都是简单的委托方式:各个监听器作为参数从监听者的监听列表增加或者移除。notify函数的实现与观察者模式的标准格式稍微偏离,它包括输入参数:新增加的animal,这样一来notify函数就可以把新增加的animal引用传递给监听器了。用streams API的forEach函数遍历监听器,对每个监听器执行theonAnimalAdded函数。

在addAnimal函数中,新增的animal对象和监听器各自添加到对应list。如果不考虑通知过程的复杂性,这一逻辑应包含在方便调用的方法中,只需要传入指向新增animal对象的引用即可,这就是通知监听器的逻辑实现封装在notifyAnimalAddedListeners函数中的原因,这一点在addAnimal的实现中也提到过。

除了notify函数的逻辑问题,需要强调一下对notify函数可见性的争议问题。在经典的观察者模型中,如GoF在设计模式一书中第301页所说,notify函数是public型的,然而尽管在经典模式中用到,这并不意味着必须是public的。选择可见性应该基于应用,例如本文的动物园的例子,notify函数是protected类型,并不要求每个对象都可以发起一个注册观察者的通知,只需保证对象能从父类继承该功能即可。当然,也并非完全如此,需要弄清楚哪些类可以激活notify函数,然后再由此确定函数的可见性。

接下来需要实现PrintNameAnimalAddedListener类,这个类用System.out.println方法将新增动物的name输出,具体代码如下:

public class PrintNameAnimalAddedListener implements AnimalAddedListener {
@Override
public void updateAnimalAdded (Animal animal) {
// Print the name of the newly added animal
System.out.println("Added a new animal with name '" + animal.getName() + "'");
}
}

最后要实现驱动应用的主函数:

public class Main {
public static void main (String[] args) {
// Create the zoo to store animals
Zoo zoo = new Zoo();
// Register a listener to be notified when an animal is added
zoo.registerAnimalAddedListener(new PrintNameAnimalAddedListener());
// Add an animal notify the registered listeners
zoo.addAnimal(new Animal("Tiger"));
}
}

主函数只是简单的创建了一个zoo对象,注册了一个输出动物name的监听器,并新建了一个animal对象以触发已注册的监听器,最后的输出为:

Added a new animal with name 'Tiger'

新增监听器

当监听器重新建立并将其添加到Subject时,观察者模式的优势就充分显示出来。例如,想添加一个计算动物园中动物总数的监听器,只需要新建一个具体的监听器类并注册到Zoo类即可,而无需对zoo类做任何修改。添加计数监听器CountingAnimalAddedListener代码如下:

public class CountingAnimalAddedListener implements AnimalAddedListener {
private static int animalsAddedCount = 0;
@Override
public void updateAnimalAdded (Animal animal) {
// Increment the number of animals
animalsAddedCount++;
// Print the number of animals
System.out.println("Total animals added: " + animalsAddedCount);
}
}

修改后的main函数如下:

public class Main {
public static void main (String[] args) {
// Create the zoo to store animals
Zoo zoo = new Zoo();
// Register listeners to be notified when an animal is added
zoo.registerAnimalAddedListener(new PrintNameAnimalAddedListener());
zoo.registerAnimalAddedListener(new CountingAnimalAddedListener());
// Add an animal notify the registered listeners
zoo.addAnimal(new Animal("Tiger"));
zoo.addAnimal(new Animal("Lion"));
zoo.addAnimal(new Animal("Bear"));
}
}

输出结果为:

Added a new animal with name 'Tiger'
Total animals added: 1
Added a new animal with name 'Lion'
Total animals added: 2
Added a new animal with name 'Bear'
Total animals added: 3 

使用者可在仅修改监听器注册代码的情况下,创建任意监听器。具有此可扩展性主要是因为Subject和观察者接口关联,而不是直接和ConcreteObserver关联。只要接口不被修改,调用接口的Subject就无需修改。

匿名内部类,Lambda函数和监听器注册

Java8的一大改进是增加了功能特性,如增加了lambda函数。在引进lambda函数之前,Java通过匿名内部类提供了类似的功能,这些类在很多已有的应用中仍在使用。在观察者模式下,随时可以创建新的监听器而无需创建具体观察者类,例如,PrintNameAnimalAddedListener类可以在main函数中用匿名内部类实现,具体实现代码如下:

public class Main {
public static void main (String[] args) {
// Create the zoo to store animals
Zoo zoo = new Zoo();
// Register listeners to be notified when an animal is added
zoo.registerAnimalAddedListener(new AnimalAddedListener() {
@Override
public void updateAnimalAdded (Animal animal) {
// Print the name of the newly added animal
System.out.println("Added a new animal with name '" + animal.getName() + "'");
}
});
// Add an animal notify the registered listeners
zoo.addAnimal(new Animal("Tiger"));
}
}

类似的,lambda函数也可以用以完成此类任务:

public class Main {
public static void main (String[] args) {
// Create the zoo to store animals
Zoo zoo = new Zoo();
// Register listeners to be notified when an animal is added
zoo.registerAnimalAddedListener(
(animal) -> System.out.println("Added a new animal with name '" + animal.getName() + "'")
);
// Add an animal notify the registered listeners
zoo.addAnimal(new Animal("Tiger"));
}
}

需要注意的是lambda函数仅适用于监听器接口只有一个函数的情况,这个要求虽然看起来严格,但实际上很多监听器都是单一函数的,如示例中的AnimalAddedListener。如果接口有多个函数,可以选择使用匿名内部类。

隐式注册创建的监听器存在此类问题:由于对象是在注册调用的范围内创建的,所以不可能将引用存储一个到具体监听器。这意味着,通过lambda函数或者匿名内部类注册的监听器不可以撤销注册,因为撤销函数需要传入已经注册监听器的引用。解决这个问题的一个简单方法是在registerAnimalAddedListener函数中返回注册监听器的引用。如此一来,就可以撤销注册用lambda函数或匿名内部类创建的监听器,改进后的方法代码如下:

public AnimalAddedListener registerAnimalAddedListener (AnimalAddedListener listener) {
// Add the listener to the list of registered listeners
this.listeners.add(listener);
return listener;
}

重新设计的函数交互的客户端代码如下:

public class Main {
public static void main (String[] args) {
// Create the zoo to store animals
Zoo zoo = new Zoo();
// Register listeners to be notified when an animal is added
AnimalAddedListener listener = zoo.registerAnimalAddedListener(
(animal) -> System.out.println("Added a new animal with name '" + animal.getName() + "'")
);
// Add an animal notify the registered listeners
zoo.addAnimal(new Animal("Tiger"));
// Unregister the listener
zoo.unregisterAnimalAddedListener(listener);
// Add another animal, which will not print the name, since the listener
// has been previously unregistered
zoo.addAnimal(new Animal("Lion"));
}
}

此时的结果输出只有Added a new animal with name 'Tiger',因为在第二个animal加入之前监听器已经撤销了:

Added a new animal with name 'Tiger'

如果采用更复杂的解决方案,register函数也可以返回receipt类,以便unregister监听器调用,例如:

public class AnimalAddedListenerReceipt {
private final AnimalAddedListener listener;
public AnimalAddedListenerReceipt (AnimalAddedListener listener) {
this.listener = listener;
}
public final AnimalAddedListener getListener () {
return this.listener;
}
}

receipt会作为注册函数的返回值,以及撤销注册函数输入参数,此时的zoo实现如下所示:

public class ZooUsingReceipt {
// ...Existing attributes and constructor...
public AnimalAddedListenerReceipt registerAnimalAddedListener (AnimalAddedListener listener) {
// Add the listener to the list of registered listeners
this.listeners.add(listener);
return new AnimalAddedListenerReceipt(listener);
}
public void unregisterAnimalAddedListener (AnimalAddedListenerReceipt receipt) {
// Remove the listener from the list of the registered listeners
this.listeners.remove(receipt.getListener());
}
// ...Existing notification method...
}

上面描述的接收实现机制允许保存信息供监听器撤销时调用的,也就是说如果撤销注册算法依赖于Subject注册监听器时的状态,则此状态将被保存,如果撤销注册只需要指向之前注册监听器的引用,这样的话接收技术则显得麻烦,不推荐使用。

除了特别复杂的具体监听器,最常见的注册监听器的方法是通过lambda函数或通过匿名内部类注册。当然,也有例外,那就是包含subject实现观察者接口的类和注册一个包含调用该引用目标的监听器。如下面代码所示的案例:

public class ZooContainer implements AnimalAddedListener {
private Zoo zoo = new Zoo();
public ZooContainer () {
// Register this object as a listener
this.zoo.registerAnimalAddedListener(this);
}
public Zoo getZoo () {
return this.zoo;
}
@Override
public void updateAnimalAdded (Animal animal) {
System.out.println("Added animal with name '" + animal.getName() + "'");
}
public static void main (String[] args) {
// Create the zoo container
ZooContainer zooContainer = new ZooContainer();
// Add an animal notify the innerally notified listener
zooContainer.getZoo().addAnimal(new Animal("Tiger"));
}
}

这种方法只适用于简单情况而且代码看起来不够专业,尽管如此,它还是深受现代Java开发人员的喜爱,因此了解这个例子的工作原理很有必要。因为ZooContainer实现了AnimalAddedListener接口,那么ZooContainer的实例(或者说对象)就可以注册为AnimalAddedListener。ZooContainer类中,该引用代表当前对象即ZooContainer的一个实例,所以可以被用作AnimalAddedListener。

通常,不是要求所有的container类都实现此类功能,而且实现监听器接口的container类只能调用Subject的注册函数,只是简单把该引用作为监听器的对象传给register函数。在接下来的章节中,将介绍多线程环境的常见问题和解决方案。

OneAPM 为您提供端到端的Java 应用性能解决方案,我们支持所有常见的 Java 框架及应用服务器,助您快速发现系统瓶颈,定位异常根本原因。分钟级部署,即刻体验,Java 监控从来没有如此简单。想阅读更多技术文章,请访问OneAPM 官方技术博客。

以上内容给大家介绍了使用Java8实现观察者模式的方法(上)的相关内容,下篇文章给大家介绍使用java8实现观察者模式的方法(下),感兴趣的朋友继续学习吧,希望对大家有所帮助!

(0)

相关推荐

  • Java中泛型的用法总结

    本文实例总结了Java中泛型的用法.分享给大家供大家参考.具体如下: 1 基本使用 public interface List<E> { void add(E); Iterator<E> iterator(); } 2 泛型与子类 Child是Parent的子类,List<Child>却不是List<Parent>的子类. 因此:List<Object> list = new ArrayList<String>()是错误的. 如果上面

  • java 在观察者模式中使用泛型T的实例

    被观察者 public class Observable<T> { List<Observer> observers = new ArrayList<Observer>(); boolean changed = false; /** * Adds the specified observer to the list of observers. If it is already * registered, it is not added a second time. *

  • Java观察者模式例子

    观察者模式是一种行为设计模式.观察者模式的用途是,当你对一个对象的状态感兴趣,希望在它每次发生变化时获得通知.在观察者模式中,观察另外一个对象状态的对象叫做Observer观察者,被观察的对象叫着Subject被观察者. 观察者模式 Observer 观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象. 这个主题对象在状态上发生变化时,会通知所有观察者对象,让它们能够自动更新自己. 观察者模式的组成 抽象主题角色:把所有对观察者对象的引用保存在一个集合中,每个抽象主题角

  • Java设计模式开发中使用观察者模式的实例教程

    观察者模式是软件设计模式中的一种,使用也比较普遍,尤其是在GUI编程中.关于设计模式的文章,网络上写的都比较多,而且很多文章写的也不错,虽然说有一种重复早轮子的嫌疑,但此轮子非彼轮子,侧重点不同,思路也不同,讲述方式也不近相同. 关键要素 主题: 主题是观察者观察的对象,一个主题必须具备下面三个特征. 持有监听的观察者的引用 支持增加和删除观察者 主题状态改变,通知观察者 观察者: 当主题发生变化,收到通知进行具体的处理是观察者必须具备的特征. 为什么要用这种模式 这里举一个例子来说明,牛奶送奶

  • 使用 Java8 实现观察者模式的方法(下)

    在上篇文章给大家介绍了使用Java8 实现观察者模式的方法(上),本文继续给大家介绍java8观察者模式相关知识,具体内容如下所述: 线程安全的实现 前面章节介绍了在现代Java环境下的实现观察者模式,虽然简单但很完整,但这一实现忽略了一个关键性问题:线程安全.大多数开放的Java应用都是多线程的,而且观察者模式也多用于多线程或异步系统.例如,如果外部服务更新其数据库,那么应用也会异步地收到消息,然后用观察者模式通知内部组件更新,而不是内部组件直接注册监听外部服务. 观察者模式的线程安全主要集中

  • Java中的观察者模式实例讲解

    观察者模式是一种行为设计模式.观察者模式的用途是,当你对一个对象的状态感兴趣,希望在它每次发生变化时获得通知.在观察者模式中,观察另外一个对象状态的对象叫做Observer观察者,被观察的对象叫着Subject被观察者.根据GoF规则,观察者模式的意图是: 复制代码 代码如下: 定义对象之间一对多的依赖关系,一个对象状态改变,其他相关联的对象就会得到通知并被自动更新. Subject(被观察者)包含了一些需要在其状态改变时通知的观察者.因此,他应该提供给观察者可以register(注册)自己和u

  • 使用Java8实现观察者模式的方法(上)

    观察者(Observer)模式又名发布-订阅(Publish/Subscribe)模式,是四人组(GoF,即 Erich Gamma.Richard Helm.Ralph Johnson 和 John Vlissides)在1994合著的<设计模式:可复用面向对象软件的基础>中提出的(详见书中293-313页).尽管这种模式已经有相当长的历史,它仍然广泛适用于各种场景,甚至成为了标准Java库的一个组成部分.目前虽然已经有大量关于观察者模式的文章,但它们都专注于在 Java 中的实现,却忽视了

  • Java8中如何通过方法引用获取属性名详解

    前言 在我们开发过程中常常有一个需求,就是要知道实体类中Getter方法对应的属性名称(Field Name),例如实体类属性到数据库字段的映射,我们常常是硬编码指定 属性名,这种硬编码有两个缺点. 1.编码效率低:因为要硬编码写属性名,很可能写错,需要非常小心,时间浪费在了不必要的检查上. 2.容易让开发人员踩坑:例如有一天发现实体类中Field Name定义的不够明确,希望换一个Field Name,那么代码所有硬编码的Field Name都要跟着变更,对于未并更的地方,是无法在编译期发现的

  • Java8中的默认方法(面试者必看)

    背景 在Java8之前,定义在接口中的所有方法都需要在接口实现类中提供一个实现,如果接口的提供者需要升级接口,添加新的方法,那么所有的实现类都需要把这个新增的方法实现一遍,如果说所有的实现类能够自己控制的话,那么还能接受,但是现实情况是实现类可能不受自己控制.比如说Java中的集合框架中的List接口添加一个方法,那么Apache Commons这种框架就会很难受,必须修改所有实现了List的实现类 现在的接口有哪些不便 向已经发布的接口中添加新的方法是问题的根源,一旦接口发生变化,接口的实现者

  • Python用GET方法上传文件

    之前在osc看到一个文章讨论Get和Post的不同, 有人说不能用Get来上传文件.这就是用Get上传文件的例子,client用来发Get请求,server用来收请求.文件内容是在http请求的body内传过去的.用了不同的语言,因为我觉得各自处理起来都要方便些.而且我觉得浏览器也是可以发出这样的请求的,之后我会尝试一下. 请求端代码 复制代码 代码如下: import requests #需要安装requests with open('test.txt', 'rb') as f:     re

  • Java8接口的默认方法

    Java8接口的默认方法 什么是默认方法,为什么要有默认方法? 简单说,就是接口可以有实现方法,而且不需要实现类去实现其方法.只需在方法名前面加个default关键字即可. 为什么要有这个特性?首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的 java 8之前的集合框架没有foreach方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现.然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有

  • 使用java的注解(用在java类的方法上的注解)方法

    场景:根据方法上的注解,通过java反射方式找到需要执行的的方法. 1.注解类 /**注解作用在方法上*/ @Target({ElementType.METHOD}) /**注解的生命周期一直程序运行时都存在VM运行期间保留注解,可以通过反射机制读取注解信息*/ @Retention(RetentionPolicy.RUNTIME) /**注解包含在Javadoc中*/ @Documented public @interface Item { String value(); } 2.在类的方法上

  • java8新特性之方法引用示例代码

    简介 方法引用是java8的新特性之一, 可以直接引用已有Java类或对象的方法或构造器.方法引用与lambda表达式结合使用,可以进一步简化代码. 方法引用的使用场景 我们用Lambda表达式来实现匿名方法.但有些情况下,我们用Lambda表达式仅仅是调用一些已经存在的方法,除了调用动作外,没有其他任何多余的动作,在这种情况下,我们倾向于通过方法名来调用它,而Lambda表达式可以帮助我们实现这一要求,它使得Lambda在调用那些已经拥有方法名的方法的代码更简洁.更容易理解.方法引用可以理解为

  • Java8新特性之方法引用的实践指南

    一 前言 日常开发中,经常使用到Lambda表达式,例如: public static void main(String[] args) { List<Integer> list = Arrays.asList(1, 5, 10, 4, 2); // 打印列表中的每一个数字 list.forEach((x) -> System.out.println(x)); } 其中(x) -> System.out.println(x)就是使用的Lambda表达式.Lambda表达式可以分为三

  • Java8接口之默认方法与静态方法详解

    目录 前言 为什么选择默认方法? Java 8示例:接口中的默认方法 Java 8示例:接口中的静态方法 Java 8 - 抽象类与接口 总结 前言 在Java8之前,java中的接口只能有抽象方法.默认情况下,接口的所有方法都是公共和抽象的.Java8允许接口具有默认和静态方法.我们在接口中使用默认方法的原因是,允许开发人员向接口添加新方法,而不会影响实现这些接口的类. 为什么选择默认方法? 例如,如果A.B.C和D等几个类实现了一个接口XYZInterface,那么如果我们向XYZInter

随机推荐