Skip to content

Commit 87b95f1

Browse files
committed
eliminate AtomicLong from Generic(s) -> less memory
1 parent f258cfe commit 87b95f1

2 files changed

Lines changed: 58 additions & 15 deletions

File tree

src/main/java/an/awesome/pipelinr/Generic.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import java.util.HashMap;
77
import java.util.Map;
88
import java.util.concurrent.ConcurrentHashMap;
9-
import java.util.concurrent.atomic.AtomicLong;
109

1110
/**
1211
* Resolves generic types (like <C> or List<R>) into concrete runtime types in the
@@ -25,14 +24,11 @@
2524
*/
2625
public abstract class Generic<C> {
2726

28-
private static final Map<Generic<?>, Type> RESOLVED_GENERICS = new ConcurrentHashMap<>();
27+
private static Map<Generic<?>, Type> RESOLVED_GENERICS = new ConcurrentHashMap<>();
2928

3029
private final Class<?> context;
3130
private final Type diamond;
3231

33-
// For testing
34-
final AtomicLong numberOfScans = new AtomicLong();
35-
3632
protected Generic(Class<?> context) {
3733
this.context = context;
3834
this.diamond = capture();
@@ -53,7 +49,6 @@ public Class<? super C> resolve() {
5349
this,
5450
it -> {
5551
Mappings mappings = new Scanner().scan(context);
56-
numberOfScans.incrementAndGet();
5752
return mappings.get(diamond);
5853
});
5954
}
@@ -71,6 +66,11 @@ public int hashCode() {
7166
return 31 * context.hashCode() + diamond.hashCode();
7267
}
7368

69+
// for testing
70+
static void setCache(ConcurrentHashMap<Generic<?>, Type> cache) {
71+
Generic.RESOLVED_GENERICS = cache;
72+
}
73+
7474
/** Walks the class hierarchy, collecting mappings between type variables and actual types. */
7575
private static class Scanner {
7676

src/test/java/an/awesome/pipelinr/GenericTest.java

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
import static org.assertj.core.api.Assertions.assertThat;
44
import static org.junit.jupiter.api.Assertions.assertThrows;
55

6+
import java.lang.reflect.Type;
67
import java.util.List;
8+
import java.util.concurrent.ConcurrentHashMap;
9+
import java.util.concurrent.atomic.AtomicInteger;
10+
import java.util.function.Function;
711
import org.junit.jupiter.api.Test;
812

913
class GenericTest {
@@ -23,21 +27,54 @@ void throwsIfNotParameterized() {
2327

2428
@Test
2529
void resolvesMultipleGenericTypes() {
30+
class ConcurrentHashMapWithStats<K, V> extends ConcurrentHashMap<K, V> {
31+
final AtomicInteger misses = new AtomicInteger(0);
32+
33+
@Override
34+
public V computeIfAbsent(K key, Function<? super K, ? extends V> compute) {
35+
Function<? super K, ? extends V> computeAndTrackMisses =
36+
(Function<K, V>)
37+
k -> {
38+
misses.incrementAndGet();
39+
return compute.apply(k);
40+
};
41+
return super.computeIfAbsent(key, computeAndTrackMisses);
42+
}
43+
}
44+
45+
ConcurrentHashMapWithStats<Generic<?>, Type> cache = new ConcurrentHashMapWithStats<>();
46+
Generic.setCache(cache);
47+
2648
abstract class IKnowMyType<Foo, Bar> {
27-
final Generic<Foo> foo = new Generic<Foo>(getClass()) {};
28-
final Generic<Bar> bar = new Generic<Bar>(getClass()) {};
49+
final Generic<Foo> foo1 = new Generic<Foo>(getClass()) {};
50+
final Generic<Foo> foo2 = new Generic<Foo>(getClass()) {};
51+
final Generic<Bar> bar1 = new Generic<Bar>(getClass()) {};
52+
final Generic<Bar> bar2 = new Generic<Bar>(getClass()) {};
2953
}
3054

55+
// no reads upon construction -> no cache misses
3156
IKnowMyType<String, Integer> subj = new IKnowMyType<String, Integer>() {};
57+
assertThat(cache.misses).hasValue(0);
58+
59+
// reading the same generic instance twice -> 1 miss
60+
assertThat(subj.foo1.resolve()).isEqualTo(String.class);
61+
assertThat(subj.foo1.resolve()).isEqualTo(String.class);
62+
assertThat(cache.misses).hasValue(1);
3263

33-
assertThat(subj.foo.resolve()).isEqualTo(String.class);
34-
assertThat(subj.bar.resolve()).isEqualTo(Integer.class);
35-
assertThat(subj.bar.resolve()).isEqualTo(Integer.class);
36-
assertThat(subj.bar.resolve()).isEqualTo(Integer.class);
64+
// reading the same generic, but difference instance twice -> still 1 miss
65+
assertThat(subj.foo2.resolve()).isEqualTo(String.class);
66+
assertThat(subj.foo2.resolve()).isEqualTo(String.class);
67+
assertThat(cache.misses).hasValue(1);
3768

38-
// caching test
39-
assertThat(subj.foo.numberOfScans).hasValue(1);
40-
assertThat(subj.bar.numberOfScans).hasValue(1);
69+
// reading different generic (same instance, twice) -> +1 new miss => 2 total
70+
assertThat(subj.bar1.resolve()).isEqualTo(Integer.class);
71+
assertThat(subj.bar1.resolve()).isEqualTo(Integer.class);
72+
assertThat(cache.misses).hasValue(2);
73+
74+
// reading the same generic, difference instance -> no new misses => 2 total
75+
assertThat(subj.bar2.resolve()).isEqualTo(Integer.class);
76+
assertThat(subj.bar2.resolve()).isEqualTo(Integer.class);
77+
assertThat(cache.misses).hasValue(2);
4178
}
4279

4380
@Test
@@ -48,4 +85,10 @@ abstract class IKnowMyType<T> {
4885
IKnowMyType<List<String>> subj = new IKnowMyType<List<String>>() {};
4986
assertThat(subj.foo.resolve()).isEqualTo(List.class);
5087
}
88+
89+
@Test
90+
void resolvesRawTypes() {
91+
Generic<String> foo = new Generic<String>(getClass()) {};
92+
assertThat(foo.resolve()).isEqualTo(String.class);
93+
}
5194
}

0 commit comments

Comments
 (0)