
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);