Изучение основ языка программирования, такого как 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, и мы должны поставить для десериализации произойти (без необходимости других фокусов).setAccountValueString
Например, если 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, и ничто не мешает пользователю, незнакомому с нашим приложением, правильно установить accountValuea 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-кода, который вы должны знать