“Передача по ссылке” или “передача по значению”?

Источник: Medium Эта статья поможет найти ответ на один из вечных вопросов, который вызывает путаницу и споры среди Java-разработчиков: использует ли Java “передачу по ссылке” (pass-by-reference) или “передачу по значению” (pass-by-value). Кофе-брейк #234. “Передача по ссылке” или “передача по значению”? Загрузчики классов в Java: в чем магия динамической загрузки классов - 1Давай выясним, что лучше использовать в Java: “передачу по ссылке” (pass-by-reference) или “передачу по значению” (pass-by-value). Но сначала разберемся с обоими терминами, что они означают.
  • Передача по значению: значение переменной здесь передается методу, и когда метод вызывается, то в области действия метода создается новая копия переменной. Любые изменения, внесенные в эту переменную в методе, не влияют на исходную переменную.
  • Передача по ссылке: при вызове метода в метод передается ссылка (адрес памяти) на исходную переменную. Любые изменения, внесенные в переменную в методе, также влияют на исходную переменную.

Что же использовать: передачу по значению или передачу по ссылке?

В языке Java применяется строго передача по значению. Путаница часто возникает из-за того, как Java обрабатывает объекты и их ссылки. Когда мы передаем объект методу, мы не передаем фактический объект (как в случае “передачи по ссылке”) — вместо этого мы передаем ссылочное значение (адрес памяти) объекта. Учитывая это, любые изменения, внесенные в объект в методе, влияют на исходный объект. Проще говоря, примитивные типы данных (int, float, char и другие) передаются по значению. Ссылки на объекты также передаются по значению. Но поскольку передаваемое значение является ссылкой (адресом памяти) объекта, может показаться, что оно передается по ссылке. Давайте проиллюстрируем это парой примеров кода. Пример 1: примитивные типы данных.

public class PassByValueDemo {

    public static void main(String[] args) {
        int number = 10;
        System.out.println("Before: " + number); // Вывод: Before: 10
        modifyNumber(number);
        System.out.println("After: " + number); // Вывод: After: 10
    }

    public static void modifyNumber(int value) {
        value = value * 2;
        System.out.println("Modified: " + value); // Вывод: Modified: 20
    }
}
В этом примере переменная number представляет собой примитивный тип данных (int). Когда мы передаем эту переменную в метод modifyNumber(), то передается только копия ее значения. В результате исходная переменная остается неизменной. Пример 2: Объекты.

public class PassByValueDemo {

    public static void main(String[] args) {
        Point point = new Point(2, 3);
        System.out.println("Before: " + point); // Вывод: Before: (2, 3)
        modifyPoint(point);
        System.out.println("After: " + point); // Вывод: After: (20, 30)
    }

    public static void modifyPoint(Point point) {
        point.setX(point.getX() * 10);
        point.setY(point.getY() * 10);
        System.out.println("Modified: " + point); // Вывод: Modified: (20, 30)
    }
}

class Point {
    private int x;
    private int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    @Override
    public String toString() {
        return "(" + x + ", " + y + ")";
    }
}
В данном примере, когда мы передаем объект point в метод modifyPoint(), передается только копия значения ссылки на объект (адрес памяти). Исходный объект модифицируется в методе, в результате чего изменение отражается в выходных данных. Однако важно понимать, что сама ссылка на объект передается по значению, а это означает, что адрес памяти объекта не может быть изменен.

Заключение

Java является языком передачи по значению в обоих случаях — как для примитивных типов данных, так и для объектов. При передаче объектов в методы важно помнить, что их ссылочные значения (адреса памяти) передаются по значению.

Загрузчики классов в Java: в чем магия динамической загрузки классов

Источник: Medium Данное руководство поможет вам понять внутреннюю работу загрузчиков классов и их важность при использовании в приложениях Java. В огромном мире Java-программирования есть одна концепция, которая часто привлекает внимание разработчиков, — это загрузка классов. Она играет важную в работе виртуальной машины Java (JVM) роль, поскольку загрузчики классов (Classloaders) динамически загружают классы в память.

Что такое загрузчики классов?

Загрузчики классов динамически загружают классы Java во время выполнения в виртуальную машину Java (JVM). Классы Java загружаются экземпляром java.lang.ClassLoader. Поскольку загрузчики также являются частью JRE (Java Runtime Environment), это означает, что JVM не требует знаний о файлах, используемых при создании файловых систем для запуска приложений. Java поддерживает три типа загрузчиков классов:

Начальный загрузчик классов (Bootstrap Classloader)

Начальный (bootstrap) загрузчик классов, также известный как первоначальный (primordial) загрузчик классов, — это машинный код, запускающий операцию при запуске JVM. Он реализован в собственном коде и обычно является родителем всех других загрузчиков классов.

Загрузчик классов расширений (Extensions Classloader)

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

Загрузчик классов приложений (Application Classloader)

Загрузчик классов приложения, также известный как системный загрузчик классов, отвечает за загрузку классов из пути к классам приложения. Он загружает классы из каталогов и JAR-файлов, указанных в переменной среды CLASSPATH или параметре командной строки -classpath.

Иерархия загрузчика классов

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

Пользовательские загрузчики классов

Разработчикам часто приходится создавать собственные загрузчики классов для удовлетворения конкретных требований. Пользовательские загрузчики классов (Custom Classloaders) позволяют загружать классы из нестандартных источников, таких как базы данных, сетевые расположения, или даже динамически генерировать классы. Давайте рассмотрим пример пользовательского загрузчика классов, который загружает классы из каталога:

public class DirectoryClassLoader extends ClassLoader {
    private String directoryPath;

    public DirectoryClassLoader(String directoryPath, ClassLoader parent) {
        super(parent);
        this.directoryPath = directoryPath;
    }

    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        try {
            byte[] classData = loadClassData(className);
            return defineClass(className, classData, 0, classData.length);
        } catch (IOException e) {
            throw new ClassNotFoundException("Failed to load class: " + className, e);
        }
    }

    private byte[] loadClassData(String className) throws IOException {
        String filePath = directoryPath + File.separator + className.replace('.', File.separatorChar) + ".class";
        try (InputStream inputStream = new FileInputStream(filePath)) {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            return outputStream.toByteArray();
        }
    }
}

Динамическая загрузка классов

Динамическая загрузка классов позволяет разработчикам загружать классы и создавать экземпляры объектов во время выполнения, предоставляя приложениям высокую степень гибкости. Вот пример, демонстрирующий загрузку динамического класса с помощью пользовательского DirectoryClassLoader:

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        String className = "com.example.MyClass";
        ClassLoader classLoader = new DirectoryClassLoader("/path/to/classes", ClassLoader.getSystemClassLoader());
        Class<?> clazz = classLoader.loadClass(className);
        MyClass myObject = (MyClass) clazz.newInstance();
        myObject.doSomething();
    }
}

Изоляция и безопасность при работе загрузчика классов

Загрузчики классов играют жизненно важную роль в обеспечении изоляции и безопасности в приложениях Java. Используя те или иные загрузчики классов, приложения могут инкапсулировать и защищать свои классы и ресурсы. Эта функция особенно полезна в таких средах, как сервлеты, где несколько веб-приложений выполняются в одной и той же JVM.

Заключение

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