Skip to content

Commit 52ef489

Browse files
claudemperor
authored andcommitted
Add Java 25 Scoped Values vs ThreadLocal examples
1 parent b209ba5 commit 52ef489

1 file changed

Lines changed: 50 additions & 0 deletions

File tree

  • JavaReleases/src/test/java/pl/mperor/lab/java

‎JavaReleases/src/test/java/pl/mperor/lab/java/Java25.java‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import org.junit.jupiter.api.Test;
55
import pl.mperor.lab.common.TestUtils;
66

7+
import java.util.NoSuchElementException;
8+
79
/// Java 25 (September 2025)
810
/// [JDK 25](https://openjdk.org/projects/jdk/25)
911
///
@@ -98,4 +100,52 @@ void describe() {
98100
}
99101
}
100102

103+
static final ScopedValue<String> USER = ScopedValue.newInstance();
104+
105+
@Test
106+
public void testScopedValueIsBoundOnlyInsideRunScope() {
107+
Assertions.assertFalse(USER.isBound());
108+
109+
ScopedValue.where(USER, "John Doe").run(() -> {
110+
Assertions.assertTrue(USER.isBound());
111+
Assertions.assertEquals("John Doe", USER.get());
112+
});
113+
114+
// the binding is torn down as soon as run() returns
115+
Assertions.assertFalse(USER.isBound());
116+
Assertions.assertThrows(NoSuchElementException.class, USER::get);
117+
}
118+
119+
@Test
120+
public void testNestedRebindingRestoresOuterValueAfterward() {
121+
ScopedValue.where(USER, "John Doe").run(() -> {
122+
Assertions.assertEquals("John Doe", USER.get());
123+
124+
ScopedValue.where(USER, "Jane Doe").run(() -> Assertions.assertEquals("Jane Doe", USER.get()));
125+
126+
// the nested run() only rebinds USER for its own body; the outer binding is unaffected
127+
Assertions.assertEquals("John Doe", USER.get());
128+
});
129+
}
130+
131+
@Test
132+
public void testCallReturnsValueComputedUnderTheBinding() throws Exception {
133+
String greeting = ScopedValue.where(USER, "John Doe").call(() -> "Hello, " + USER.get());
134+
Assertions.assertEquals("Hello, John Doe", greeting);
135+
}
136+
137+
@Test
138+
public void testScopedValueHasNoSetUnlikeThreadLocal() {
139+
var legacy = new ThreadLocal<String>();
140+
legacy.set("John Doe");
141+
legacy.set("Jane Doe"); // ThreadLocal is mutable: set() silently replaces the value, anywhere, anytime
142+
Assertions.assertEquals("Jane Doe", legacy.get());
143+
legacy.remove(); // must be cleared by hand, or it leaks for the thread's lifetime (e.g. in a pool)
144+
145+
// ScopedValue has no set()/remove(): the only way to associate a value is where(...).run()/.call(...),
146+
// and the binding is automatically torn down when that call returns - no leak is possible.
147+
ScopedValue.where(USER, "John Doe").run(() -> Assertions.assertEquals("John Doe", USER.get()));
148+
Assertions.assertFalse(USER.isBound());
149+
}
150+
101151
}

0 commit comments

Comments
 (0)