Pro Java
Механизм обратного вызова (callback) широко распространен в программировании. При обратном вызове программист задает действия, которые должны выполняться всякий раз, когда происходит некоторое событие. Например, можно задать действие, которое должно быть выполнено после щелчка на некоторой кнопке или при выборе определенного пункта меню.
Для примера, пакет javax.swing содержит класс Timer, который можно использовать для отсчета интервалов времени. Например, если в программе предусмотрены часы, то с помощью класса Timer можно отсчитывать каждую секунду и обновлять циферблат часов.
Устанавливая таймер, мы задаем интервал времени и указываем, что должно произойти по его истечении.
Как указать таймеру, что он должен делать? Во многих языках программирования задается имя функции, которую таймер должен периодически вызывать. Однако классы из стандартной библиотеки языка Java используют объектно-ориентированный подход. Программист должен передать таймеру объект некоторого класса. После этого таймер вызывает один из методов данного объекта. Передача объекта — более гибкий механизм, чем вызов функции, поскольку объект может нести дополнительную информацию.
Разумеется, таймер должен знать, какой метод он должен вызвать. Для этого таймеру нужно указать объект класса, реализующего интерфейс ActionListener из пакета java.awt.event. Этот интерфейс выглядит следующим образом:
public interface ActionListener extends EventListener public void actionPerformed ( ActionEvent e ) ;
>
По истечении заданного интервала времени таймер вызывает метод actionPerformed().
Предположим, нам нужно каждые две секунды выводить на экран сообщение о текущем времени. Для этого необходимо определить класс, реализующий интерфейс ActionListener, а затем поместить операторы, которые нужно выполнить, в тело метода actionPerformed().
Обратите внимание на параметр ActionEvent метода actionPerformed(). Он содержит информацию о событии, например, об объекте, его породившем. В нашей программе детальная информация о событии не важна, поэтому можно просто проигнорировать этот параметр.
Затем следует создать объект данного класса и передать его конструктору класса Timer.
Первый параметр конструктора Timer представляет собой интервал времени между точками отсчета, измеренный в миллисекундах. Сообщение должно выдаваться на экран каждые десять секунд. Второй параметр является объектом класса ActionListener.
t.start() запускает таймер и на экране каждые две секунды будет появляться сообщение о текущем времени.
После запуска таймера программа выводит на экран окно с сообщением и ждет, пока пользователь не щелкнет на кнопке OK, чтобы завершить работу.
Callback Functions in Java
The Kubernetes ecosystem is huge and quite complex, so it’s easy to forget about costs when trying out all of the exciting tools.
To avoid overspending on your Kubernetes cluster, definitely have a look at the free K8s cost monitoring tool from the automation platform CAST AI. You can view your costs in real time, allocate them, calculate burn rates for projects, spot anomalies or spikes, and get insightful reports you can share with your team.
Connect your cluster and start monitoring your K8s costs right away:
We rely on other people’s code in our own work. Every day.
It might be the language you’re writing in, the framework you’re building on, or some esoteric piece of software that does one thing so well you never found the need to implement it yourself.
The problem is, of course, when things fall apart in production — debugging the implementation of a 3rd party library you have no intimate knowledge of is, to say the least, tricky.
Lightrun is a new kind of debugger.
It’s one geared specifically towards real-life production environments. Using Lightrun, you can drill down into running applications, including 3rd party dependencies, with real-time logs, snapshots, and metrics.
Learn more in this quick, 5-minute Lightrun tutorial:
Slow MySQL query performance is all too common. Of course it is. A good way to go is, naturally, a dedicated profiler that actually understands the ins and outs of MySQL.
The Jet Profiler was built for MySQL only, so it can do things like real-time query performance, focus on most used tables or most frequent queries, quickly identify performance issues and basically help you optimize your queries.
Critically, it has very minimal impact on your server’s performance, with most of the profiling work done separately — so it needs no server changes, agents or separate services.
Basically, you install the desktop application, connect to your MySQL server, hit the record button, and you’ll have results within minutes:
DbSchema is a super-flexible database designer, which can take you from designing the DB with your team all the way to safely deploying the schema.
The way it does all of that is by using a design model, a database-independent image of the schema, which can be shared in a team using GIT and compared or deployed on to any database.
And, of course, it can be heavily visual, allowing you to interact with the database using diagrams, visually compose queries, explore the data, generate random data, import data or build HTML5 database reports.
The Kubernetes ecosystem is huge and quite complex, so it’s easy to forget about costs when trying out all of the exciting tools.
To avoid overspending on your Kubernetes cluster, definitely have a look at the free K8s cost monitoring tool from the automation platform CAST AI. You can view your costs in real time, allocate them, calculate burn rates for projects, spot anomalies or spikes, and get insightful reports you can share with your team.
Connect your cluster and start monitoring your K8s costs right away:
We’re looking for a new Java technical editor to help review new articles for the site.
1. Overview
A callback function is a function passed as an argument to another function and executed when that function completes or some event happens. In most programming languages, callback functions are especially useful when we’re working with asynchronous code.
In this article, we’ll learn the practical use cases of callback functions in Java and how we can implement them.
2. Implementing Callback Functions
Generally, we can create a callback function in Java by exposing an interface and accepting its implementation as a parameter. Such a callback can be called synchronously or asynchronously.
2.1. Synchronous Callbacks
Synchronous operations are those where one task needs to complete before another one starts.
For example, imagine such an interface:
public interface EventListener
The above snippet declares an EventListener interface with an onTrigger() method with a String return type. This will be our callback.
Next, let’s declare a concrete class that implements this interface:
public class SynchronousEventListenerImpl implements EventListener < @Override public String onTrigger()< return "Synchronously running callback function"; >>
The SynchronousEventListenerImpl class implements the EventListener interface, as shown above.
Next, let’s create a SynchronousEventConsumer class that composes an instance of the EventListener interface and invokes its onTrigger() method:
public class SynchronousEventConsumer < private final EventListener eventListener; // constructor public String doSynchronousOperation()< System.out.println("Performing callback before synchronous Task"); // any other custom operations return eventListener.onTrigger(); >>
The SyncronousEventConsumer class has an EventListener property that it initializes through its constructor. When the doSynchronousOperation() method is invoked, it returns the value obtained from the onTrigger() method belonging to the EventListener.
Let’s write a test to demonstrate that the doSynchronousOperation() method invokes the onTrigger() method of the listener variable and obtains its returned value:
EventListener listener = new SynchronousEventListenerImpl(); SynchronousEventConsumer synchronousEventConsumer = new SynchronousEventConsumer(listener); String result = synchronousEventConsumer.doSynchronousOperation(); assertNotNull(result); assertEquals("Synchronously running callback function", result);
2.2. Asynchronous Callback Function
Asynchronous operations are operations that run in parallel to one another. Unlike synchronous operations illustrated in the previous section, asynchronous tasks are non-blocking. They don’t wait for one another before performing their operations. Let’s update the EventListener interface to illustrate an asynchronous callback function in Java:
public interface EventListener
Next, let’s create an implementation for the revised EventListener:
public class AsynchronousEventListenerImpl implements EventListener < @Override public String onTrigger()< respondToTrigger(); return "Asynchronously running callback function"; >@Override public void respondToTrigger() < System.out.println("This is a side effect of the asynchronous trigger."); >>
The above class implements the EventListener interface we declared in the previous section and returns a String literal in its overridden onTrigger() method.
Next, we declare the class that asynchronously runs the onTrigger() method as a callback function:
public class AsynchronousEventConsumer < private EventListener listener; public AsynchronousEventConsumer(EventListener listener) < this.listener = listener; >public void doAsynchronousOperation() < System.out.println("Performing operation in Asynchronous Task"); new Thread(() ->listener.onTrigger()).start(); > >
The AsynchronousEventConsumer class above declares a doAsynchronousOperation() method that implicitly invokes the onTrigger() method of the EventListener in a new thread.
Note that this approach of creating a new Thread for each method call is an anti-pattern and is used here for demonstration purposes. Production-ready code should rely on properly sized and tuned thread pools. Check out some of our other articles to learn more about concurrency in Java.
Let’s verify that the program indeed invokes the onTrigger() method from within the doAsynchronousOperation() method:
EventListener listener = Mockito.mock(AsynchronousEventListenerImpl.class); AsynchronousEventConsumer synchronousEventListenerConsumer = new AsynchronousEventConsumer(listener); synchronousEventListenerConsumer.doAsynchronousOperation(); verify(listener, timeout(1000).times(1)).onTrigger();
2.3. Using Consumers
Consumers are functional interfaces that are commonly used in functional programming in Java. Implementations of the interface accept an argument and perform an operation with the provided argument but don’t return a result.
Using Consumers, we can pass a method as an argument to another method. This allows us to invoke and run the operations of the inner method from within the parent method.
Let’s consider a method that increases the value of a given number represented as age. We can pass the initial age as the first argument and a Consumer that will serve as the second method to increment the age.
Here’s an illustration of how we can implement this as a callback function using Consumers:
public class ConsumerCallback < public void getAge(int initialAge, Consumercallback) < callback.accept(initialAge); >public void increaseAge(int initialAge, int ageDifference, Consumer callback) < System.out.println("===== Increase age ===="); int newAge = initialAge + ageDifference; callback.accept(newAge); >>
In the getAge() method above, we pass the initialAge variable as an argument to the callback.accept() method. The accept() method takes an argument (in this case, an integer), and then performs any operation on the input through the method or function passed to the getAge() method as an argument at runtime.
The increaseAge() method will perform the increment on the initialAge variable. It adds the value of the initialAge to the ageDifference and then passes the result to the accept() method of the third argument, the Consumer.
Here’s a demonstration of the above implementations:
ConsumerCallback consumerCallback = new ConsumerCallback(); int ageDifference = 10; AtomicInteger newAge1 = new AtomicInteger(); int initialAge = 20; consumerCallback.getAge(initialAge, (initialAge1) -> < consumerCallback.increaseAge(initialAge, ageDifference, (newAge) -> < System.out.printf("New age ==>%s", newAge); newAge1.set(newAge); >); >); assertEquals(initialAge + ageDifference, newAge1.get());
In the above snippet, we pass a function to the getAge() method. This function invokes the increaseAge() method and asserts that the value of the newAge variable equals the sum of the initialAge and ageDifference.
The callback functions in this context are the functions passed to the getAge() and increaseAge() methods. These functions are triggered to perform any custom operation after each of the getAge() and increaseAge() methods have completed their tasks.
3. Conclusion
In this article, we learned about the concept of callback functions in Java. We demonstrated how we could synchronously and asynchronously implement callback functions through interfaces. We also learned how to use the Java Consumer functional interface to perform callback operations in Java.
The code snippets provided in this article are available over on GitHub.
Slow MySQL query performance is all too common. Of course it is. A good way to go is, naturally, a dedicated profiler that actually understands the ins and outs of MySQL.
The Jet Profiler was built for MySQL only, so it can do things like real-time query performance, focus on most used tables or most frequent queries, quickly identify performance issues and basically help you optimize your queries.
Critically, it has very minimal impact on your server’s performance, with most of the profiling work done separately — so it needs no server changes, agents or separate services.
Basically, you install the desktop application, connect to your MySQL server, hit the record button, and you’ll have results within minutes: