Null check chain vs catching NullPointerException

Веб-служба возвращает огромный XML, и мне нужно получить доступ к глубоко вложенным полям. Например:

return wsObject.getFoo().getBar().getBaz().getInt() 

Проблема в том, что getFoo() , getBar() , getBaz() могут возвращать null .

Однако, если я проверяю значение null во всех случаях, код становится очень многословным и трудночитаемым. Более того, я могу пропустить проверки некоторых полей.

 if (wsObject.getFoo() == null) return -1; if (wsObject.getFoo().getBar() == null) return -1; // maybe also do something with wsObject.getFoo().getBar() if (wsObject.getFoo().getBar().getBaz() == null) return -1; return wsObject.getFoo().getBar().getBaz().getInt(); 

Допустимо ли писать

 try { return wsObject.getFoo().getBar().getBaz().getInt(); } catch (NullPointerException ignored) { return -1; } 

или это будет считаться антипаттерном?

19 Solutions collect form web for “Null check chain vs catching NullPointerException”

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

Я предлагаю вам вместо этого использовать Optional class. Это часто лучший подход, когда вы хотите работать со значениями, которые присутствуют или отсутствуют.

Используя это, вы можете написать свой код следующим образом:

 public Optional m(Ws wsObject) { return Optional.ofNullable(wsObject.getFoo()) // Here you get Optional.empty() if the Foo is null .map(f -> f.getBar()) // Here you transform the optional or get empty if the Bar is null .map(b -> b.getBaz()) .map(b -> b.getInt()); // Add this if you want to return an -1 int instead of an empty optional if any is null // .orElse(-1); // Or this if you want to throw an exception instead // .orElseThrow(SomeApplicationException::new); } 

Почему необязательно?

Использование Optional s вместо null для значений, которые могут отсутствовать, делает этот факт очень понятным и понятным для читателей, и система типов будет следить за тем, чтобы вы не случайно забыли об этом.

Вы также получаете доступ к методам работы с такими значениями более удобно, например map и orElse .


Является ли отсутствие действительным или ошибкой?

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


Может быть, больше вариантов?

Если, с другой стороны, отсутствующие значения из промежуточных методов действительны, возможно, вы также можете переключиться на Optional s для них?

Тогда вы можете использовать их следующим образом:

 public Optional mo(Ws wsObject) { return wsObject.getFoo() .flatMap(f -> f.getBar()) .flatMap(b -> b.getBaz()) .flatMap(b -> b.getInt()); } 

Почему не факультативно?

Единственная причина, по которой я могу думать о том, что не использовать параметр « Optional – это то, что это действительно важная часть кода для критически важной производительности, и если накладные расходы на garbage collection оказываются проблемой. Это связано с тем, что при выполнении кода выделяется несколько Optional объектов, и виртуальная машина может не удалять их. В этом случае ваши оригинальные if-тесты могут быть лучше.

Я предлагаю рассмотреть Objects.requireNonNull(T obj, String message) . Вы можете создавать цепочки с подробным сообщением для каждого исключения, например

 requireNonNull(requireNonNull(requireNonNull( wsObject, "wsObject is null") .getFoo(), "getFoo() is null") .getBar(), "getBar() is null"); 

Я бы предложил вам не использовать специальные возвращаемые значения, например -1 . Это не стиль Java. Java разработала механизм исключений, чтобы избежать этого старомодного способа, который исходил с языка C.

Выброс NullPointerException не самый лучший вариант. Вы можете предоставить свое собственное исключение (сделав его проверенным, чтобы гарантировать, что оно будет обработано пользователем или не проверено, чтобы обработать его более простым способом) или использовать конкретное исключение из синтаксического анализа XML, который вы используете.

Если предположить, что classовая структура действительно находится вне нашего контроля, как кажется, это так, я думаю, что поймать NPE, как было предложено в вопросе, действительно разумное решение, если только производительность не является серьезной проблемой. Одним из небольших улучшений может быть объединение логики throw / catch во избежание беспорядка:

 static  T get(Supplier supplier, T defaultValue) { try { return supplier.get(); } catch (NullPointerException e) { return defaultValue; } } 

Теперь вы можете просто сделать:

 return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), -1); 

Как уже указывал Том в комментарии,

Следующее заявление не подчиняется Закону Деметры ,

 wsObject.getFoo().getBar().getBaz().getInt() 

То, что вы хотите, это int и вы можете получить его от Foo . Закон Деметры говорит, никогда не разговаривайте с незнакомцами . Для вашего случая вы можете скрыть фактическую реализацию под капотом Foo и Bar .

Теперь вы можете создать метод в Foo для извлечения int из Baz . В конечном счете, Foo будет иметь Bar а в Bar мы можем получить доступ к Int не подвергая Baz напрямую Foo . Таким образом, нулевые проверки, вероятно, делятся на разные classы, и для classов будут использоваться только необходимые атрибуты.

Мой ответ почти в той же строке, что и @janki, но я хотел бы немного изменить fragment кода, как показано ниже:

 if (wsObject.getFoo() != null && wsObject.getFoo().getBar() != null && wsObject.getFoo().getBar().getBaz() != null) return wsObject.getFoo().getBar().getBaz().getInt(); else return something or throw exception; 

Вы также можете добавить нулевую проверку для wsObject , если есть вероятность, что этот объект будет null.

Чтобы улучшить читаемость, вы можете использовать несколько переменных, например

 Foo theFoo; Bar theBar; Baz theBaz; theFoo = wsObject.getFoo(); if ( theFoo == null ) { // Exit. } theBar = theFoo.getBar(); if ( theBar == null ) { // Exit. } theBaz = theBar.getBaz(); if ( theBaz == null ) { // Exit. } return theBaz.getInt(); 

Вы говорите, что некоторые методы «могут возвращать null », но не говорят, в каких обстоятельствах они возвращают null . Вы говорите, что поймаете NullPointerException но не говорите, почему вы его поймаете. Это отсутствие информации свидетельствует о том, что у вас нет четкого представления о том, какие исключения и почему они превосходят альтернативу.

Рассмотрим метод classа, который предназначен для выполнения действия, но метод не может гарантировать, что он выполнит действие из-за не зависящих от него обстоятельств (что на самом деле имеет место для всех методов в Java ). Мы называем этот метод, и он возвращается. Код, который вызывает этот метод, должен знать, был ли он успешным. Как он может это знать? Как он может быть структурирован, чтобы справиться с двумя возможностями, успехами или неудачами?

Используя исключения, мы можем написать методы, которые имеют успех в качестве постположения . Если метод возвращается, он был успешным. Если он выдает исключение, он потерпел неудачу. Это большая победа для ясности. Мы можем написать код, который четко обрабатывает нормальный случай успеха и переводит весь код обработки ошибок в предложения catch . Часто выясняется, что информация о том, как и почему метод не увенчалась успехом, не важна для вызывающего, поэтому одно и то же предложение catch может использоваться для обработки нескольких типов сбоев. И часто бывает так, что методу вообще не нужно перехватывать исключения, а просто разрешать им распространять его вызывающий. Исключения из-за ошибок программы в этом последнем classе; несколько методов могут реагировать соответствующим образом, когда есть ошибка.

Итак, те методы, которые возвращают null .

  • null значение указывает на ошибку в коде? Если это так, вы не должны вообще ловить исключение. И ваш код не должен пытаться догадаться сам. Просто напишите, что ясно и лаконично, исходя из предположения, что он будет работать. Является ли цепочка методов понятной и лаконичной? Тогда просто используйте их.
  • null значение указывает неверный ввод в вашу программу? Если это так, исключение NullPointerException не является подходящим исключением для throw, потому что условно оно зарезервировано для указания ошибок. Вероятно, вы захотите создать собственное исключение, полученное из IllegalArgumentException (если вы хотите исключить исключение ) или IOException (если вы хотите, чтобы исключение было исключено). Требуется ли ваша программа для предоставления подробных сообщений об ошибках синтаксиса при наличии недопустимого ввода? Если это так, проверка каждого метода на null возвращаемое значение, а затем бросание соответствующего диагностического исключения – единственное, что вы можете сделать. Если ваша программа не нуждается в подробной диагностике, цепочка вызовов метода соединяется вместе, перехватывая любое исключение NullPointerException а затем бросая ваше настраиваемое исключение, является самым ясным и самым кратким.

Один из ответов утверждает, что метод цепных методов нарушает Закон Деметры и, следовательно, является плохим. Это утверждение ошибочно.

  • Когда дело доходит до разработки программы, на самом деле нет абсолютных правил о том, что хорошо, а что плохо. Существуют только эвристики: правильные правила (даже почти все) того времени. Часть навыков программирования – это знать, когда это нормально, чтобы нарушить эти правила. Таким образом, краткое утверждение о том, что «это против правила X » на самом деле не является ответом. Является ли это одной из ситуаций, когда правило должно быть нарушено?
  • Закон Деметры – это действительно правило о дизайне интерфейса API или classа. При разработке classов полезно иметь иерархию абстракций . У вас есть classы низкого уровня, которые используют примитивы языка для непосредственного выполнения операций и представления объектов в абстракции, которая выше уровня, чем языковые примитивы. У вас есть classы среднего уровня, которые делегируют classы низкого уровня и реализуют операции и представления на более высоком уровне, чем classы низкого уровня. У вас есть classы высокого уровня, которые делегируются classам среднего уровня и выполняют еще более высокие операции и абстракции. (Я говорил о трех уровнях абстракции здесь, но возможно больше). Это позволяет вашему коду выражать себя с точки зрения соответствующих абстракций на каждом уровне, тем самым скрывая сложность. Обоснование Закона Деметры состоит в том, что если у вас есть цепочка вызовов методов, это предполагает, что у вас есть class высокого уровня, проходящий через class среднего уровня, чтобы напрямую обращаться к деталям низкого уровня, и поэтому ваш class среднего уровня не имеет обеспечил абстрактную операцию среднего уровня, требуемую classом высокого уровня. Но, похоже, это не та ситуация, которая у вас здесь: вы не проектировали classы в цепочке вызовов методов, они являются результатом некоторого автоматически сгенерированного кода сериализации XML (правильно?), А цепочка вызовов не убывает через иерархию абстракции, потому что дес-сериализованный XML находится на одном уровне иерархии абстракции (правильно?)?

Не перехватывайте NullPointerException . Вы не знаете, откуда это происходит (я знаю, что это не вероятно в вашем случае, но, возможно, что-то еще его бросило), и это медленно. Вы хотите получить доступ к указанному полю, и для этого каждое другое поле должно быть не равным нулю. Это идеальная причина для проверки каждого поля. Я бы, вероятно, проверил его в одном, если и затем создаст метод для удобочитаемости. Как уже указывали другие, уже возвращается -1, это очень старая школа, но я не знаю, есть ли у вас причина для этого или нет (например, общение с другой системой).

 public int callService() { ... if(isValid(wsObject)){ return wsObject.getFoo().getBar().getBaz().getInt(); } return -1; } public boolean isValid(WsObject wsObject) { if(wsObject.getFoo() != null && wsObject.getFoo().getBar() != null && wsObject.getFoo().getBar().getBaz() != null) { return true; } return false; } 

Edit: Это спорно , если это disobeyes Закон Деметры , так как WsObject это , вероятно , только структура данных (проверка https://stackoverflow.com/a/26021695/1528880 ).

Если вы не хотите реорганизовывать код, и вы можете использовать Java 8, можно использовать ссылки на методы.

Простая демонстрация сначала (извините статические внутренние classы)

 public class JavaApplication14 { static class Baz { private final int _int; public Baz(int value){ _int = value; } public int getInt(){ return _int; } } static class Bar { private final Baz _baz; public Bar(Baz baz){ _baz = baz; } public Baz getBar(){ return _baz; } } static class Foo { private final Bar _bar; public Foo(Bar bar){ _bar = bar; } public Bar getBar(){ return _bar; } } static class WSObject { private final Foo _foo; public WSObject(Foo foo){ _foo = foo; } public Foo getFoo(){ return _foo; } } interface Getter { R get(T value); } static class GetterResult { public R result; public int lastIndex; } /** * @param args the command line arguments */ public static void main(String[] args) { WSObject wsObject = new WSObject(new Foo(new Bar(new Baz(241)))); WSObject wsObjectNull = new WSObject(new Foo(null)); GetterResult intResult = getterChain(wsObject, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt); GetterResult intResult2 = getterChain(wsObjectNull, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt); System.out.println(intResult.result); System.out.println(intResult.lastIndex); System.out.println(); System.out.println(intResult2.result); System.out.println(intResult2.lastIndex); // TODO code application logic here } public static  GetterResult getterChain(V1 value, Getter g1, Getter g2, Getter g3, Getter g4) { GetterResult result = new GetterResult<>(); Object tmp = value; if (tmp == null) return result; tmp = g1.get((V1)tmp); result.lastIndex++; if (tmp == null) return result; tmp = g2.get((V2)tmp); result.lastIndex++; if (tmp == null) return result; tmp = g3.get((V3)tmp); result.lastIndex++; if (tmp == null) return result; tmp = g4.get((V4)tmp); result.lastIndex++; result.result = (R)tmp; return result; } } 

Вывод

241
4

ноль
2

Интерфейс Getter – это просто функциональный интерфейс, вы можете использовать любой эквивалент.
Класс GetterResult , аксессоры, GetterResult для ясности, удерживают результат геттерной цепочки, если таковой имеется, или индекс последнего геттера.

Метод getterChain – это простая, шаблонная часть кода, которая может генерироваться автоматически (или вручную при необходимости).
Я структурировал код так, чтобы повторяющийся блок был само собой разумеющимся.


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

Я бы реорганизовал код вместо этого, но если не удается, и вы обнаружите, что вы сами используете длинные геттерные цепи, вы можете подумать о создании classа с перегрузками, которые берут от 2 до, скажем, 10, getters.

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

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

Существует тысячи случаев, когда ваш код может пойти не так: не удается подключиться к базе данных, IO Exception, Network error … Если вы имеете дело с ними один за другим (например, нулевая проверка здесь), это будет слишком много хлопот.

В коде:

 wsObject.getFoo().getBar().getBaz().getInt(); 

Даже когда вы знаете, какое поле пустое, вы не знаете, что происходит не так. Может быть, Bar имеет значение NULL, но ожидаем? Или это ошибка данных? Подумайте о людях, которые читают ваш код

Как и в случае xenteros, я предлагаю использовать нестандартное исключение . Например, в этой ситуации: Foo может быть нулевым (действительные данные), но Bar и Baz никогда не должны быть нулевыми (недопустимыми данными)

Код может быть переписан:

 void myFunction() { try { if (wsObject.getFoo() == null) { throw new FooNotExistException(); } return wsObject.getFoo().getBar().getBaz().getInt(); } catch (Exception ex) { log.error(ex.Message, ex); // Write log to track whatever exception happening throw new OperationFailedException("The requested operation failed") } } void Main() { try { myFunction(); } catch(FooNotExistException) { // Show error: "Your foo does not exist, please check" } catch(OperationFailedException) { // Show error: "Operation failed, please contact our support" } } 

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

Вам придется поймать исключение, где бы вы ни называли метод (или он будет распространять стек). Тем не менее, если в вашем случае вы можете продолжать работать с этим результатом со значением -1, и вы уверены, что он не будет распространяться, потому что вы не используете ни одну из «частей», которая может быть нулевой, тогда мне кажется правильным Лови

Редактировать:

Я согласен с более поздним ответом от @xenteros, лучше запустить собственное исключение, вместо этого возвращая -1, вы можете назвать это InvalidXMLException например.

Следи за этим сообщением со вчерашнего дня.

Я комментирую / голосую комментарии, которые говорят, что улавливать NPE плохо. Вот почему я это делаю.

 package com.todelete; public class Test { public static void main(String[] args) { Address address = new Address(); address.setSomeCrap(null); Person person = new Person(); person.setAddress(address); long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { try { System.out.println(person.getAddress().getSomeCrap().getCrap()); } catch (NullPointerException npe) { } } long endTime = System.currentTimeMillis(); System.out.println((endTime - startTime) / 1000F); long startTime1 = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { if (person != null) { Address address1 = person.getAddress(); if (address1 != null) { SomeCrap someCrap2 = address1.getSomeCrap(); if (someCrap2 != null) { System.out.println(someCrap2.getCrap()); } } } } long endTime1 = System.currentTimeMillis(); System.out.println((endTime1 - startTime1) / 1000F); } } 

  public class Person { private Address address; public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } } 

 package com.todelete; public class Address { private SomeCrap someCrap; public SomeCrap getSomeCrap() { return someCrap; } public void setSomeCrap(SomeCrap someCrap) { this.someCrap = someCrap; } } 

 package com.todelete; public class SomeCrap { private String crap; public String getCrap() { return crap; } public void setCrap(String crap) { this.crap = crap; } } 

Вывод

3,216

0,002

Я вижу здесь явного победителя. Если проверка слишком дешевле, чем исключение. Я видел этот способ Java-8. Учитывая, что 70% текущих приложений по-прежнему работают на Java-7, я добавляю этот ответ.

Нижняя линия. Для любых приложений с критической нагрузкой обработка NPE является дорогостоящей.

Если эффективность является проблемой, следует рассмотреть вариант «улов». Если «catch» не может использоваться, потому что он будет распространяться (как упоминается «SCouto»), используйте локальные переменные, чтобы избежать множественных вызовов методам getFoo() , getBar() и getBaz() .

Стоит подумать о создании собственного Исключения. Назовем это MyOperationFailedException. Вы можете бросить его, вместо этого вернув значение. Результат будет таким же – вы выйдете из функции, но вы не вернете жестко заданное значение -1, которое является анти-шаблоном Java. В Java мы используем Исключения.

 try { return wsObject.getFoo().getBar().getBaz().getInt(); } catch (NullPointerException ignored) { throw new MyOperationFailedException(); } 

РЕДАКТИРОВАТЬ:

Согласно обсуждению в комментариях, позвольте мне добавить что-то к моим предыдущим мыслям. В этом коде есть две возможности. Один из них заключается в том, что вы принимаете значение null, а другое – то, что это ошибка.

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

Если это приемлемо, вам неважно, где этот нуль появился. Если вы это сделаете, вы определенно не должны связывать эти запросы.

Метод, который у вас есть, длинный, но очень читаемый. Если бы я был новым разработчиком, который приходил на вашу базу кода, я мог бы видеть, что вы делаете довольно быстро. Большинство других ответов (в том числе ловить исключение), похоже, не делают вещи более читаемыми, а некоторые делают их менее читаемыми, на мой взгляд.

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

 private int getFooBarBazInt() { if (wsObject.getFoo() == null) return -1; if (wsObject.getFoo().getBar() == null) return -1; if (wsObject.getFoo().getBar().getBaz() == null) return -1; return wsObject.getFoo().getBar().getBaz().getInt(); } 

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

Когда вы общаетесь с удаленной веб-службой, очень типично иметь «удаленный домен» и «домен приложения» и переключаться между ними. Удаленный домен часто ограничивается веб-протоколом (например, вы не можете отправлять вспомогательные методы назад и вперед в чистом RESTful-сервисе, а глубоко вложенные объектные модели являются общими, чтобы избежать множества вызовов API), и поэтому они не идеальны для прямого использования в ваш клиент.

Например:

 public static class MyFoo { private int barBazInt; public MyFoo(Foo foo) { this.barBazInt = parseBarBazInt(); } public int getBarBazInt() { return barBazInt; } private int parseFooBarBazInt(Foo foo) { if (foo() == null) return -1; if (foo().getBar() == null) return -1; if (foo().getBar().getBaz() == null) return -1; return foo().getBar().getBaz().getInt(); } } 
 return wsObject.getFooBarBazInt(); 

применяя Закон Деметры,

 class WsObject { FooObject foo; .. Integer getFooBarBazInt() { if(foo != null) return foo.getBarBazInt(); else return null; } } class FooObject { BarObject bar; .. Integer getBarBazInt() { if(bar != null) return bar.getBazInt(); else return null; } } class BarObject { BazObject baz; .. Integer getBazInt() { if(baz != null) return baz.getInt(); else return null; } } class BazObject { Integer myInt; .. Integer getInt() { return myInt; } } 

Давать ответ, который кажется отличным от всех остальных.

Я рекомендую вам проверить NULL if s.

Причина:

Мы не должны оставлять шанс, чтобы наша программа разбилась. NullPointer генерируется системой. Поведение генерируемых системой исключений невозможно предсказать . Вы не должны оставлять свою программу в armх Системы, когда у вас уже есть способ ее обработки самостоятельно. И поставьте механизм обработки исключений для дополнительной безопасности. !!

Для упрощения чтения кода попробуйте это для проверки условий:

 if (wsObject.getFoo() == null || wsObject.getFoo().getBar() == null || wsObject.getFoo().getBar().getBaz() == null) return -1; else return wsObject.getFoo().getBar().getBaz().getInt(); 

РЕДАКТИРОВАТЬ :

Здесь вам нужно сохранить эти значения wsObject.getFoo() , wsObject.getFoo().getBar() , wsObject.getFoo().getBar().getBaz() в некоторых переменных. Я не делаю этого, потому что я не знаю возвращаемых типов этих функций.

Any suggestions will be appreciated..!!

I wrote a class called Snag which lets you define a path to navigate through a tree of objects. Here is an example of its use:

 Snag ENGINE_NAME = Snag.createForAndReturn(Car.class, String.class).toGet("engine.name").andReturnNullIfMissing(); 

Meaning that the instance ENGINE_NAME would effectively call Car?.getEngine()?.getName() on the instance passed to it, and return null if any reference returned null :

 final String name = ENGINE_NAME.get(firstCar); 

It’s not published on Maven but if anyone finds this useful it’s here (with no warranty of course!)

It’s a bit basic but it seems to do the job. Obviously it’s more obsolete with more recent versions of Java and other JVM languages that support safe navigation or Optional .

  • NullPointerException - доступ к представлениям в onCreate ()
  • Местоположение JavaFX не установлено сообщение об ошибке
  • Почему сравнение Integer с int может вызывать NullPointerException в Java?
  • Загрузите листок данных Excel в базу данных Oracle
  • Android - Как переопределить кнопку «Назад», чтобы она не закончила () мою активность?
  • Android startCamera дает мне null Intent и ... разрушает мою глобальную переменную?
  • NullPointerException при создании массива объектов
  • Null pointer Exception - findViewById ()
  • Доступ к инъецируемой зависимости в конструкторе управляемого компонента вызывает NullPointerException
  • Android - NullPointerException в SearchView в панели действий
  • Избегание! = Null
  • Interesting Posts
    Давайте будем гением компьютера.