Posts Tagged ‘SealedClasses’
[DevoxxBE2024] Java Language Futures by Gavin Bierman
Gavin Bierman, from Oracle’s Java Platform Group, captivated attendees at Devoxx Belgium 2024 with a forward-looking talk on Java’s evolution under Project Amber. Focusing on productivity-oriented language features, Gavin outlined recent additions like records, sealed classes, and pattern matching, while previewing upcoming enhancements like simplified main methods and flexible constructor bodies. His session illuminated Java’s design philosophy—prioritizing readability, explicit programmer intent, and compatibility—while showcasing how these features enable modern, data-oriented programming paradigms suited for today’s microservices architectures.
Project Amber’s Mission: Productivity and Intent
Gavin introduced Project Amber as a vehicle for delivering smaller, productivity-focused Java features, leveraging the six-month JDK release cadence to preview and finalize enhancements. Unlike superficial syntax changes, Amber emphasizes exposing programmer intent to improve code readability and reduce bugs. Compatibility is paramount, with breaking changes minimized, as Java evolves to address modern challenges distinct from its 1995 origins. Gavin highlighted how features like records and sealed classes make intent explicit, enabling the compiler to enforce constraints and provide better error checking, aligning with the needs of contemporary applications.
Records: Simplifying Data Carriers
Records, introduced to streamline data carrier classes, were a key focus. Gavin demonstrated how a Point class with two integers requires verbose boilerplate (constructors, getters, equals, hashCode) that obscures intent. Records (record Point(int x, int y)) eliminate this by auto-generating a canonical constructor, accessor methods, and value-based equality, ensuring immutability and transparency. This explicitness allows the compiler to enforce a contract: constructing a record from its components yields an equal instance. Records also support deserialization via the canonical constructor, ensuring domain-specific constraints, making them safer than traditional classes.
Sealed Classes and Pattern Matching
Sealed classes, shipped in JDK 17, allow developers to restrict class hierarchies explicitly. Gavin showed a Shape interface sealed to permit only Circle and Rectangle implementations, preventing unintended subclasses at compile or runtime. This clarity enhances library design by defining precise interfaces. Pattern matching, enhanced in JDK 21, further refines this by enabling type patterns and record patterns in instanceof and switch statements. For example, a switch over a sealed Shape interface requires exhaustive cases, eliminating default clauses and reducing errors. Nested record patterns allow sophisticated data queries, handling nulls safely without exceptions.
Data-Oriented Programming with Amber Features
Gavin illustrated how records, sealed classes, and pattern matching combine to support data-oriented programming, ideal for microservices exchanging pure data. He reimagined the Future class’s get method, traditionally complex due to multiple control paths (success, failure, timeout, interruption). By modeling the return type as a sealed AsyncReturn interface with four record implementations (Success, Failure, Timeout, Interrupted), and using pattern matching in a switch, developers handle all cases uniformly. This approach simplifies control flow, ensures exhaustiveness, and leverages Java’s type safety, contrasting with error-prone exception handling in traditional designs.
Future Features: Simplifying Java for All
Looking ahead, Gavin previewed features in JDK 23 and beyond. Simplified main methods allow beginners to write void main() without boilerplate, reducing cognitive load while maintaining full Java compatibility. The with expression for records enables concise updates (e.g., doubling a component) without redundant constructor calls, preserving domain constraints. Flexible constructor bodies (JEP 482) relax top-down initialization, allowing pre-super call logic to validate inputs, addressing issues like premature field access in subclass constructors. Upcoming enhancements include patterns for arbitrary classes, safe template programming, and array pattern matching, promising further productivity gains.
Links:
[KotlinConf2023] Dissecting Kotlin: Exploring Idiomatic Usage of Sealed Types, SAMs, and More with Huyen Tue Dao
Huyen Tue Dao, a respected Android and Kotlin developer (formerly of Trello, more recently Lead Android Developer at Adobe), returned to KotlinConf’23 with her insightful talk, “Dissecting Kotlin: Unsealing the Sealed, the SAM, and Other Syntax”. Continuing her exploration of what constitutes “idiomatic Kotlin,” Huyen examined several language and library features introduced or refined over the past few years, delving into their semantics, syntax, and underlying implementations to understand their best use cases and how they fit into Kotlin’s broader themes. She referenced Andre Breslav’s 2018 KotlinConf keynote, which emphasized Kotlin’s pragmatic goals: readability over concision, reuse over expressiveness, interoperability, and safety/tooling.
Huyen’s approach involved dissecting features to see if they guide developers toward more idiomatic Kotlin or present choices where idiomatic usage might be nuanced.
Sealed Hierarchies: Evolution and Flexibility
Huyen began by revisiting sealed classes and interfaces, a cornerstone for modeling restricted hierarchies, often used for representing states or a fixed set of types. Key evolutions she discussed include:
* Sealed Interfaces (Kotlin 1.5): Previously, sealed hierarchies were restricted to sealed class and abstract class. The introduction of sealed interface provided more flexibility, allowing classes to implement multiple sealed interfaces and enabling a wider range of domain modeling possibilities. She illustrated this by evolving a movie genre example, initially a sealed class, to use sealed interfaces for sub-genres, demonstrating how a class (e.g., a specific movie) could belong to multiple genre classifications.
* Unsealing Sealed Classes (Implicitly): While not “unsealing” in the sense of breaking the restriction, the ability for subclasses of sealed classes to be defined in different files within the same compilation unit and module (introduced before Kotlin 1.5 for sealed classes, and a natural fit for sealed interfaces) offers more organizational flexibility for larger hierarchies.
* Data Objects (Kotlin 1.9): For singleton instances within a sealed hierarchy (or elsewhere) that benefit from data class-like behavior (e.g., a meaningful toString()), Kotlin 1.9 introduced data object. This combines the singleton nature of object with the auto-generated toString, equals, and hashCode methods of data classes, providing a cleaner way to represent simple, named instances in a hierarchy.
A “bytecode break” showed that sealed classes are compiled as abstract classes with private constructors and that their permitted subclasses are often checked using instanceof and specific class metadata generated by the compiler.
Unsigned Integers and Value Classes: Expressiveness and Performance
Huyen then explored features aimed at enhancing expressiveness and performance around data representation:
* Unsigned Integer Types (UByte, UShort, UInt, ULong – Stable in Kotlin 1.5): These types provide a way to represent non-negative numbers, utilizing the full bit-width for the magnitude. This is particularly useful when interacting with native APIs (like C++) that use unsigned types, or when dealing with data where negative values are meaningless (e.g., quantities, bitmasks). They come with their own set of operations and ranges. Huyen highlighted how they avoid the complexities of two’s complement interpretation when only positive values are needed.
* Value Classes (Inline Classes became Value Classes, stable with JVM backend in Kotlin 1.5): Value classes (@JvmInline value class) are wrappers around a single underlying property. Their primary benefit is providing type safety for primitive-like data (e.g., Email, UserId, Frequency) without the runtime overhead of heap allocation for the wrapper object in many cases. When possible, the compiler “inlines” the value, using the underlying type directly in bytecode, thus avoiding object allocation and offering performance benefits similar to primitives while retaining type distinction at compile time. Huyen used an audio processing example with distinct types like Frequency, SamplingRate, and FramesPerBuffer to illustrate how value classes can prevent accidental misuse of simple types like Int or Float.
SAM Conversions and Functional Interfaces: Java Interop and Kotlin Idiom
Finally, Huyen discussed Single Abstract Method (SAM) interfaces and Kotlin’s fun interface:
* SAM Conversions for Java Interfaces: Kotlin has long supported SAM conversions for Java interfaces, allowing a lambda to be used where an instance of a Java interface with a single abstract method is expected.
* fun interface (Kotlin 1.4): To enable similar idiomatic usage for Kotlin-defined interfaces, Kotlin introduced fun interface. By marking a Kotlin interface with the fun keyword, developers explicitly opt-in to SAM conversion, allowing a lambda to be directly passed where an instance of that interface is required. This promotes a more functional style and reduces boilerplate for simple callback-like interfaces. This feature aims to provide compatible syntax between Java and Kotlin code for functional interfaces.
Huyen concluded by reiterating that while understanding syntax and semantics is helpful, “idiomatic Kotlin” ultimately is about what best solves the problem at hand for the developer and their team, aligning with Kotlin’s pragmatic principles.
Links:
[DevoxxPL2022] Java 17 & 18: What’s New and Noteworthy • Piotr Przybył
Piotr Przybył, a seasoned software gardener at AtomicJar, captivated the audience at Devoxx Poland 2022 with a comprehensive deep dive into the new features and enhancements introduced in Java 17 and 18. His presentation, rich with technical insights and practical demonstrations, explored key updates that empower developers to write more robust, maintainable, and efficient code. Piotr’s engaging style, peppered with humor and real-world examples, provided a clear roadmap for leveraging these advancements in modern Java development.
Sealed Classes for Controlled Inheritance
One of the standout features of Java 17 is sealed classes, introduced as JEP 409. Piotr explained how sealed classes allow developers to restrict which classes or interfaces can extend or implement a given type, offering fine-grained control over inheritance. This is particularly useful for library maintainers who want to prevent unintended code reuse while allowing specific extensions. By using the sealed keyword and a permits clause, developers can define a closed set of subclasses, with options to mark them as final, sealed, or non-sealed. Piotr’s demo illustrated this with a library type hierarchy, showing how sealed classes enhance code maintainability and prevent misuse through inheritance.
Enhanced Encapsulation and UTF-8 by Default
Java 17’s JEP 403 strengthens encapsulation by removing illegal reflective access, a change Piotr humorously likened to “closing the gates to reflection demons.” Previously, developers could bypass encapsulation using setAccessible(true), but Java 17 enforces stricter access controls, requiring code fixes or the use of --add-opens flags for legacy systems. Additionally, Java 18’s JEP 400 sets UTF-8 as the default charset for I/O operations, resolving discrepancies across platforms. Piotr demonstrated how to handle encoding issues, advising developers to explicitly specify charsets to ensure compatibility, especially for Windows users.
Deprecating Finalization and Introducing Simple Web Server
Java 18’s JEP 421 marks the deprecation of the finalize method for removal, signaling the end of a problematic mechanism for resource cleanup. Piotr’s demo highlighted the non-deterministic nature of finalization, advocating for try-with-resources as a modern alternative. He also showcased Java 18’s simple web server (JEP 408), a lightweight tool for serving static files during development or testing. Through a programmatic example, Piotr demonstrated how to start a server on port 9000 and dynamically modify CSS files, emphasizing its utility for quick prototyping.
Pattern Matching for Switch and Foreign Function API
Piotr explored Java 18’s pattern matching for switch (JEP 420), a preview feature that enhances switch statements and expressions. This feature supports null handling, guarded patterns, and type-based switching, eliminating the need for cumbersome if-else checks. His demo showed how to switch over objects, handle null cases, and use guards to refine conditions, making code more concise and readable. Additionally, Piotr introduced the Foreign Function and Memory API (JEP 419), an incubator module for safe, efficient interoperation with native code. He demonstrated allocating off-heap memory and calling C functions, highlighting the API’s thread-safety and scope-bound memory management.
Random Generators and Deserialization Filters
Java 17’s JEP 356 introduces enhanced pseudo-random number generators, offering a unified interface for various random number implementations. Piotr’s demo showcased switching between generators like Random, SecureRandom, and ThreadLocalRandom, simplifying random number generation for diverse use cases. Java 17 also improves deserialization filters (JEP 415), allowing per-stream customization to enhance security against malicious data. These updates, combined with other enhancements like macOS Metal rendering and larger G1 heap regions, underscore Java’s commitment to performance and security.
Links:
[KotlinConf2018] Taming State with Sealed Classes: Patrick Cousins’ Approach at Etsy
Lecturer
Patrick Cousins is a software engineer at Etsy with nearly 20 years of programming experience, passionate about new patterns and languages. He is known for his work on state management and seal-related puns. Relevant links: Etsy Code as Craft Blog (publications); LinkedIn Profile (professional page).
Abstract
This article examines Patrick Cousins’ use of Kotlin sealed classes to manage complex state in Etsy’s mobile apps. Contextualized in event-driven architectures, it explores methodologies for event streams with RxJava and when expressions. The analysis highlights innovations in exhaustiveness and type safety, contrasting Java’s limitations, with implications for robust state handling.
Introduction and Context
Patrick Cousins spoke at KotlinConf 2018 about sealed classes, inspired by his blog post on Etsy’s engineering site. Etsy’s mobile apps juggle complex state—listings, tags, shipping profiles—forming a “matrix of possibilities.” Sealed classes offer a type-safe way to model these, replacing Java’s error-prone instanceof checks and visitor patterns. This narrative unfolds where mobile apps demand reliable state management to avoid costly errors.
Methodological Approaches to State Management
Cousins modeled state as sealed class hierarchies, emitting events via RxJava streams. Using filterIsInstance and when, he ensured exhaustive handling of state types like Loading, Success, or Error. This avoided Java’s polymorphic indirection, where unrelated types forced artificial interfaces. Sealed classes, confined to one file, prevented unintended extensions, ensuring safety.
Analysis of Innovations and Features
Sealed classes innovate by guaranteeing exhaustiveness in when, unlike Java’s instanceof, which risks missing branches. Kotlin’s final-by-default classes eliminate Liskov substitution issues, avoiding polymorphic pitfalls. RxJava integration enables reactive updates, though requires careful ordering. Compared to Java, sealed classes simplify state logic without forced commonality, though complex hierarchies demand discipline.
Implications and Consequences
Cousins’ approach implies safer, more maintainable state management, critical for e-commerce apps. It reduces bugs from unhandled states, enhancing user experience. Consequences include a shift from polymorphic designs, though developers must adapt to sealed class constraints. The pattern encourages adoption in reactive systems.
Conclusion
Cousins’ use of sealed classes redefines state handling at Etsy, leveraging Kotlin’s type safety to create robust, readable mobile architectures.
Links
- Lecture video: https://www.youtube.com/watch?v=uGMm3StjqLI
- Lecturer’s X/Twitter: @patrickcousins
- Lecturer’s LinkedIn: Patrick Cousins
- Organization’s X/Twitter: @EtsyEng
- Organization’s LinkedIn: Etsy
[KotlinConf2017] A View State Machine for Network Calls on Android
Lecturer
Amanda Hill is an experienced Android developer currently working as a consultant at thoughtbot, a firm specializing in mobile and web application development. A graduate of Cornell University, Amanda previously served as the lead Android developer at Venmo, where she honed her expertise in building robust mobile applications. Based in San Francisco, she brings a practical perspective to Android development, with a passion for tackling challenges posed by evolving design specifications and enhancing user interfaces through innovative solutions.
Abstract
Managing network calls in Android applications requires robust solutions to handle dynamic UI changes. This article analyzes Amanda Hill’s presentation at KotlinConf 2017, which introduces a view state machine using Kotlin’s sealed classes to streamline network request handling. It explores the context of Android development challenges, the methodology of implementing a state machine, its practical applications, and the implications for creating adaptable, maintainable UI code. Amanda’s approach leverages Kotlin’s type-safe features to address the complexities of ever-changing design specifications, offering a reusable framework for Android developers.
Context of Android Network Challenges
At KotlinConf 2017, Amanda Hill addressed a common pain point in Android development: managing network calls amidst frequently changing UI requirements. As an Android developer at thoughtbot, Amanda drew on her experience at Venmo to highlight the frustrations caused by evolving design specs, which often disrupt UI logic tied to network operations. Traditional approaches to network calls, such as direct API integrations or ad-hoc state management, often lead to fragile code that struggles to adapt to UI changes, resulting in maintenance overhead and potential bugs.
Kotlin’s adoption in Android development, particularly after Google’s 2017 endorsement, provided an opportunity to leverage its type-safe features to address these challenges. Amanda’s presentation focused on creating a view state machine using Kotlin’s sealed classes, a feature that restricts class hierarchies to a defined set of states. This approach aimed to encapsulate UI states related to network calls, making Android applications more resilient to design changes and improving code clarity for developers working on dynamic, data-driven interfaces.
Methodology of the View State Machine
Amanda’s methodology centered on using Kotlin’s sealed classes to define a finite set of UI states for network calls, such as Loading, Success, and Error. Sealed classes ensure type safety by restricting possible states, allowing the compiler to enforce exhaustive handling of all scenarios. Amanda proposed a view model interface to standardize state interactions, with methods like getTitle and getPicture to format data for display. This interface serves as a contract, enabling different view models (e.g., for ice-cream cones) to implement specific formatting logic while adhering to a common structure.
In her live demo, Amanda illustrated building an Android app that uses the view state machine to manage network requests. The state machine processes API responses, mapping raw data (e.g., a calorie count of 120) into formatted outputs (e.g., “120 Calories”). By isolating formatting logic in the view model, independent of Android’s activity or fragment lifecycles, the approach ensures testability and reusability. Amanda emphasized flexibility, encouraging developers to customize the state machine for specific use cases, balancing genericity with adaptability to meet diverse application needs.
Practical Applications and Testability
The view state machine’s practical applications lie in its ability to simplify UI updates in response to network call outcomes. Amanda demonstrated how the state machine handles transitions between states, ensuring that UI components reflect the current state (e.g., displaying a loading spinner or an error message). By decoupling state logic from Android’s lifecycle methods, the approach reduces dependencies on activities or fragments, making the code more modular and easier to maintain. This modularity is particularly valuable in dynamic applications where UI requirements evolve frequently.
Testability is a key strength of Amanda’s approach. The view model’s independence from lifecycle components allows unit tests to verify formatting logic without involving Android’s runtime environment. For example, tests can assert that a view model correctly formats a calorie count, ensuring reliability across UI changes. Amanda’s focus on simplicity ensures that developers can implement the state machine without extensive refactoring, making it accessible for teams adopting Kotlin in Android projects.
Implications for Android Development
Amanda’s view state machine has significant implications for Android development, particularly in enhancing code maintainability and adaptability. By leveraging Kotlin’s sealed classes, developers can create robust, type-safe state management systems that reduce errors caused by unhandled states. The approach aligns with Kotlin’s emphasis on conciseness and safety, enabling developers to handle complex network interactions with minimal boilerplate. This is particularly valuable in fast-paced development environments where UI requirements change frequently, such as in fintech or e-commerce apps.
For the broader Android ecosystem, the state machine promotes best practices in separating concerns, encouraging developers to isolate business logic from UI rendering. Its testability supports agile development workflows, where rapid iterations and reliable testing are critical. Amanda’s encouragement to customize the state machine fosters a flexible approach, empowering developers to tailor solutions to specific project needs while leveraging Kotlin’s strengths. As Kotlin continues to dominate Android development, such innovations enhance its appeal for building scalable, user-friendly applications.
Conclusion
Amanda Hill’s presentation at KotlinConf 2017 introduced a powerful approach to managing network calls in Android using Kotlin’s sealed classes. The view state machine simplifies state management, enhances testability, and adapts to evolving UI requirements, addressing key challenges in Android development. By leveraging Kotlin’s type-safe features, Amanda’s methodology offers a reusable, maintainable framework that aligns with modern development practices. As Android developers increasingly adopt Kotlin, this approach underscores the language’s potential to streamline complex workflows, fostering robust and adaptable applications.