$ sudo teach IT
МОДУЛЬ 7 · УРОК 5

static: поля и методы

Статические члены принадлежат классу, а не отдельным объектам. Вы уже пользуетесь ими: Math.PI, Integer.parseInt. Сегодня разберём, как это работает.

~30 минут Для новичков Java ООП

Что такое static

static означает «статический», то есть принадлежащий КЛАССУ, а не конкретному объекту. Когда вы создаёте объект через new, у каждого объекта свои поля. Но статические поля — ОДНИ на всех.

Аналогия: представьте classroom教室 в школе. У каждого ученика есть свой учебник (нестатическое поле). Но доска в教室е одна на всех (статическое поле). Доска принадлежит教室у, а не конкретному ученику. Учебники можно переписать, но доска общая.

public class Dog {
    String name;
    static int count;

    Dog(String name) {
        this.name = name;
        count++;
    }
}

Dog d1 = new Dog("Рекс");
Dog d2 = new Dog("Бобик");
Dog d3 = new Dog("Мухтар");

System.out.println(d1.count); // 3
System.out.println(d2.count); // 3
System.out.println(Dog.count); // 3 — лучше обращаться через имя класса!

Обратите внимание: поле count увеличивается каждый раз при создании нового объекта. И оно одинаковое для всех трёх собак. Потому что оно статическое — принадлежит классу Dog, а не конкретной собаке.

Статические vs нестатические поля

Давайте сравним:

public class Student {
    String name;           // нестатическое — у каждого студента своё
    int grade;             // нестатическое — у каждого своё
    static String school;  // статическое — одно на всех студентов
    static int totalStudents; // статическое — общее число

    Student(String name, int grade) {
        this.name = name;
        this.grade = grade;
        totalStudents++;
    }
}

Student.school = "Школа №42";

Student s1 = new Student("Алиса", 10);
Student s2 = new Student("Борис", 11);
Student s3 = new Student("Варвара", 10);

System.out.println(s1.name);         // Алиса
System.out.println(s2.name);         // Борис
System.out.println(s1.school);       // Школа №42
System.out.println(s2.school);       // Школа №42
System.out.println(Student.school);  // Школа №42
System.out.println(Student.totalStudents); // 3

Нестатические поля (name, grade) — у каждого объекта свои значения. Статические поля (school, totalStudents) — одинаковые для всех объектов.

В памяти это выглядит так: каждый объект Student в куче имеет свои name и grade. А поле school и totalStudents — одна копия на всех, хранящаяся отдельно.

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

Статические методы можно вызывать БЕЗ создания объекта. Они не привязаны к конкретному экземпляру.

public class MathUtils {
    public static int sum(int a, int b) {
        return a + b;
    }

    public static boolean isEven(int n) {
        return n % 2 == 0;
    }

    public static long factorial(int n) {
        long result = 1;
        for (int i = 2; i <= n; i++) {
            result *= i;
        }
        return result;
    }

    public static double average(int a, int b, int c) {
        return (a + b + c) / 3.0;
    }
}

// Вызов без объекта:
int s = MathUtils.sum(5, 3);           // 8
boolean even = MathUtils.isEven(4);     // true
long f = MathUtils.factorial(5);        // 120
double avg = MathUtils.average(10, 20, 30); // 20.0

Вы уже использовали статические методы: Math.sqrt(), Math.max(), Integer.parseInt(), System.out.println(). Все они статические.

Внутри статического метода НЕЛЬЗЯ использовать this и обращаться к нестатическим полям. Потому что статический метод не привязан ни к какому объекту — «this» просто не существует.

public class Example {
    int value = 10;

    public static void doSomething() {
        // System.out.println(this);      // Ошибка!
        // System.out.println(value);      // Ошибка!
        System.out.println("Статический метод работает");
    }
}

Math — класс со статическими методами

Класс Math — наглядный пример статических методов. Создавать объект Math не нужно — все методы статические:

public class MathDemo {
    public static void main(String[] args) {
        System.out.println(Math.PI);           // 3.141592653589793
        System.out.println(Math.E);            // 2.718281828459045

        System.out.println(Math.sqrt(16));     // 4.0
        System.out.println(Math.pow(2, 10));   // 1024.0
        System.out.println(Math.abs(-42));     // 42
        System.out.println(Math.max(10, 20));  // 20
        System.out.println(Math.min(10, 20));  // 10

        System.out.println(Math.round(3.7));   // 4
        System.out.println(Math.ceil(3.2));    // 4.0
        System.out.println(Math.floor(3.7));   // 3.0

        System.out.println(Math.sin(Math.PI / 2)); // 1.0
        System.out.println(Math.log(Math.E));      // 1.0
    }
}

Все эти методы статические. Вы вызываете их через имя класса: Math.sqrt(16), а не new Math().sqrt(16).

Поле Math.PI — это статическая константа: public static final double PI = 3.141592653589793;

public static final — константы

Когда вы комбинируете static и final, получаете константу — значение, которое нельзя изменить:

public class Config {
    public static final String APP_NAME = "Моё приложение";
    public static final int MAX_USERS = 1000;
    public static final double PI = 3.14159;
    public static final String VERSION = "1.0.0";
    public static final int MAX_LOGIN_ATTEMPTS = 3;
}

System.out.println(Config.APP_NAME); // Моё приложение
// Config.APP_NAME = "Другое";      // Ошибка! final нельзя изменить

Соглашение об именовании: константы пишутся ЗАГЛАВНЫМИ БУКВАМИ с подчёркиванием: MAX_USERS, PI, VERSION.

Почему public static final — это константа?

  • public — доступна отовсюду
  • static — принадлежит классу (не нужно создавать объект)
  • final — значение нельзя изменить после присваивания

import static

import static позволяет импортировать статические成员, чтобы не писать имя класса каждый раз:

// Без static import:
double x = Math.sqrt(16);
double y = Math.PI * 2;
int max = Math.max(10, 20);
System.out.println(Math.abs(-5));

// С static import:
import static java.lang.Math.*;
import static java.lang.System.out;

public class Test {
    public static void main(String[] args) {
        double x = sqrt(16);     // 4.0
        double y = PI * 2;       // 6.28...
        int max = max(10, 20);   // 20
        out.println(abs(-5));    // 5
    }
}

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

Статический инициализатор

Блок static выполняется ОДИН РАЗ при первом обращении к классу:

public class Database {
    private static Connection connection;
    private static List<String> tables;

    static {
        System.out.println("Инициализация базы данных...");
        connection = new Connection("jdbc:mysql://localhost/mydb");
        tables = new ArrayList<>();
        tables.add("users");
        tables.add("products");
        tables.add("orders");
        System.out.println("База данных готова!");
    }

    public static Connection getConnection() {
        return connection;
    }

    public static List<String> getTables() {
        return tables;
    }
}

// При первом обращении:
Database.getConnection(); // "Инициализация..." → "База данных готова!"
Database.getTables();     // Сразу работает, инициализация уже была

Статический инициализатор полезен для настройки ресурсов, которые нужны всем объектам класса: подключение к БД, чтение конфигурации, загрузка данных.

Полезный пример: счётчик объектов

public class User {
    private String name;
    private int id;
    private static int nextId = 1;
    private static int totalUsers = 0;

    public User(String name) {
        this.name = name;
        this.id = nextId++;
        totalUsers++;
    }

    public String getName() {
        return name;
    }

    public int getId() {
        return id;
    }

    public static int getTotalUsers() {
        return totalUsers;
    }

    public void display() {
        System.out.printf("User #%d: %s%n", id, name);
    }
}

User u1 = new User("Алиса");
User u2 = new User("Борис");
User u3 = new User("Варвара");

u1.display(); // User #1: Алиса
u2.display(); // User #2: Борис
u3.display(); // User #3: Варвара

System.out.println("Всего пользователей: " + User.getTotalUsers()); // 3

Поле nextId статическое — оно одно на всех. Каждый раз при создании пользователя id увеличивается. Поле totalUsers тоже статическое — считает общее число пользователей.

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

Статические методы удобны для утилитных функций, которые не зависят от состояния объекта:

public class StringUtils {
    public static boolean isNullOrEmpty(String s) {
        return s == null || s.isEmpty();
    }

    public static boolean isNumeric(String s) {
        if (s == null || s.isEmpty()) return false;
        for (int i = 0; i < s.length(); i++) {
            if (!Character.isDigit(s.charAt(i))) return false;
        }
        return true;
    }

    public static String capitalize(String s) {
        if (s == null || s.isEmpty()) return s;
        return Character.toUpperCase(s.charAt(0)) + s.substring(1).toLowerCase();
    }

    public static String repeat(String s, int times) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < times; i++) {
            sb.append(s);
        }
        return sb.toString();
    }

    public static String reverse(String s) {
        return new StringBuilder(s).reverse().toString();
    }
}

System.out.println(StringUtils.isNullOrEmpty(""));    // true
System.out.println(StringUtils.isNumeric("12345"));   // true
System.out.println(StringUtils.capitalize("hello"));  // Hello
System.out.println(StringUtils.repeat("abc", 3));     // abcabcabc
System.out.println(StringUtils.reverse("Java"));      // avaJ

Когда использовать static

Используйте static когда:

  • Значение должно быть ОДНО на все объекты (счётчик, конфигурация)
  • Метод не зависит от состояния конкретного объекта (математические операции, утилиты)
  • Вы хотите вызвать метод без создания объекта (Math.sqrt())
  • Константа (public static final)

Не используйте static когда:

  • Значение должно быть разным для каждого объекта (имя, возраст, баланс)
  • Метод работает с полями конкретного объекта
  • Нужно использовать this

Статические константы в реальных проектах

В реальных проектах статические константы используются повсюду:

public class AppConfig {
    public static final String APP_NAME = "МойБанк";
    public static final String VERSION = "2.1.0";
    public static final int MAX_LOGIN_ATTEMPTS = 3;
    public static final int SESSION_TIMEOUT_MINUTES = 30;
    public static final double DEFAULT_INTEREST_RATE = 0.05;
    public static final String CURRENCY = "RUB";
    public static final String DATE_FORMAT = "dd.MM.yyyy";
    public static final String ENCODING = "UTF-8";
}

public class Validation {
    public static final int MIN_PASSWORD_LENGTH = 8;
    public static final int MAX_PASSWORD_LENGTH = 64;
    public static final int MIN_NAME_LENGTH = 2;
    public static final int MAX_NAME_LENGTH = 100;
    public static final String EMAIL_REGEX = "^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$";
}

public class MathConstants {
    public static final double PI = 3.141592653589793;
    public static final double E = 2.718281828459045;
    public static final double GOLDEN_RATIO = 1.618033988749895;
    public static final double SQRT_2 = 1.4142135623730951;
}

Константы удобны тем, что:

  • Их можно изменить в одном месте, и они обновятся везде
  • Они самодокументированы — имя говорит само за себя
  • Они неизменяемы — защита от случайных изменений

Статические методы vs методы экземпляра

Давайте сравним два типа методов:

public class Circle {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    // Метод экземпляра — работает с полем radius
    public double getArea() {
        return Math.PI * radius * radius;
    }

    // Статический метод — не зависит от radius
    public static double calculateArea(double radius) {
        return Math.PI * radius * radius;
    }

    // Метод экземпляра — использует this
    public void display() {
        System.out.printf("Круг с радиусом %.1f, площадь %.1f%n",
            radius, getArea());
    }

    // Статический метод — не использует this
    public static Circle createUnit() {
        return new Circle(1.0);
    }
}

// Метод экземпляра:
Circle c = new Circle(5);
double area = c.getArea(); // 78.5

// Статический метод:
double area2 = Circle.calculateArea(5); // 78.5

// Создание объекта через статический метод:
Circle unit = Circle.createUnit();

Метод экземпляра getArea() работает с полем radius конкретного объекта. Статический метод calculateArea() принимает radius как параметр — он не зависит ни от какого объекта.

Наследование и static

Статические члены наследуются, но не переопределяются:

public class Animal {
    protected String name;
    static int count = 0;

    Animal(String name) {
        this.name = name;
        count++;
    }

    static int getCount() {
        return count;
    }
}

public class Dog extends Animal {
    Dog(String name) {
        super(name);
    }
}

Dog d1 = new Dog("Рекс");
Dog d2 = new Dog("Бобик");
System.out.println(Dog.getCount()); // 2
System.out.println(Animal.getCount()); // 2 — та же переменная!

Поле count — одна копия на весь класс Animal. Dog наследует его, но не создаёт свою копию.

Типичные ошибки

public class StaticMistakes {
    private int value;

    public static void wrong1() {
        // System.out.println(value); // Ошибка! Нестатическое поле
    }

    public static void wrong2() {
        // System.out.println(this);  // Ошибка! Нет this в static
    }

    public void correct() {
        System.out.println(value); // ОК — нестатический метод
    }
}

Итоги урока

  • static принадлежит классу, а не объектам.
  • Статические поля — одна копия на все экземпляры.
  • Статические методы вызываются без объекта (Math.sqrt()).
  • Внутри static нельзя использовать this и нестатические поля.
  • public static final = константа (UPPER_CASE).
  • import static позволяет опустить имя класса.
  • Статический инициализатор выполняется один раз при загрузке класса.

Следующий урок: Пакеты и организация кода — package, import, структура проекта.

Тест: static — поля и методы

10 вопросов

Статический счётчик

Premium