А действительно ли Java объектно ориентированная?

Привет, друзья!

Не так давно я начал учить новый ЯП, выбрал Scala из-за его лаконичности и просто захотелось попробовать чего-то современного а не 20-ти летнего. И что я могу сказать? Я пришел к выводу что Java не такая уж и объектно ориентированная. Я понял то, что на Java можно писать в функциональном стиле. Да, Java не умеет оптимизировать рекурсию как scala, например посмотрим на такую функцию в java и на нее же в scala

Java:

public class RecursiveSearch {
public static void main(String ...args) {
final List<Integer> nums = Arrays.asList(1, 3, 5, 7, 9, 11, 12);
System.out.println("Is exist even number: " + recursiveIsPresentEvenNumber(nums));
}

private static boolean recursiveIsPresentEvenNumber(final List<Integer> numbers) {
if (nums.size() == 1) {
return nums.get(0) % 2 == 0;
}
return isPresentEvenNum(nums.subList(1, nums.size()));
}
}

Этот код выполняется рекурсивно без оптимизаций, чтобы доказать это, давайте перепишем `recursiveIsPresentEvenNumber` вот так

 private static boolean isPresentEvenNum(final List<Integer> nums) {
if (nums.size() == 1) {
throw new RuntimeException();
}
return isPresentEvenNum(nums.subList(1, nums.size()));
}

Мы просто выбросим исключение, когда приблизимся к концу списка, увидим мы вот что

Exception in thread "main" java.lang.RuntimeException
at RecursiveSearch.isPresentEvenNum(RecursiveSearch.java:13)
at RecursiveSearch.isPresentEvenNum(RecursiveSearch.java:15)
at RecursiveSearch.isPresentEvenNum(RecursiveSearch.java:15)
at RecursiveSearch.isPresentEvenNum(RecursiveSearch.java:15)
at RecursiveSearch.isPresentEvenNum(RecursiveSearch.java:15)
at RecursiveSearch.isPresentEvenNum(RecursiveSearch.java:15)
at RecursiveSearch.isPresentEvenNum(RecursiveSearch.java:15)
at RecursiveSearch.main(RecursiveSearch.java:8)

Здесь мы видим несколько фреймов на стеке, вот это все

 at RecursiveSearch.isPresentEvenNum(RecursiveSearch.java:13)
at RecursiveSearch.isPresentEvenNum(RecursiveSearch.java:15)
at RecursiveSearch.isPresentEvenNum(RecursiveSearch.java:15)
at RecursiveSearch.isPresentEvenNum(RecursiveSearch.java:15)
at RecursiveSearch.isPresentEvenNum(RecursiveSearch.java:15)
at RecursiveSearch.isPresentEvenNum(RecursiveSearch.java:15)
at RecursiveSearch.isPresentEvenNum(RecursiveSearch.java:15)

Это рекурсивный вызов на стеке, что доказывает нам, что Java компилятор не может оптимизировать рекурсию при компиляции. Посмотрим тот же самый пример на scala

object Main {
def main(args: Array[String]): Unit = {
val input = List(1, 3, 5, 7, 9, 11, 12)

println(s"Is present even number: ${isPresentEvenNum(input)}")
}

private def isPresentEvenNum(nums: List[Int]): Boolean =
if (nums.size == 1) nums.head % 2 == 0 else isPresentEvenNum(nums.drop(1))
}

Во-первых, давайте посмотрим на то, насколько scala лаконичнее Java, нет лишней многословности присущей Java. Во-вторых, давайте проведем тот же самый эксперимент со стек фреймами, выкинем исключение, код у нас в итоге будет следующим

object Main {
def main(args: Array[String]): Unit = {
val input = List(1, 3, 5, 7, 9, 11, 12)

println(s"Is present even number: ${isPresentEvenNum(input)}")
}

private def isPresentEvenNum(nums: List[Int]): Boolean =
if (nums.size == 1) throw new RuntimeException else isPresentEvenNum(nums.drop(1))
}

Теперь давайте взглянем на стек трейс

java.lang.RuntimeException
at Main$.isPresentEvenNum(recursion.scala:9)
at Main$.main(recursion.scala:5)
at Main.main(recursion.scala)

Опа, а scala смогла оптимизировать хвостовую рекурсию, на самом деле удивляться нечему, у скалы уклон в функциональную парадигму сильнее чем в императивную. Но я не просто так начал писать о том, что Java тоже поддерживает ФП, хоть и хуже чем scala.

Почему я так решил?

Потому что в Java есть все что есть в Scala для функционального программирования, за исключением оптимизации хвостовой рекурсии :)

Кстати, scala ее оптимизирует практически до цикла, она вызывает функцию в том же фрейме что и вызвался в первый раз, грубо говоря это идентично циклу while, подробнее могу рассказать если захотите, пишите в комментах.

Так вот, Java поддерживает следующие вещи которые есть в Scala

  • Функции высшего порядка
  • Лямбды
  • Замыкания
  • Иммутабельность
  • Не изменяемые значения (final аргументы и локальные переменные и члены классов)

Эти составляющие позволяют нам использовать функциональную парадигму в Java. Давайте рассмотрим на примерах (помните что Scala функциональная), напишем простой пример с функцией высшего порядка

def filter(items: List[Int], predicate: (Int) => Boolean): Iterable[Int] = 
for (item <- items if predicate(item)) yield item

val items = List(1, 2, 3, 4, 5, 6, 7, 8)

println(filter(items, _ % 2 == 0))

Здесь мы создали функцию filter которая принимает на входе некоторую коллекцию целочисленных элементов и предикат которым мы будем фильтровать эту коллекцию (да, в реальном коде можно сразу использовать метод filter у самой коллекции, но для примера нам нужно написать свой). Эта функция дальше в теле проходит по каждому элементу и генерирует новый список с числами которые удовлетворяют предикату переданному в функцию, наш предикат это

_ % 2 == 0

Это эквивалент записи без символов подстановки

x => x % 2 == 0

Предикат берет только те числа, которые делятся на 2 без остатка. Теперь давайте напишем тоже самое только на Java

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class Predicates {
public static void main(String[] args) {
final List<Integer> items = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);

System.out.println(filter(items, item -> item % 2 == 0));
}

private static List<Integer> filter(final List<Integer> items,
final Predicate<Integer> predicate) {
final List<Integer> output = new ArrayList<>();
for (final Integer n : items) {
if (predicate.test(n)) {
output.add(n);
}
}
return output;
}
}

Это полный эквивалент на Java, да, он менее лаконичный, но имеет тот же смысл и не чуть не уступает в функциональности! Здесь демонстрируются неизменяемые аргументы и функция высшего порядка (predicate).

Остальные примеры я покажу в следующей статье. Хоп!

Java
14.03.2018
1 ответ
авторизуйтесь чтобы ответить