Как выйти из Main и уменьшить статику

Это часть небольшой серии статей о хороших привычках для Java-программистов.

В начале…

Когда вы пишете свою первую программу на Java, вы сталкиваетесь с необходимостью иметь метод с сигнатурой

public static void main(String args[])
Войти в полноэкранный режим Выйти из полноэкранного режима

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

Затем, что бы вы ни изучали дальше, вы, вероятно, будете читать и писать короткие программы, в которых будут использоваться новые биты языка Java внутри этого метода public static void main. Возможно, вы учитесь выполнять итерации с помощью цикла for, и поэтому прочитаете или напишете небольшую программу для суммирования всех четных чисел от 0 до 10.

public static void main(String args[]) {
    int sumOfEvens = 0;
    for (int even = 0; even <= 10; even += 2) {
        sumOfEvens += even;
    }
    System.out.println("The sum of evens from 0 to 10 is " + sumOfEvens);
}
Вход в полноэкранный режим Выход из полноэкранного режима

С точки зрения преподавания и обучения имеет смысл держать этот код в main: это самое простое место для кода, и, не внося дополнительных сложностей, преподаватель и вы можете сосредоточиться на других изучаемых концепциях — скажем, на том, как работает цикл for, или как использовать локальную переменную (sumOfEvens), или как работает алгоритм (прибавление 2 каждый раз приводит нас к следующему чету). У меня нет проблем с такой методикой преподавания. На самом деле, я думаю, что это хорошая идея — оставить вас кодировать в main, пока вы сосредоточены на других вещах.

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

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

public class Square {
    public static void main(String args[]) {
        float sideLength = 4.0;
        System.out.println("The area of the square is " + sideLength * sideLength);
        System.out.println("The perimiter of the square is " + 4 * sideLength);
    }
}
Войти в полноэкранный режим Выход из полноэкранного режима

Это хорошее начало. Оно делает то, о чем вас просят. И в какой-то степени приятно, что класс назван Square. Название класса говорит мне, что в этом классе мы делаем что-то связанное с квадратом, но на самом деле мы не делаем много моделирования квадрата. В классе Square нет представления длины стороны, и нет методов, которые мы ожидали бы от класса Square. Мы называем класс Square, но поскольку вся логика для квадратных вещей, которые мы делаем, находится в main, на самом деле в этом классе нет ничего квадратного, кроме того, что волшебная точка входа в программу Java, main, содержит ряд шагов, связанных с выполнением некоторых операций над квадратом.

Давайте остановимся на этом подробнее. Метод main — не лучшее место для размещения ваших алгоритмов или моделирования проблемной области, которую вы моделируете: необходимость в main проистекает из договора между средой выполнения Java и вами о том, как начать вашу программу. Вот и все. Это то, как среда выполнения Java находит место для запуска вашей программы, и это ее основная роль. Она не предназначена для моделирования проблемного пространства или написания интересных алгоритмов: это моделирование и эти интересные алгоритмы относятся к отдельным методам и полям ваших классов. Рассматривайте main исключительно как точку входа, которую может найти среда выполнения Java, и поместите интересную работу в методы вашего класса.

Не попадайте в ловушку static.

Итак, вы изучили методы класса и поля класса и переписали свой класс Square:

public class Square {
    private static float sideLength = 0.0f;

    public static void main(String args[]) {
        setSideLength(3.2f);
        System.out.println("The area of the square is " + getArea());
        System.out.println("The perimiter of the square is " + getPerimeter());
    }

    public static void setSideLength(float length) {
        sideLength = length;
    }

    public static float getArea() {
        return sideLength * sideLength;
    }

    public static float getPerimeter() {
        return 4 * sideLength;
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Это улучшение. Я теперь понимаю, почему этот класс назван Square: он хранит длину стороны, и у него есть методы для вычисления площади и периметра квадрата, используя правильные алгоритмы для них, основанные на длине стороны квадрата.

Но здесь есть кое-что забавное. Почему все статично? Поле sideLength является статическим, как и getArea и getPerimeter? По моему опыту, это не столько сознательный выбор, сколько перенос того факта, что main является static. main должен быть static, потому что таков контракт со средой выполнения Java. И студенты застревают, когда выясняют, как не продолжать эту static. Они обнаруживают, что не могут напрямую вызвать метод с именем setSideLength из своего static main, если setSideLength тоже static. И тогда static setSideLength не может установить значение для поля sideLength, если оно не static.

Итак, здесь есть два больших вопроса. Первый — хотим ли мы, чтобы все это было статическим. Мы не хотим, и я объясню почему. Но более того, большая концептуальная проблема заключается не в том, что студенты сделали выбор, с которым я не согласен, сделать поле и методы Square статическими: а в том, что студенты чувствуют, что их принуждают к этому их статические главные.

Существует большая разница между статическими полями и нестатическими полями (экземпляр или член) и большая разница между статическими методами и нестатическими методами (экземпляр или член). Разница, на которой я хочу сосредоточиться, заключается в том, что поле static является общим для всех экземпляров Square, в то время как каждый экземпляр Square имеет свою частную копию неstatic полей. Концептуально, хотим ли мы сделать так, чтобы все Square имели длину стороны 3.2, когда я устанавливаю это значение, или чтобы только конкретный Square имел длину стороны 3.2? Или, говоря более широко, хотим ли мы, чтобы класс Square моделировал квадраты так, как будто у них у всех одинаковая длина стороны, или мы хотим позволить ему моделировать, что у разных квадратов разная длина стороны? Конечно, второе. В этом случае мы не хотим, чтобы sideLength был статическим.

Хорошо, мы не хотим, чтобы это поле было статическим, но, как я уже сказал, большинство студентов не решают сделать его статическим: они чувствуют себя вынужденными сделать это из-за вызова setSideLength в их public void static main, который сам, похоже, вынужден быть статическим.

Решением является создание экземпляра Square, и вызов метода на нем:

public class Square {
    private float sideLength = 0.0f;

    public static void main(String args[]) {
        Square square = new Square();
        square.setSideLength(3.2f);
        System.out.println("The area of the square is " + square.getArea());
        System.out.println("The perimiter of the square is " + square.getPerimeter());
    }

    public void setSideLength(float length) {
        sideLength = length;
    }

    public float getArea() {
        return sideLength * sideLength;
    }

    public float getPerimeter() {
        return 4 * sideLength;
    }
}
Войти в полноэкранный режим Выйти из полноэкранного режима

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

Резюме

Здесь есть два урока, которые я хотел бы подытожить:

  • Очень легко застрять в необходимости сделать все в классе статическим, потому что вы начинаете вызывать его из статического метода, public static void main. Но это не обязательно: внутри своего main вы можете создать экземпляр своего класса и затем выполнить вызовы методов экземпляра этого экземпляра. Эти методы экземпляра могут обращаться к полям экземпляра. Если вы хотите сделать поле или метод вашего класса static, потому что это тоже уместно, то отлично! Но вы не обязаны этого делать.

Вот соответствующая заметка о том, где должен находиться main.

Оцените статью
devanswers.ru
Добавить комментарий