Я хочу, чтобы вместо того, чтобы писать свой собственный метод или класс, я мог бы просто проверить и передать содержимое необязательного элемента другому классу или иметь пустой объект.
Для этой подзадачи в приложении я хочу, чтобы пользовательский объект экземпляра TreeNode
был преобразован в CustomUserObject
, при условии, что TreeNode
является экземпляром DefaultMutableTreeNode
.
private Optional<CustomUserObject> getCustomUserObject(TreeNode node) {
Optional<DefaultMutableTreeNode> optDefaultMutableTreeNode = OptionalUtil.cast(Optional.ofNullable(node), DefaultMutableTreeNode.class);
Optional<Object> optUserObject = optDefaultMutableTreeNode.map(DefaultMutableTreeNode::getUserObject); //
return OptionalUtil.cast(optUserObject, CustomUserObject.class);
}
/**
* Maps the given optional, if the containing element is an instance of the given class.
* Returns empty if the containing object is not an instance of the given class.
*
* @param orgOptional
* given optional
* @param clazz
* given class.
* @return the resulting {@link Optional}.
*/
public static <T, X> Optional<T> cast(Optional<X> orgOptional, Class<T> clazz) {
return orgOptional //
.filter(clazz::isInstance) // check instance
.map(clazz::cast); // cast
}
/**
* Maps the given stream, if the containing element is an instance of the given class.
* Returns empty if the containing object is not an instance of the given class.
*
* @param orgStream
* given optional
* @param clazz
* given class.
* @return the resulting {@link Optional}.
*/
public static <T, X> Stream<T> cast(Stream<X> orgStream, Class<T> clazz) {
return orgStream //
.filter(clazz::isInstance) // check instance
.map(clazz::cast); // cast
}
Я помню, что мне довольно часто нужно приводить таким образом опции или потоки. Это не бегло. На самом деле я хочу, чтобы у java Optional или Stream был метод приведения, который выполняет вышеуказанный шаг. Я не хочу писать свой собственный CustomOptional. Я что-нибудь пропустил? Есть ли способ сделать это проще?
Напишите ? extends what
где? Если я вас правильно понял, это не решит мою проблему, потому что мне нужно быть уверенным, что это DefaultMutableTreeNode
или CustomUserObject
, а не просто заявить об этом и пожелать, чтобы это было так.
В любом случае спасибо за подсказку - просто: в этом примере X
и T
не имеют отношения, поэтому extends
не добавляет здесь никакой ценности.
но это так. Дело в том, что, используя Class<? extends T>
, вы можете получить Optional<Object>
, производный от Optional<Serializable>
, который вы разливаете с помощью String.class
. Это своего рода гибкость, которая ничего не стоит, но действительно раздражает, если у вас ее нет, в редких случаях, когда она вам нужна.
Другой случай, который может быть более вероятным, - это Optional<YourClass> opt = cast(otherOpt, instanceOfYourClass.getClass());
. В нынешнем виде opt
должен быть типа Optional<? extends YourClass>
.
Прежде всего, я не вижу преимущества написания ? extends YourClass
как результата метода, потому что все, что можно сделать с этим результатом, - это получить доступ к YourClass
, методы или атрибуты подклассов не могут быть доступны с этим. Во-вторых, запись ?
имеет некоторые проблемы и может привести к выводам SonarLint.
Основным преимуществом записи ? extends YourClass
является то, что он компилируется, в то время как он не будет компилироваться только с YourClass
.
Можете привести пример того, что вы хотите написать, а что не компилируется?
Конечно: ideone.com/HXaUE2. Попробуйте удалить ? extends
из main
.
Спасибо за ваши старания. Но для меня монета не падает. Если вы хотите применить приведение к классу, к которому хотите выполнить приведение. И писать ?
для меня воняет.
Хорошо, но почему нужно выполнять приведение к динамическому классу, например instance.getClass()
?
Хорошо, я думаю, что понял вашу точку зрения. Итак, вы говорите, что предпочитаете писать public static <T, X> Optional<T> cast(Optional<X> orgOptional, Class<? extends T> clazz)
, чтобы он работал с динамическими классами, верно?
Вы можете легко сделать это более плавным, полагаясь на методы map()
/ flatMap()
и cast
, которые вместо этого возвращают функции.
Для Optional
это очень просто, поскольку map()
может действовать как фильтр, возвращая null
. Так что просто определите:
public static <U> Function<Object, U> filterAndCast(Class<? extends U> clazz) {
return t -> clazz.isInstance(t) ? clazz.cast(t) : null;
}
и используйте его как:
Optional<Number> number = Optional.of(42L);
System.out.println(number.map(filterAndCast(Integer.class)));
System.out.println(number.map(filterAndCast(Long.class)));
Выход:
Optional.empty
Optional[42]
Для потоков вы можете применить более или менее тот же трюк, полагаясь на flatMap()
с функцией, возвращающей пустой Stream
:
public static <U> Function<Object, Stream<U>> streamFilterAndCast(Class<? extends U> clazz) {
return t -> clazz.isInstance(t) ? Stream.of(clazz.cast(t)) : Stream.empty();
// or alternatively
return t -> Stream.of(t).filter(clazz::isInstance).map(clazz::cast);
}
и используйте его как:
Stream.of(42L, "Hello world", 1024, 3.14)
.flatMap(streamFilterAndCast(Number.class))
.forEach(System.out::println);
Выход:
42
1024
3.14
Спасибо за вдохновляющий ответ. Как замечание, тернарный оператор ?
запрещен в большинстве проектов, над которыми я работаю, потому что он подвержен ошибкам и его трудно читать.
@michaeak Я понимаю часть "трудночитаемую" (хотя я думаю, что это чистое мнение), но что касается части, подверженной ошибкам, я думаю, что правда как раз наоборот. Использование if-else имеет гораздо больше возможностей для ошибки, чем троичное, потому что троичное более ограничено. Кроме того, операторы должны оперировать побочными эффектами, но тернарное выражение является выражением, что делает его более сложным. Поэтому, когда вы столкнетесь с тем, кто написал это руководство по стилю, скажите им, что я сказал, что все в порядке :)
@ brian-goetz: у меня был проект, в котором один человек использовал 3 или 4 составных трояна. А также он широко используется для нулевых проверок, которые, как предполагается, должны выполняться Optional
, поэтому для меня тернарный код не подходит, я не использую его сам, хотя я знаю, как он работает.
@michaeak Я уверен, что это раздражало, но вы приводите абсурдный аргумент, по сути: «поскольку кто-то однажды злоупотребил функцией, никто никогда не сможет использовать ее снова, даже если это абсолютно правильно». (Это все равно, что сказать «машины - это не хорошо», потому что кого-то однажды сбил пьяный водитель.) Тернар - важный инструмент в языковом наборе инструментов, и во многих случаях, когда он применяется, объективно лучший выбор, чем если- тогда, и при правильном использовании, он будет полностью читаемым. Вините плохих разработчиков в их плохом коде, а не в инструментах, которые они выбирают.
Это я придумал для По желанию
/**
* Creates a function that tries to cast to the given class.
* If the given argument is not an instance of the given class <code>Optional.empty()</code> is returned.
* This function can be used in an <code>optionalObject.flatMap(Functional.cast(SomeClass.class))</code> context.
*
* @param clazz
* given Clazz
* @return Returns an optional of the given class.
* @param <X>
* generic input argument for the function
* @param <T>
* type of the output of the function
*/
public static <X, T> Function<X, Optional<T>> cast(Class<? extends T> clazz) {
return x -> {
if (clazz.isInstance(x)) {
return Optional.of(clazz.cast(x));
}
return Optional.empty();
};
}
и используйте это так:
Optional<DefaultMutableTreeNode> optParent = Optional.of(node) // optional of original node
.map(ProfileTreeNode::getParent) // parent of this node.
.flatMap(FunctionalUtil.cast(DefaultMutableTreeNode.class)); // cast to DefaultMutableTreeNode
optParent.ifPresent(y -> y.remove(node));
Для потоки написал
/**
* Creates a function that tries to cast to the given class.
* If the given argument is not an instance of the given class an empty stream is returned.
* This function can be used in an <code>stream.flatMap(Functional.castStream(SomeClass.class))</code> context.
*
* @param clazz
* given Clazz
* @return Returns an optional of the given class.
* @param <X>
* generic input argument for the function
* @param <T>
* type of the output of the function
*/
public static <X, T> Function<X, Stream<T>> castStream(Class<? extends T> clazz) {
return x -> Stream.of(x) // stream of x.
.filter(clazz::isInstance) // check if instance
.map(clazz::cast);
}
В сторону: добавьте
? extends
ко всем переменным типа в параметрах метода, чтобы сделать API более гибким.