
Del 28 al 29 de octubre, 
Joker 2019 se celebró en San Petersburgo, la conferencia más grande y más intensa en la inmensidad de Rusia dedicada al desarrollo de Java. El evento se realizó por séptima vez y, como siempre, rompió el récord de asistencia, esta vez atrajo a más de 2000 especialistas.
Los compañeros de clase tradicionalmente participan en Joker como socios del evento. Este año, en nuestro stand, uno podría tratar de hacer frente a las famosas tareas "irresolubles" de los principales ingenieros de OK.RU. Los participantes de la conferencia que respondieron las preguntas correctamente recibieron premios.
Para ser justos, debo decir que de 1,000 folletos con las tareas que entregamos, se devolvieron menos de 100. Lo mejor fue la solución, que obtuvo 4.5 puntos de 5.
Publicamos tareas y sus soluciones para que pueda probar su fortaleza.
1. Enum heroica
El código fuente de un juego poco conocido reveló dicho código. ¿Cuál es la mala implementación de 
Group.of y cómo solucionarlo?
 enum Group { Few(1, 4), Several(5, 9), Pack(10, 19), Lots(20, 49), Swarm(50, Integer.MAX_VALUE); Group(int min, int max) { ... } public static Group of(int count) { for (Group group : Group.values()) { if (count >= group.min && count <= group.max) { return group; } } throw new IllegalArgumentException(); } } 
SoluciónSi no habla de estilos de codificación, este fragmento tiene un inconveniente objetivo: un posible problema de rendimiento. Aunque la búsqueda lineal a menudo resulta ser un cuello de botella, en este caso no es así, porque esta enumeración tiene solo cinco elementos. Y lo que realmente puede afectar negativamente el rendimiento es la asignación excesiva de memoria al llamar a 
Group.values() . El problema es que el método de 
values() de enum siempre devuelve una nueva copia de la matriz, y HotSpot aún no puede optimizarlo. Una solución simple es hacer su propia copia de la matriz de 
values() e iterar sobre ella:
 private static final Group[] ALL_GROUPS = Group.values(); public static Group of(int count) { for (Group group : ALL_GROUPS) { .... } 
 2. sueños
Java 13 ya se ha lanzado, y Nikolai todavía solo comprende transmisiones. Indique errores en el método que calcula la diferencia entre los elementos de flujo máximo y mínimo.
 int getDiameter(Stream<Integer> stream) { int min = stream.min(Integer::compare).get(); int max = stream.max(Integer::compare).get(); return max - min; } 
SoluciónLas secuencias en Java suelen ser de una sola vez: la llamada a la segunda operación del terminal (en este caso, 
max ) fallará:
 java.lang.IllegalStateException: stream has already been operated upon or closed 
Además, 
min y 
max return 
Optional , la operación 
get() en la que se lanzará una 
NoSuchElementException para una secuencia vacía. Por lo tanto, es más correcto verificar 
isPresent() antes de llamar a 
get() o usar otros métodos 
Optional : o 
Else , o 
ElseThrow , etc.
Finalmente, el hecho de que la diferencia entre las dos 
int ya no puede caber en el 
int no escapará al desarrollador cuidadoso, y valdría la pena cambiar el tipo del valor de retorno a 
long .
 3. Tampón seguro
ByteBuffer primitiva de sincronización de Java puede hacer 
get operaciones de 
put y 
get subprocesos sean seguras en un 
ByteBuffer genérico?
 final ByteBuffer buf = ByteBuffer.allocate(SIZE); int get(int offset) { return buf.get(offset); } void put(int offset, int value) { buf.putInt(offset, value); } 
Elija la opción más efectiva si sabe que hay muchos subprocesos y obtenga ejecuciones con mucha más frecuencia que la puesta.
- sincronizado
- Reentrantlock
- ReentrantReadWriteLock
- Stampedlock
- Semáforo
- Leer y escribir int en Java siempre es atómico
SoluciónReentrantReadWriteLock pide 
ReentrantReadWriteLock lector y escritor, y a menudo esta será una solución efectiva. Pero tenga en cuenta que en este caso, las operaciones get y put son muy simples: la probabilidad de que un put competitivo pueda interferir con get es pequeña, además, es menos probable que ocurran condiciones de put con las operaciones put. Por lo tanto, puede aplicar el mecanismo de 
bloqueo optimista que proporciona 
StampedLock .
StampedLock será más eficiente que 
ReentrantReadWriteLock debido al hecho de que, en caso de un éxito de ruta rápida optimista, las variables compartidas no se actualizan en absoluto, mientras que 
ReentrantReadWriteLock realiza al menos un 
CAS en el mejor de los casos.
 4. Regalos
Ilya está desarrollando una vitrina de regalos en una red social. Ayúdelo a escribir el método 
add para una estructura que no contenga más de N de los regalos más nuevos. No se debe agregar un regalo si ya está presente, o si es más antiguo que el resto de N.
 interface Present { long getId(); Date getCreated(); } void add(Present p) {  
SoluciónTreeSet o 
PriorityQueue naturalmente adecuado como una estructura de datos con el fin de agregar regalos de forma efectiva y eliminar el más antiguo, no peor que para O (log N). Todo el truco está solo en el comparador: no es suficiente comparar regalos solo con 
getCreated() , porque la fecha de creación no tiene que ser única. Por lo tanto, debe comparar primero 
getCreated() , luego 
getId() . Tal comparador asegurará tanto la unicidad de los elementos como el orden por fecha.
 TreeSet<Present> tree = new TreeSet<>( Comparator.comparing(Present::getCreated) .thenComparing(Present::getId)); 
Sigue siendo un asunto pequeño: al agregar un regalo, verifique que el tamaño no exceda N y, si es necesario, elimine el primer elemento más antiguo de la colección.
 void add(Present p) { if (tree.add(p) && tree.size() > N) { tree.pollFirst(); } } 
 5. No esperarás
¿Por qué Julia nunca esperará el final de este programa?
 var executor = Executors.newFixedThreadPool(4); for (File f : File.listRoots()) { executor.submit(() -> f.delete()); } executor.awaitTermination(2, TimeUnit.HOURS);