В языке Java есть особый тип интерфейсов, который называется функциональным интерфейсом. Функциональный интерфейс - это интерфейс, в котором определен только один абстрактный метод (метод без реализации). Простейший пример:
interface Message{
void print();
}
Данный интерфейс Message можно назвать функциональным.
И как и в случае с любым интерфейсом, мы можем определить переменную этого интерфейса:
Message mes;
Хотя функциональный интерфейс, на первый взгляд, кажется обычным интерфейсом, но у него есть особенность в том, что он может представлять ссылки на методы, то есть на некоторое действие.
Ссылка на метод (method reference) представляет синтаксическую конструкцию следующего вида:
имя_класса::имя_статического_метода // если метод статический объект_класса::имя_метода // если метод нестатический
Например:
public class Program {
public static void main(String[] args) {
Message mes;
mes = Program::hello;
}
static void hello(){
System.out.println("Hello METANIT.COM");
}
}
interface Message{
void print();
}
Здесь переменной mes передаем ссылку на метод hello, который определен в классе Program как статический. Поэтому ссылка на метод выглядит следующим образом:
Program::hello
И тут также важно, что ссылка на метод из ссылки (hello) должен соответствовать единственному методу функционального интерфейса по типами параметров и типу результат. Так, в нашем случае метод
print в интерфейсе Message имеет тип void и не принимает никаких параметров. Соответственно и статический метод hello, ссылка на который присваивается переменной интерфейса,
также не принимает параметров и имеет тип void
Далее мы можем вызвать метод по ссылке:
public class Program {
public static void main(String[] args) {
Message mes;
mes = Program::hello;
mes.print(); // вызываем метод по ссылке
}
static void hello(){
System.out.println("Hello METANIT.COM");
}
}
interface Message{
void print();
}
То есть при вызове
mes.print();
фактически вызывается метод по ссылке - метод hello()
Ссылка на метод предписывает компилятору создать экземпляр функционального интерфейса, переопределяя единственный метод интерфейса для его вызова. То есть в данном случае мы получаем некоторый объект интерфейса Message,
где метод hello() выступает в качестве реализации метода print() из интерфейса.
Если нам надо вызвать нестатические методы, то в ссылке вместо имени класса применяется имя объекта этого класса:
public class Program {
public static void main(String[] args) {
Program prog = new Program();
Message mes = prog::hello;
mes.print(); // Hello METANIT.COM
}
void hello(){
System.out.println("Hello METANIT.COM");
}
}
interface Message{
void print();
}
В данном случае метод hello() определен как нестатический. Поэтому для формирования ссылки на него создаем объект класса Program и передаем ссылку переменной mes:
Program prog = new Program(); Message mes = prog::hello;
Рассмотрим другой пример, когда переменная ссылается на метод с параметрами и некоторым возвращаемым результатом:
public class Program {
public static void main(String[] args) {
Operation op = Operations::add;
System.out.println(op.execute(5, 4)); // 9
op = Operations::sub;
System.out.println(op.execute(5, 4)); // 1
op = Operations::mul;
System.out.println(op.execute(5, 4)); // 20
}
}
interface Operation{
int execute(int x, int y);
}
class Operations{
static int add(int a, int b){ return a + b; }
static int sub(int a, int b){ return a - b; }
static int mul(int a, int b){ return a * b; }
}
Здесь мы определяем функциональный интерфейс Operation с методом execute()
interface Operation{
int execute(int x, int y);
}
В программе в методе main() создаем переменную op, которая представляет данный интерфейс, и присваиваем ей ссылку на статический метод Operations.add:
Operation op = Operations::add;
Мы можем так сделать, поскольку типы параметров и возвращаемый тип метода Operations.add соответствуют типам параметров и результата абстрактного метода execute() из интерфейса Operation.
Затем через переменную op вызываем метод по ссылке:
System.out.println(op.execute(5, 4));
Далее присваиваем переменной ссылку на другой метод и повторно вызываем и соответственно получаем другой результат при тех же аргументах:
op = Operations::sub; System.out.println(op.execute(5, 4)); // 1
Возможность определять ссылки на методы открываем нам дополнительные возможности при написании программ. Так, мы можем определить параметр, который представляет некоторое действие, и передать ему ссылку на метод. Рассмотрим на примере:
public class Program {
public static void main(String[] args) {
doOperation(10, 4, Operations::add); // 14
doOperation(10, 4, Operations::sub); // 6
doOperation(10, 4, Operations::mul); // 40
}
static void doOperation(int a, int b, Operation op)
{
System.out.println(op.execute(a, b));
}
}
interface Operation{
int execute(int x, int y);
}
class Operations{
static int add(int a, int b){ return a + b; }
static int sub(int a, int b){ return a - b; }
static int mul(int a, int b){ return a * b; }
}
Здесь возможные вычисления вынесены в отдельный класс Operations и представляют статические методы. Поэтому для передачи этих методов применяются выражения типа Operations::add
doOperation(10, 4, Operations::add); // 14
Если нам надо вызвать нестатические методы, то в ссылке вместо имени класса применяется имя объекта этого класса:
public class Program {
public static void main(String[] args) {
Operations ops = new Operations();
doOperation(10, 4, ops::add); // 14
doOperation(10, 4, ops::sub); // 6
doOperation(10, 4, ops::mul); // 40
}
static void doOperation(int a, int b, Operation op)
{
System.out.println(op.execute(a, b));
}
}
interface Operation{
int execute(int x, int y);
}
class Operations{
int add(int a, int b){ return a + b; }
int sub(int a, int b){ return a - b; }
int mul(int a, int b){ return a * b; }
}
Кроме того, мы можем возвращать ссылки на методы из других методов. Например:
import java.util.function.BiFunction;
public class Program {
public static void main(String[] args) {
Operation action = select(1); // получаем ссылку на функцию Sum
System.out.println(action.execute(8, 5)); // 13
action = select(2); // получаем ссылку на функцию Subtract
System.out.println(action.execute(8, 5));// 3
action = select(3); // получаем ссылку на функцию Multiply
System.out.println(action.execute(8, 5)); // 40
}
static Operation select(int choice){
// возвращаем нужную функцию в зависимости от choice
switch (choice)
{
case 2:
return Program::subtract;
case 3:
return Program::multiply;
default:
return Program::sum;
}
}
static int sum(int a, int b) { return a + b; }
static int subtract(int a, int b) { return a - b; }
static int multiply(int a, int b) { return a * b; }
}
// Operation представляет метод, который принимает два числа int и возвращает int
interface Operation {
int execute(int a, int b);
}
Здесь метод select() в зависимости от значения параметра возвращает ссылку на один из методов: sum, subtract или multiply.
static Operation select(int choice){
switch (choice){
case 2:
return Program::subtract;
case 3:
return Program::multiply;
default:
return Program::sum;
}
}
В методе main() получаем результат метода select() в переменную:
Operation action = select(1);
То есть в данном случае переменная action фактически будет представлять ссылку Program::sum
Подобным образом мы можем использовать конструкторы: название_класса::new. Например:
public class Program {
public static void main(String[] args) {
UserBuilder userBuilder = User::new;
User user = userBuilder.create("Tom");
System.out.println(user.getName());
}
}
interface UserBuilder{
User create(String name);
}
class User{
private String name;
String getName(){
return name;
}
User(String n){
this.name=n;
}
}
При использовании конструкторов методы функциональных интерфейсов должны принимать тот же список параметров, что и конструкторы класса, и должны возвращать объект данного класса.
Аналогично выражение Person[]::new представляет создание массива объектов Person.