Binding WPF vs Binding Android – I

Binding WPF vs Binding Android – I
Este post, es el inicio de una tetralogía con el que pretendo mostrar los aspectos de desarrollo de ambas plataformas, Microsoft y Android en cuanto al uso de la arquitectura MVVM y Databinding.
Antes de nada veamos cuales son las diferencias esenciales del modelo vista controlador (MVC) y el modelo vista vista-modelo (MVVM).
Anuncios

List & Set

List & Set
A veces es muy importante elegir bien el tipo de listas de objetos y para ello debemos valorar distintos aspectos como si es necesario mantener el orden en nuestra lista, si la velocidad de cálculo es importante, si podemos usar iteraciones en paralelo, el hecho en si de usar arrays o listas.
En Java tenemos objetos que implementan la interfaz Collection, que a su vez implementan las interfaces Set y List y por otra parte la interfaz Map, todas parecidas pero no iguales.
La interfaz List. Esta está diseñada para trabajar con colecciones ordenadas y con valores repetidos e implementan esta interfaz ArrayList, LinkedList, Vector y Stack
La interfaz Set y SortedSet que hereda de la primera y la implementan las colecciones HashSet y TreeSet.
Otra interfaz es Map y SortedMap con las clases HashMap HashTable y TreeMap.
Listas01
Interfaces implementadas por las  colecciones
Listas02
Jerarquía de clases de las listas
Veamos algunos ejemplos basándonos en el rendimiento y para ello vamos a crear una clase llamada Factura y crearemos una lista con 1.000.000 de Facturas pasándole como argumento una base imponible aleatoria y a toda la colección le calcularemos el promedio del importe final con IVA obteniendo el tiempo empleado en ms. El cálculo del promedio lo haremos con dos expresiones lambda, una con una iteración en línea y otra en paralelo, de modo que también veremos si una colección es mejor para aprovechar las características de varios procesadores o no.

public class Factura {
    public Factura(double bi){
        baseImponible=bi;
    }
    String numFactura;
    double baseImponible;
    double iva=.21;

    public double getBaseImponible() {
        return baseImponible;
    }
    public void setBaseImponible(double baseImponible) {
        this.baseImponible = baseImponible;
    }
    public double getIva() {
        return iva;
    }
    public void setIva(double iva) {
        this.iva = iva;
    }
    public String getNumFactura() {
        return numFactura;
    }
    public void setNumFactura(String numFactura) {
        this.numFactura = numFactura;
    }
    public double getImporte() {
        return baseImponible * (1+iva);
    }
    @Override
    public String toString() {
        return "Factura{" + "numFactura=" + numFactura + ", importe=" + getImporte() + '}';
    }
}
static void addFacturas(){
        List facturas=new Stack();

        for (int i = 0; i f.getImporte()).average();
        long endTime = System.currentTimeMillis() - startTime;
        System.out.printf("Tiempo:          %s ms\n" , endTime);
        System.out.printf("Promedio:        %f\n" , promedio.getAsDouble());

        long startTime1 = System.currentTimeMillis();
        OptionalDouble promedio1= facturas.parallelStream().mapToDouble(f->f.getImporte()).average();
        long endTime1 = System.currentTimeMillis() - startTime1;

        System.out.printf("Tiempo paralelo: %s ms\n" , endTime1);
        System.out.printf("Promedio:        %f\n" , promedio1.getAsDouble());
    }
Colección Tiempo ms llenado Tiempo ms Tiempo paralelo
ArrayList 3266 ms 88 ms 48
LinkedList 6410 ms 134 ms 5830 ms
Vector 4444 ms 92 ms 52 ms
Stack 4594 ms 84 ms 53 ms
HashSet 9683 ms 789 ms 369 ms
TreeSet 18861 ms 637 ms 636 ms

¿Que ha pasado? ¿por qué tenemos valores de carga y valores de cálculo distintos? si son listas! Veamos los objetos que implementan la interfaz List.

El ArrayList es una lista de objetos basada en indices de acceso aleatorio donde el primero de los objetos tiene índice 0, permitiendo el ordenamiento a criterio del programador y elementos duplicados. Si nos fijamos en la tabla, se trata del objeto más eficiente en cuanto a carga de objetos y proceso de cálculo. (En el ejemplo claro, el post pretende mostrar que depende del tipo de lista, podemos obtener una mayor eficiencia).
Un ArrayList podríamos usarlo cuando necesitamos acceder frecuentemente a objetos gracias a su acceso aleatorio y acceso multihilo y no deberíamos usarlo cuando añadimos o eliminamos objetos de la lista con frecuencia. Debería ser la colección más usada.
LinkedList es una lista vinculada a otra con acceso secuencial, por tanto no es tan eficiente como los arrays. En el ejemplo, podemos observar que la carga es más tediosa y el cálculo mediante stream paralelo, se vuelve casi intratable, es decir con este objeto si intentamos realizar un stream aprovechando las capacidades multiprocesador, el resultado es totalmente opuesto al esperado. Podemos usar este tipo de lista cuando añadimos o eliminamos objetos con frecuencia.
Vector. Este tal y como lo hemos instanciado, con el constructor sin argumentos, se crea inicialmente con una longitud de 10 que una vez rebasado, se duplica sucesivamente por lo que en 18 pasos, la capacidad del Vector soporta el millón de facturas, de modo que al cargar los objetos, se produce un ligero retardo con respecto a un ArrayList que no hace ningún redimensionamiento, además sus métodos son sincronizados y solo permite un hilo de acceso a los datos, por tanto, su rendimiento es menor que el de un ArrayList.
Stack. Este objeto es una pila LIFO, Last In, First Out, último en entrar, primero en salir, por tanto solo debe usar para este tipo de funcionalidad.
Ahora analicemos los objetos que implementan la interfaz Set, la cual usamos para listas con objetos ordenados o no, sin objetos repetidos usando los métodos equals() y hashcode() para determinar la igualdad; el hecho de no incluir elementos repetidos, se debe a los métodos señalados.
HashSet. Esta colección es un fiel reflejo de su interfaz, no permite elementos repetidos, su crecimiento se basa en una capacidad inicial de 16, doblada en la siguiente redimensión y con factor de crecimiento de 0,75 en las posteriores y no permite ordenamiento. El hecho de no permitir duplicados, atiende a la falta de rendimiento del ejemplo, ya que cada vez que se añade un registro, se debe comprobar la no duplicidad del elemento.
TreeSetEsta colección ordena los elementos cada vez que se inserta uno nuevo, de ahí el gran retardo en la inserción de datos.

Como conclusión, es fundamental que analicemos la cantidad de datos a tratar, el número de inserciones y eliminaciones, ordenamientos, posibilidad de proceso multihilo y que cálculos vamos a realizar con los datos. Una vez analizados los datos, la elección de vuestra colección es el siguiente paso y a continuación, los test de rendimiento para retroalimentar el análisis en su caso.

Orden en la sala

Orden en la sala
Cuando queremos ordenar una lista sin que esta implemente la interfaz Comparator, podemos hacerlo también mediante programación funcional. Para comenzar, usaremos el objeto Persona y la colección de personas que tenemos en el post sobre Colecciones en Java.
En primer lugar usaremos el ordenamiento estándar con la clase Persona, en la que la clase implementa la interfaz Comparator de modo que debe existir un método llamado compareTo el cual podemos usar para ordenar por defecto.
Al crear un ArrayList personas=new ArrayList();, este tiene un método llamado sort(Comparator comparator) y por tanto necesitamos un objeto de este tipo con el que ordenar los elementos de la lista, de modo que tenemos que crear una clase Comparator

public class ComparatorByName implements Comparator{

    @Override
    public int compare(Persona o1, Persona o2) {
        return o1.compareTo(o2);
    }
    
}

o podemos instanciar el objeto desde el argumento del método sort, generando el bloque de código con el método compareTo

ArrayList personas=new ArrayList();
        
        personas.sort(new Comparator() {
            @Override
            public int compare(Persona o1, Persona o2) {
                return o1.compareTo(o2);
            }
        });

Con programación funcional, todo se vuelve más fácil, en el siguiente ejemplo creamos dos objetos Comparator a los que le pasamos dos argumentos de los dos objetos a comparar que coincide con la firma del método de comparación del objeto original, pudiendo incluso aprovechar los métodos compareTo de la clase String para crear nuestro comparador personalizado.

Comparator comparatorEstandar= (pa, pb)-> pa.compareTo(pb);
Comparator comparatorByName= (pa, pb)-> pa.getNombre().compareTo(pb.getNombre());
personas.sort(comparatorByName);
personas.sort(comparatorEstandar);

Com ya vimos en el post Colecciones, mediante stream podemos ordenar con:

  • Método de la clase
  • Añadiendo un objeto Comparator creado mediante programación funcional o estándar
  • Añadiendo a la expresión lambda dos parámetros y la comparación.
personas.stream().
            sorted().
            forEach(p-> System.out.println(p.getFullName()));
        
        personas.stream().
            sorted(comparatorByName).
            forEach(p-> System.out.println(p.getFullName()));
        
        personas.stream().
            sorted((pa,pb)-> pa.getNombre().compareTo(pb.getNombre())).
            forEach(p-> System.out.println(p.getFullName()));

Por último para rizar el rizo… y ¿si queremos ordenar por varios campos, por ejemplo primero por apellido y después por edad? esto es posible con el método thenComparing para añadir un nuevo orden con otro comparador anidado.

        
        Comparator<Persona> comparatorByNameAndAge= comparatorByName.thenComparing((p0,p1)->p0.compareTo(p1));.
            forEach(p-> System.out.println(p.getFullName()));

Colecciones II

Colecciones II
Continuando con las colecciones, vamos a ver los mismo métodos y procedimientos usados en el post anterior, pero esta vez exclusivamente con Java.

Nuestra clase Persona en Java quedaría del siguiente modo:

public class Persona implements Comparable{
    public Persona(String _name,
            String _apellidos,
            boolean _isMan,
            LocalDate fechaNacimiento,
            String _lugarNacimiento){
        nombre=_name;
        apellidos=_apellidos;
        isMan =_isMan;
        nacimiento=fechaNacimiento;
        lugarNacimiento=_lugarNacimiento;
    }

    String nombre;
    String apellidos;
    LocalDate nacimiento;
    String lugarNacimiento;
    Boolean isMan;

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public String getApellidos() {
        return apellidos;
    }

    public void setApellidos(String apellidos) {
        this.apellidos = apellidos;
    }

    public LocalDate getNacimiento() {
        return nacimiento;
    }

    public void setNacimiento(LocalDate nacimiento) {
        this.nacimiento = nacimiento;
    }

    public String getLugarNacimiento() {
        return lugarNacimiento;
    }

    public void setLugarNacimiento(String lugarNacimiento) {
        this.lugarNacimiento = lugarNacimiento;
    }

    public Boolean getIsMan() {
        return isMan;
    }

    public void setIsMan(Boolean isMan) {
        this.isMan = isMan;
    }

    public int getEdad(){
        LocalDate now= LocalDate.now();
        return now.getYear() -
                nacimiento.getYear() +
                Integer.compare(now.getDayOfYear(), nacimiento.getDayOfYear());
    }

    @Override
    public String toString() {
        return "Persona{" + "nombre=" + nombre + ", apellidos=" + apellidos + ", edad= " + getEdad() + "}";
    }

    public boolean isFrom(String city){
        return lugarNacimiento.equals(city);
    }

    public String getFullName(){
        return String.format("%s, %s", getApellidos(), getNombre());
    }

    @Override
    public int compareTo(Persona o) {
        return this.apellidos.compareTo(o.apellidos);
    }
}

añadimos los elementos a la colección de igual modo, esta vez usando para la fecha la clase LocalDate

 static void addPersonas(){
        personas.add(new Persona("Joaquin", "Martinez Rus", true,LocalDate.of(1969, 2, 20), "Linares"));
      personas.add(new Persona("Vicky", "Diez Calatayud", false,LocalDate.of(1996, 4, 19), "Calatayud"));
      personas.add(new Persona("Ana Isabel", "Martinez Baloo", false,LocalDate.of(1998, 10, 20), "Cordoba"));
      personas.add(new Persona("Victoria", "Martinez Nurse", false,LocalDate.of(1992, 1, 21), "Cordoba"));
      personas.add(new Persona("Ana", "Rus Maria", false,LocalDate.of(1961, 11, 3), "Linares"));
      personas.add(new Persona("Manuel", "Bonillo Contador", true,LocalDate.of(1978, 12, 26), "Cordoba"));
      personas.add(new Persona("Jose Antonio", "Martinez Arcos", true,LocalDate.of(1974, 9, 4), "Zaragoza"));
      personas.add(new Persona("Vicente", "Rodriguez Iglesias", true,LocalDate.of(1981, 11, 8), "Cordoba"));
      personas.add(new Persona("Alfonso", "Perez Judicial", true,LocalDate.of(2000, 5, 5), "Linares"));
      personas.add(new Persona("Ana", "Martinez Maestre", false,LocalDate.of(2004, 3, 4), "Linares"));
      personas.add(new Persona("Magdalena", "Fuentes Cruz", false,LocalDate.of(2002, 7, 11), "Jaen"));
      personas.add(new Persona("Jose", "Martinez Cintas", true,LocalDate.of(1981, 6, 21), "Linares"));
      personas.add(new Persona("Jose", "Aldea Morata", true,LocalDate.of(1994, 11, 22), "Calatayud"));
      personas.add(new Persona("Maria Isabel", "Diez Campieles", false,LocalDate.of(2005, 8, 1), "Calatayud"));
      personas.add(new Persona("Maria", "Martinez Adriá", false,LocalDate.of(2001, 10, 22), "Jaen"));
      personas.add(new Persona("Pedro José", "Garcia Civil", true,LocalDate.of(1980, 9, 15), "Cordoba"));
      personas.add(new Persona("Luis", "Jiloca Diez", true,LocalDate.of(1995, 6, 15), "Zaragoza"));
      personas.add(new Persona("Juan", "Pintor Escultor", true,LocalDate.of(2000, 10, 22), "Jaen"));
      personas.add(new Persona("Juan", "Goran Esteban", true,LocalDate.of(1974, 7, 14), "Jaen"));
      personas.add(new Persona("Andrea", "Jiloca Diez", false,LocalDate.of(1999, 9, 21), "Calatayud"));
      personas.add(new Persona("Daniel", "Ceular Flower", true,LocalDate.of(1975, 2, 4), "Cordoba"));
    }

y pasamos al primer ejemplo aunque lógicamente solo mostraré el método tradicional y la expresión lambda, las cuales fueron incluidas con Java 8.
Ejemplo 1. ¿cual es el promedio de edad de todas las personas nacidas en Calatayud?
Java tiene algunas variaciones con respecto a C#, de modo que para crear una expresión lambda sobre una colección, debemos crear un stream (o iteración) y sobre este con filter hacemos la función where en C# y por último con mapToDouble, convertimos o mejor dicho creamos un stream de objetos tipo Double a los cuales le calculamos el promedio.

 
static void getAverageAge(String lugarNacimiento){
        double suma=0;
        double count=0;
        for (Persona persona : personas) {
            if (persona.getLugarNacimiento().equals(lugarNacimiento)) {
                suma += persona.getEdad();
                count ++;
            }
        }
        double promedio = suma / count;
        System.out.printf("Las personas nacidas en %s tienen un promedio de edad de %s\n", lugarNacimiento, promedio);
    }
static void getStreamAverageAge(String lugarNacimiento){
        OptionalDouble promedio= personas.
                       stream().
                       filter(k->k.isFrom(lugarNacimiento)).
                       mapToDouble(e->e.getEdad()).
                       average();
        System.out.printf("Las personas nacidas en %s tienen un promedio de edad de %s\n", lugarNacimiento, promedio.getAsDouble());
    }

Ejemplo 2. Queremos listar todos los mayores de edad por orden alfabético de los apellidos. En este ejemplo, debemos implementar la interface Comparator incluir el método compareTo y con esto mostramos el código

     static void printAdults(){
         personas.sort(new Comparator() {
             @Override
             public int compare(Persona o1, Persona o2) {
                 return o1.apellidos.compareTo(o2.apellidos);
             }
         });
         for (Persona persona : personas) {
              if (persona.getEdad()>17)
                {
                    System.out.println(persona.getFullName());
                }
         }
     }
     
     static void printStreamAdults(){
         personas.stream().
                 filter(p->p.getEdad()>17).
                 sorted().
                 forEach(p-> System.out.println(p.getFullName()));
     }

y el resultado que debe ser exactemente igual.
coleccion3

DefaultTableModel extendido

DefaultTableModel extendido

A veces cuando programamos en varios lenguajes, no podemos impedir comparar el modo de hacer entre unos y otros y uno de ellos es el uso de un control JTable y su relleno con DefaultTableModel. Comparado con un DataGrid en WPF al que le pasamos la colección de objetos y si así lo deseamos, las columnas pueden generarse solas, nuestro JTable está un poco lejos ya que hay que crear las columnas y pasarle un array de objetos por fila, pero por ejemplo al seleccionar una fila, como el objeto no está incluido en ella,sino un array, hay que recomponer de nuevo el objeto, en fin, demasiados inconvenientes.

Para solucionar esto, vamos a crear un objeto DefaultTableModel al que le pasaremos como argumentos la lista de objetos, los nombres de las columnas, el valor de la key para localizar objetos y un texto para filtros y con todo esto añadiremos algunas funciones adicionales como filtrar u obtener un objeto.

Para generalizar, he creado una interface ObjectTableable para el objeto en sí, que implementa tres métodos:

  • Object[] toArray(). Obtiene un array del objeto con el fin de añadirlo a cada fila
  • int GetIndex(). Nos informa de la columna que contiene el key del objeto y con el cual podemos luego capturar.
  • Boolean Contains(String text). Comprueba en sus propiedades si contiene un texto
public interface ObjectTableable{
    public Object[] toArray();
    public Object getIndex();
    public Boolean contains(String text);
}

La siguiente interface la implementará nuestra clase extendida DefaultTableModel a la cual le pasamos un objeto genérico que implementa la interface ObjectTableable, con esto nos aseguramos que todos los objetos de nuestra clase extendida funciona por igual. Esta interface se llama Tableable y contiene los siguientes métodos:

  • int getKeyIndex(). Devuelve el número de columna que contiene el key del objeto en la lista
  • void setKeyIndex(int index). Asigna el valor de la columna key
  • T getRowObject(). Devuelve un objeto que implementa la interfaz ObjectTableable
  • ArrayList getColumns(). Obtiene una lista de las columnas
  • void setColumns(String [] columns). Asigna el valor de las columnas
  • void clearColumns(). Borra las columnas. Esto solo se puede hacer desde el objeto jTable.ColumnTableModel.
  • void filter(String text). Filtra el contenido de la colección mediante un texto.
public interface Tableable{
    int getKeyIndex();
    void setKeyIndex(int index);
    ArrayList getColumns();
    void setColumns(String [] columns);
    void clearColumns();
    T getRowObject(int _row);
    void filter(String text);
}

Un ejemplo de un un objeto que implementa la interfaz ObjectTableable sería este:

public class Cliente implements ObjectTableable {
// campos y métodos
//...
@Override
    public Object[] toArray() {
        return new Object[]{
            getCustomerNumber(),
            getCustomerName(),
            getContactFirstName(),
            getContactLastName(),
            getPhone(),
            getAddressLine1(),
            getAddressLine2(),
            getCity(),
            getState(),
            getPostalCode(),
            getCountry(),
            getSalesRepEmployeeNumber(),
            getCreditLimit()
        };
    }

    @Override
    public Object getIndex() {
        return 0;
    }

    @Override
    public Boolean contains(String text) {
        String lowerText=text.toLowerCase();
        return this.getCustomerName().toLowerCase().contains(lowerText) ||
                String.valueOf(getCustomerNumber()).contains(lowerText) ||
                this.getContactFirstName().toLowerCase().contains(lowerText) ||
                this.getContactLastName().toLowerCase().contains(lowerText);
    }
}

El método toArray(), devuelve un array de los objetos que pretendemos visualizar y contains le indicamos si en alguno de sus campos contiene el elemento que buscamos.

Y ahora la extensión de TableDefaultModel, la cual debe tratar objetos genéricos para que nos sirva para cualquier objeto que implemente nuestra initerfaz.

public class ExtendedTableModel<T extends ObjectTableable> extends DefaultTableModel implements Tableable{
     
    ArrayList<T> objectList;
    ArrayList<String> columns;
    int keyIndex=0;
    
    public ExtendedTableModel(ArrayList<T> _list, String[] _columns){ 
        this(_list,_columns,0, "");  
    }
    
    public ExtendedTableModel(ArrayList<T> _list, String[] _columns, int _key){ 
        this(_list,_columns,_key,"");         
    }
    
        public ExtendedTableModel(ArrayList<T> _list, String[] _columns, int _key, String text){ 
        objectList=new ArrayList<>();
        columns=new ArrayList<>();
        keyIndex=_key;
                
        objectList.addAll(_list);
        setColumns(_columns);
        if (!text.isEmpty()) {
            filter(text);
        }
        setModel();           
    }
        
    void setModel(){ 
        setColumns();
        objectList.stream().forEach((object) -> {
            this.addRow(object.toArray());
        });
    }
    
    void setColumns(){
        getColumns().stream().forEach((column) -> {
            this.addColumn(column);
        });
    }
    
    @Override
    public T getRowObject(int row){     
        Object keyRow=getValueAt(row, keyIndex);
        for (T object : objectList) {
            Object a= object.getIndex();
            if (object.getIndex().equals(keyRow)) {
                return object;
            }
        }
        return null;
    }

    @Override
    public int getKeyIndex() {
        return keyIndex;
    }

    @Override
    public void setKeyIndex(int index) {
        keyIndex=index;
    }

    @Override
    public ArrayList<String> getColumns() {
        return columns;
    }

    @Override
    public void setColumns(String[] _columns) {
        columns.addAll(Arrays.asList(_columns));
    }

    @Override
    public void clearColumns() {
        setColumns(new String[0]);
    }    
    
    @Override
    public void filter(String text){
        Object[] temp=objectList.stream().filter(k->k.contains(text)).toArray();
        objectList.clear();
        for (Object object : temp) {
            objectList.add((T)object);
        } 
    }       

al instanciar el objeto, ya se crea el modelo con las columnas y filas idenpendientemente del tipo de objeto siempre implemente nuestra interfaz, pero si hacemos dobleclick sobre el jTable, podemos llamar al método getRowObject que devolverá el objeto completo o si le passamos como parámetro un texto, iniciará un filtrado sobre los objetos mostrando solo los que cumplan el criterio.Con esto, simulamos un itemsource a un control jTable de Java, primero obtenemos los datos, segundo las columnas, el texto de filtrado y la columna del indice.

ArrayList empleados=negocio.getEmpleados();
String [] _columnsE=new String[] { "Cargo", "Código empleado", "Nombre", "Apellidos","Extensión", "Email", "Oficina"};
                etm=new ExtendedTableModel(empleados,_columnsE,1, jTextField1.getText());

Y el resultado.

Video13.gif

Descomposición factorial con hilos II

Descomposición factorial con hilos II
Y ahora vamos a emular un lugar donde se descomponen los números y cada vez que se encuentra uno, se obtiene y se muestra su descomposición.

Para realizar esto, usaremos los monitores de Java, los métodos wait() y notify(). Estos métodos tienen que ir siempre incluidos sobre métodos marcados como syncronized o usar el método syncronized(metodo sincronizado). wait() y notify() (hay alguno más) heredan del Object, por lo que todos los objetos que hereden de esta clase, lo tienen disponible.

¿Que hace wait()? suspende indefinidamente el hilo hasta que se reanude con notify().

Así que crearé una clase que solicitará la descomposición del número, el cual es enviado al supuesto servidor compartido de descomposiciones factoriales, este se encarga de descomponerlo (tarea dura) y una vez que termine, habrá un receptor de datos esperando a su finalización para mostrar su descomposición.

DESC

La clase SolicitudDescomposicionVisualizadorDescomposicion heredan de Thread, los iniciamos desde la clase principal y a trabajar por parte del servidor. He aquí el código:

public static void main(String[] args) {
        // TODO code application logic here
        
        ServidorDescomposicion r=new ServidorDescomposicion();
        for (int i = 0; i < 10; i++) {
            SolicitudDescomposicion productor=new SolicitudDescomposicion(i, r);
            VisualizadorDescomposicion consumidor=new VisualizadorDescomposicion(i, r);
            
            productor.start();
            consumidor.start();
        }
        
    }
public class SolicitudDescomposicion extends Thread {
    
    ServidorDescomposicion r;
    
    public SolicitudDescomposicion(int n, ServidorDescomposicion _r){
        this.setName("Productor " + n);
        r=_r;
    }
    
    @Override
    public void run(){
        int n=Math.abs(new Random().nextInt());
        System.out.println("Enviando número descomposición desde el productor:\t" + n);
        r.descomponerNumero(n);
    }
}

public class VisualizadorDescomposicion extends Thread{
    
    ServidorDescomposicion r;
    
    public VisualizadorDescomposicion(int n, ServidorDescomposicion _r){
        this.setName("Consumidor " + n);
        r=_r;
    }
    
    @Override
    public void run(){        
        String desc="";
        try {
            desc = r.getDescomposicion();
        } catch (InterruptedException ex) {
            Logger.getLogger(VisualizadorDescomposicion.class.getName()).log(Level.SEVERE, null, ex);
        }
        System.out.println("Datos obtenidos desde el consumidor.Resultado:\t\t" + desc);        
    }
}
public class ServidorDescomposicion {

    ArrayList buffer;
    boolean existData;


    //&& estado.equals(estadoEnum.DESCOMPONIENDO
    //&& estado.equals(estadoEnum.LIBRE)
    public ServidorDescomposicion() {
        buffer = new ArrayList();
    }

    public synchronized void descomponerNumero(int numero) {
        descomponer(numero);
    }

    void retardo(int ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException ex) {
            Logger.getLogger(ServidorDescomposicion.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public synchronized String getDescomposicion() throws InterruptedException {
        
        while(!existData){
            try {
                wait();
            } catch (InterruptedException ex) {
                Logger.getLogger(ServidorDescomposicion.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        
        String descomposicion = "";
        descomposicion = buffer.remove(0);
        if (buffer.size()==0) {
            existData=false;
        }
        notify();
        
        return descomposicion;
    }

    void descomponer(int numero) {
        // Cálculos y resultados
        int iniNum = numero;
        int numeroAux = numero;
        int n = 0;
        int nn = 0;

        String text = "";
        String puntos = ".";

        for (long i = 2; i < numero; i++) { 
            while (numeroAux % i == 0) {
                numeroAux /= i;
                text += i + ".";
                if (numeroAux == 1) {
                    numero = numeroAux;
                }
            }
        }
        if (text.isEmpty()) {
            System.out.printf("El número %s es primo!\n", iniNum);
            text = String.valueOf(iniNum);
        }
        buffer.add(String.format("%s=%s", iniNum, text));
        existData=true;

    }

}

y el resultado para 10 números enteros


Enviando número descomposición desde el productor:	1007666687
Enviando número descomposición desde el productor:	233955388
Enviando número descomposición desde el productor:	403028651
Enviando número descomposición desde el productor:	1405658987
Enviando número descomposición desde el productor:	809322159
Enviando número descomposición desde el productor:	263332588
Enviando número descomposición desde el productor:	1552584352
Enviando número descomposición desde el productor:	1342709543
Datos obtenidos desde el consumidor.Resultado:		1007666687=17.31.43.53.839.
Datos obtenidos desde el consumidor.Resultado:		1342709543=7.191815649.
Datos obtenidos desde el consumidor.Resultado:		1552584352=2.2.2.2.2.11.89.49559.
Datos obtenidos desde el consumidor.Resultado:		263332588=2.2.487.135181.
Datos obtenidos desde el consumidor.Resultado:		809322159=3.113.733.3257.
Datos obtenidos desde el consumidor.Resultado:		403028651=67.6015353.
Datos obtenidos desde el consumidor.Resultado:		233955388=2.2.31.443.4259.
Datos obtenidos desde el consumidor.Resultado:		1405658987=127.199.55619.

Saludos

Descomposición factorial con hilos I

Descomposición factorial con hilos I

Un ejemplo de aprovechamiento de recursos sobre procesos penosos en los que ponemos el pc a prueba, puede ser la descomposición de números grandes en factores primos. En este ejemplo sobre la plataforma Java, se descomponen números del tipo Long con un método estándar (no muy eficiente) con una clase que implementa la Interfaz Runnable. A la clase le pasamos un número como parámetro y otro de identificador.Si creamos varios hilos, cada uno descomponiendo un número grande, aprovechará los distintos núcleos para el cálculo. Este es el resultado para números enteros pequeños mostrando el número de hilos vivos.

INICIO Hilo nº0-> Descomposición de 1763458
INICIO Hilo nº2-> Descomposición de 882375341
INICIO Hilo nº3-> Descomposición de 396209470
INICIO Hilo nº1-> Descomposición de 1633289887
Hilo nº 0-> Descomponiendo 1763458. Nº iteraciones: 1
Hilo nº 2-> Descomponiendo 882375341. Nº iteraciones: 96
Hilo nº 3-> Descomponiendo 396209470. Nº iteraciones: 1
Hilo nº 3-> Descomponiendo 198104735. Nº iteraciones: 4
Hilo nº 3-> Descomponiendo 39620947. Nº iteraciones: 18
Hilo nº 3-> Descomponiendo 2085313. Nº iteraciones: 210
Hilo nº 1-> Descomponiendo 1633289887. Nº iteraciones: 66
Hilo nº 1-> Descomponiendo 24377461. Nº iteraciones: 100
Hilo nº 3-> Descomponiendo 9883. Nº iteraciones: 9882
FIN Hilo nº 3-> 396209470 = 2*5*19*211*9883*. Nº iteraciones: 9882
Quedan 3 hilos vivos
Hilo nº 2-> Descomponiendo 9096653. Nº iteraciones: 1810
Hilo nº 2-> Descomponiendo 5023. Nº iteraciones: 5022
FIN Hilo nº 2-> 882375341 = 97*1811*5023*. Nº iteraciones: 5022
Quedan 2 hilos vivos
Hilo nº 1-> Descomponiendo 241361. Nº iteraciones: 241360
FIN Hilo nº 1-> 1633289887 = 67*101*241361*. Nº iteraciones: 241360
Quedan 1 hilos vivos
Hilo nº 0-> Descomponiendo 881729. Nº iteraciones: 881728
FIN Hilo nº 0-> 1763458 = 2*881729*. Nº iteraciones: 881728
Quedan 0 hilos vivos

Y el código de las clases…

Clase Main

package Descompositor;

import java.util.Random;

// <editor-fold defaultstate="collapsed" desc="Cabecera de clase">
/******************************************************
 *  @author:    Joaquín Martínez Rus (c) 2017
 *  @version:   1.0
 *  File:       Main.java
 *  Created:    29/11/2017
 *  Project:    PSP. Ejemplo de descomposición multihilo.
 *  Comments:
 *******************************************************/
// </editor-fold>
public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
        for (int i = 0; i < 5; i++) {
            Descomposicion d=new Descomposicion(Math.abs(new Random().nextLong()),i);
            Thread hilo=new Thread(d);
            hilo.start();
        }
    }
}

y la clase encargada de la descomposición

package Descompositor;
import java.util.Date;
// <editor-fold defaultstate="collapsed" desc="Cabecera de clase">
/******************************************************
 *  @author:    Joaquín Martínez Rus (c) 2017
 *  @version:   1.0
 *  File:       Descomposicion.java
 *  Created:    29/11/2017
 *  Project:    PSP. Ejemplo de descomposición multihilo.
 *  Comments:
 *******************************************************/
// </editor-fold>
public class Descomposicion implements Runnable{

    public Descomposicion(long n, int j){
        this.name=j;
        this.number=n;
        Descomposicion.instanciasVivas++;
    }

    public static int instanciasVivas;
    int name;
    long number;

    @Override
    public void run() {
        descomponer(number, name);
    }

    void descomponer(long numero, int j){
        // Cálculos y resultados

        long iniNum=numero;
        long numeroAux = numero;
        long n=0;
        int nn=0;

        System.out.printf("INICIO Hilo nº%s-> Descomposición de %s\n" , j, numeroAux);
        String text="";
        String puntos=".";

        for (long i = 2; i 1000000000) {
                nn++;
                String cadena="Hilo Nº %s-> Seguimos calculando para %s...\n";
                if (nn>2) {
                    if (text.isEmpty()) {
                        cadena = "Hilo Nº %s-> parece que el número %s es primo...\n";
                    }
                    else{
                        cadena ="Hilo Nº %s-> No es primo y seguimos calculando para %s...\n";
                        nn=0;
                    }
                }
                System.out.printf(cadena, j, numeroAux);
                n=0;

            }
            // DESCOMPOSICIÓN
            while (numeroAux % i == 0) {
                System.out.printf("Hilo nº %s-> Descomponiendo %s. Nº iteraciones: %s\n",j, numeroAux, n);
                numeroAux /= i;
                text += i + "*";
                if (numeroAux == 1) {
                    numero=numeroAux;
                }
            }
        }
        if (text.isEmpty()){
            System.out.printf("El número %s es primo!\n",iniNum);
            text = String.valueOf(iniNum);
        }
        System.out.printf("FIN Hilo nº %s-> %s = %s. Nº iteraciones: %s\n",j, iniNum, text, n);

        Descomposicion.instanciasVivas--;
        System.out.printf("Quedan %s hilos vivos\n",Descomposicion.instanciasVivas);

    }
}

Os adjunto el proyecto de NetBeans.

Saludos