2 votes

Java Try-With-Resources Compte de ressources inconnu

J'ai un besoin d'ouvrir N sockets multicast (où N vient de la taille d'une liste d'arguments). Je vais ensuite envoyer les mêmes données à chacun des N sockets dans une boucle, et enfin, fermer chaque socket. Ma question est la suivante : comment puis-je faire cela en utilisant le bloc try-with-resources ? Voici comment je pourrais faire cela avec une seule ressource :

final int port = ...;
try (final MulticastSocket socket = new MulticastSocket(port)) {
    // Do a bunch of sends of small packet data over a long period of time
    ...
}

Le seul moyen auquel je pense pour faire cela avec plusieurs ports est le suivant :

final List<Integer> ports = ...;
final List<MulticastSocket> sockets = new ArrayList<>(ports.size());
try {
    for (final Integer port : ports) {
        sockets.add(new MulticastSocket(port));
    }

    // Do a bunch of sends of small packet data over a long period of time
    ...
} finally {
    for (final MulticastSocket socket : sockets) {
        try {
            socket.close();
        } catch (final Throwable t) {
            // Eat the exception
        }
    }
}

Existe-t-il un moyen plus concis d'y parvenir, ou la solution que je propose est-elle la meilleure possible ?

2voto

Andy Turner Points 13883

Faites-le récursivement pour conserver les garanties de try-with-resources :

void foo(List<Integer> ports, List<Socket> sockets) {
  if (sockets.size() == ports.size()) {
    // Do something with your sockets.
  } else {
    try (Socket s = new MulticastSocket(ports.get(sockets.size())) {
      sockets.add(s);
      foo(ports, sockets);
      // You could call sockets.remove(sockets.size()-1) here.
      // Not convinced whether it's worth it.
    }
  }
}

1voto

Mike Nakis Points 7259

Ce que vous faites est pratiquement aussi bon que possible.

Vous pourriez créer un AutoCloseable multi-fermeture à usage général qui contient un List<AutoCloseable> et accepte comme paramètre de construction un nombre de fermetures et une fabrique à invoquer pour créer chaque fermeture, puis les fermer toutes lorsque sa fonction de fermeture est activée. close() est invoqué, de sorte que vous pouvez l'utiliser comme ceci :

try( MultiCloser<MulticastSocket> multiCloser = 
         new MultiCloser<>( ports.size(), i -> new MulticastSocket( ports.get( i ) ) )
{
    for( MulticastSocket socket : multiCloser.getItems() )
    {
        do something with the socket
    }
}

...mais ce serait probablement exagéré.

0voto

davidh Points 107

Quel est l'intérêt d'utiliser un ArrayList pour stocker le MulticastSocket instances ?

Vous avez dit que :

J'enverrai ensuite les mêmes données à chacune des personnes suivantes boucle, et enfin, fermer chaque socket.

Vous pouvez donc les créer dans une boucle et envoyer à chaque itération le même traitement.
Pour ce faire, vous devez modifier un peu votre design.
La tâche de traitement de l MulticastSocket pourrait être effectuée par une interface fonctionnelle qui permet également de spécifier le port à utiliser.

Par exemple :

@FunctionalInterface
public interface SocketProcessor {
    void process(MulticastSocket multicastSocket) ;
}

Vous pourriez avoir une méthode qui prend comme paramètre cette interface fonctionnelle pour appliquer le traitement :

public static void processSocket(SocketProcessor socketProcessor, Integer port) throws IOException {
  try (final MulticastSocket socket = new MulticastSocket(port)) {
    socketProcessor.process(socket);
  }
}

Enfin, à partir du code client, vous pouvez créer une instance de socketProcessor avec un lambda :

SocketProcessor socketProcessor = (MulticastSocket socket) -> {
    socket.send(...);
    socket.send(...);
};

Et ensuite, vous pourriez boucler sur les ports afin d'invoquer processSocket avec le port approprié et l'attribut SocketProcessor instance qui vient d'être créée :

for (final Integer port : ports) {
    try {
      processSocket(socketProcessor, port);
    } catch (IOException e) {
      // do processing
    }
}

Cette solution n'est pas nécessairement plus courte (sans être vraiment plus longue) mais elle est vraiment plus claire.
Les deux préoccupations principales sont séparées :

  • processSocket(SocketProcessor) qui exécute le code de base

  • SocketProcessor qui définit la tâche concrète.

0voto

Jeff G Points 1870

Inspiré par l'idée proposée par Mike Nakis, j'ai imaginé la classe suivante...

package myNamespace;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

import myNamespace.ThrowingFunction;
import myNamespace.ThrowingSupplier;

/** Collection of AutoCloseable objects */
public class ResourceCollection<T extends AutoCloseable>
        implements Iterable<T>, AutoCloseable {

    /** Resources owned by this instance */
    private final List<T> myResources;

    /**
     * Constructor
     * @param allocator Function used to allocate each resource
     * @param count     Number of times to call the allocator
     * @throws E Thrown if any of the allocators throw
     */
    public <E extends Throwable> ResourceCollection(
            final ThrowingSupplier<T, E> allocator, final int count)
            throws E {
        myResources = new ArrayList<>(count);
        try {
            while (myResources.size() < count) {
                final T resource = allocator.getThrows();
                myResources.add(resource);
            }
        } catch (final Throwable e) {
            close();
            throw e;
        }
    }

    /**
     * Constructor
     * @param allocator Function used to allocate each resource
     * @param input     List of input parameters passed to the allocator
     * @throws E Thrown if any of the allocators throw
     */
    public <U, E extends Throwable> ResourceCollection(
            final ThrowingFunction<U, T, E> allocator, final Collection<U> input)
            throws E {
        myResources = new ArrayList<>(input.size());
        try {
            for (final U value : input) {
                final T resource = allocator.applyThrows(value);
                myResources.add(resource);
            }
        } catch (final Throwable e) {
            close();
            throw e;
        }
    }

    /**
     * Gets the number of resources in the collection
     * @return The number of resources in the collection
     */
    public int size() {
        return myResources.size();
    }

    /**
     * Gets whether the collection contains no resources
     * @return Whether the collection contains no resources
     */
    public boolean isEmpty() {
        return myResources.isEmpty();
    }

    /**
     * Gets the resource at index i
     * @param i The index of a resource, in the range [0, size())
     * @return The resource at index i
     */
    public T get(final int i) {
        return myResources.get(i);
    }

    @Override
    public Iterator<T> iterator() {
        return myResources.iterator();
    }

    @Override
    public void close() {
        final ListIterator<T> resourceIter =
                myResources.listIterator(myResources.size());
        while (resourceIter.hasPrevious()) {
            final T resource = resourceIter.previous();
            if (resource != null) {
                try {
                    resource    .close ();
                    resourceIter.remove();
                } catch (final Throwable t) {
                    // Eat the exception
                }
            }
        }
    }

}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X