Как подключить arrayutils java
Теперь вы можете использовать методы ArrayUtils в своем коде.
int[] nums = 1, 2, 3, 4, 5>; int[] newNums = ArrayUtils.removeElement(nums, 3); // [1, 2, 3, 5]
В приведенном выше примере метод removeElement удаляет значение 3 из массива nums , и новый массив без этого элемента сохраняется в переменную newNums
Как обработать более двух чисел в методе Math.max?
Начал учить Java, у Хорстманна в задании требуется с помощью метода Math.max определить наибольшее из 3-х введенных чисел, но насколько я понял, метод может сравнивать только 2 числа, или я чего-то не допонял?
- Вопрос задан более трёх лет назад
- 1048 просмотров
Комментировать
Решения вопроса 1

Писатель-прозаик
Да вы правы, но принимает два аргумента, но деллается задание по другому. int max = Math.max(Math.max(a,b),c); т.е. находится максимум двух чисел, и далее этот находим наибольшее среди максимума и третьего числа.
Ответ написан более трёх лет назад
Комментировать
Нравится 2 Комментировать
Как определить, является ли строка числом?
У меня тоже работает. Я просто про объявление throws NumberFormatException — по-моему оно лишнее, т.к. это исключение из функции никогда не вылетит.
2 дек 2011 в 22:39
Еще можно строку подвергнуть trim(), чтобы отсечь пробелы в начале и в конце (если это, конечно, не принципиально). По поводу имени функции — согласен с @Kremchik
2 дек 2011 в 23:03
Тут главный выбор из предложенных вариантов — выбор по скорости работы. Сделайте тестовую программку, прогоните эту функцию в разных вариантах много-много раз и проанализируйте время выполнения программы.
2 дек 2011 в 23:08
9 ответов 9
Сортировка: Сброс на вариант по умолчанию
Я так понимаю, цель именно на Integer проверить? Тогда можно:
s.matches("[-+]?\\d+") // для списка из 1 млн целых чисел и не чисел // (примерно 50/50) эффективность этой строчки практически // не отличается от описанной в вопросе функции.
Если double , но без экспоненциальной нотации, то
((-|\\+)?[0-9]+(\\.[0-9]+)?)+
такое регулярное подойдёт.
Хотя я бы не парился и сделал бы точно так же, только с Double.parseDouble(s) , но я не показатель 🙂 И ещё я обратил внимание на название метода — оно не очень корректное, т.к. во-первых, такой есть в классе Character , во-вторых, по смыслу подходит isNumeric() . Но это просто комментарии из серии «что я думаю по этому поводу».
Отслеживать
ответ дан 2 дек 2011 в 22:58
2,828 14 14 серебряных знаков 18 18 бронзовых знаков
8 апр 2016 в 14:23
Проверка выражения является ли 2-1+1 числом, даёт положительный ответ.
24 сен 2019 в 6:06
Это же логично. Разве нет?
24 сен 2019 в 9:47
Спасибо, мне помогло!
14 мая 2021 в 11:12
Есть очень хороший static-метод в commons-lang (класс NumberUtils ), который учитывает множество особенностей чисел Java:
/** * Checks whether the String a valid Java number.
* * Valid numbers include hexadecimal marked with the 0x * qualifier, scientific notation and numbers marked with a type * qualifier (e.g. 123L).
* * Null and empty String will return * false.
* * @param str the String to check * @return true if the string is a correctly formatted number */ public static boolean isNumber(String str)
Отслеживать
ответ дан 8 апр 2016 в 13:41
Evgeny Lazarev Evgeny Lazarev
1,120 1 1 золотой знак 8 8 серебряных знаков 14 14 бронзовых знаков
Метод, описанный в вопросе, и есть самый правильный метод.
Методы, которые проверяют, состоит ли строка лишь из цифр, не справятся с переполнением (попробуйте число 100000000000000000000000000000000 ). Правильный паттерн для целого числа такой:
^(-?([1-9][0-9] |1[0-9] |20[0-9] |21[0-3][0-9] |214[0-6][0-9] |2147[0-3][0-9] |21474[0-7][0-9] |214748[0-2][0-9] |2147483[0-5][0-9] |21474836[0-3][0-9] |214748364[0-7] ) |-2147483648 |0)$
и поверьте, вам не хочется отлаживать его или адаптировать для других типов.
При этом решения, основанные на паттернах, не будут учитывать локаль и разрешённые разделители разрядов. Так что попробовать распарсить и поймать исключение — практически единственно правильное решение.
Отслеживать
ответ дан 8 апр 2016 в 15:02
207k 28 28 золотых знаков 293 293 серебряных знака 526 526 бронзовых знаков
8 апр 2016 в 15:26
@Qwertiy: Ну, мало ли какие баги и где. Писать кривые велосипеды к багам отдельной версии отдельной платформы — неправильно.
8 апр 2016 в 15:27
Использовать исключения для стандартной логики — неправильно 🙂
8 апр 2016 в 15:33
@Qwertiy: Да, именно для этого и нужен этот метод — заврапить дыру в дизайне платформы. В .NET, например, есть честный int.TryParse .
8 апр 2016 в 15:34
@Qwertiy: Но создавать по сути два отдельных парсера int ‘а и молиться Гослингу, чтобы они оказались консистентными — очень неправильно. Логика разбора int из строки должна быть лишь в одном месте, и это место — Integer.parseInt .
8 апр 2016 в 15:36
В связи с повышенным интересом по данному вопросу, сделал маленькое исследование 🙂
В исследовании принимали участия классы:
CharacterDelegator() // код @Sergey ComplexMatcher() // код @VladD NumberUtilsDelegator() // мой код SimpleMatcher() // код @ivkremer (для простых чисел) GuavaDelegator() // код @Nofate♦ SimpleMatcherWithDot() // код @ivkremer (для чисел с точкой) SimpleParser() // оригинальный код от @pol GuavaComplexDelegator() // модифицированный вариант кода @Nofate♦ для Float InnerSetImpl() // мой специфический вариант
(@VladD, извиняюсь, понимаю, что делает ваш регэксп, но завести его у меня не получилось)
Вывода много, поэтому вкратце:
- абсолютно все строки из примера распарсил только NumberUtils (commons-lang)
- вариант с использованием Guava (расширенный) справился со всем кроме long-нотаций («l») и «0xCAFEBABE» 🙂 (честно, не понимаю, почему он варианты с «f»-нотацией прожевал)
- остальные варианты в большей степени рассчитаны на парсинг именно интов, хотя в изначальном вопросе об этом ни слова 🙂
А вот самое интересное — это время работы данного кода.
Start performance test for core.impl.CharacterDelegator Ints: 125ms Numbers: 67ms Numbers with 25% errors: 50ms Small Ints: 43ms
Start performance test for core.impl.ComplexMatcher Ints: 10825ms Numbers: 11134ms Numbers with 25% errors: 10606ms Small Ints: 10380ms
Start performance test for core.impl.InnerSetImpl Ints: 50ms Numbers: 52ms Numbers with 25% errors: 54ms Small Ints: 42ms
Start performance test for core.impl.NumberUtilsDelegator Ints: 111ms Numbers: 91ms Numbers with 25% errors: 99ms Small Ints: 51ms
Start performance test for core.impl.SimpleMatcher Ints: 1072ms Numbers: 853ms Numbers with 25% errors: 847ms Small Ints: 766ms
Start performance test for core.impl.GuavaDelegator Ints: 131ms Numbers: 108ms Numbers with 25% errors: 124ms Small Ints: 119ms
Start performance test for core.impl.SimpleMatcherWithDot Ints: 3069ms Numbers: 5855ms Numbers with 25% errors: 5484ms Small Ints: 2548ms
Start performance test for core.impl.SimpleParser Ints: 157ms Numbers: 2189ms Numbers with 25% errors: 2117ms Small Ints: 81ms
Start performance test for core.impl.GuavaComplexDelegator Ints: 980ms Numbers: 943ms Numbers with 25% errors: 1016ms Small Ints: 837ms
Тест построен следующим образом
-
Генерируем 2 рандомных списка со стрингами (числа). Тут есть 4 варианта:
1. Просто Инты. 2. Числа (с 50% вероятностью в стринге есть точка). 3. Числа с возможной ошибкой (25% что в строке есть подстрока "error"). 4. Набор Интов в небольшом диапазоне.
Из приведенных выкладок видно, что NumberUtils работает быстрее всего. Схожее время работы у простого варианты Guava`ы и простых regexp. Даже добавление простой точки значительно замедляет код. Также замечу, что код @Sergey работает очень быстро, но он рассчитан на проверку строго интов.
А еще есть мой специфический пример InnerSetImpl . Он основан на допущении, что у нас есть ограниченное число возможных вариантов (то есть, мы можем и готовы держать их в памяти). Тогда мы просто помещаем их в HashSet и проверяем наличие строк в нем. Собственно, такой вариант самый быстрый, но допущение многое портит 🙂
ИТОГО: если нужно простое и элегантное решение — то лучше всего воспользоваться NumberUtils.isNumber(str) и не париться.
А если вдруг у вас стоит специфическая задача на парсинг не просто чисел, но чисел в Java-нотациях, то это единственный полностью рабочий вариант.
(Все вышесказанное относится только к приведенному коду. Я не исключаю, что можно придумать (или даже существует) более правильное или более быстрое решение).
Dagger 2. Часть третья. Новые грани возможного
Всем привет! Наконец-то подоспела третья часть цикла статей о Dagger 2!
Перед дальнейшим прочтением настоятельно рекомендую ознакомиться с первой и второй частями.
Большое спасибо за отзывы и комментарии. Я очень рад, что мои статьи действительно помогают разработчикам окунуться в мир Даггера. Именно это и придает силы творить для вас дальше.
В третьей части мы с вами рассмотрим различные интересные и немаловажные фичи библиотеки, которые могут вам очень пригодиться.
Вообще библиотека существует уже приличное время, но документация по-прежнему крайне отвратная. Разработчику, который только начинает свое знакомство с Даггером, я бы даже посоветовал не заглядывать в официальную документацию вначале, дабы не разочаровываться в этом жестком и несправедливом мире.
Есть, конечно, моменты, которые расписаны более-менее. Но вот всякие новые фичи описаны так, что мне приходилось методом проб и ошибок, залезая в сгенерированный код, самому разбираться, как оно все работает. Благо хорошие люди пишут хорошие статьи, но даже иногда они не дают четкого и ясного ответа сразу.
Итак, хватит разглагольствовать, и вперед к новым знаниям!
Qualifier annotation
В прошлой статье в комментариях попросили осветить данный вопрос. Не будем откладывать в долгий ящик.
Часто бывает, что нам необходимо провайдить несколько объектов одного типа. Например, мы хотим иметь в системе два Executor : один однопоточный, другой с CachedThreadPool . В этом случае нам приходит на помощь «qualifier annotation». Это кастомная аннотация, которая имеет в себе аннотацию @Qualifier . Звучит немного как масло масляное, но на примере все гораздо проще.
В общем, Dagger2 предоставляет нам уже одну готовую «qualifier annotation», которой, пожалуй, вполне достаточно в повседневной жизни:
@Qualifier @Documented @Retention(RUNTIME) public @interface Named < /** The name. */ String value() default ""; >
А теперь посмотрим, как это все выглядит в бою:
Qualifier annotation пример
@Module public class AppModule < @Provides @Singleton @Named("SingleThread") public Executor provideSingleThreadExecutor() < return Executors.newSingleThreadExecutor(); >@Provides @Singleton @Named("MultiThread") public Executor provideMultiThreadExecutor() < return Executors.newCachedThreadPool(); >> public class MainActivity extends AppCompatActivity < @Inject @Named("SingleThread") Executor singleExecutor; @Inject @Named("MultiThread") Executor multiExecutor; @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); MyApplication.getInstance().getAppComponent().inject(this); setContentView(R.layout.activity_main); >>
В итоге у нас два разных экземпляра ( singleExecutor , multiExecutor ) одного класса ( Executor ). То, что нам и нужно! Замечу, что объекты одного класса с аннотацией @Named могут провайдиться также как с абсолютно разных и независимых компонентов, так и c зависимых друг от друга.
Отложенная инициализация
Одна из распространенных наших разработческих проблем — это долгий старт приложения. Обычно причина в одном — мы слишком много всего грузим и инициализируем при старте. Кроме того, Dagger2 строит граф зависимостей в основном потоке. И часто далеко не все конструируемые Даггером объекты нужны сразу же. Поэтому библиотека дает нам возможность отложить инициализацию объекта до первого вызова с помощью интерфейсов Provider<> и Lazy<> .
Сразу же обратим наш взор на пример:
Пример отложенной инициализации
@Module public class AppModule < @Provides @Named("SingleThread") public Executor provideSingleThreadExecutor() < return Executors.newSingleThreadExecutor(); >@Provides @Named("MultiThread") public Executor provideMultiThreadExecutor() < return Executors.newCachedThreadPool(); >> public class MainActivity extends AppCompatActivity < @Inject @Named("SingleThread") ProvidersingleExecutorProvider; @Inject @Named("MultiThread") Lazy multiExecutorLazy; @Inject @Named("MultiThread") Lazy multiExecutorLazyCopy; @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); MyApplication.getInstance().getAppComponent().inject(this); setContentView(R.layout.activity_main); // Executor singleExecutor = singleExecutorProvider.get(); Executor singleExecutor2 = singleExecutorProvider.get(); // Executor multiExecutor = multiExecutorLazy.get(); Executor multiExecutor2 = multiExecutorLazy.get(); Executor multiExecutor3 = multiExecutorLazyCopy.get(); >>
Начнем с Provider singleExecutorProvider . До первого вызова singleExecutorProvider.get() Даггер не инициализирует соответствующий Executor . Но при каждом последующем вызове singleExecutorProvider.get() будет создаваться новый экземпляр. Таким образом singleExecutor и singleExecutor2 — это два разных объекта. Такое поведение по сути идентично поведению unscoped объекта.
В каких вообще ситуациях уместен Provider ? Он пригождается, когда мы провайдим какую-то мутабельную зависимость, меняющую свое состояние в течении времени, и при каждом обращении нам необходимо получать актуальное состояние. «Что за кривая архитектура?» — скажите вы, и я с вами соглашусь. Но при работе с legacy кодом и не такое увидишь.
Отмечу, что авторы библиотеки тоже не советуют злоупотреблять интерфейсом Provider в тех местах, где достаточно обойтись обычным unscope, так как это чревато «кривой архитектурой», как говорилось выше, и трудно отлавливаемыми багами.
Теперь Lazy multiExecutorLazy и Lazy multiExecutorLazyCopy . Dagger2 инициализирует соответствующие Executor только при первом вызове multiExecutorLazy.get() и multiExecutorLazyCopy.get() . Далее Даггер кэширует проинициализированные значения для каждого Lazy<> и при втором вызове multiExecutorLazy.get() и multiExecutorLazyCopy.get() выдает закэшированные объекты.
Таким образом multiExecutor и multiExecutor2 ссылаются на один объект, а multiExecutor3 на второй объект.
Но, если мы в AppModule к методу provideMultiThreadExecutor() добавим аннотацию @Singleton , то объект будет кешироваться для всего дерева зависимостей, и multiExecutor , multiExecutor2 , multiExecutor3 будут ссылаться на один объект.
Асинхронная загрузка
Мы подошли с вами к весьма нетривиальной задаче. А что, если мы хотим, чтобы конструирование графа зависимостей проходило в бэкграунде? Звучит многообещающе? Да-да, я про Producers.
Честно скажу, это тема заслуживает вообще отдельного рассмотрения. Там много особенностей и нюансов. По ней достаточно хорошего материала. Сейчас же я коснусь только плюсов и минусов Producers.
Плюсы. Ну самый главный плюс — это загрузка в бэкграунде и возможность управлять этим процессом загрузки.
Минусы. Producers «тащат» за собой Guava, а это плюс 15 тысяч методов к апк. Но самое плохое, что применение Producers немного «портят» общую архитектуру и делают код более запутанным. Если у вас уже был Даггер, а потом вы решили перенести инициализацию объектов в бэкграунд, вам придется хорошенько постараться.
В официальной документации данная темы выделена в специальный раздел. Но я очень рекомендую статьи Miroslaw Stanek. У него вообще очень хороший блог, и там много статей про Dagger2. Собственно, некоторые даже макеты картинок с прошлых статей я заимствовал у него.
Про Producers он пишет в этой статье.
А вот в следующей предлагает очень интересную альтернативу для загрузки дерева зависимостей в бэкграунде. На помощь приходит родная RxJava. Мне очень нравится его решение, так как оно полностью лишено недостатков использования Producers, но при этом решает вопрос асинхронной загрузки.
Один только минус: Мирослав не совсем верно применяет Observable.create(. ) . Но я об этом написал в комментарии к статье, так что обратите внимание обязательно.
А теперь посмотрим, как будет выглядеть тогда код для scope объекта (с «правильной» RxJava):
Пример со scope
@Module public class AppModule < @Provides @Singleton // or custom scope for "local" singletons HeavyExternalLibrary provideHeavyExternalLibrary() < HeavyExternalLibrary heavyExternalLibrary = new HeavyExternalLibrary(); heavyExternalLibrary.init(); //This method takes about 500ms return heavyExternalLibrary; >@Provides @Singleton // or custom scope for "local" singletons Observable provideHeavyExternalLibraryObservable( final Lazy heavyExternalLibraryLazy) < return Observable.fromCallable(heavyExternalLibraryLazy::get) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); >> public class MainActivity extends AppCompatActivity < @Inject ObservableheavyExternalLibraryObservable; //This will be injected asynchronously HeavyExternalLibrary heavyExternalLibrary; @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); MyApplication.getInstance().getAppComponent().inject(this); setContentView(R.layout.activity_main); // init HeavyExternalLibrary in background thread! heavyExternalLibraryObservable.subscribe( heavyExternalLibrary1 ->heavyExternalLibrary = heavyExternalLibrary1, throwable -> <> ); > >
Обратите внимание на @Singleton и интерфейс Lazy в AppModule . Lazy как раз и гарантирует, что тяжеловесный объект будет проинициализирован, когда мы запросим, а затем закеширован.
А как нам быть, если мы хотим каждый раз получать новый экземпляр этого «тяжелого» объекта? Тогда стоит немного поменять AppModule :
Пример с unscope
@Module public class AppModule < @Provides // No scope! HeavyExternalLibrary provideHeavyExternalLibrary() < HeavyExternalLibrary heavyExternalLibrary = new HeavyExternalLibrary(); heavyExternalLibrary.init(); //This method takes about 500ms return heavyExternalLibrary; >@Provides @Singleton // or custom scope for "local" singletons Observable provideHeavyExternalLibraryObservable( final Provider heavyExternalLibraryLazy) < return Observable.fromCallable(heavyExternalLibraryLazy::get) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); >> public class MainActivity extends AppCompatActivity < @Inject ObservableheavyExternalLibraryObservable; //This will be injected asynchronously HeavyExternalLibrary heavyExternalLibrary; HeavyExternalLibrary heavyExternalLibraryCopy; @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); MyApplication.getInstance().getAppComponent().inject(this); setContentView(R.layout.activity_main); // init HeavyExternalLibrary and heavyExternalLibraryCopy in background thread! heavyExternalLibraryObservable.subscribe( heavyExternalLibrary1 ->heavyExternalLibrary = heavyExternalLibrary1, throwable -> <> ); heavyExternalLibraryObservable.subscribe( heavyExternalLibrary1 -> heavyExternalLibraryCopy = heavyExternalLibrary1, throwable -> <> ); > >
Для метода provideHeavyExternalLibrary() мы убрали scope, а в provideHeavyExternalLibraryObservable(final Provider heavyExternalLibraryLazy) используем Provider вместо Lazy . Таким образом heavyExternalLibrary и heavyExternalLibraryCopy в MainActivity — это разные объекты.
А можно еще вообще весь процесс инициализации дерева зависимостей вынести в бэкграунд. Вы спросите, как? Очень даже легко. Сначала посмотрим на то, как было:
SplashActivity со статьи Мирослава
public class SplashActivity extends BaseActivity < @Inject SplashActivityPresenter presenter; @Inject AnalyticsManager analyticsManager; @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setupActivityComponent(); >@Override protected void setupActivityComponent() < final SplashActivityComponent splashActivityComponent = GithubClientApplication.get(SplashActivity.this) .getAppComponent() .plus(new SplashActivityModule(SplashActivity.this)); splashActivityComponent.inject(SplashActivity.this); >>
А теперь взглянем на обновленный метод void setupActivityComponent() (с моими правками по RxJava):
void setupActivityComponent()
@Override protected void setupActivityComponent() < Completable.fromAction(() ->< final SplashActivityComponent splashActivityComponent = GithubClientApplication.get(SplashActivity.this) .getAppComponent() .plus(new SplashActivityModule(SplashActivity.this)); splashActivityComponent.inject(SplashActivity.this); >) .doOnCompleted(() -> < //Here is the moment when injection is done. analyticsManager.logScreenView(getClass().getName()); presenter.callAnyMethod(); >) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(() -> <>, throwable -> <>); >
Замеры
В прошлом разделе мы говорили про производительность при старте приложения. Однако мы знаем, что, если вопрос касается производительности и скорости, мы должны замерять! Полагаться на интуицию и чувство «вроде бы стало быстрее» нельзя. И с этим нам снова поможет Мирослав в этой и этой статьях. Чтобы мы все делали без него, вообще не представляю.
Новые интересные возможности
У Даггера появляются новые интересные фичи, обещающие нам облегчить жизнь. Но вот понять, как все работает и что же нам это все дает, — было задачей не из легких. Ну что же, начнем!
@Reusable scope
Интересная аннотация. Позволяет экономить память, но при этом по сути не ограничена никаким scope , что делает очень удобным переиспользование зависимостей в любых компонентах. То есть это нечто среднее между scope и unscope .
В доках пишут очень важный момент, который как-то не бросается в глаза с первого раза: «Для каждого компонента, который использует @Reusable зависимость, данная зависимость кешируется отдельно«. И мое дополнение: «В отличии от scope аннотации, где объект кешируется при создании и его экземпляр используется дочерними и зависимыми компонентами«.
А теперь сразу пример, чтобы все понять:
Длинный пример с разъяснениями
Наш главный компонент.
@Component(modules = ) @Singleton public interface AppComponent
У AppComponent есть два Subcomponent . Обратили внимание на эту конструкцию — FirstComponent.Builder ? О ней мы чуть позже.
Теперь посмотрим на UtilsModule .
@Module public class UtilsModule < @Provides @NonNull @Reusable public NumberUtils provideNumberUtils() < return new NumberUtils(); >@Provides @NonNull public StringUtils provideStringUtils() < return new StringUtils(); >>
NumberUtils с аннотацией @Reusable , а StringUtils оставим unscoped .
Далее у нас два Subcomponents .
@FirstScope @Subcomponent(modules = FirstModule.class) public interface FirstComponent < @Subcomponent.Builder interface Builder < FirstComponent.Builder firstModule(FirstModule firstModule); FirstComponent build(); >void inject(MainActivity mainActivity); > @SecondScope @Subcomponent(modules = ) public interface SecondComponent < @Subcomponent.Builder interface Builder < SecondComponent.Builder secondModule(SecondModule secondModule); SecondComponent build(); >void inject(SecondActivity secondActivity); void inject(ThirdActivity thirdActivity); >
Как мы видим, FirstComponent инжектирует только в MainActivity , а SecondComponent — в SecondActivity и ThirdActivity .
Посмотрим код.
public class MainActivity extends AppCompatActivity < @Inject NumberUtils numberUtils; @Inject StringUtils stringUtils; @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyApplication.getInstance().getFirstComponent() .inject(this); // other. >> public class SecondActivity extends AppCompatActivity < @Inject NumberUtils numberUtils; @Inject StringUtils stringUtils; @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); MyApplication.getInstance().getSecondComponent() .inject(this); // other. >> public class ThirdActivity extends AppCompatActivity < @Inject NumberUtils numberUtils; @Inject StringUtils stringUtils; @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.activity_third); MyApplication.getInstance().getSecondComponent() .inject(this); // other. >>
Коротко про навигацию. Из MainActivity мы попадаем в SecondActivity , а затем в ThirdActivity . А теперь вопрос. Когда мы будем уже на третьем экране, сколько объектов NumberUtils и StringUtils будет создано?
Так как StringUtils — unscoped , то будет создано три экземпляра, то есть при каждой инъекции создается новый объект. Это мы знаем.
А вот объектов NumberUtils будет два — один для FirstComponent , а другой для SecondComponent . И здесь я снова приведу основную мысль про @Reusable с документации: «Для каждого компонента, который использует @Reusable зависимость, данная зависимость кешируется отдельно!«, в отличии от scope аннотации, где объект кешируется при создании и его экземпляр используется дочерними и зависимыми компонентами.
Но сами гугловцы предупреждают, что если вам необходим уникальный объект, который может быть еще и mutable, то используйте только scoped аннотации.
Еще приведу ссылку на вопрос про сравнение @Singleton и @Reusable со SO.
@Subcomponent.Builder
Фича, которая делает код красивее. Раньше, чтобы создать @Subcomponent нам приходилось писать нечто такое:
Как было
@Component(modules = ) @Singleton public interface AppComponent < FirstComponent plusFirstComponent(FirstModule firstModule, SpecialModule specialModule); >@FirstScope @Subcomponent(modules = ) public interface FirstComponent
appComponent .plusFirstComponent(new FirstModule(), new SpecialModule());
Мне не нравилось в этом подходе то, что родительский компонент был загружен ненужными знаниями о модулях, которые используют дочерние сабкомпоненты. Ну и плюс передача большого количества аргументов выглядит не очень красиво, ведь для этого есть паттерн Builder. Теперь стало красивее:
Как стало
@Component(modules = ) @Singleton public interface AppComponent < FirstComponent.Builder firstComponentBuilder(); >@FirstScope @Subcomponent(modules = ) public interface FirstComponent < @Subcomponent.Builder interface Builder < FirstComponent.Builder firstModule(FirstModule firstModule); FirstComponent.Builder specialModule(SpecialModule specialModule); FirstComponent build(); >void inject(MainActivity mainActivity); >
Создание FirstComponent теперь выглядит следующим образом:
appComponent .firstComponentBuilder() .firstModule(new FirstModule()) .specialModule(new SpecialModule()) .build();
static
Теперь у нас есть возможность делать вот так:
@Provides static User currentUser(AuthManager authManager)
То есть методы, отвечающие за провайдинг зависимостей в модулях, мы можем делать статическими. Я сначала не совсем понимал, а зачем это вообще нужно то? А оказывается, запрос на такую фичу существовал довольно давно, и есть ситуации, когда это выгодно.
На SO задали хороший вопрос на эту тему, мол, а чем собственно отличаются @Singleton от @Provide static . Чтобы хорошо понять эту разницу, нужно читать ответ на вопрос, параллельно экспериментируя и смотря сгенерированный код.
Итак, у нас есть вводная. Мы имеем три варианта одного и того же метода в модуле:
@Provides User currentUser(AuthManager authManager) < return authManager.currentUser(); >@Provides @Singleton User currentUser(AuthManager authManager) < return authManager.currentUser(); >@Provides static User currentUser(AuthManager authManager)
При этом authManager.currentUser() в разные моменты времени может отдавать разные экземпляры.
Логичный вопрос: а чем эти методы отличаются.
В первом случае у нас классический unscope . При каждом запросе будет отдаваться новый экземпляр authManager.currentUser() (точнее новая ссылка на currentUser ).
Во втором случае при первом запросе будет закеширована ссылка на currentUser , и при каждом новом запросе будет отдаваться эта ссылка. То есть, если поменялся currentUser в AuthManager , то отдаваться то будет старая ссылка на невалидный уже экземпляр.
Третий случай уже интереснее. Данный метод по поведению аналогичен unscope , то есть при каждом запросе будет отдаваться новая ссылка. Это первое отличие от @Singleton , который кеширует объекты. Таким образом размещать в @Provide static методе инициализацию объекта не совсем уместно.
Но в чем тогда @Provide static отличается от unscope ? Допустим у нас есть такой модуль:
@Module public class AuthModule < @Provides User currentUser(AuthManager authManager) < return authManager.currentUser(); >>
AuthManager поставляется из другого модуля в качестве Singleton . Теперь быстро окинем взглядом сгенерированный код AuthModule_CurrentUserFactory (в студии просто поставьте курсор на currentUser и нажмите Ctrl+B):
Unscope
@Generated( value = "dagger.internal.codegen.ComponentProcessor", comments = "https://google.github.io/dagger" ) public final class AuthModule_CurrentUserFactory implements Factory < private final AuthModule module; private final ProviderauthManagerProvider; public AuthModule_CurrentUserFactory( AuthModule module, Provider authManagerProvider) < assert module != null; this.module = module; assert authManagerProvider != null; this.authManagerProvider = authManagerProvider; >@Override public User get() < return Preconditions.checkNotNull( module.currentUser(authManagerProvider.get()), "Cannot return null from a non-@Nullable @Provides method"); >public static Factory create(AuthModule module, Provider authManagerProvider) < return new AuthModule_CurrentUserFactory(module, authManagerProvider); >/** Proxies . */ public static User proxyCurrentUser(AuthModule instance, AuthManager authManager) < return instance.currentUser(authManager); >>
А если добавить static к currentUser :
@Module public class AuthModule < @Provides static User currentUser(AuthManager authManager) < return authManager.currentUser(); >>
static
@Generated( value = "dagger.internal.codegen.ComponentProcessor", comments = "https://google.github.io/dagger" ) public final class AuthModule_CurrentUserFactory implements Factory < private final ProviderauthManagerProvider; public AuthModule_CurrentUserFactory(Provider authManagerProvider) < assert authManagerProvider != null; this.authManagerProvider = authManagerProvider; >@Override public User get() < return Preconditions.checkNotNull( AuthModule.currentUser(authManagerProvider.get()), "Cannot return null from a non-@Nullable @Provides method"); >public static Factory create(Provider authManagerProvider) < return new AuthModule_CurrentUserFactory(authManagerProvider); >/** Proxies . */ public static User proxyCurrentUser(AuthManager authManager) < return AuthModule.currentUser(authManager); >>
Обратите внимание, что в варианте со static нет AuthModule . Таким образом, статический метод дергается компонентом напрямую, минуя модуль. А если в модуле только одни статические методы, то экземпляр модуля даже не создается.
Экономия и минус лишние вызовы. Собственно у нас выигрыш по производительности. Также пишут, что вызов статического метода на 15-20% быстрее вызова аналогичного нестатического метода. Если я ошибаюсь, iamironz поправит меня. Уж он то точно знает, а если нужно, и замерит.
@Binds + Inject конструктора
Мегаудобная связка, которая значительно уменьшает boilerplate-code. На заре изучения Даггера я не понимал, зачем нужны инъекции конструктора. Что и откуда берется. А тут еще появился @Binds. Но все на самом деле довольно просто. Спасибо за помощь Владимиру Тагакову и вот этой статье.
Рассмотрим типичную ситуацию. Есть интерфейс Презентера и его реализация:
public interface IFirstPresenter < void foo(); >public class FirstPresenter implements IFirstPresenter < public FirstPresenter() <>@Override public void foo() <> >
Мы, как белые люди, провайдим все это дело в модуле и инжектим интерфейс Презентера в активити:
@Module public class FirstModule < @Provides @FirstScope public IFirstPresenter provideFirstPresenter() < return new FirstPresenter(); >> public class MainActivity extends AppCompatActivity < @Inject IFirstPresenter firstPresenter; @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyApplication.getInstance().getFirstComponent() .inject(this); // others >>
Допустим, что наш FirstPresenter нуждается в классах-помощниках, которым он делегирует часть работы. Для этого необходимо в модуле создать еще два метода, которые будут провайдить новые классы, потом изменить конструктор FirstPresenter , а следственно и обновить соответствующий метод в модуле.
Модуль будет такой:
@Module public class FirstModule < @Provides @FirstScope public HelperClass1 provideHelperClass1() < return new HelperClass1(); >@Provides @FirstScope public HelperClass2 provideHelperClass2() < return new HelperClass2(); >@Provides @FirstScope public IFirstPresenter provideFirstPresenter( HelperClass1 helperClass1, HelperClass2 helperClass2) < return new FirstPresenter(helperClass1, helperClass2); >>
И так вот каждый раз, если нужно добавить какой-то класс и «расшарить» его другим. Модуль «загрязняется» очень быстро. И как-то слишком много кода, не находите? Но есть решение, которое существенно уменьшает код.
Во-первых, если нам необходимо создать зависимость и отдавать готовый класс, а не интерфейс ( HelperClass1 и HelperClass2 ), мы можем прибегнуть к инъекции конструктора. Выглядеть это будет следующим образом:
@FirstScope public class HelperClass1 < @Inject public HelperClass1() < >> @FirstScope public class HelperClass2 < @Inject public HelperClass2() < >>
Обратите внимание, что к классам была добавлена аннотация @FirstScope , таким образом Даггер понимает, в какое дерево зависимостей отнести данные классы.
Теперь с модуля мы можем смело убирать провайдинг HelperClass1 и HelperClass2 :
@Module public class FirstModule < @Provides @FirstScope public IFirstPresenter provideFirstPresenter( HelperClass1 helperClass1, HelperClass2 helperClass2) < return new FirstPresenter(helperClass1, helperClass2); >>
Как можно еще уменьшить код в модуле? Вот здесь применим @Binds :
@Module public abstract class FirstModule
А в FirstPresenter сделаем инъекцию конструктора:
@FirstScope public class FirstPresenter implements IFirstPresenter < private HelperClass1 helperClass1; private HelperClass2 helperClass2; @Inject public FirstPresenter(HelperClass1 helperClass1, HelperClass2 helperClass2) < this.helperClass1 = helperClass1; this.helperClass2 = helperClass2; >@Override public void foo() <> >
Какие здесь новшества? FirstModule стал у нас абстрактным, как и метод provideFirstPresenter . У provideFirstPresenter убрали аннотацию @Provide , зато добавили @Binds . А в аргументы передаем не необходимые зависимости, а конкретную реализацию!
У FirstPresenter добавилась scope аннотация — @FirstScope , по которой Даггер понимает, куда отнести данный класс. Также к конструктору добавили аннотацию @Inject . Стало намного чище, и добавлять новые зависимости стало еще проще!
Пару ценных дополнений по абстрактным модулям от Mujahit.
Давайте вспомним, что FirstModule относится к FirstComponent , который в свою очередь является сабкомпонентом от AppComponent . И чтобы создать FirstComponent мы делали вот так:
appComponent .firstComponentBuilder() .firstModule(new FirstModule()) .specialModule(new SpecialModule()) .build();
Но как нам создать то экземпляр FirstModule , если он является абстрактным? В прошлых статья я упоминал, что если мы в конструктор модулей ничего не передаем, то есть используем конструкторы по умолчанию, то при создании компонента инициализацию этих модулей можно опустить:
appComponent .firstComponentBuilder() .build();
А у себя внутри Даггер уже сам разруливает, что делать с абстрактными и неабстрактными модулями и как провайдить все необходимые зависимости.
Также замечу, что если у модуля одни абстрактные методы, то модуль можно реализовать через интерфейс:
@Module public interface FirstModule
Кроме того в абстрактный модуль мы можем также добавить только статические методы. «Обычные» методы добавить не можем:
@Module public abstract class FirstModule < @FirstScope @Binds public abstract IFirstPresenter provideFirstPresenter(FirstPresenter firstPresenter); @FirstScope @Provides public HelperClass3 provideHelperClass3() < // @FirstScope @Provides public static HelperClass3 provideHelperClass3() < // >
О чем еще не сказано
Далее я приведу еще список фич с коротким описанием и ссылками на качественное объяснение:
- Muitibindings. Позволяет «байндить» объекты в коллекции ( Set и Map ). Подходит для реализации архитектуры расширения («plugin architecture»). Крайне рекомендую вот это очень подробное описание с азов. Более интересные примеры применения Muitibindings можно найти в статьях Мирослава тут и тут. И еще в придачу ссылка на официальную документацию. Так что мне даже нечего добавить по данному вопросу.
- Releasable references. Если уж с памятью совсем беда. С помощью соответствующих аннотаций мы помечаем объекты, которыми можем пожертвовать при недостатке памяти. Вот такой вот хак.
В доках (подраздел Releasable references) вполне все понятно описано, как ни странно. - Тестирование. Конечно же, для Unit-тестирования Даггер не нужен. А вот для функциональных, интеграционных и UI тестов может пригодиться возможность подмены определенных модулей. Очень здорово эту тему раскрывает Artem_zin в своей статье и примере. В документации выделен раздел по вопросу тестирования. Но опять-таки гугловцы не могут нормально описать, как именно подменить компонент. Как правильно создать фэйковые модули и подставить их. Для подмены компонента (отдельных модулей) я пользуюсь способом Артема. Да, хотелось бы, чтобы можно было создать отдельным классом тестовый компонент и отдельными классами тестовые модули, и красиво все это подключить в тестовом Application файле. Может кто знает?
- @BindsOptionalOf. Работает вместе с Optional от Java 8 или Guava, что делает данную фичу уже труднодоступной для нас. Если интересно, в конце документации можно найти описание.
- @BindsInstance. К сожалению, в dagger 2.8 мне данная фича оказалась недоступной. Основной посыл ее в том, что хватит передавать какие-либо объекты через конструктор модуля. Очень распространенный пример, когда через конструктор AppComponent передается глобальный Context . Так вот с этой аннотацией такого делать станет не нужно. В конце документации есть пример.
Ну вот и все! Вроде все моменты удалось осветить. Если что-то пропустил или недостаточно описал, пишите! Исправим. Также рекомендую группу по Dagger2 в Телеграме, где ваши вопросы не останутся без ответов.
Кроме того, правильное применение библиотеки очень связано с чистой архитектурой. Поэтому вот вам и группа по архитектуре. И да, скоро на AndroidDevPodcast планируется выпуск, посвященный Даггеру. Следите за новостями!
- android
- android development
- dagger 2
- java
- architecture design
- mobile development
- Java
- Разработка мобильных приложений
- Разработка под Android