Изучение основ языка программирования, такого как Java, является важной частью становления хорошим программистом, но именно мелкие детали позволяют нам перейти от хороших программистов к великим мастерам. Подобно тому, как плотник понимает нюансы своего долота и фрезерного станка, так и профессиональный боец понимает тонкости баланса и рычага, мы должны понимать небольшие аспекты, которые обеспечивают наиболее значимые результаты.
Вам также может понравиться: 4 метода написания лучшей Java
В этой статье серии « Методы написания лучшего Java » мы рассмотрим три часто пропускаемых аспекта языка Java. Во-первых, мы рассмотрим valueOf
методы, предоставляемые блочными примитивными типами, и как по возможности избегать этих методов. Далее, мы будем следовать той же последовательности мыслей и исследовать instanceof
ключевое слово и как избежать злоупотребления этой функцией.
Наконец, мы рассмотрим, когда и где создавать исключения для максимальной эффективности, и как использование исключения в правильном месте может сделать разницу между хорошо разработанным классом и кошмаром отладки.
Заинтересованным читателям предлагается ознакомиться с другими статьями этой серии, чтобы узнать еще 12 способов использования языка Java для более эффективного решения проблем:
- 4 Техники для написания лучшей Java
- Еще 4 Техники для написания лучшего Java
- Еще 4 метода для написания лучшей Java
1. Избегайте, valueOf
когда это возможно
Одним из наиболее значительных преимуществ языков со строгой типизацией, таких как Java, является то, что компилятор может обеспечить выполнение наших намерений во время компиляции. Применяя тип к каждому фрагменту данных, мы делаем явное утверждение о природе этих данных.
Например, если мы определяем переменную как имеющую тип int
, мы утверждаем, что переменная не может быть больше 2 31 — 1 и не может быть меньше -2 31 . С введением объектно-ориентированного программирования (ООП) мы можем определять новую природу, создавая классы и создавая объекты этого класса. Например, мы можем определить Address
класс и создать экземпляр переменной с определенным Address
состоянием:
Джава
1
public class Address {
2
3
private final String name;
4
private final String street;
5
6
public Address(String name, String street) {
7
this.name = name;
8
this.street = street;
9
}
10
11
public String getName() {
12
return name;
13
}
14
15
public String getStreet() {
16
return street;
17
}
18
}
19
Address someAddress = new Address("John Doe", "117 Spartan Way");
Эта строгая типизация является основой основных концепций ООП, таких как полиморфизм и динамическая диспетчеризация. Эти концепции объединяются, чтобы создать приложение, которое явно заявляет о наших намерениях до того, как Java выполнит программу. Несмотря на то, что строгая типизация может быть утомительной во многих контекстах, во многих случаях она позволяет нам знать, являются ли наши намерения логически обоснованными, прежде чем развертывать приложение. Например, если мы попытаемся передать int
значение как name
или street
для создания экземпляра нашего Address
объекта (то есть new Address(1, 2)
), компилятор Java будет жаловаться:
Простой текст
xxxxxxxxxx
1
error: incompatible types: int cannot be converted to String
2
Address someAddress = new Address(1, 2);
Мы намеревались String
представлять наши name
и street
ценности, но вместо этого мы предоставили int
. Поскольку компилятор не может обрабатывать a int
как a String
(то есть int
не имеет по крайней мере поведения a String
), компилятор выдает ошибку и отказывается компилировать наше приложение.
Безопасность этого типа является важным инструментом при создании более крупных программистов, где сотни и тысячи классов взаимодействуют друг с другом и выполняют по существу сложные задачи посредством своих отношений.
Несмотря на то, что Java является языком со строгой типизацией, существуют способы обхода этой проверки типов. Одним из наиболее распространенных способов является использование String
объектов для представления всех данных. Например, нередки случаи, когда следующие данные нотации объектов JavaScript (JSON) отправляются как тело запроса или ответа о представлении состояния (REST):
JSON
xxxxxxxxxx
1
{
2
"name": "John Doe",
3
"accountValue": "100"
4
}
Поначалу может показаться очевидным, что accountValue
поле является int
значением или, возможно, даже значением a long
, но здесь существует гораздо более сомнительная проблема. Хотя значение, которое мы видим в этом ответе, является целым числом, оно может быть любым String
. Например, ничто не мешает этому телу быть:
Джава
xxxxxxxxxx
1
{
2
"name": "John Doe",
3
"accountValue": "unknown"
4
}
Это теперь становится намного более сложной проблемой разбора. Корень нашей проблемы: какие ценности могут accountValue
взять на себя? С точки зрения типизации это может быть любое значение, представленное String
классом. На практике мы знаем, что это значение должно быть целым числом, но семантически нет гарантии, что оно будет .
Эта проблема становится намного хуже, когда мы принимаем решение о характере accountValue
остальной части нашего приложения. Например, мы можем использовать простой простой Java-объект (POJO) для десериализации этого JSON:
Джава
xxxxxxxxxx
1
public class Account {
2
3
private String name;
4
private String accountValue;
5
6
public String getAccountValue() {
7
return accountValue;
8
}
9
10
// ...getters & setters...
11
}
Когда другой класс в нашем приложении вызывает Account#getAccountValue
, String
возвращается. Это String
ничего не говорит о природе accountValue
(например, о том, каким может быть его максимум или минимум, или выше или ниже, численно, чем другое значение счета). Одним из быстрых решений этой проблемы является String
немедленное преобразование в int
или или long
(в этом случае мы будем использовать long
из-за его большей точности):
Джава
xxxxxxxxxx
1
public class Account {
2
3
private String name;
4
private String accountValue;
5
6
public long getAccountValueAsLong() {
7
return Long.valueOf(accountValue);
8
}
9
10
// ...getters & setters...
11
}
Используя этот подход, мы инкапсулируем accountValue
поле и скрываем его точное представление. Снаружи другой класс подумал бы, что accountValue
это a long
, так как getAccountValueAsLong
возвращает a long
. Это хорошее использование ООП, но оно только откладывает проблему. Мы знаем, что это accountValue
должно быть только long
, но это не мешает ему быть какой-либо String
ценностью.
Кроме того, так как реальное accountValue
поле в нашем JSON это String
, мы все же должны обеспечить getAccountValue
, возвращающими String
, и мы должны поставить для десериализации произойти (без необходимости других фокусов).setAccountValue
String
Например, если accountValue
в нашем JSON установлено значение unknown, вызов Long.valueOf("unknown")
приведет к следующей ошибке:
Простой текст
xxxxxxxxxx
1
Exception in thread "main" java.lang.NumberFormatException: For input string: "unknown"
2
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
3
at java.lang.Long.parseLong(Long.java:589)
4
at java.lang.Long.valueOf(Long.java:803)
5
at Main.main(Main.java:5)
Важно отметить, что это исключение возникает во время выполнения, а не во время компиляции: во время компиляции у компилятора не было достаточно информации, чтобы сделать вывод, что предоставленное значение Long.valueOf
не будет a, String
которое он мог бы безопасно преобразовать в a long
. Вместо этого во время выполнения было выдано исключение при "unknown"
передаче Long.valueOf
, в результате чего наше приложение внезапно завершилось.
Из-за нашего решения представлять accountValue
как String
, мы теперь отложили нашу проверку типов со времени компиляции до времени выполнения. Вместо того, чтобы позволить компилятору выполнять статический анализ проверки типов для исходного кода нашей программы, мы вместо этого отложили эту проверку до времени выполнения, где Long.valueOf
теперь она отвечает за реализацию проверки типов.
Даже если мы попытаемся уловить это NumberFormatException
в нашем getAccountValue
методе, мы теперь несем ответственность за решение о том, что делать, когда мы встречаем число, которое не может быть преобразовано в a long
. У нас может возникнуть соблазн думать, что это невозможно, поскольку каждый должен обязательно знать, что значение учетной записи - это число.
Однако было бы неправильно думать, что, поскольку нигде в нашем JSON или даже в нашем коде не задокументировано, что long
ожидается. Тип нашего accountValue
поля - a String
, и ничто не мешает пользователю, незнакомому с нашим приложением, правильно установить accountValue
a String
.
Вместо этого мы должны изменить наш JSON для правильного представления нашего намерения:
JSON
xxxxxxxxxx
1
{
2
"name": "John Doe",
3
"accountValue": 100
4
}
Это позволяет нам правильно представлять accountValue
в нашем Account
классе:
Джава
xxxxxxxxxx
1
public class Account {
2
3
private String name;
4
private long accountValue;
5
6
// ...getters & setters...
7
}
Когда другие классы в нашем приложении теперь получают доступ к Account
объекту, они знают, что accountValue
он будет действительным long
. Это позволяет им принимать правильные решения, основанные на характере long
. Например, один класс может легко определить , если accountValue
отрицательна, сравнивая значение 0
: getAccountValue() < 0
.
В некоторых случаях требуются valueOf
вызовы методов (например, Long.valueOf
). Если бы представление JSON было вне нашего контроля, у нас не было бы другого выбора, кроме accountValue
как рассматривать его как String
. В этом случае мы должны немедленно преобразовать accountValue
в a long
и убедиться, что все другие классы взаимодействуют со значением как a long
, а не a String
. Один из подходов - обернуть проанализированный аккаунт:
Джава
xxxxxxxxxx
1
public class AccountState {
2
3
private String name;
4
private String accountValue;
5
6
// ...getters & setters...
7
}
8
public class Account {
10
11
private String name;
12
private long accountValue;
13
14
public Account(AccountState state) {
15
this.name = state.getName();
16
this.accountValue = extractAccountValue(state.getAccountValue());
17
}
18
19
private static long extractAccountValue(String value) {
20
21
try {
22
return Long.valueOf(value);
23
}
24
catch (NumberFormatException e) {
25
return 0;
26
}
27
}
28
29
// ...getters & setters...
30
}
31
AccountState state = // ...parse JSON into AccountState object
33
Account account = new Account(state);
34
account.getAccountValue() > 0;
Заметьте, однако, что мы должны принять осознанное решение о том, какое значение устанавливать, accountValue
когда не long
предоставляется, даже если оно никогда не должно предоставляться в первую очередь.
Поэтому, когда это возможно, нам следует избегать использования valueOf
методов, преобразующих a String
в некоторое примитивное значение, включая:
Integer.valueOf
Long.valueOf
Float.valueOf
Double.valueOf
Boolean.valueOf
Их включение в приложение следует рассматривать как запах кода и указывает на то, что у нас есть объект, String
который должен быть представлен другим типом данных.
2. Избегайте, instanceof
когда это возможно
Аналогично valueOf
, instanceof
ключевое слово предоставляет возможность обойти систему проверки типов компилятора Java. Хотя существуют случаи (особенно при работе с низкоуровневым кодом или при использовании отражения), которые instanceof
могут быть необходимы, это следует рассматривать как запах кода. Вероятно, это признак того, что мы пропускаем строгую проверку типов (и, следовательно, теряем преимущества системы проверки типов Java).
Во многих случаях instanceof
используется для безопасного преобразования объекта известного супертипа в объект требуемого подтипа (так называемый downcasting ). Это снижение рейтинга распространено при реализации метода equals в пользовательском классе:
Джава
xxxxxxxxxx
1
public class Foo {
2
3
private int value;
4
5
6
public boolean equals(Object obj) {
7
8
if (this == obj) {
9
return true;
10
}
11
else if (!(obj instanceof Foo)) {
12
return false;
13
}
14
else {
15
Foo other = (Foo) obj;
16
return value == other.value;
17
}
18
}
19
}
Сначала проверяя, obj
является ли экземпляр Foo
класса (т. Е. obj instanceof Foo
), Мы гарантируем, что мы понижаем фактический Foo
объект до Foo
. Это известно как безопасный или проверенный, удрученный. Непроверенная потеря происходит, если мы не проверяем тип реализации объекта перед приведением этого объекта к этому типу:
Джава
xxxxxxxxxx
1
public interface Vehicle {}
2
public class Boat implements Vehicle {}
4
public class Truck implements Vehicle {}
6
public class Foo {
8
9
public void doSomething(Vehicle vehicle) {
10
Truck truck = (Truck) vehicle;
11
System.out.println(truck);
12
}
13
}
14
Foo foo = new Foo();
16
Vehicle vehicle = new Truck();
17
foo.doSomething(vehicle);
Мы знаем, что это приведение безопасно, потому что мы априори знаем, что предоставляем doSomething
объект с типом реализации Truck
. Мы могли бы предоставить Boat
объект вместо этого:
Джава
xxxxxxxxxx
1
Foo foo = new Foo();
2
Vehicle vehicle = new Boat();
3
foo.doSomething(vehicle);
Это приводит к следующей ошибке:
Джава
xxxxxxxxxx
1
Exception in thread "main" java.lang.ClassCastException: class Boat cannot be cast to class Truck (Boat and Truck are in unnamed module of loader 'app')
2
at Foo.doSomething(Application.java:16)
3
at Application.main(Application.java:29)
Обратите внимание, что это исключение времени выполнения, так как компилятор не может определить, какой тип vehicle
будет до времени выполнения. В нашем случае мы знаем тип реализации, потому что мы статически установили его, но могут быть случаи, когда тип реализации не может быть определен во время компиляции:
Джава
xxxxxxxxxx
1
public class Bar {
2
3
public Vehicle createVehicle() {
4
5
int random = // randomly select 0 or 1
6
7
if (random == 0) {
8
return new Truck();
9
}
10
else {
11
return new Boat();
12
}
13
}
14
}
15
Foo foo = new Foo();
17
Bar bar = new Bar();
18
foo.doSomething(bar.createVehicle());
В этом случае тип реализации определяется случайным образом на основе некоторых критериев, которые могут быть известны только во время выполнения (например, пользовательский ввод или генератор случайных чисел). Поэтому эта проверка должна быть отложена до времени выполнения, где выдается исключение, если выполняется небезопасное снижение. Эта проблема настолько распространена, что Java даже выдает предупреждение при выполнении небезопасных понижений, используя универсальные шаблоны (универсальный тип времени выполнения которых не может быть определен из-за использования в Java нераспознанных универсальных типов ):
Джава
xxxxxxxxxx
1
public class Foo {
2
3
public void doSomething(List<?> list) {
4
List<Foo> foos = (List<Foo>) list;
5
}
6
}
В этом случае в List<Foo>
списке ( ) будет указано следующее предупреждение:
Простой текст
xxxxxxxxxx
1
Type safety: Unchecked cast from List<capture#1-of ?> to List<Foo>
Поскольку снижение рейтинга может вызвать проблемы, всегда целесообразно использовать проверенный переход вниз, instanceof
когда это возможно. Более того, мы должны избегать использования в instanceof
целом. Во многих случаях instanceof
вызовы используются в качестве альтернативы правильному полиморфизму.
Например, следующее является общим использованием instanceof
:
Джава
xxxxxxxxxx
1
public interface Vehicle {}
2
public class Boat implements Vehicle {
4
5
public void engagePropeller() {
6
// ...
7
}
8
}
9
public class Truck implements Vehicle {
11
12
public void engageAxel() {
13
// ...
14
}
15
}
16
public class VehicleDriver {
18
19
public void drive(Vehicle vehicle) {
20
if (vehicle instanceof Boat) {
22
Boat boat = (Boat) vehicle;
23
boat.engagePropeller();
24
}
25
else if (vehicle instanceof Truck) {
26
Truck truck = (Truck) vehicle;
27
truck.engageAxel();
28
}
29
}
30
}
По сути, мы пытаемся обрабатывать каждый Vehicle
тип реализации (т. Е. Boat
И Truck
) по-разному. Это именно тот случай использования полиморфизма. Вместо того, чтобы проверять тип реализации Vehicle
сквозного соединения instanceof
и выполнять downcast, мы можем создать новый метод в Vehicle
интерфейсе с именем drive
и заставить каждый тип реализации выполнять соответствующий метод.
Джава
xxxxxxxxxx
1
public interface Vehicle {
2
public void drive();
3
}
4
public class Boat implements Vehicle {
6
7
8
public void drive() {
9
engagePropeller();
10
}
11
12
public void engagePropeller() {
13
// ...
14
}
15
}
16
public class Truck implements Vehicle {
18
19
20
public void drive() {
21
engageAxel();
22
}
23
24
public void engageAxel() {
25
// ...
26
}
27
}
28
public class VehicleDriver {
30
31
public void drive(Vehicle vehicle) {
32
vehicle.drive();
33
}
34
}
Хотя может быть несколько конкретных случаев, когда instanceof
это необходимо (например, реализация equals
метода), в целом instanceof
следует избегать проверок и небезопасных снижений. Вместо этого мы должны использовать полиморфизм для изменения поведения в зависимости от типа реализации объекта.
3. Бросайте исключения рано
Когда проверка ошибок должна происходить во время выполнения, лучше всего генерировать исключения как можно раньше. В больших сложных средах, в которых объекты создаются в одном потоке и вызываются в другом, неправильная обработка исключений может вызвать кошмары отладки. Во многих случаях результаты неправильной обработки исключений могут быть тонкими и коварными.
Например, предположим, что у нас есть следующий класс:
Джава
xxxxxxxxxx
1
public class SecurityManager {
2
3
private final SecurityTransactionRepository repo;
4
5
public SecurityManager(SecurityTransactionRepository repo) {
6
this.repo = repo;
7
}
8
9
public Optional<SecurityTransaction> findTransactionById(long id) {
10
return repo.findById(id);
11
}
12
}
Этот класс кажется достаточно простым: он принимает SecurityTransactionRepository
объект в своем конструкторе и откладывает поиск SecurityTransaction
объектов в этом хранилище. Мы можем выполнить findTransactionsById
, просто предоставив правильный объект SecurityManager
конструктору и вызвав findTransactionById
метод:
Джава
xxxxxxxxxx
1
SecurityTransactionRepository repo = // create repository...
2
SecurityManager manager = new SecurityManager(repo);
3
Optional<SecurityTransaction> transaction = manager.findTransactionById(1);
Проблемы начинают возникать, когда дела идут не так, как планировалось. Например, что если бы наш repo
объект был null
? В этом случае наша последовательность вызовов будет следующей:
Джава
xxxxxxxxxx
1
SecurityManager manager = new SecurityManager(null);
2
Optional<SecurityTransaction> transaction = manager.findTransactionById(1);
Если мы попытаемся выполнить этот код, мы увидим трассировку стека, как показано ниже:
Джава
xxxxxxxxxx
1
Exception in thread "main" java.lang.NullPointerException
2
at SecurityManager.findTransactionById(Application.java:25)
3
at Application.main(Application.java:33)
Это NullPointerException
(NPE) происходит потому, что наш SecurityTransactionRepository
объект, который мы передали в SecurityManager
конструкцию null
. Как только findTransactionById
метод пытается отложить до null
SecurityTransactionRepository
объекта, NullPointerException
бросается. Простой способ предотвратить возникновение этого исключения - проверить наличие null
repo
объекта:
Джава
xxxxxxxxxx
1
public class SecurityManager {
2
3
private final SecurityTransactionRepository repo;
4
5
public SecurityManager(SecurityTransactionRepository repo) {
6
this.repo = repo;
7
}
8
9
public Optional<SecurityTransaction> findTransactionById(long id) {
10
11
if (repo == null) {
12
return Optional.empty();
13
}
14
else {
15
return repo.findById(id);
16
}
17
}
18
}
Если мы повторно выполняем наше приложение с null
SecurityTransactionRepository
объектом, переданным SecurityManager
конструктору, наш вызов findTransactionById
метода теперь приводит к пустому Optional
объекту. Хотя мы решили проблему NPE, мы ввели гораздо более тонкую проблему, которая может снова причинить нам вред.
Предположим, что наш SecurityManager
объект создан в одном месте, а findTransactionById
метод вызывается в другом месте. Также предположим, что findTransactionById
метод вызывается намного позже, может быть, через несколько минут или даже часов после того, как SecurityManager
объект был построен.
Это обычное явление в приложениях Spring и OSGi, где бины или сервисы создаются и подключаются в совершенно разные части устройства. В случае OSGi сервис может быть создан в одном пакете и внедрен в совершенно другой пакет как сервис.
В этом случае, если мы попытаемся выполнить наш findTransactionById
метод с null
SecurityTransactionRepository
объектом, Optional
будет возвращено пустое значение . Если мы попытаемся SecurityTransaction
выяснить, почему ожидаемый объект не был найден, мы увидим, что это либо потому, что такого не SecurityTransaction
существует (но мы ожидаем, что он существует), либо потому, что он SecurityTransactionRepository
есть null
.
Как только мы обнаруживаем, что SecurityTransactionRepository
есть null
, мы знаем, что оно должно быть передано в SecurityManager
конструктор как null
(поскольку оно квалифицируется как final
).
Несмотря на то, что процесс устранения неполадок является довольно стандартным, наша отладка на этом этапе ставит существенные препятствия. Нам нужно найти, где этот SecurityManager
объект создается с помощью null
аргумента конструктора. Если этот процесс создания экземпляра происходит в другом проекте или пакете, или если он произошел минуты, часы или даже дни назад, теперь мы должны выполнить поиск во всем приложении, чтобы найти основную причину.
Мы можем добавить еще один ключ в наш процесс отладки, если SecurityManager
объекты создаются более чем в одном месте . В этом случае, что один экземпляр OUR SecurityManager
с null
SecurityTransactionRepository
? Все эти проблемы проистекают из простого факта: мы не рассматриваем возможность null
SecurityTransactionRepository
в правильном месте.
Мы не хотим, чтобы наша SecurityManager
создавалась с помощью a null
SecurityTransactionRepository
, и поэтому мы должны явно указать это в конструкторе SecurityManager
. Чтобы сделать это, мы можем использовать Objects#requireNonNull
метод, который выбрасывает NPE, если переданный ему аргумент является, null
или возвращает переданный ему аргумент, если аргумент не являетсяnull.
Джава
xxxxxxxxxx
1
public class SecurityManager {
2
3
private final SecurityTransactionRepository repo;
4
5
public SecurityManager(SecurityTransactionRepository repo) {
6
this.repo = Objects.requireNonNull(repo);
7
}
8
9
public Optional<SecurityTransaction> findTransactionById(long id) {
10
return repo.findById(id);
11
}
12
}
Если мы повторно запустим наше приложение с помощью a null
SecurityTransactionRepository
, мы увидим, что NPE будет сгенерирован, но на этот раз исключение происходит из конструктора SecurityManager
:
Джава
xxxxxxxxxx
1
this.repo = Objects.requireNonNull(repo);
Это гарантирует, что если наше приложение запустится и null
SecurityTransactionManager
объект будет передан в конструктор SecurityManager
объекта, приложение выдаст ошибку из источника проблемы. Используя трассировку стека, о которой сообщается, мы можем найти вызывающего SecurityManager
конструктора:
Простой текст
xxxxxxxxxx
1
Exception in thread "main" java.lang.NullPointerException
2
at java.base/java.util.Objects.requireNonNull(Objects.java:221)
3
at SecurityManager.<init>(Application.java:22)
4
at Application.main(Application.java:32)
В случае трассировки стека выше:
Простой текст
xxxxxxxxxx
1
Application.main(Application.java:32)
Эта концепция переноса null
-check в конструктор тесно связана с концепцией Design by Contract (DBC) . В этой технике каждый метод имеет следующие аспекты:
- Предварительные условия: вещи, которые должны быть верны для вызова метода
- Постусловия: вещи, которые должны быть истинными после завершения метода
- Инварианты: вещи, которые должны быть правдой на протяжении всей жизни объекта
Например, предварительным условием может быть то, что некоторые данные доступны для использования, а последующим условием может быть то, что результат метода, если он умножен сам на себя, должен быть равен аргументу, предоставленному методу (то есть функции квадратного корня). Хотя DBC может быть чрезмерно формализован, мы можем использовать концепцию инварианта для выражения нашего null
-check. Когда мы проверяем, что SecurityTransactionRepository
объект, предоставленный нашему SecurityManager
конструктору, не является null
, мы знаем, что если SecurityManager
объект успешно создан, SecurityTransactionRepository
то никогда не будет null
.
Так как SecurityTransactionManager
помечен как final
, конструктор является единственным механизмом, который может установить его значение. Если значение, предоставленное конструктору, не является null
, тогда мы знаем, что repo
поле будет не null
для жизни объекта. Поэтому нам больше не нужно проверять, является ли поле репо null
: мы предполагаем, что оно не является null
. Это составляет инвариант в нашем SecurityManager
классе. Таким образом, было бы пустой тратой кода проверить, находится ли repo
объект null
внутри нашего findTransactionById
определения. Вместо этого мы предполагаем, что это не null
в тот момент в классе.
Как правило, лучше всего генерировать исключения как можно скорее и в тот момент, когда возникает ошибка. Откладывание исключения до более поздней точки запутает процесс отладки и потратит время разработчиков, пытающихся найти причину проблем. В случае нашего SecurityManager
, ошибка состоит в том, что a null
SecurityTransactionManager
был передан его конструктору, и, следовательно, NPE должен быть брошен в конструктор. Если мы ожидаем, что SecurityTransactionRepository
может быть null
(и, следовательно, выбрасывать NPE в конструктор было бы неверно), мы должны явно сформулировать это предположение, используя механизм, такой как шаблонOptional
или объект Null Object .
Заключение
В этой статье мы рассмотрели valueOf
методы, включенные в каждый из примитивных классов в штучной упаковке, и как избежать возникновения ошибок при их использовании. Далее мы посмотрели на instanceof
метод и как избежать его использования, если в этом нет полной необходимости.
Наконец, мы рассмотрели, как использование исключений в нужной точке нашего кода может иметь значение для хорошо спроектированных классов и тонких ловушек. Хотя эти детали могут быть похоронены в повседневной работе по написанию кода на Java, именно эти маленькие детали делают разницу между хорошим программистом и хорошим мастером.
Дальнейшее чтение
7 советов по написанию лучшего Java-кода, который вы должны знать