Estoy buscando implementar una funcionalidad en una lista de objetos como lo haría en C # usando un método de extensión.
Algo como esto:
List<DataObject> list;
// ... List initialization.
list.getData(id);
¿Cómo hago eso en Java?
Estoy buscando implementar una funcionalidad en una lista de objetos como lo haría en C # usando un método de extensión.
Algo como esto:
List<DataObject> list;
// ... List initialization.
list.getData(id);
¿Cómo hago eso en Java?
Respuestas:
Java no admite métodos de extensión.
En cambio, puede hacer un método estático regular o escribir su propia clase.
Los métodos de extensión no son solo métodos estáticos y no solo azúcar de sintaxis de conveniencia, de hecho son una herramienta bastante poderosa. Lo principal es la capacidad de anular diferentes métodos basados en la creación de instancias de diferentes parámetros genéricos. Esto es similar a las clases de tipo de Haskell y, de hecho, parece que están en C # para admitir las mónadas de C # (es decir, LINQ). Incluso dejando caer la sintaxis de LINQ, todavía no conozco ninguna forma de implementar interfaces similares en Java.
Y no creo que sea posible implementarlos en Java, debido a la semántica de borrado tipo Java de los parámetros genéricos.
Project Lombok proporciona una anotación @ExtensionMethod
que se puede utilizar para lograr la funcionalidad que está solicitando.
java.lang.String
. Demostración: http://manifold.systems/images/ExtensionMethod.mp4
Técnicamente, la extensión C # no tiene equivalente en Java. Pero si desea implementar tales funciones para un código más limpio y fácil de mantener, debe usar el marco de Manifold.
package extensions.java.lang.String;
import manifold.ext.api.*;
@Extension
public class MyStringExtension {
public static void print(@This String thiz) {
System.out.println(thiz);
}
@Extension
public static String lineSeparator() {
return System.lineSeparator();
}
}
El lenguaje XTend , que es un superconjunto de Java y se compila en el código fuente 1 de Java , lo admite.
Manifold proporciona Java con métodos de extensión de estilo C # y varias otras características. A diferencia de otras herramientas, colector no tiene limitaciones y no sufren de problemas con los genéricos, lambdas, etc. IDE colector proporciona varias otras características tales como F # al estilo de tipos personalizados , de estilo mecanografiado las interfaces estructurales y de estilo Javascript tipos expando .
Además, IntelliJ proporciona soporte integral para Manifold a través del complemento Manifold .
Manifold es un proyecto de código abierto disponible en github .
Otra opción es usar las clases ForwardingXXX de la biblioteca google-guava.
Java no tiene esa característica. En su lugar, puede crear una subclase regular de la implementación de su lista o crear una clase interna anónima:
List<String> list = new ArrayList<String>() {
public String getData() {
return ""; // add your implementation here.
}
};
El problema es llamar a este método. Puedes hacerlo "en su lugar":
new ArrayList<String>() {
public String getData() {
return ""; // add your implementation here.
}
}.getData();
Parece que hay una pequeña posibilidad de que los métodos de defensa (es decir, los métodos predeterminados) puedan ingresar a Java 8. Sin embargo, hasta donde yo los entiendo, solo permiten que el autor de un lo interface
extienda retroactivamente, no usuarios arbitrarios.
Los métodos de defensa + inyección de interfaz podrían implementar completamente los métodos de extensión de estilo C #, pero AFAICS, la inyección de interfaz ni siquiera está en la hoja de ruta de Java 8 todavía.
Un poco tarde para la fiesta en esta pregunta, pero en caso de que alguien lo encuentre útil, acabo de crear una subclase:
public class ArrayList2<T> extends ArrayList<T>
{
private static final long serialVersionUID = 1L;
public T getLast()
{
if (this.isEmpty())
{
return null;
}
else
{
return this.get(this.size() - 1);
}
}
}
Podemos simular la implementación de los métodos de extensión de C # en Java utilizando la implementación del método predeterminado disponible desde Java 8. Comenzamos definiendo una interfaz que nos permitirá acceder al objeto de soporte a través de un método base (), de esta manera:
public interface Extension<T> {
default T base() {
return null;
}
}
Devolvemos nulo ya que las interfaces no pueden tener estado, pero esto tiene que repararse más tarde a través de un proxy.
El desarrollador de extensiones tendría que extender esta interfaz mediante una nueva interfaz que contenga métodos de extensión. Supongamos que queremos agregar una interfaz para cada consumidor en la Lista:
public interface ListExtension<T> extends Extension<List<T>> {
default void foreach(Consumer<T> consumer) {
for (T item : base()) {
consumer.accept(item);
}
}
}
Debido a que ampliamos la interfaz de Extensión, podemos llamar al método base () dentro de nuestro método de extensión para acceder al objeto de soporte al que nos adjuntamos.
La interfaz de extensión debe tener un método de fábrica que creará una extensión de un objeto de soporte dado:
public interface Extension<T> {
...
static <E extends Extension<T>, T> E create(Class<E> type, T instance) {
if (type.isInterface()) {
ExtensionHandler<T> handler = new ExtensionHandler<T>(instance);
List<Class<?>> interfaces = new ArrayList<Class<?>>();
interfaces.add(type);
Class<?> baseType = type.getSuperclass();
while (baseType != null && baseType.isInterface()) {
interfaces.add(baseType);
baseType = baseType.getSuperclass();
}
Object proxy = Proxy.newProxyInstance(
Extension.class.getClassLoader(),
interfaces.toArray(new Class<?>[interfaces.size()]),
handler);
return type.cast(proxy);
} else {
return null;
}
}
}
Creamos un proxy que implementa la interfaz de extensión y toda la interfaz implementada por el tipo del objeto de soporte. El controlador de invocación dado al proxy distribuiría todas las llamadas al objeto de soporte, excepto el método "base", que debe devolver el objeto de soporte, de lo contrario su implementación predeterminada devolverá nulo:
public class ExtensionHandler<T> implements InvocationHandler {
private T instance;
private ExtensionHandler(T instance) {
this.instance = instance;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if ("base".equals(method.getName())
&& method.getParameterCount() == 0) {
return instance;
} else {
Class<?> type = method.getDeclaringClass();
MethodHandles.Lookup lookup = MethodHandles.lookup()
.in(type);
Field allowedModesField = lookup.getClass().getDeclaredField("allowedModes");
makeFieldModifiable(allowedModesField);
allowedModesField.set(lookup, -1);
return lookup
.unreflectSpecial(method, type)
.bindTo(proxy)
.invokeWithArguments(args);
}
}
private static void makeFieldModifiable(Field field) throws Exception {
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField
.setInt(field, field.getModifiers() & ~Modifier.FINAL);
}
}
Luego, podemos usar el método Extension.create () para adjuntar la interfaz que contiene el método de extensión al objeto de soporte. El resultado es un objeto que puede ser lanzado a la interfaz de extensión por el cual todavía podemos acceder al objeto de soporte que llama al método base (). Al tener la referencia convertida en la interfaz de extensión, ahora podemos llamar con seguridad a los métodos de extensión que pueden tener acceso al objeto de soporte, de modo que ahora podemos adjuntar nuevos métodos al objeto existente, pero no a su tipo de definición:
public class Program {
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "b", "c");
ListExtension<String> listExtension = Extension.create(ListExtension.class, list);
listExtension.foreach(System.out::println);
}
}
Entonces, esta es una forma en que podemos simular la capacidad de extender objetos en Java al agregarles nuevos contratos, lo que nos permite llamar a métodos adicionales en los objetos dados.
A continuación puede encontrar el código de la interfaz de Extensión:
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
public interface Extension<T> {
public class ExtensionHandler<T> implements InvocationHandler {
private T instance;
private ExtensionHandler(T instance) {
this.instance = instance;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if ("base".equals(method.getName())
&& method.getParameterCount() == 0) {
return instance;
} else {
Class<?> type = method.getDeclaringClass();
MethodHandles.Lookup lookup = MethodHandles.lookup()
.in(type);
Field allowedModesField = lookup.getClass().getDeclaredField("allowedModes");
makeFieldModifiable(allowedModesField);
allowedModesField.set(lookup, -1);
return lookup
.unreflectSpecial(method, type)
.bindTo(proxy)
.invokeWithArguments(args);
}
}
private static void makeFieldModifiable(Field field) throws Exception {
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
}
}
default T base() {
return null;
}
static <E extends Extension<T>, T> E create(Class<E> type, T instance) {
if (type.isInterface()) {
ExtensionHandler<T> handler = new ExtensionHandler<T>(instance);
List<Class<?>> interfaces = new ArrayList<Class<?>>();
interfaces.add(type);
Class<?> baseType = type.getSuperclass();
while (baseType != null && baseType.isInterface()) {
interfaces.add(baseType);
baseType = baseType.getSuperclass();
}
Object proxy = Proxy.newProxyInstance(
Extension.class.getClassLoader(),
interfaces.toArray(new Class<?>[interfaces.size()]),
handler);
return type.cast(proxy);
} else {
return null;
}
}
}
Se podría usar el patrón de diseño orientado a objetos decorador . Un ejemplo de este patrón que se usa en la biblioteca estándar de Java sería DataOutputStream .
Aquí hay un código para aumentar la funcionalidad de una Lista:
public class ListDecorator<E> implements List<E>
{
public final List<E> wrapee;
public ListDecorator(List<E> wrapee)
{
this.wrapee = wrapee;
}
// implementation of all the list's methods here...
public <R> ListDecorator<R> map(Transform<E,R> transformer)
{
ArrayList<R> result = new ArrayList<R>(size());
for (E element : this)
{
R transformed = transformer.transform(element);
result.add(transformed);
}
return new ListDecorator<R>(result);
}
}
PD: Soy un gran admirador de Kotlin . Tiene métodos de extensión y también se ejecuta en la JVM.
Puede crear un método auxiliar / extensión similar a C # mediante (RE) implementando la interfaz de Colecciones y agregando un ejemplo para la Colección Java:
public class RockCollection<T extends Comparable<T>> implements Collection<T> {
private Collection<T> _list = new ArrayList<T>();
//###########Custom extension methods###########
public T doSomething() {
//do some stuff
return _list
}
//proper examples
public T find(Predicate<T> predicate) {
return _list.stream()
.filter(predicate)
.findFirst()
.get();
}
public List<T> findAll(Predicate<T> predicate) {
return _list.stream()
.filter(predicate)
.collect(Collectors.<T>toList());
}
public String join(String joiner) {
StringBuilder aggregate = new StringBuilder("");
_list.forEach( item ->
aggregate.append(item.toString() + joiner)
);
return aggregate.toString().substring(0, aggregate.length() - 1);
}
public List<T> reverse() {
List<T> listToReverse = (List<T>)_list;
Collections.reverse(listToReverse);
return listToReverse;
}
public List<T> sort(Comparator<T> sortComparer) {
List<T> listToReverse = (List<T>)_list;
Collections.sort(listToReverse, sortComparer);
return listToReverse;
}
public int sum() {
List<T> list = (List<T>)_list;
int total = 0;
for (T aList : list) {
total += Integer.parseInt(aList.toString());
}
return total;
}
public List<T> minus(RockCollection<T> listToMinus) {
List<T> list = (List<T>)_list;
int total = 0;
listToMinus.forEach(list::remove);
return list;
}
public Double average() {
List<T> list = (List<T>)_list;
Double total = 0.0;
for (T aList : list) {
total += Double.parseDouble(aList.toString());
}
return total / list.size();
}
public T first() {
return _list.stream().findFirst().get();
//.collect(Collectors.<T>toList());
}
public T last() {
List<T> list = (List<T>)_list;
return list.get(_list.size() - 1);
}
//##############################################
//Re-implement existing methods
@Override
public int size() {
return _list.size();
}
@Override
public boolean isEmpty() {
return _list == null || _list.size() == 0;
}
Java
8 ahora admite métodos predeterminados , que son similares a C#
los métodos de extensión de.