B4J Question 'New' B4XCollections helper methods

Chris2

Well-Known Member
Licensed User
Longtime User
It was a while ago now that they were released, but I'm a little confused by some of the new helper methods that were added to B4XCollections in B4J v10.2.

Can some please tell me:
1. What is the intended purpse of GetEmptyList & GetEmptyMap, particularly when the hint states 'Do not modify the list/map as it will affect other instances'.
What's the use of an empty list/map that you can't add anything to?

2. I also don't seem to have access to CopyOnWriteMap and CopyOnWriteList even though they're listed in the change log under v10.2. What do they do and how can I get access to them?

I have B4J 10.3 installed and the latest (I think) B4XCollections V1.15
1754470965276.png


Many thanks.
 
Solution
1. If you want to create a new list or map then use B4XCollections.CreateList or the built-in CreateMap keyword.
The purpose of GetEmptyList / GetEmptyMap is for cases where you need an empty collection with the expectation that it will remain empty.
For example, calling a method that expects a collection as a parameter which isn't needed in some cases. Instead of creating a new collection each time you can pass an empty collection. Same is true for methods that return a collection.

2. v1.15 is the latest version. I've updated the index.

Full example of using CopyOnWriteList:
Code: Copy
Sub AppStart (Args() As String)
    start
    StartMessageLoop
End Sub

Private Sub start
    Dim CopyList As CopyOnWriteList...
1. If you want to create a new list or map then use B4XCollections.CreateList or the built-in CreateMap keyword.
The purpose of GetEmptyList / GetEmptyMap is for cases where you need an empty collection with the expectation that it will remain empty.
For example, calling a method that expects a collection as a parameter which isn't needed in some cases. Instead of creating a new collection each time you can pass an empty collection. Same is true for methods that return a collection.

2. v1.15 is the latest version. I've updated the index.

Full example of using CopyOnWriteList:
Code: Copy
Sub AppStart (Args() As String)
    start
    StartMessageLoop
End Sub

Private Sub start
    Dim CopyList As CopyOnWriteList
    CopyList.Initialize(Array(1, 2, 3))
    DoSomethingWithList("a", CopyList.GetList)
    CopyList.Add(4)
    DoSomethingWithList("b", CopyList.GetList)
End Sub

Private Sub DoSomethingWithList (Name As String, l1 As List)
    For i = 1 To 3
        Log(Name & ": " & l1)
        Sleep(1000)
    Next
End Sub

Output:
a: (ArrayList) [1, 2, 3]
b: (ArrayList) [1, 2, 3, 4]
a: (ArrayList) [1, 2, 3]
b: (ArrayList) [1, 2, 3, 4]
a: (ArrayList) [1, 2, 3]
b: (ArrayList) [1, 2, 3, 4]

Whenever the CopyOnWriteList is modified, a new internal list is created. So if you passed the internal list to a method, the data for this method will not be unexpectedly modified.

Same example with a regular list:
Code: Copy
Private Sub start
    Dim l As List = B4XCollections.CreateList(Array(1, 2, 3))
    DoSomethingWithList("a", l)
    l.Add(4)
    DoSomethingWithList("b", l)
End Sub

Private Sub DoSomethingWithList (Name As String, l1 As List)
    Dim len As Int = l1.Size
    For i = 1 To 3
        Log($"Expecting to log ${len} items: "$ & Name & ": " & l1)
        Sleep(1000)
    Next
End Sub

Output:
Expecting to log 3 items: a: (ArrayList) [1, 2, 3]
Expecting to log 4 items: b: (ArrayList) [1, 2, 3, 4]
Expecting to log 3 items: a: (ArrayList) [1, 2, 3, 4]
Expecting to log 4 items: b: (ArrayList) [1, 2, 3, 4]
Expecting to log 3 items: a: (ArrayList) [1, 2, 3, 4]
Expecting to log 4 items: b: (ArrayList) [1, 2, 3, 4]
 
Upvote 0
Solution
Well explained. Thanks @Erel.

Follow up question:
I came across a java.util.ConcurrentModificationException error in an app a while ago. The situation was something like:
B4X: Copy
Sub Process_Globals
Private MyMap as Map
End Sub

Private Sub DoMapStuff
       
    For Each i As Int In MyMap.Keys
   
        Dim Something as object = MyMap.Get(i)
        Wait For (SomeResumableSub(Something)) Complete (Success as Boolean)

    Next

End Sub

Private Sub UpdateMyMap (newMap as Map)
   
    MyMap.Clear
    For Each i As Int In newMap.Keys
   
        MyMap.Put(i, newMap.Get(i))

    Next

End Sub
DoMapStuff was being called every minute in a timer, UpdateMyMap was called when a particular Websocket message was received by the app.
I reasoned that the error was caused by MyMap being updated while DoMapStuff was in the middle of running the loop (which could happen because of Wait For being equivalent to Return).

Would making MyMap here a CopyOnWriteMap instead of a regular map have prevented the ConcurrentModificationException error?
 
Upvote 0
Yes, though in this case you don't really need to modify the global map. You should instead assign the new map to the global map:
Code: Copy
Private Sub DoMapStuff
 Dim LocalMap As Map = MyMap
 'Work with LocalMap

Private Sub UpdateMyMap (newMap as Map)
 MyMap = newMap
End Sub

Everything is safe here as DoMapStuff will not be affect by the external assignment.
 
Upvote 0
I confess: I've never looked at those "new" things.
This thread is convincing me to do it.

For example, calling a method that expects a collection as a parameter which isn't needed in some cases. Instead of creating a new collection each time you can pass an empty collection. Same is true for methods that return a collection.
In those cases it would be better to pass Null, I think.
 
Upvote 0
Nulls can be annoying, especially as a return value.
I often return an empty map (CreateMap()) from a sub on error conditions, returning GetEmptyMap will save a few clock cycles and Bytes of memory each time as the only validation test is on the size of the Map and the approach is more 'B4X'. Nulls can be nasty.
 
Upvote 0
Remember that you can't add elements.
Modifying a returned collection is a bad practice unless it is documented or clear that you can modify the collection.

With an empty collection you can do:
Code: Copy
For Each Item As SomeItem In DB.GetItems
And you don't need to check anything.

Which pattern is better, depends on the specific use case.
 
Upvote 0
Modifying a returned collection is a bad practice unless it is documented or clear that you can modify the collection.

With an empty collection you can do:
Code: Copy
For Each Item As SomeItem In DB.GetItems
And you don't need to check anything.

Which pattern is better, depends on the specific use case.
I don't want to lengthen the thread; it might be annoying.
But I can't help but say that I disagree with either statement.
If you want to know why, ask me 😁
 
Upvote 0
I'm sure I remember being hit by returning Nulls in the past when they actually came back as "null" (string), so checking for Null failed.

Another tangent here, but....
Yes, though in this case you don't really need to modify the global map. You should instead assign the new map to the global map:
Code: Copy
Private Sub DoMapStuff
Dim LocalMap As Map = MyMap
 'Work with LocalMap

Private Sub UpdateMyMap (newMap as Map)
 MyMap = newMap
End Sub

Everything is safe here as DoMapStuff will not be affect by the external assignment.
This actually highlighted a fundamental misunderstanding on my part! (probably arrising from my complete lack of formal training and having spent my life largely just 'winging it' :))

I had thought that with the whole 'objects being passed as reference' thing, any time that we did something like:
B4X: Copy
Dim LocalMap As Map = MyMap
LocalMap was just a reference to MyMap. So anything that changed in MyMap would change in the reference LocalMap

But I think what you're implying @Erel is that LocalMap is actually an independent copy of MyMap, is that correct?
 
Upvote 0
I'm sure I remember being hit by returning Nulls in the past when they actually came back as "null" (string), so checking for Null failed.

check against Null works only if the variable is of type Object, or else it will be converted to string "null"
 
Upvote 0
LocalMap was just a reference to MyMap. So anything that changed in MyMap would change in the reference LocalMap

But I think what you're implying @Erel is that LocalMap is actually an independent copy of MyMap, is that correct?
Correct.

Code: Copy
Dim A As List = Array("a", "a")
Dim B As List = Array("b", "b")
Dim C As List = Array("c", "c")
A = B
Log(A) '["b", "b"]
B = C
Log(A) '['b", "b"]

I will generalize it: assigning Y to X doesn't affect any variable other than X.
 
Last edited:
Upvote 0
B4X: Copy
Dim A As List = Array("a", "a")
Dim B As List = Array("b", "b")
Dim C As List = Array("c", "c")
A = B
Log(A) '["b", "b"]
B = C
Log(A) '["a", "a"]
There's a typo here, right?
Shouldn't it be:
B4X: Copy
Dim A As List = Array("a", "a")
Dim B As List = Array("b", "b")
Dim C As List = Array("c", "c")
A = B
Log(A) '["b", "b"]
B = C
Log(A) '["b", "b"]
?
 
Upvote 0
Back
Top