{"id":26740645,"url":"https://github.com/brianburton/java-immutable-collections","last_synced_at":"2025-07-23T01:31:39.539Z","repository":{"id":31536425,"uuid":"35100991","full_name":"brianburton/java-immutable-collections","owner":"brianburton","description":"Efficient Immutable/Persistent Collections for Java","archived":false,"fork":false,"pushed_at":"2024-09-21T14:11:33.000Z","size":10766,"stargazers_count":43,"open_issues_count":2,"forks_count":4,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-14T16:52:34.488Z","etag":null,"topics":["avl-tree","collections","containers","hashmap","hashset","immutability","immutable","immutable-collections","java","jimmutable-collections","jvm","multiset","persistent-data-structure","streams","trie"],"latest_commit_sha":null,"homepage":"http://brianburton.github.io/java-immutable-collections/","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/brianburton.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2015-05-05T13:22:16.000Z","updated_at":"2025-01-02T11:31:17.000Z","dependencies_parsed_at":"2025-04-14T16:44:03.080Z","dependency_job_id":"d281d663-4f94-4dce-8fa9-2e297934b0ae","html_url":"https://github.com/brianburton/java-immutable-collections","commit_stats":null,"previous_names":[],"tags_count":33,"template":false,"template_full_name":null,"purl":"pkg:github/brianburton/java-immutable-collections","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianburton%2Fjava-immutable-collections","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianburton%2Fjava-immutable-collections/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianburton%2Fjava-immutable-collections/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianburton%2Fjava-immutable-collections/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/brianburton","download_url":"https://codeload.github.com/brianburton/java-immutable-collections/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianburton%2Fjava-immutable-collections/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266507262,"owners_count":23940047,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-22T02:00:09.085Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["avl-tree","collections","containers","hashmap","hashset","immutability","immutable","immutable-collections","java","jimmutable-collections","jvm","multiset","persistent-data-structure","streams","trie"],"created_at":"2025-03-28T05:20:32.896Z","updated_at":"2025-07-23T01:31:39.503Z","avatar_url":"https://github.com/brianburton.png","language":"Java","readme":"# Immutable Collections For Java\n\nOverview\n---\n\nThe immutable collections for Java library (JImmutable Collections) is a bundle of high performance immutable\ncollections intended to replace or supplement the standard `java.util` collections. Functional replacements are provided\nfor each of the most commonly used collections:\n\n| Java Class    | JImmutable Interface | Factory Methods                             |\n|---------------|----------------------|---------------------------------------------|\n| ArrayDeque    | IDeque               | `IDeques.of()`, `IDeques.allOf()`           |\n| ArrayList     | IList                | `ILists.of()`, `ILists.allOf()`             |\n| LinkedList    | IList                | `ILists.of()`, `ILists.allOf()`             |\n| HashMap       | IMap                 | `IMaps.hashed()`                            |\n| TreeMap       | IMap                 | `IMaps.sorted()` `IMaps.sorted(Comparator)` |\n| LinkedHashMap | IMap                 | `IMaps.ordered()`                           |\n| HashSet       | ISet                 | `ISets.hashed()`                            |\n| TreeSet       | ISet                 | `ISets.sorted()` `ISets.sorted(Comparator)` |\n| LinkedHashSet | ISet                 | `ISets.ordered()`                           |\n\nThere are also a number of highly useful collections with no equivalent in the standard Java library.\n\n| Description                                                  | JImmutable Interface | Factory Method                                                                                      |\n|--------------------------------------------------------------|----------------------|-----------------------------------------------------------------------------------------------------|\n| Map of lists of items related by a key.                      | IListMap             | `IListMaps.hashed()` `IListMaps.sorted()`  `IListMaps.sorted(Comparator)`  `IListMaps.ordered()`    |\n| Map of sets of items related by a key.                       | ISetMap              | `ISetMaps.hashed()` `ISetMaps.sorted()`  `ISetMaps.sorted(Comparator)`  `ISetMaps.ordered()`        |\n| Set that tracks number of times any given element was added. | IMultiset            | `IMultisets.hashed()`  `IMultisets.sorted()` `IMultisets.sorted(Comparator)` `IMultisets.ordered()` |\n| Sparse array of elements indexed by an Integer.              | IArray               | `IArrays.of()` `IArrays.allOf()`                                                                    |\n\nThe collections support these standard Java features:\n\n- All are fully `Serializable` to facilitate storing to disk or sending over a network (i.e. in an Apache Spark\n  application)\n- All allow creation of Streams (parallel or sequential) over their contents. Maps support streams over their keys and\n  values separately or both at the same time.\n- All are `Iterable`. Maps support iterators over their keys and values separately or both at the same time.\n- Where appropriate they provide views that can be passed to code that requires a standard collection interface.  (\n  e.g. `IMap` has a `getMap()` method to create a view that implements `Map`)\n- Most provide collectors for use with Streams to create new collections in `collect()` method call. (see `ICollectors`)\n- Most provide efficient builder classes for constructing new collections in imperative fashion.\n\nImmutability/Persistence\n---\n\nThe collections are all [immutable](https://en.wikipedia.org/wiki/Immutable_object)\nand [persistent](https://en.wikipedia.org/wiki/Persistent_data_structure). Any method that adds or removes an item in a\ncollection actually creates a new collection. The old and new collections share almost all of their structure in common\nwith only the minimum number of new objects needed to implement the change in the new version. The process of creating a\nnew collection from an old one is extremely fast.\n\nSince the collections are immutable they can be safely shared throughout a program without the need for synchronization\nor defensive copying. In fact structure sharing is a theme throughout the library. For example, you never actually\ncreate an empty IList instance. The `ILists.of()` factory method always returns a single, shared, empty\nlist instance. The other factory methods work the same way.\n\nThe collections are still highly dynamic and fully support addition, deletion, and replacement of elements via efficient\ncreation of modified versions of themselves. This sets them apart from the static immutable collections in\nthe [Guava](https://github.com/google/guava) collections library.\n\n**Note:** Keep in mind that while the collections themselves are immutable the values you choose to store in them might\nnot be. Always [use immutable objects as keys](https://github.com/brianburton/java-immutable-collections/wiki/Hash-Keys)\nand if you use mutable objects as values be aware that your code could mutate them between when you add them to a\ncollection and when you retrieve them later.\n\nDependencies\n---\n\nThe library is designed to have no dependencies on other libraries, but it should interact well with others. Standard\njava interfaces are used where appropriate.\n\n# Examples\n\nFactory Methods\n---\n\nStatic factory methods make it easy to create new collections. Here are various ways to\ncreate the same basic list. Similar factory methods exist for the other collections.\n\n````\n        List\u003cString\u003e sourceList = Arrays.asList(\"these\", \"are\", \"some\", \"strings\");\n        IList\u003cString\u003e empty = ILists.of();\n        IList\u003cString\u003e aList = empty\n            .insert(\"these\")\n            .insert(\"are\")\n            .insert(\"some\")\n            .insert(\"strings\");\n        IList\u003cString\u003e literal = ILists.of(\"these\", \"are\", \"some\", \"strings\");\n        IList\u003cString\u003e fromJavaList = ILists.allOf(sourceList);\n        IList\u003cString\u003e fromBuilder = ILists.\u003cString\u003ebuilder()\n            .add(\"these\")\n            .add(\"are\")\n            .addAll(\"some\", \"strings\")\n            .build();\n        assertThat(aList).isEqualTo(literal);\n        assertThat(fromJavaList).isEqualTo(literal);\n        assertThat(fromBuilder).isEqualTo(literal);\n````\n\nIterators\n---\n\nThe collections are all `Iterable` so they can be used in standard `for` loops.\n\n````\n        int eWordCount = 0;\n        for (String word : fromBuilder) {\n            if (word.contains(\"e\")) {\n                eWordCount += 1;\n            }\n        }\n        assertThat(eWordCount).isEqualTo(3);\n````\n\nStreams and Collectors\n---\n\nStreams can be used along with the provided collector methods to easily create new collections. For example this\nfunction creates a list of the integer factors (other than 1) of an integer.\n\n````\n    private IList\u003cInteger\u003e factorsOf(int number)\n    {\n        final int maxPossibleFactor = (int)Math.sqrt(number);\n        return IntStream.range(2, maxPossibleFactor + 1).boxed()\n            .filter(candidate -\u003e number % candidate == 0)\n            .collect(ICollectors.toList());\n    }\n````\n\nThis code creates a lookup table of all the factors of the first 1000 integers into an `IMap`.\n\n````\n        IMap\u003cInteger, IList\u003cInteger\u003e\u003e factorMap =\n            IntStream.range(2, 100).boxed()\n                .map(i -\u003e IMapEntry.of(i, factorsOf(i)))\n                .collect(ICollectors.toMap());\n````\n\nThis code shows how the lookup table could be used to get a list of the prime numbers in the map:\n\n````\n        IList\u003cInteger\u003e primes = factorMap.stream()\n            .filter(e -\u003e e.getValue().isEmpty())\n            .map(e -\u003e e.getKey())\n            .collect(ICollectors.toList());\n        assertThat(primes)\n            .isEqualTo(ILists.of(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, \n                                 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97));\n````\n\nIteration\n----\n\nIn addition to fully supporting Streams and Iterators the collections also provide their own iteration methods that\noperate in a more functional style. For example the `forEach()` method takes a lambda and invokes it for each element of\nthe collection:\n\n````\n        ISet\u003cInteger\u003e numbers = IntStream.range(1, 20).boxed().collect(ICollectors.toSet());\n        numbers.forEach(i -\u003e System.out.println(i));\n````\n\nMethods are also provided to iterate over an entire collection to produce a new one by applying a predicate or\ntransformation. All of these operations can be done with Stream/map/filter/collect as well of course, but these\nlight-weight versions are faster when a single thread is sufficient for the job.\n\n````\n        ISet\u003cInteger\u003e numbers = IntStream.range(1, 20).boxed().collect(ICollectors.toSet());\n        ISet\u003cInteger\u003e changed = numbers.reject(i -\u003e i % 3 != 2);\n        assertThat(changed).isEqualTo(ISets.hashed(2, 5, 8, 11, 14, 17));\n        \n        changed = numbers.select(i -\u003e i % 3 == 1);\n        assertThat(changed).isEqualTo(ISets.hashed(1, 4, 7, 10, 13, 16, 19));\n\n        IDeque\u003cInteger\u003e transformed = changed.stream().collect(ICollectors.toDeque());\n        assertThat(transformed).isEqualTo(IDeques.of(1, 4, 7, 10, 13, 16, 19));\n````\n\nSlicing and Dicing Lists\n----\n\nLists allow elements (and even whole lists) to be added or deleted at any index. They also support grabbing sub-lists\nfrom anywhere within themselves. This example shows how various sub-lists can be extracted from a list and then inserted\ninto the middle of another.\n\n````\n        IList\u003cInteger\u003e numbers = IntStream.range(1, 21).boxed().collect(ICollectors.toList());\n        IList\u003cInteger\u003e changed = numbers.prefix(6);\n        assertThat(changed).isEqualTo(ILists.of(1, 2, 3, 4, 5, 6));\n        \n        changed = numbers.suffix(16);\n        assertThat(changed).isEqualTo(ILists.of(17, 18, 19, 20));\n        \n        changed = changed.insertAll(2, numbers.prefix(3).insertAllLast(numbers.middle(9, 12)));\n        assertThat(changed).isEqualTo(ILists.of(17, 18, 1, 2, 3, 10, 11, 12, 19, 20));\n````\n\nInserting entire lists will always reuse structure from both lists as much as possible. Likewise, removing sub-lists\nfrom within a large list will produce a new list that shares most of its structure with the original list. This means\nbuilding a large list by successively appending other lists to it can be faster than inserting the individual values\ninto a builder.\n\nMaps of Sets and Lists\n---\n\nThe `ISetMap` makes it easy to index values or accumulate values related to a key. The `IListMap`\nworks similarly but accumulates lists of values by key so it can preserve the order in which they are added and track\nduplicates.\n\nThe example below shows a trivial example of indexing a sequence of sentences by the words they contain.\n\n````\n        IList\u003cString\u003e source = ILists.of(\"Now is our time.\",\n                                         \"Our moment has arrived.\",\n                                         \"Shall we embrace immutable collections?\",\n                                         \"Or tread in dangerous synchronized waters forever?\");\n        ISetMap\u003cString, String\u003e index = source\n            .stream()\n            .flatMap(line -\u003e Stream.of(line\n                                           .toLowerCase()\n                                           .replace(\".\", \"\")\n                                           .replace(\"?\", \"\")\n                                           .split(\" \"))\n                .map(word -\u003e MapEntry.entry(word, line)))\n            .collect(ICollectors.toSetMap());\n        assertThat(index.get(\"our\")).isEqualTo(ISets.hashed(\"Now is our time.\", \"Our moment has arrived.\"));\n````\n\nThese classes offer a variety of methods for adding elements individually or in groups as well as iterating over all the\nvalues for a given key as well as over the entire collection.\n\n````\n        IListMap\u003cString, Integer\u003e index = IListMaps.\u003cString, Integer\u003esorted()\n            .insert(\"c\", 2)\n            .insert(\"a\", 1)\n            .insert(\"d\", 640)\n            .insert(\"b\", 3)\n            .insert(\"d\", 512)\n            .insertAll(\"a\", ILists.of(-4, 40, 18)); // could be any Iterable not just list\n        // keys are sorted in the map\n        assertThat(ILists.allOf(index.keys())).isEqualTo(ILists.of(\"a\", \"b\", \"c\", \"d\"));\n        // values appear in the list in order they are added\n        assertThat(index.getList(\"a\")).isEqualTo(ILists.of(1, -4, 40, 18));\n        assertThat(index.getList(\"d\")).isEqualTo(ILists.of(640, 512));\n        assertThat(index.getList(\"x\")).isEqualTo(ILists.of());\n````\n\nConcurrentModificationException\n---\n\nImmutable collections never throw these. The example below is contrived, but it illustrates the problem of updating a\nmutable collection while iterating over its contents. Since immutable collections are persistent you are always\nmodifying a different version of the collection, and the iterator doesn't become confused.\n\n````\n        assertThatThrownBy(() -\u003e {\n            Map\u003cInteger, Integer\u003e ints = IntStream.range(1, 11).boxed().collect(Collectors.toMap(i -\u003e i, i -\u003e i));\n            for (Map.Entry\u003cInteger, Integer\u003e entry : ints.entrySet()) {\n                ints.put(2 * entry.getKey(), 2 * entry.getValue());\n            }\n        }).isInstanceOf(ConcurrentModificationException.class);\n\n        IMap\u003cInteger, Integer\u003e myMap = IntStream.range(1, 11).boxed().map(i -\u003e IMapEntry.of(i, i)).collect(ICollectors.toMap());\n        for (IMapEntry\u003cInteger, Integer\u003e entry : myMap) {\n            myMap = myMap.assign(2 * entry.getKey(), 2 * entry.getValue());\n        }\n        assertThat(ILists.allOf(myMap.keys())).isEqualTo(ILists.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 20));\n        assertThat(ILists.allOf(myMap.values())).isEqualTo(ILists.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 20));\n````\n\nThe static collector factory methods create collectors that add elements from the stream to an empty collection.\nInstances of the collection classes also provide an instance method to create a collector based on that instance (rather\nthan an empty instance). This can be used with a Stream to add entries to the collection. The example below adds entries\nto the map. Some keys update existing entries while others are new keys to be added to the collection.\n\n````\n        myMap = IntStream.range(1, 11).boxed().map(i -\u003e IMapEntry.of(i, i)).collect(ICollectors.toMap());\n        IMap\u003cInteger, Integer\u003e changed = myMap.stream()\n            .map(entry -\u003e IMapEntry.of(5 + entry.getKey(), 10 + entry.getValue()))\n            .collect(myMap.mapCollector());\n        // 6-10 were updated, 11-15 were added\n        assertThat(ILists.allOf(changed.keys())).isEqualTo(ILists.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15));\n        assertThat(ILists.allOf(changed.values())).isEqualTo(ILists.of(1, 2, 3, 4, 5, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20));\n        // original map is unchanged \n        assertThat(ILists.allOf(myMap.keys())).isEqualTo(ILists.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));\n````\n\nMaybe - Handling Optional Values\n---\n\nThe `Maybe` class, returned by `find()` methods in all collections, is similar to `Optional` but has several\nadvantages:\n\n- `Maybe` is serializable so you can use it as a field in `Serializable` objects if desired.\n- `Maybe` offers more utility methods than `Optional`.\n- `Maybe` supports null values (sometimes you just want null...)\n\n`Maybe` and `NotNull` can interoperate with one another. Calling `notNull()` on a `Maybe` returns a `NotNull` reflecting\nthe presence and nullity of a value in the `Maybe`. Similarly, calling `maybe()` on a `NotNull` returns a `Maybe`\nreflecting the presence of a value in the `NotNull`. Note that this is not always a round trip.  \nIf `m` is a `Maybe` containing a null value calling `m.notNull().maybe()` will return an empty `Maybe`.\n\n\nNotNull - Avoiding null\n---\n\nThe use of null has been controversial. The JImmutable collections are mostly indifferent to null. Nulls are not\npermitted as keys to maps or values in sets. However, they can be stored as values in lists and maps. Maybes returned\nby the `find()` method permit nulls as well.\n\nThere are many disadvantages to nulls though. In particular, they cannot be used in call chains. The `NotNull` class\nprovides an alternative to null that can be easily chained in a functional style.  `NotNull` is similar to `Maybe` but\ndoes not allow nulls and provides more monadic functionality. It is meant to be used in sequences of method calls.\n\nThere are two possible states for a `NotNull` object:\n\n- `Empty` indicates no value is stored within the `NotNull`. The `unsafeGet` methods cannot be called on these objects\n  but all others can be called safely.\n- `Full` indicates a non-null value is stored within the `NotNull`. All methods can be called on these objects.\n\nNotNull should be used when a value might not exist or might be null. For example as the result of a database query for\na single object. Once you have a NotNull value you can call the `map` method with a lambda that transforms the value\n(if one exists). The transformed value can be of the same or another type. If your lambda returns another NotNull you\nshould use the `flatMap` method to \"unwrap\" the resulting value.\n\n````\n// simplified class for illustration - normally you'd use getters\nclass Person\n{\n  final String emailAddress;\n  final NotNull\u003cPhoneNumber\u003e homePhone;\n  final NotNull\u003cPhoneNumber\u003e mobilePhone;\n}\n\nNotNull\u003cPerson\u003e customer = customers.lookupCustomerByName(\"Jones\", \"Patrick\");\nNotNull\u003cString\u003e email = customer.map(c -\u003e c.emailAddress);\n\n// get the area code from the home phone number if we have one, \"\" otherwise\nString areaCode = customer.flatMap(c -\u003e c.homePhone)\n          .map(phone -\u003e phone.getAreaCode())\n          .get(\"\");\n\n// another way to do the same - using match\nareaCode = customer.flatMap(c -\u003e c.homePhone)\n                   .match(\"\", phone -\u003e phone.getAreaCode());\n````\n\n# Resources\n\nWiki Pages\n---\n\n[JImmutables Factory Methods](https://github.com/brianburton/java-immutable-collections/wiki/JImmutables-Factory-Methods)  \n[Collections Overview](https://github.com/brianburton/java-immutable-collections/wiki/Collections-Overview)  \n[List Tutorial](https://github.com/brianburton/java-immutable-collections/wiki/List-Tutorial)  \n[Map Tutorial](https://github.com/brianburton/java-immutable-collections/wiki/Map-Tutorial)  \n[Array Tutorial](https://github.com/brianburton/java-immutable-collections/wiki/Array-Tutorial)  \n[Streams and Lambdas](https://github.com/brianburton/java-immutable-collections/wiki/Streams-and-Lambdas)  \n[Comparative Performance](https://github.com/brianburton/java-immutable-collections/wiki/Comparative-Performance)  \n[Hash Keys](https://github.com/brianburton/java-immutable-collections/wiki/Hash-Keys)  \n[Project Javadoc](http://brianburton.github.io/java-immutable-collections/apidocs/index.html)  \n[Jackson Module for JSON Support](https://github.com/brianburton/javimmutable-jackson)\n\n\nProject Status\n---\nAll production releases undergo stress testing and pass all junit tests. Of course, you should evaluate the collections\nfor yourself and perform your own tests before deploying the collections to production systems.\n\nAll releases are uploaded to the [releases section](https://github.com/brianburton/java-immutable-collections/releases)\non GitHub and are also available via Maven\nin [Maven Central](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.javimmutable%22%20AND%20a%3A%22javimmutable-collections%22)\n. You can add JImmutable Collections to your Maven project by adding a dependency like this to your pom.xml. The maven\nreleases include source jars for easy reference in your IDE.\n\n````\n    \u003cdependency\u003e\n        \u003cgroupId\u003eorg.javimmutable\u003c/groupId\u003e\n        \u003cartifactId\u003ecollections\u003c/artifactId\u003e\n        \u003cversion\u003e4.0.1\u003c/version\u003e\n    \u003c/dependency\u003e\n````\n\nProject Members:\n---\n\n- [Brian Burton](https://github.com/brianburton) (admin)\n- [Angela Burton](https://github.com/anjbur)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrianburton%2Fjava-immutable-collections","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrianburton%2Fjava-immutable-collections","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrianburton%2Fjava-immutable-collections/lists"}