arrow-left

All pages
gitbookPowered by GitBook
1 of 4

Loading...

Loading...

Loading...

Loading...

Статические поля и методы

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

Но есть ситуации, в которых такой подход недостаточен. Первая ситуация - это когда некоторые данные должны храниться "в единственном числе" независимо от того, сколько было создано объектов класса. Вторая - когда вам потребуется метод, не привязанный ни к какому конкретному объекту класса (то есть метод, который можно вызвать даже при полном отсутствии объектов класса).

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

Некоторые ОО языки используют термин данные уровня класса и методы уровня класса, подразумевая, что данные и методы существуют только на уровне класса в целом, а не для отдельных объектов этого класса.

hashtag
Понятие статического поля

Чтобы сделать данные или метод статическими, просто поместите ключевое слово static перед их определением. Например, следующий код создает статическое поле класса и инициализирует его:

Теперь, даже при создании двух объектов StaticTest, для элемента StaticTest.val выделяется единственный блок памяти. Оба объекта совместно используют одно значение val. Пример:

В данном примере как st1.val, так и st2.val имеют одинаковые значения, равные 924, потому что они ссылаются на один блок памяти.

Существует два способа обратиться к статической переменной. Как было видно выше, на нее можно ссылаться по имени объекта, например st2.val. Также возможно обратиться к ней прямо через имя класса; для нестатических членов класса такая возможность отсутствует.

После выполнения операции, значения st1.val и st2.val будут равны 48.

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

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

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

hashtag
Понятие статического метода

Та же логика верна и для статических методов. Вы можете обратиться к такому методу или через объект, как это делается для всех методов, или в специальном синтаксисе ИмяКласса.метод(). Статические методы определяются по аналогии со статическими данными:

Нетрудно заметить, что метод increment() класса Incrementable увеличивает значение статического поля val. Метод можно вызвать стандартно, через объект:

Или, поскольку метод increment() является статическим, можно вызвать его с прямым указанием класса:

Если применительно к полям ключевое слово static радикально меняет способ определения данных (статические данные существуют на уровне класса, в то время как нестатические данные существуют на уровне объектов), то в отношении методов изменения не столь принципиальны. Одним из важных применений static является определение методов, которые могут вызываться без объектов.

На статические методы налагаются следующие ограничения:

  • они могут непосредственно вызывать только другие статические методы;

  • им непосредственно доступны только статические переменные;

  • им недоступны ключевые слова this или super.

hashtag
Статический импорт

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

Рассмотрим пример без использования статического импорта. Представим себе, что мы вычисляем гипотенузу прямоугольного треугольника. Мы будем часто использовать методы Math.pow() и Math.sqrt()

Как мы видим, строка

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

После использования статического импорта нет нужды использовать имя класс Math для вызова статических методов pow() и sqrt().

Если предполагается применять много статических методов или полей, определенных в классе, то можно импортировать класс Math полностью

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

StaticTest.java
class StaticTest {
    static int val = 924;
}
StaticTest st1 = new StaticTest();
StaticTest st2 = new StaticTest();
StaticTest.val++;
class NetworkConnector {

    public static final int baud;
    private static final int bits_per_interval = 4;

    static {
        baud = 9600 / bits_per_interval;
    }

    // ...
}
class Incrementable {
    static void increment() {
        StaticTest.val++;
    }
}
Incrementable sf = new Incrementable();
sf.increment();
Incrementable.increment();
double side1 = 3.0;
double side2 = 4.0;
double hypot;

hypot = Math.sqrt(Math.pow(side1, 2) + Math.pow(si de2, 2));
hypot = Math.sqrt(Math.pow(side1, 2) + Math.pow(side2, 2));
import static java.lang.Math.pow;
import static java.lang.Math.sqrt;

public class Main {
    public static void main(String[] args) {

        double side1 = 3.0;
        double side2 = 4.0;
        double hypot;

        hypot = sqrt(pow(side1, 2) + pow(side2, 2));
    }
}
import static java.lang.Math.*;

Блок лекций 5

Перечисления

Механизм перечислений был добавлен в JDK 5 версии, он позволяет удобно хранить и работать с так называемыми "категориальными данными".

Категориальные данные - это данные с ограниченным числом уникальных значений или категорий. Приведем примеры категориальных данных:

  • месяцы года (12 значений: январь, февраль и так далее);

  • пол (мужской, женский, андроген, бесполый и так далее);

  • вероисповедание (православие, католицизм, ислам и так далее);

  • день недели (понедельник, вторник, среда и так далее).

Рассмотрим пример. Для информационной системы «Электронный институт» необходимо создать класс сотрудника кафедры «DepartmentMember», в котором, среди прочих атрибутов, есть атрибут «должность» (position). С целью упрощения, предположим, что сотрудники кафедры могут занимать одну из следующих должностей:

  • «Инженер» (Engineer);

  • «Ассистент» (Assistant);

  • «Старший преподаватель» (Lecturer);

Каким образом закодировать данное значение? Самый первый и очевидный вариант - просто использовать тип данных String.

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

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

Другой вариант - это создать несколько статических целочисленных констант, которые можно использовать вместо строк.

Теперь мы сможем использовать именованные константы вместо строго фиксированных строк.

Использование именованных констант позволяет отчасти избавиться от недостатков предыдущего подхода, но и этот вариант не является приемлемым. Ничто не мешает пользователю класса при создании объекта указать любое целое числа вместо именованной константы, осуществлять проверку ввода – достаточно трудоемкая задача, если в классе предусмотрено более одного категориального значения будет тяжело определять какие конкретно именованные константы необходимо использовать. К тому же, при использовании конструктора или сеттера поля «должность», нет никакого указания на необходимость использования именованных констант, программист сам должен догадаться о наличии нужных констант или читать сопроводительную документацию.

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

Корректным вариантом решения данной проблемы является использование специального механизма перечислений (enumeration). По сути, перечисление – это тип, ссылочная переменная которого может принимать одно из нескольких заранее определенных значений. Реализуем класс «DepartmentMember» с помощью механизма перечислений:

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

Обратите внимание на синтаксис перечисления. Перечисление является классом, но очень своеобразным. Вместо ключевого слова class используется ключевое слово enum (от слова enumeration – перечисление). Вместо полей и методов у перечисления идет просто набор констант через запятую.

Как уже было сказано, перечисление – это класс, а это значит, что он может иметь методы, и мы можем инкапсулировать перечисление и операции работы с ним в одной оболочке.

В данном примере, экземпляр перечисления хранит строку с цветом.

«Доцент» (Senior Lecturer);
  • «Профессор» (Professor).

  • public class DepartmentMember {
    
        private String firstName;
        private String lastName;
        private String position;
    
        public DepartmentMember
                (String firstName, String lastName, String position) {
            this.firstName = firstName;
            this.lastName = lastName;
            this.position = position;
        }
    }
    public static void main(String[] args) {
    
        DepartmentMember member1 = new DepartmentMember(
                "Иван",
                "Иванов",
                "доцент"
        );
    
        DepartmentMember member2 = new DepartmentMember(
                "Иван",
                "Иванов",
                "старший преподаватель"
        );
    }
    class DepartmentMember {
    
        public static final int ENGINEER = 0;
        public static final int ASSISTANT = 1;
        public static final int LECTURER = 2;
        public static final int SENIOR_LECTURER = 3;
        public static final int PROFESSOR = 4;
    
        private String firstName;
        private String lastName;
        private int position;
    
        public DepartmentMember
                (String firstName, String lastName, int position) {
            this.firstName = firstName;
            this.lastName = lastName;
            this.position = position;
        }
    }
    public static void main(String[] args) {
    
        DepartmentMember member1 = new DepartmentMember(
                "Иван",
                "Иванов",
                DepartmentMember.ENGINEER
        );
    
        DepartmentMember member2 = new DepartmentMember(
                "Петр",
                "Петров",
                DepartmentMember.PROFESSOR
        );
    }
    class DepartmentMember {
    
        public static final int ENGINEER = 0;
        public static final int ASSISTANT = 1;
        public static final int LECTURER = 2;
        public static final int SENIOR_LECTURER = 3;
        public static final int PROFESSOR = 4;
    
        public static final int FULL_TIME = 100;
        public static final int PART_TIME = 101;
    
        private String firstName;
        private String lastName;
        private int position;
        private int contractType;
    
        public DepartmentMember
                (String firstName, String lastName, int position, int contractType) {
            this.firstName = firstName;
            this.lastName = lastName;
            this.position = position;
            this.contractType = contractType;
        }
    }
    public static void main(String[] args) {
    
        DepartmentMember member1 = new DepartmentMember(
                "Петр",
                "Петров",
                DepartmentMember.FULL_TIME, // ???
                DepartmentMember.PART_TIME  // ???
        );
    }
    class DepartmentMember {
    
        private String firstName;
        private String lastName;
        private Position position;
    
        public DepartmentMember
                (String firstName, String lastName, Position position) {
            this.firstName = firstName;
            this.lastName = lastName;
            this.position = position;
        }
    
        public enum Position {
            Engineer, Assistant, Lecturer, SeniorLecturer, Professor;
        }
    }
    public static void main(String[] args) {
    
        DepartmentMember member1 = new DepartmentMember(
                "Иван",
                "Иванов",
                DepartmentMember.Position.Assistant
        );
    
        DepartmentMember member2 = new DepartmentMember(
                "Петр",
                "Петров",
                DepartmentMember.Position.Professor
        );
    }
    enum Palette {
    
        RED("#ff0000"),
        YELLOW("#ffff00"),
        CYAN("#00ffff"),
        GREEN("#008000");
    
        private final String color;
    
        Palette(String color) {
            this.color = color;
        }
    }
    class Controller implements Initializable {
        @FXML Button button1;
        @FXML Button button2;
        @FXML Button button3;
        @FXML Button button4;
    
        @Override
        public void initialize(URL location, ResourceBundle resources) {
            button1.setStyle("-fx-background-color: " + Palette.CYAN);
            button2.setStyle("-fx-background-color: " + Palette.GREEN);
            button3.setStyle("-fx-background-color: " + Palette.YELLOW);
            button4.setStyle("-fx-background-color: " + Palette.RED);
        }
    }

    Вложенные и внутренние классы

    В Java определены вложенные классы. Вложенным называется такой класс, который объявляется в другом классе.

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

    Существуют два типа вложенных классов. Одни вложенные классы объявляются с помощью модификатора доступа static, а другие - без него. Нестатический вариант вложенных классов называются внутренними.

    Внутренние классы позволяют вам группировать классы, логически принадлежащие друг другу, и управлять доступом к ним. Однако важно понимать, что внутренние классы заметно отличаются от композиции.

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

    hashtag
    Создание внутренних классов

    Создать внутренний класс несложно - достаточно разместить определение класса внутри окружающего класса.

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

    Иногда внутренний класс используется для предоставления ряда услуг внешнему классу, в котором он содержится.

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

    Также внутренний класс можно быть анонимным классом (анонимные классы уже были рассмотрены ранее).

    public class Main {
        public static void main(String[] args) {
            
            class Local {
                private int value;
    
                private Local(int value) {
                    this.value = value;
                }
            }
            
            Local local = new Local(10);
        }
    }
    public class Main {
    
        public static void main(String[] args) {
            Parcel parcel = new Parcel();
            parcel.ship("Лимпопо");
        }
    }
    
    class Parcel {
    
        class Contents {
            private int i = 11;
            public int value() {return i;}
        }
        
        class Destination {
            private String label;
    
            public Destination(String label) {
                this.label = label;
            }
    
            public String getLabel() {
                return label;
            }
        }
        
        public void ship(String destination) {
            Contents c = new Contents();
            Destination d = new Destination(destination);
            System.out.println(d.getLabel());
        }
    }
    public class Outer {
    
        private List<Integer> numbers = new ArrayList<>();
    
        public void printAnalyticalData() {
            Inner inner = new Inner();
    
            System.out.println("Min: " + inner.min());
            System.out.println("Max: " + inner.max());
            System.out.println("Sum: " + inner.sum());
        }
    
        private class Inner {
            private int min() {
                return numbers.stream().min(Integer::compareTo).orElse(0);
            }
    
            private int max() {
                return numbers.stream().max(Integer::compareTo).orElse(0);
            }
    
            private int sum() {
                return numbers.stream().reduce(Integer::sum).orElse(0);
            }
        }
    }