Статьи

Класс Bean для программирования на Java

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

Первоначальная концепция bean-компонента, называемого JavaBean, датируется 1996 годом. Она должна была стать компонентной моделью для визуальных элементов программы. Этими компонентами можно легко манипулировать с помощью инструмента визуального моделирования. Вы можете прочитать текущую спецификацию по адресу http://tinyurl.com/ybzwmldq, который последний раз обновлялся в 1997 году.

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

Обязательное

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

  • Должен быть конструктор по умолчанию.

  • Методы для чтения или записи переменной экземпляра должен начинаться с префиксов установить  или получить .

  • Класс bean-компонента должен реализовывать сериализуемый интерфейсOptional:

Необязательный

  • Класс бина должен иметь  метод toString .

  • Класс bean должен иметь hashCode  и  метод equals

  • Класс bean-компонента должен реализовывать интерфейс Comparable и иметь  метод CompareTo .

бонус

  • Сопутствующий сопоставимый объект для случаев, когда вам нужно иметь более одного возможного сравнения

  • Java 8 лямбда-реализация Comparator

  • Функциональная реализация компаратора в Java 8

Например, рассмотрим проблему, в которой необходимо смоделировать книгу. Вот основной боб.

public class Book implements Serializable{

    private String isbn;
    private String title;
    private String author;
    private String publisher;
    private int pages;

    /**
     * Default constructor
     */
    public Book() {
        this.isbn = "";
        this.title = "";
        this.author = "";
        this.publisher = "";
        this.pages = 0;
    }

    public String getIsbn() {
        return isbn;
    }

    public void setIsbn(final String isbn) {
        this.isbn = isbn;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(final String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(final String author) {
        this.author = author;
    }

    public String getPublisher() {
        return publisher;
    }

    public void setPublisher(final String publisher) {
        this.publisher = publisher;
    }

    public int getPages() {
        return pages;
    }

    public void setPages(final int pages) {
        this.pages = pages;
    }
}

Основной Бин 1

Методы чтения и записи должны соответствовать соглашению об именах. Они должны начать с набором , получить или это все в нижнем регистре. Является префиксом является альтернативой получить , когда тип возвращаемого значения является логическим. За ними должна следовать заглавная буква. Они обычно соответствуют закрытым полям в классе. Вы также можете добавить любые дополнительные методы, которые могут потребоваться вашей логике.

Теперь у вас есть класс, который моделирует книгу. Любая часть вашего кода теперь требует только одну ссылку на объект типа Book для представления всех точек данных или полей для книги. Чтение или запись в любое поле теперь должно проходить через метод. Сериализуемый интерфейс позволяет сохранить состояние объекта, записав его на диск. Для распределенных приложений, таких как веб-службы, это позволяет передавать объект через Интернет. Просмотрите документацию по сериализации, поскольку не каждый тип класса может быть сериализован без дополнительного кода. Примитивы и строки могут быть сериализованы без дополнительного кода.


Специальное примечание 1: у

меня здесь был параграф, в котором неправильно указывалось, что сериализуемый объект требует конструктора по умолчанию и методов установки, но Майкл Саймонс @ rotnroll666 указал, что это неверно. Я должен был просмотреть документацию, которую я рекомендовал вам просмотреть. Сериализация не требует конструктора по умолчанию или методов установки.

public class Book implements Serializable{

    private String isbn;
    private String title;
    private String author;
    private String publisher;
    private int pages;

    /**
     * Default constructor
     */
    public Book() {
        this.isbn = "";
        this.title = "";
        this.author = "";
        this.publisher = "";
        this.pages = 0;
    }

    /**
     * Non-default constructor
     *
     * @param isbn
     * @param title
     * @param author
     * @param publisher
     * @param pages
     */
    public Book(final String isbn, final String title, final String author, final String publisher, final int pages) {
        this.isbn = isbn;
        this.title = title;
        this.author = author;
        this.publisher = publisher;
        this.pages = pages;
    }

Основной Бин 2

Обратите внимание, что все параметры в методах являются окончательными . Конечная переменная не может изменить свое значение. Если вы передаете ссылку в качестве аргумента любому методу, который не объявляет свой параметр как окончательный, вы можете изменить адрес, на который указывает аргумент, и это может привести к непреднамеренному побочному эффекту. Бин классы никогда не должны изменять аргумент.


Аргумент — это значение или ссылка, переданная методу.
Параметр — это заполнитель в сигнатуре метода, который будет представлять значение аргумента в методе.

Примитивные типы аргументов, такие как int или double , не имеют связи с исходным значением вызывающей стороны, поэтому final не требуется. Тем не менее, рекомендуется объявить окончательный вариант, так как не следует его менять.

Теперь у нас есть правильный класс бобов. Можно добавить четыре дополнительных метода, которые сделают компонент более полезным в определенных обстоятельствах. Первое из этих обстоятельств — когда вам нужно представить бин в виде строки. Наиболее распространенной причиной является поддержка отладки, позволяя вам исследовать значение каждого поля в bean-компоненте в операторе журнала. Это работа метода toString .

@Override
public String toString() {
        return "Book{" + "isbn=" + isbn + ", title=" + title + ", author=" + author + ", publisher=" + publisher + ", pages=" + pages + '}';
}

Метод toString наследуется от объекта суперкласса, который расширяет каждый класс в Java. Аннотация @Override, хотя и является необязательной, проясняет, что вы переопределяете метод суперкласса. То, что вы положили в строку, зависит от вас. Например, при размещении объекта в дереве GUI метод toString является значением по умолчанию для того, что должно отображаться на экране в дереве, и поэтому может возвращать значение одного поля.

hashCode и Equals

Следующими двумя методами являются hashCode и equals . Они всегда должны идти вместе, когда ваш код должен проверять, имеют ли два объекта одинаковые значения в своих полях. Может показаться, что равно равно все, что вам нужно, но здесь есть проблема с производительностью. Наш класс Book имеет 4 строковых поля. При сравнении двух объектов Book необходимо будет сравнить каждый символ в каждой строке, чтобы определить, равны ли они. Метод hashCode создает целочисленное значение, называемое хеш-кодом, который представляет все значения в объекте. Это значение можно сравнить с соответствующим значением в объекте, с которым мы сравниваем. Если значение хеш-кода не совпадает, то мы точно знаем, что они не обладают одинаковыми значениями.

Хеш-коды не являются уникальными. Возможно, что два объекта с разными значениями могут возвращать один и тот же хэш-код. Поэтому, если хэш-коды равны, мы должны вызвать метод equals, чтобы убедиться, что они одинаковы. Хеш-коды используются в структурах данных коллекций Java. Если вы намереваетесь использовать такую ​​структуру, как HashMap или HashSet, у вас должен быть hashCode и equals в бине.

@Override
    public int hashCode() {
        int hash = 7;
        hash = 61 * hash + Objects.hashCode(this.isbn);
        hash = 61 * hash + Objects.hashCode(this.title);
        hash = 61 * hash + Objects.hashCode(this.author);
        hash = 61 * hash + Objects.hashCode(this.publisher);
        hash = 61 * hash + this.pages;
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Book other = (Book) obj;
        if (this.pages != other.pages) {
            return false;
        }
        if (!Objects.equals(this.isbn, other.isbn)) {
            return false;
        }
        if (!Objects.equals(this.title, other.title)) {
            return false;
        }
        if (!Objects.equals(this.author, other.author)) {
            return false;
        }
        if (!Objects.equals(this.publisher, other.publisher)) {
            return false;
        }
        return true;
    }

hashCode и равно

Эти два метода были созданы в среде IDE, которую я использую, NetBeans. Большинство IDE, таких как Eclipse и IntelliJ, будут генерировать эти методы. Вы можете исключить определенные поля из этих методов, но поля, которые вы используете, должны быть одинаковыми в hashCode и равно .


Специальное примечание 2:

в комментариях упоминалось, что, как представлено, этот метод hashCode проблематичен при использовании в структуре данных Hash. Поля в классе являются изменяемыми / изменяемыми, и это может привести к тому, что объект будет помещен в хеш-структуру, которая никогда не будет найдена при изменении поля. Это тип утечки памяти. Решение состоит в том, чтобы сделать все поля, используемые для построения хеш-кода, окончательными / неизменяемыми. 


Специальное примечание 3:

Этот следующий раздел был переписан из первой версии этой статьи на основе комментариев Майкла Саймонса @ rotnroll666. Первоначально я предложил несколько сопоставимых интерфейсов и несколько методов сравнения в компоненте. Майкл отметил, что это приведет к раздуванию кода, поскольку бин будет увеличиваться с каждым дополнительным методом compareTo. Он рекомендовал сопоставимые объекты, которые существуют за пределами бобов.

Сопоставимый интерфейс и сравнение с

У вас есть два варианта упорядочения объектов при использовании методов сортировки или самосортирующихся коллекций, таких как карта. Первый вариант — реализовать   метод сравнения в бине . Этот метод необходим при реализации интерфейса Comparable. Этот отсортированный порядок, полученный в результате, называется естественным порядком, а метод CompareTo называется методом естественного упорядочения. Вы должны решить, какие поля будут соответствовать критериям порядка сортировки. Я хотел бы иметь возможность сортировать книги по названию.

/**
 * Natural comparison method for Title
 *
 * @param book
 * @return negative value, 0, or positive value
 */
@Override
public int compareTo(Book book) {
    return this.title.compareTo(book.title);
}

метод сравнения

Я использую тот факт, что объекты String реализуют интерфейс Comparable. Если мой заголовок this.title стоит перед заголовком в параметре Book, тогда разница в кодах ASCII для первых отличающихся символов возвращается как отрицательное значение. Если строки идентичны, возвращается 0. Если мой заголовок идет после заголовка в параметре Book, тогда разница в коде ASCII для первых отличающихся символов возвращается как положительное значение

компаратор

У вас может быть только один метод compareTo, который переопределяет требование интерфейса Comparable. Это приводит ко второму подходу к сравнению. Если вам нужно несколько сравнений, каждое из которых имеет разные поля в качестве критерия, то лучшим решением будет создание сопоставимых объектов. Таким образом, вы можете сравнить любые части объектов. Вам просто нужен Comparable объект для каждого сравнения, и оригинальный компонент не изменяется.

package com.kenfogel.book;

import java.util.Comparator;

public class BookPageComparator implements Comparator<Book> {

    /**
     * The interface mandated compare method
     *
     * @param book1
     * @param book2
     * @return negative value, 0, or positive value
     */
    @Override
    public int compare(Book book1, Book book2) {
        return book1.getPages() - book2.getPages();
    }
}

Класс компаратора

Компаратор лямбда

Интерфейс Comparable позволяет использовать лямбда-выражения, что продемонстрировано в тестовом коде. Это устраняет необходимость в сопоставимом объекте. Однако, если логика для определения порядка сортировки имеет длину более трех строк, вам лучше использовать объект Comparator. Вот как выглядят эти два подхода сравнения для сортировки массива.

Arrays.sort(myBooks); // uses the Comparable compareTo in the bean
Arrays.sort(myBooks, bookPageComparator); // uses the Comparator object
Arrays.sort(myBooks, (s1, s2) -> {
            return s1.getPublisher().compareTo(s2.getPublisher());
        }); // uses a Comparator lambda

Сортировка массива

Java 8 Функциональный Компаратор

Java 8 представила новые методы в интерфейсе Comparator, который упрощает сравнение с объектами. Я хочу отсортировать свои книги по номеру ISBN. Поскольку строка имеет естественный порядок, я могу сообщить методу сортировки, какой компаратор использовать, следующим образом.

Arrays.sort(myBooks, comparing(Book::getIsbn)); // Comparable function

Посмотрите тестовую программу чуть ниже, чтобы увидеть, как работают Comparable и Comparator.

Полный Боб

Вот окончательный вариант боба Book.

mport java.io.Serializable;
import java.util.Objects;

public class Book implements Serializable, Comparable<Book> {

    private String isbn;
    private String title;
    private String author;
    private String publisher;
    private int pages;

    /**
     * Default constructor
     */
    public Book() {
        this.isbn = "";
        this.title = "";
        this.author = "";
        this.publisher = "";
        this.pages = 0;
    }

    /**
     * Non-default constructor
     *
     * @param isbn
     * @param title
     * @param author
     * @param publisher
     * @param pages
     */
    public Book(final String isbn, final String title, final String author, final String publisher, final int pages) {
        this.isbn = isbn;
        this.title = title;
        this.author = author;
        this.publisher = publisher;
        this.pages = pages;
    }

    public String getIsbn() {
        return isbn;
    }

    public void setIsbn(final String isbn) {
        this.isbn = isbn;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(final String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(final String author) {
        this.author = author;
    }

    public String getPublisher() {
        return publisher;
    }

    public void setPublisher(final String publisher) {
        this.publisher = publisher;
    }

    public int getPages() {
        return pages;
    }

    public void setPages(final int pages) {
        this.pages = pages;
    }

    @Override
    public String toString() {
        return "Book{" + "isbn=" + isbn + ", title=" + title + ", author=" + author + ", publisher=" + publisher + ", pages=" + pages + '}';
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 61 * hash + Objects.hashCode(this.isbn);
        hash = 61 * hash + Objects.hashCode(this.title);
        hash = 61 * hash + Objects.hashCode(this.author);
        hash = 61 * hash + Objects.hashCode(this.publisher);
        hash = 61 * hash + this.pages;
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Book other = (Book) obj;
        if (this.pages != other.pages) {
            return false;
        }
        if (!Objects.equals(this.isbn, other.isbn)) {
            return false;
        }
        if (!Objects.equals(this.title, other.title)) {
            return false;
        }
        if (!Objects.equals(this.author, other.author)) {
            return false;
        }
        if (!Objects.equals(this.publisher, other.publisher)) {
            return false;
        }
        return true;
    }

    /**
     * Natural comparison method for Title
     *
     * @param book
     * @return negative value, 0, or positive value
     */
    @Override
    public int compareTo(Book book) {
        return this.title.compareTo(book.title);
    }
}

Полный бин

Тестер бобов

Вот тестовая программа, которая демонстрирует два способа сравнения: комментарии описывают, какой подход используется.

// Required for Arrays.sort
import java.util.Arrays;
// Required by the Comparator function
import static java.util.Comparator.comparing;

public class BeanTester {

    /**
     * Here is where I am testing my comparisons
     *
     */
    public void perform() {
        // Lets create four books
        Book b0 = new Book("200", "Xenon", "Hamilton", "Harcourt", 99);
        Book b1 = new Book("500", "Boron", "Bradbury", "Prentice", 108);
        Book b2 = new Book("300", "Radon", "Heinlein", "Thompson", 98);
        Book b3 = new Book("404", "Argon", "Campbell", "Hachette", 102);

        // Using Comparable to compare two books
        System.out.println("Value returned by Comparable");
        System.out.println(b0.getTitle() + " compared to " + b1.getTitle() + ": "
                + b0.compareTo(b1));
        System.out.println();

        // Using Comparator to compare two books
        System.out.println("Value returned by Comparator");
        BookPageComparator bookPageComparator = new BookPageComparator();
        System.out.println(b0.getPages() + " compared to " + b1.getPages() + ": "
                + bookPageComparator.compare(b0, b1));
        System.out.println();

        // Create an array we can sort
        Book[] myBooks = new Book[4];
        myBooks[0] = b0;
        myBooks[1] = b1;
        myBooks[2] = b2;
        myBooks[3] = b3;
        System.out.println("Unsorted");
        displayBooks(myBooks);

        System.out.println("Sorted with Comparable Interface on Title");
        Arrays.sort(myBooks); // uses the Comparable compareTo in the bean
        displayBooks(myBooks);

        System.out.println("Sorted with Comparable Object on Pages");
        Arrays.sort(myBooks, bookPageComparator); // uses the Comparator object
        displayBooks(myBooks);

        System.out.println("Sorted with Comparable lambda expression on Publishers");
        Arrays.sort(myBooks, (s1, s2) -> {
            return s1.getPublisher().compareTo(s2.getPublisher());
        }); // uses the Comparator lambda
        displayBooks(myBooks);

        System.out.println("Sorted with Comparable lambda function on ISBN");
        Arrays.sort(myBooks, comparing(Book::getIsbn)); // Comparable function
        displayBooks(myBooks);
    }

    /**
     * Print the contents of each Book object in the array
     *
     * @param theBooks
     */
    private void displayBooks(Book[] theBooks) {
        for (Book b : theBooks) {
            System.out.print(b.getIsbn() + "\t");
            System.out.print(b.getTitle() + "\t");
            System.out.print(b.getAuthor() + "\t");
            System.out.print(b.getPublisher() + "\t");
            System.out.println(b.getPages());
        }
        System.out.println();
    }

    /**
     * Where it all begins
     *
     * @param args
     */
    public static void main(String[] args) {
        BeanTester bt = new BeanTester();
        bt.perform();
        System.exit(0);
    }
}

Bean Tester

Выход этой программы будет:

Value returned by Comparable
Xenon compared to Boron: 22

Value returned by Comparator
99 compared to 108: -9

Unsorted
200XenonHamiltonHarcourt99
500BoronBradburyPrentice108
300RadonHeinleinThompson98
404ArgonCampbellHachette102

Sorted with Comparable Interface on Title
404ArgonCampbellHachette102
500BoronBradburyPrentice108
300RadonHeinleinThompson98
200XenonHamiltonHarcourt99

Sorted with Comparable Object on Pages
300RadonHeinleinThompson98
200XenonHamiltonHarcourt99
404ArgonCampbellHachette102
500BoronBradburyPrentice108

Sorted with Comparable lambda expression on Publishers
404ArgonCampbellHachette102
200XenonHamiltonHarcourt99
500BoronBradburyPrentice108
300RadonHeinleinThompson98

Sorted with Comparable lambda functions based on ISBN
200XenonHamiltonHarcourt99
300RadonHeinleinThompson98
404ArgonCampbellHachette102
500BoronBradburyPrentice108

Вывод

Биновые классы необходимы при использовании нескольких библиотек в Java, таких как внедрение зависимостей контекста (CDI) и проверка бинов. Вы также будете использовать классы компонентов для представления данных из базы данных при использовании Java Database Connectivity (JDBC). Бины, которые поддерживают элементы управления JavaFX, кодируются по-разному, но на 100% обратно совместимы с простыми классами компонентов. Смотрите мою следующую статью о бинах JavaFX.