Модификатор native для объявления методов - Вопросы

Всего: 6 вопросов

1. 

Что означает модификатор native в Java?

native — модификатор метода, который сообщает, что тело метода реализовано вне JVM: в скомпилированной библиотеке (.dll, .so, .dylib), написанной на языке с C-совместимым ABI — обычно C или C++, но подойдут и Rust, Fortran, ассемблер. В Java остаётся только сигнатура, тела нет, объявление заканчивается точкой с запятой: public native int hashCode();

Само ключевое слово ничего не компилирует и не подключает. Оно решает одну задачу: разрешает объявить метод без тела и помечает его как «реализация придёт извне». Связыванием объявления с настоящей функцией во время выполнения занимается JNI (Java Native Interface). Применяют native ради доступа к возможностям ОС, переиспользования готовых C/C++-библиотек (кодеки, криптография, драйверы) и внутри самой JDK.

2. 

Как JNI находит реализацию native-метода и что для этого должно совпасть?

Должны совпасть три вещи.

1. Библиотека загружена. Обычно в статическом инициализаторе класса: System.loadLibrary("demo") ищет библиотеку в java.library.path, а System.load("/абсолютный/путь/libdemo.so") берёт полный путь. Расширение и префикс lib в loadLibrary() писать не нужно — JVM подставляет их сама по правилам платформы.

2. Имя функции совпадает. Символ строится по строгому правилу: Java_ + пакет с точками, заменёнными на подчёркивания + _ + имя класса + _ + имя метода. Для com.example.Demo.sum() это Java_com_example_Demo_sum.

3. Сигнатура совпадает. Первый параметр C-функции — всегда JNIEnv*, второй — jobject для обычного метода или jclass для static, дальше идут параметры в JNI-типах (jint, jlong, jstring).

Вручную имена писать не нужно: заголовок генерирует компилятор командой javac -h . Demo.java (флаг доступен с JDK 8). Отдельная утилита javah объявлена устаревшей в JDK 9 и удалена в JDK 10.

3. 

С какими модификаторами можно сочетать native и где его применять нельзя?

Разрешено: static (в C-функцию вторым аргументом придёт jclass вместо jobject), synchronized (монитор захватывается до входа в нативный код), final и любой модификатор доступа — private, public, protected. Native-метод может объявлять throws и бросать обычные Java-исключения (нативный код возбуждает их через ThrowNew), а также участвует в рефлексии: Modifier.isNative(m.getModifiers()).

Запрещено:

  • native + тело метода — ошибка native methods cannot have a body, объявление заканчивается ;;
  • native + abstractillegal combination of modifiers: оба означают «тела здесь нет», но abstract обещает реализацию в подклассе, а native — в библиотеке;
  • native у поля или класса — modifier native not allowed here, модификатор применим только к методам;
  • native у конструктора — нативную инициализацию выносят в отдельный native-метод и вызывают из конструктора;
  • native в интерфейсе — методы интерфейса либо абстрактные, либо default/static с телом на Java.
4. 

Можно ли переопределить native-метод обычным Java-методом в подклассе?

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

Самый наглядный пример есть в самой JDK: Object.hashCode() объявлен нативным, а String переопределяет его чистым Java-кодом. Проверяется рефлексией:

System.out.println(Modifier.isNative(
        Object.class.getDeclaredMethod("hashCode").getModifiers())); // true
System.out.println(Modifier.isNative(
        String.class.getDeclaredMethod("hashCode").getModifiers())); // false
5. 

Правда ли, что native-метод работает быстрее обычного, и где native встречается в стандартной библиотеке?

Нет, слово native не означает «быстрее». Каждый переход из Java в нативный код и обратно стоит дорого: JVM фиксирует состояние потока, JIT теряет часть оптимизаций (например, встраивание через границу вызова), а до объектов приходится добираться через JNI-функции. Мелкий метод, вызываемый в цикле, после переноса в C нередко работает медленнее Java-версии.

При этом нативные методы вы вызываете каждый день: Object.hashCode(), Object.getClass(), Object.clone(), System.arraycopy(), System.currentTimeMillis(), System.nanoTime(), Thread.currentThread(), Runtime.availableProcessors().

Многие из них помечены в исходниках JDK аннотацией @IntrinsicCandidate: JIT-компилятор HotSpot вправе подставить вместо вызова готовый машинный код (интринсик) прямо в точке вызова. Поэтому System.arraycopy() или Math.sqrt() в горячем коде вообще не проходят через JNI — здесь native лишь фиксирует, что тела на Java нет.

6. 

Чем в современной Java заменяют JNI и native-методы?

Foreign Function & Memory API — пакет java.lang.foreign (проект Panama). Прошёл preview-итерации в JDK 19—21 и финализирован в JDK 22 (JEP 454). Он позволяет вызвать функцию из нативной библиотеки без единой строки на C:

Linker linker = Linker.nativeLinker();
MethodHandle strlen = linker.downcallHandle(
        linker.defaultLookup().find("strlen").orElseThrow(),
        FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS));

try (Arena arena = Arena.ofConfined()) {
    MemorySegment text = arena.allocateFrom("Hello, native!");
    System.out.println((long) strlen.invoke(text)); // 14
}

Отличия от JNI: не нужна C-обвязка и отдельная сборка .so/.dll под каждую ОС и архитектуру; часть ошибок превращается в Java-исключения вместо падения всей JVM; память освобождается детерминированно через Arena.

Переписывать существующий JNI-код срочно не нужно: он поддерживается, а модификатор native остаётся частью языка. Но с JDK 24 (JEP 472) и JNI, и FFM считаются ограниченными операциями: JVM печатает предупреждение при загрузке нативной библиотеки и при связывании native-методов, убирается флагом --enable-native-access=ALL-UNNAMED.

Страница 1 из 1