Сравнение сопоставления узоров на разных языках: Java, Scala и многое другое

Сравнение сопоставления узоров на разных языках: Java, Scala и многое другое

26 июля 2025 г.

Сопоставление образцов является основной функцией разработки программного обеспечения. В то время как сопоставление рисунков применяется в нескольких местах, его текущее использование ограниченоswitch caseблоки. Я хочу сравнить силу соответствия шаблонов на нескольких языках программирования, с которыми я знаком в этом посте.

Я предполагаю, что каждый читатель знаком сswitch caseСинтаксис унаследован от C. Короче говоря:

  • АswitchСсылка на пункт о возвращении стоимости заявления.
  • КаждыйcaseПункт устанавливает другое утверждение; Если значение соответствует оператору, оно выполняет соответствующий блок.
  • caseПоложения оцениваются по порядку. Первый пункт, который соответствует, получает свой блок.
  • .In c,caseПоложения проходят через; вам нужно явноbreakчтобы избежатьswitch, в противном случае, в следующемcaseоценивается

Образец Java

Я начну с Java, так как это был первый язык программирования, который я использовал в профессиональном контексте.

Сопоставление рисунков Java сильно развивалось в своих версиях. Вот «официальный» образец для версии 23, слегка справляется. Это определяет несколькоShapeреализации и аstaticМетод оценки его периметра. Это определеннонетХороший пример объектно-ориентированного программирования.

interface Shape { }
record Rectangle(double length, double width) implements Shape { }
record Circle(double radius) implements Shape { }

public class Main {
    static double getPerimeter(Shape s) throws IllegalArgumentException {
        return switch (s) {                                                 //1
            case Rectangle r when r.length() == r.width() -> {              //2
                System.out.println("Square detected");
                    yield 4 * r.length();
            }
            case Rectangle r ->                                             //3
                2 * r.length() + 2 * r.width();
            case Circle c ->                                                //4
                2 * c.radius() * Math.PI;
            default ->                                                      //5
                throw new IllegalArgumentException("Unrecognized shape");
        };
    }
}
  1. Ссылка наShapeпараметр какs
  2. Оценить, есть лиsявляется аRectangleибудь тоRectangleэто квадрат
  3. Оценить, есть лиsявляется аRectangle
  4. Оценить, есть лиsявляется аCircle
  5. Если ни один из предыдущих положений не совпадает, по умолчанию бросить исключение

Эта версия Java будет нашей базовой линией.

Характеристики новогоswitchСинтаксис

Новый синтаксис имеет некоторые преимущества по сравнению с Legacy unrrowswitchунаследован от C.

C-стильcaseПоложения проходят. Как только время выполнения выполнилаcaseБлок, он работает в следующемcaseблокировать. Чтобы избежать этого, вам нужно установитьbreakявно. В не очень хорошие старые времена некоторые разработчики использовали эту функцию, чтобы избежать дублирования кода.

Тем не менее, опыт показал, что это было слишком большой причиной потенциальных ошибок: современные методы кодирования имеют тенденцию избегать этого. Тем не менее, слишком легко забытьbreakПолем С новым синтаксисом стрел время выполнения только оценивает соответствующееcaseблокировать.

C-стильcaseПоложения оценивают только простые значения. Сообщество отметило Java 7 как огромный благо, когда он разрешал струнные значения. Java 21 пошла еще дальше: в сочетании с синтаксисом стрелки, это позволило включить типы. В приведенном выше примере мы проверяем точноеShapeтип.

Кроме того, при включении классовиЕсли классовая иерархияsealedКомпилятор может автоматически обнаружить отсутствующие случаи. Наконец, версия 24 добавила дополнительную дополнительнуюwhenфильтр.

С другой стороны, в старом синтаксисе С, время выполнения прыгает непосредственно на правильноеcaseпункт. Синтаксис новой стрелки оценивает их последовательно, точно так же, как если сif elseзаявления.

В следующих разделах мы перенесем код на другие языки.

Сопоставление рисунка Скалы

Соответствие шаблона Скалы было непревзойденным с момента его создания. Котлин черпал из этого много вдохновения.

trait Shape
case class Rectangle(length: Double, width: Double) extends Shape
case class Circle(radius: Double) extends Shape

def getPerimeter(s: Shape) = {
  s match {                                                                 //1
    case Rectangle(length, width) if length == width =>                     //2-3
      println("Square detected")
      4 * length
    case Rectangle(length, width) => 2 * length + 2 * width                 //3-4
    case Circle(radius) => 2 * radius * Math.PI                             //3-5
    case _ => throw new IllegalArgumentException("Unrecognized shape")      //6
  }
}
  1. Используйтеsссылка напрямую
  2. Оценить, есть лиsявляется аRectangleи будь тоRectangleэто квадрат
  3. Scala дополнительно соответствует атрибутам класса
  4. Оценить, есть лиsявляется аRectangle
  5. Оценить, есть лиsявляется аCircle
  6. Если ни один из предыдущих положений не совпадает, по умолчанию бросить исключение

Сопоставление рисунка Котлина

Давайте переведем код Java в Котлин. Для этого мыдолженактивироватьэкспериментальныйXwhen-guardsФункция компиляции, описанная вKeep-371Полем

interface Shape
data class Rectangle(val length: Double, val width: Double): Shape
data class Circle(val radius: Double): Shape

fun getPerimeter(s: Shape) = when (s) {                                     //1
        is Rectangle if s.length == s.width -> {                            //2
            println("Square detected")
            4 * s.length
        }
        is Rectangle -> 2 * s.length + 2 * s.width                          //3
        is Circle -> 2 * s.radius * Math.PI                                 //4
        else -> throw IllegalArgumentException("Unknown shape")             //5
    }
}
  1. Ссылка наShapeпараметр какs
  2. Оценить, есть лиsявляется аRectangleибудь тоRectangleS.widthравен егоheight
  3. Оценить, есть лиsявляется аRectangle
  4. Оценить, есть лиsявляется аCircle
  5. Если ни один из предыдущих положений не совпадает, по умолчанию бросить исключение

Узор Котлина, совпадающий, очень напоминает Java, с небольшими изменениями синтаксиса,напримерВifзаменяетwhenПолем

Сравнительная эволюция Kotlin vs. Java в соответствии с узорами довольно поучительна. Java отстал за синтаксисом Legacy C, в то время как Kotlin уже мог совпадать с конкретными типами с самого начала. В 2020 году Java 14 сократила разрыв с оператором стрелы; Год спустя Java 16 полностью закрыла разрыв с возможностью соответствовать конкретным типам. Наконец, Java 23 добавилаwhenпункт.

Экспериментальный характер котлинаifозначает, что Java обогнала Java - по крайней мере, в этой области! Это достаточно редко, чтобы быть отмеченным.

Паттерн Пайтона

Раньше Python не предлагал ничего похожего наswitchЗаявление вышеупомянутых языков JVM. Из версии 3.10 он предлагает такую же возможность элегантно:

class Shape:
    pass


class Rectangle(Shape):
    def __init__(self, length: float, width: float):
        self.length = length
        self.width = width


class Circle(Shape):
    def __init__(self, radius: float):
        self.radius = radius


def get_perimeter(s: Shape) -> float:
    match s:
        case Rectangle(length=l, width=w) if l == w:
            print("Square detected")
            return 4 * l
        case Rectangle(length=l, width=w):
            return 2 * l + 2 * w
        case Circle(radius=r):
            return 2 * pi * r
        case _:
            raise ValueError("Unknown shape")

Средство выполнения оцениваетcaseположения последовательно, как на языках JVM.

Растерный рисунок

Подход Rust к управлению памятью не хорошо играет с проверкой типов. Короче говоря, Rust предлагает две базовые концепции:

  • Структуры являются структурированными заполнителями данных; Их размер памяти известен во время компиляции
  • Черты - это контракты, сродни интерфейсам
  • Вы можете предоставить реализацию черты для структуры
  • Ссылка на переменную по его черту имеет последствия. Поскольку ссылка может указывать на структуры разных размеров, компилятор не может знать его размер во время компиляции и должен разместить переменную на кучу вместо стека.

Я попытался перенести оригинальный код в Rust One-One для образовательных целей. Оригинальная Java не использует полиморфизм; Это не здорово. В ржавчине это даже уроднее. Интересно понимать, что, хотя ржавчина не является объектно-ориентированным языком программирования, это заставляет вас использовать полиморфизм.

ПРЕДУПРЕЖДЕНИЕ: Я не рекомендую следующий код и буду отрицать какую -либо связь с ним!

trait Shape: Any {
    fn as_any(&self) -> &dyn Any;                                     //1
}

struct Circle {
    radius: f64,
}

struct Rectangle {
    width: f64,
    height: f64,
}

impl Shape for Circle {
    fn as_any(&self) -> &dyn Any {                                    //2
        self
    }
}
impl Shape for Rectangle {
    fn as_any(&self) -> &dyn Any {                                    //2
        self
    }
}

fn get_perimeter(s: Box<dyn Shape>) -> f64 {
    match s.as_any() {                                                //3
        any if any.is::<Rectangle>() => {                             //4
            let rectangle = any.downcast_ref::<Rectangle>().unwrap(); //5
            if rectangle.width == rectangle.height {
                println!("Square matched");
                4.0 * rectangle.width
            } else {
                2.0 * (rectangle.width + rectangle.height)
            }
        }
        any if any.is::<Circle>() => {                                //4
            let circle = any.downcast_ref::<Circle>().unwrap();       //5
            2.0 * std::f64::consts::PI * circle.radius
        }
        _ => panic!()
    }
}
  1. Система типа Rust не предлагает способ получить тип переменной. Мы должны создать специальную функцию для этого.
  2. Реализовать функцию для структур
  3. Сопоставление на основном типе структуры
  4. Проверьте тип
  5. Вниз до основной структуры, чтобы использовать свои поля

Этот искусственный кодовый порт выше искажает способности Rust's Pattern. Это относится кмного местПолем

Заключение

Среди всех языков, описанных в посте, Scala была первой, чтобы обеспечить сопоставление моделей вswitchположения. В течение многих лет это был Грааль, с которым другие пытались догнать; Котлин и Ява наконец достигли этой стадии.

За пределами JVM, Python и Rust имеют мощные возможности для сопоставления узоров.

Благодаря разрушению, сопоставление шаблонов является огромной помощью для разработчиков, которые хотят написать читаемый и поддерживаемый код.

Полный исходный код для этого поста можно найти наGitHub^Полем

Идти дальше:

  • Образец, соответствующий Java 23
  • Сопоставление рисунков в котлине
  • Сопоставление рисунков в Scala
  • Заявление о матче в Python
  • Паттерн, сопоставляющий в ржавчине

Первоначально опубликовано вJava Geek20 июля 2025 года


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE