Модификаторы без доступа в Java

Введение Модификаторы - это ключевые слова, которые позволяют нам точно настроить доступ к нашему классу и его членам, их область действия и поведение в определенных ситуациях. Например, мы можем контролировать, какие классы / объекты могут получить доступ к определенным членам нашего класса, может ли класс быть унаследованным или нет, можем ли мы переопределить метод позже, должны ли мы переопределить метод позже и т. Д. Ключевые слова модификатора записываются раньше тип и имя переменной / метода / класса (возвращаемого значения), например private int myVar или public String toSt

Вступление

Модификаторы - это ключевые слова, которые позволяют нам точно настроить доступ к нашему классу и его членам, их область действия и поведение в определенных ситуациях. Например, мы можем контролировать, какие классы / объекты могут получить доступ к определенным членам нашего класса, может ли класс быть унаследованным или нет, можем ли мы переопределить метод позже, должны ли мы переопределить метод позже и т. Д.

Ключевые слова-модификаторы записываются перед типом и именем переменной / метода / класса (возвращаемого значения), например private int myVar или public String toString() .

Модификаторы в Java делятся на две группы - доступ и отсутствие доступа :

native не рассматривается более подробно ниже, поскольку это простое ключевое слово, которое отмечает метод, который будет реализован на других языках, а не на Java. Он работает вместе с Java Native Interface (JNI). Он используется, когда мы хотим писать критические для производительности разделы кода на языках, более ориентированных на производительность (например, C).

Хотите узнать больше о модификаторах доступа , а не о запрете доступа? Если да, ознакомьтесь с нашей статьей Модификаторы доступа в Java .

Модификаторы без доступа

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

Краткий обзор этих модификаторов можно найти в следующей таблице:

Название модификатора Обзор


    статический       Член принадлежит классу, а не объектам этого класса.
   окончательный      Значения переменных не могут быть изменены после назначения, методы не могут быть переопределены, классы не могут быть унаследованы.
    абстрактный       Если применяется к методу - должен быть реализован в подклассе, если применяется к классу - содержит абстрактные методы
синхронизированный    Управляет доступом потока к блоку / методу.
      летучий         Значение переменной всегда считывается из основной памяти, а не из памяти конкретного потока.
    преходящий        Член пропускается при сериализации объекта.

Статический модификатор

static делает член класса независимым от любого объекта этого класса. Здесь следует помнить о нескольких особенностях:

  • Переменные, объявленные как static являются общими для всех объектов класса (поскольку в данном случае переменная по существу принадлежит самому классу), то есть объекты не имеют своих собственных значений для этой переменной, вместо этого все они имеют одно общее значение.
  • Переменные и методы, объявленные как static могут быть доступны через имя класса (вместо обычной ссылки на объект, например MyClass.staticMethod() или MyClass.staticVariable ), и к ним можно получить доступ без создания экземпляра класса .
  • static методы могут использовать только static переменные и вызывать другие static методы и никоим образом не могут ссылаться на this или super (экземпляр объекта может даже не существовать, когда мы вызываем static метод, поэтому this не имеет смысла).

Примечание: Очень важно отметить , что static переменные и методы не могут получить доступ к ОТСУТСТВИЮ static (например) переменных и методы. С другой стороны, static переменные и методы могут обращаться к static переменным и методам.

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

Статические переменные

Для переменных мы используем static если хотим, чтобы переменная была общей / общей для всех объектов.

Давайте посмотрим, как static переменные ведут себя иначе, чем обычные переменные экземпляра:

 class StaticExample { 
 public static int staticInt = 0; 
 public int normalInt = 0; 
 
 // We'll use this example to show how we can keep track of how many objects 
 // of our class were created, by changing the shared staticInt variable 
 public StaticExample() { 
 staticInt++; 
 normalInt++; 
 } 
 } 

 // No instances of StaticExample have been created yet 
 System.out.println(StaticExample.staticInt); // Prints: 0 
 // System.out.println(StaticExample.normalInt); // this won't work, obviously 
 
 // Let's create two instances of StaticExample 
 StaticExample object1 = new StaticExample(); 
 // We can refer to static variables via an object reference as well, 
 // however this is not common practice, we usually access them via class name 
 // to make it obvious that a variable/method is static 
 System.out.println(object1.staticInt); // Prints: 1 
 System.out.println(object1.normalInt); // Prints: 1 
 
 StaticExample object2 = new StaticExample(); 
 System.out.println(object2.staticInt); // Prints: 2 
 System.out.println(object2.normalInt); // Prints: 1 
 
 // We can see that increasing object2's staticInt 
 // increases it for object1 (and all current or future objects of that class) 
 
 object1.staticInt = 10; 
 object1.normalInt = 10; 
 System.out.println(object2.staticInt); // Prints: 10 
 System.out.println(object2.normalInt); // Prints: 1 (object2 retained its own value for normalInt as it depends on the class itself) 

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

Наиболее распространенным примером использования static является метод main() , он объявлен как static потому что его нужно вызывать до того, как появятся какие-либо объекты. Другим распространенным примером является Math поскольку мы используем методы этого класса, не создавая сначала его экземпляр (например, Math.abs() ).

Хороший способ подумать о static методах: «Имеет ли смысл использовать этот метод без предварительного создания объекта этого класса?» (например, вам не нужно создавать экземпляр Math , чтобы вычислить абсолютное значение числа).

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

Эти методы называются служебными методами:

 static int average(int num1, int num2) { 
 return (num1+num2)/2; 
 } 

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

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

 public static int abs(int i) { 
 return (i < 0) ? -i : i; 
 } 
 
 public static int min(int a, int b) { 
 return (a < b) ? a : b; 
 } 
 
 public static int max(int a, int b) { 
 return (a > b) ? a : b; 
 } 

Статические блоки

Также есть static блок. static блок выполняется только один раз, когда класс создается впервые (или static член, даже если класс не создан) и перед остальной частью кода.

Давайте добавим static блок в наш класс StaticExample

 class StaticExample() { 
 ... 
 static { 
 System.out.println("Static block"); 
 } 
 ... 
 } 

 StaticExample object1 = new StaticExample(); // "Static block" is printed 
 StaticExample object2 = new StaticExample(); // Nothing is printed 

Независимо от их положения в классе static блоки инициализируются перед любыми другими нестатическими блоками, включая конструкторы:

 class StaticExample() { 
 public StaticExample() { 
 System.out.println("Hello from the constructor!"); 
 } 
 
 static { 
 System.out.println("Hello from a static block!"); 
 } 
 } 

Создание экземпляра этого класса приведет к выводу:

 Hello from a static block! 
 Hello from the constructor! 

Если присутствует несколько static блоков, они будут выполняться в соответствующем порядке:

 public class StaticExample { 
 
 static { 
 System.out.println("Hello from the static block! 1"); 
 } 
 
 public StaticExample() { 
 System.out.println("Hello from the constructor!"); 
 } 
 
 static { 
 System.out.println("Hello from the static block! 2"); 
 } 
 } 

Создание экземпляра этого класса приведет к выводу:

 Hello from the static block! 1 
 Hello from the static block! 2 
 Hello from the constructor! 

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

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

При этом, если мы static члены класса, мы можем импортировать отдельные члены или все из них, используя static import . Это позволяет нам пропустить префикс их вызовов с именем класса:

 package packageOne; 
 
 public class ClassOne { 
 static public int i; 
 static public int j; 
 
 static public void hello() { 
 System.out.println("Hello World!"); 
 } 
 } 

 package packageTwo; 
 
 static import packageOne.ClassOne.i; 
 
 public class ClassTwo { 
 public ClassTwo() { 
 i = 20; 
 } 
 } 

Или, если мы хотим импортировать все static члены ClassOne , мы могли бы сделать это так:

 package packageTwo; 
 
 import static packageOne.ClassOne.*; 
 
 public class ClassTwo { 
 public ClassTwo() { 
 i = 20; 
 j = 10; 
 } 
 } 

То же касается и методов:

 package packageTwo; 
 
 import static packageOne.ClassOne.*; 
 
 public class ClassTwo { 
 public ClassTwo() { 
 hello(); 
 } 
 } 

Запуск этого приведет к выводу:

 Hello World! 

Это может показаться не таким важным, но это помогает, когда мы вызываем много static членов класса:

 public int someFormula(int num1, int num2, int num3) { 
 return Math.ceil(Math.max(Math.abs(num1), Math.abs(num2))+Math.max(Math.abs(num2), Math.abs(num3)))/(Math.min(Math.abs(num1), Math.abs(num2))+Math.min(Math.abs(num2), Math.abs(num3))); 
 } 
 
 // Versus... 
 import static java.lang.Math.*; 
 public int someFormula(int num1, int num2, int num3) { 
 return ceil(max(abs(num1), abs(num2))+max(abs(num2), abs(num3)))/(min(abs(num1), abs(num2))+min(abs(num2), abs(num3))); 
 } 

Последний модификатор

Ключевое слово final может иметь одно из трех значений:

  • для определения именованных констант (переменных, значения которых не могут измениться после инициализации)
  • чтобы предотвратить переопределение метода
  • чтобы предотвратить наследование класса

Именованные константы

Добавление final модификатора к объявлению переменной делает эту переменную неизменной после ее инициализации.

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

 static final double GRAVITATIONAL_ACCELERATION = 9.81; 

Такие переменные часто включаются в служебные классы, такие как Math , вместе с многочисленными служебными методами.

Хотя в некоторых случаях они также гарантируют свои собственные классы, такие как Constants.java :

 public static final float LEARNING_RATE = 0.3f; 
 public static final float MOMENTUM = 0.6f; 
 public static final int ITERATIONS = 10000; 

Примечание : при использовании final с переменными ссылки на объект будьте осторожны с ожидаемым типом поведения. Учтите следующее:

 class MyClass { 
 int a; 
 int b; 
 
 public MyClass() { 
 a = 2; 
 b = 3; 
 } 
 } 

 final MyClass object1 = new MyClass(); 
 MyClass object2 = new MyClass(); 

object1 переменная object1 действительно является final и ее значение не может измениться, но что это значит для ссылочных переменных в любом случае? Это означает, что object1 больше не может изменить объект, на который он указывает, но мы можем изменить сам объект. Это то, что часто сбивает с толку:

 // object1 = object2; // Illegal! 
 object1.a = 5; // Perfectly fine 

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

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

Предотвращение переопределения

Если вы укажете final при определении метода, любой будущий подкласс не сможет его переопределить.

 class FinalExample { 
 final void printSomething() { 
 System.out.println("Something"); 
 } 
 } 

 class ExtendsFinalExample extends FinalExample { 
 // This would cause a compile-time error 
 //void printSomething() { 
 // System.out.println("Some other thing"); 
 //} 
 
 // However, you are perfectly free to overload this method 
 void printSomething(String something) { 
 System.out.println(something); 
 } 
 } 

Небольшой бонус объявления действительно final методов как final - небольшое повышение производительности всякий раз, когда мы вызываем этот метод. Обычно Java разрешает вызовы методов динамически во время выполнения, но с методами, объявленными final , Java может разрешить вызов к нему во время компиляции, или, если метод действительно мал, он может просто встроить вызовы этого метода, поскольку он «знает», что это не будет отменено. Это устраняет накладные расходы, связанные с вызовом метода.

Предотвращение наследования

Это использование final довольно прямолинейно, класс, определенный с final не может быть унаследован. Это, конечно же, неявно объявляет все методы этого класса final (они не могут быть переопределены, если класс не может быть унаследован с самого начала).

 final class FinalExample {...} 

Абстрактный модификатор

abstract используется для определения методов, которые позже будут реализованы в подклассе. Чаще всего он используется для предположить , что некоторые функции должны быть реализованы в подклассе, или (по некоторым причинам) не может быть реализована в суперкласса. Если класс содержит abstract метод, он также должен быть объявлен abstract .

Примечание : вы не можете создать объект abstract класса. Для этого вам необходимо предоставить реализацию для всех abstract методов.

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

 abstract class Employee { 
 int totalHours; // In a month 
 int perHour; // Payment per hour 
 int fixedRate; // Fixed monthly rate 
 ... 
 abstract int salary(); 
 ... 
 } 

 class Contractor extends Employee { 
 ... 
 // Must override salary if we wish to create an object of this class 
 int salary() { 
 return totalHours*perHour; 
 } 
 ... 
 } 

 class FullTimeEmployee extends Employee { 
 ... 
 int salary() { 
 return fixedRate; 
 } 
 ... 
 } 

 class Intern extends Employee { 
 ... 
 int salary() { 
 return 0; 
 } 
 ... 
 } 

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

Примечание : abstract широко используется с полиморфизмом, например, мы бы сказали ArrayList<Employee> employees = new ArrayList(); , и добавьте к нему объекты Contractor , FullTimeEmployee и Intern Несмотря на то, что мы не можем создать объект Employee , мы все равно можем использовать его как ссылочный тип переменной.

Синхронизированный модификатор

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

Этого можно достичь несколькими способами, и один простой и понятный способ (хотя и с несколько ограниченным использованием) - использовать ключевое слово synchronized

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

Потоки выходят за рамки этой статьи, поэтому я сосредоточусь только на синтаксисе synchronized .

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

 class SynchronizedExample { 
 
 ... 
 SomeClass object = new SomeClass(); 
 .... 
 
 synchronized(object) { 
 // Code that processes objects 
 // only one thread at a time 
 } 
 
 // A synchronized method 
 synchronized void doSomething() { 
 ... 
 } 
 ... 
 } 

Летучий модификатор

volatile сообщает Java, что переменная может быть неожиданно изменена какой-либо другой частью программы (например, в многопоточном программировании), и поэтому значение этой переменной всегда считывается из основной памяти (а не из кеша ЦП), и что каждое изменение volatile переменная хранится в основной памяти (а не в кэше ЦП). Имея это в виду, volatile следует использовать только при необходимости, поскольку чтение / запись в память каждый раз дороже, чем с кешем ЦП, и только чтение / запись в память только при необходимости.

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

Переходный модификатор

Когда переменная объявлена как transient , это означает, что ее значение не сохраняется, когда объект сохраняется в памяти.
transient int a; означает, что когда мы записываем объект в память, содержимое «a» не будет включено. Например, он используется, чтобы убедиться, что мы не храним личную / конфиденциальную информацию в файле.

Когда мы пытаемся прочитать объект, содержащий transient переменные, все transient переменных будут установлены на null (или значения по умолчанию для примитивных типов), независимо от того, какими они были, когда мы записали объект в файл. Другой пример использования - это когда значение переменной должно быть получено на основе других данных (например, чей-то текущий возраст) и не является частью постоянного состояния объекта.

Примечание : что-то очень интересное происходит, когда мы используем transient и final моменты вместе. Если у нас есть transient final переменная, которая оценивается как постоянное выражение (строки или примитивные типы), JVM всегда будет сериализовать ее, игнорируя любой потенциальный модификатор transient Когда transient final используется со ссылочными переменными, мы получаем ожидаемое поведение transient умолчанию.

Заключение

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

Например, осознание того, что protected контроль доступа можно легко обойти, или transient final модификатор final, когда дело доходит до константных выражений.

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus