B4J Question 'New' B4XCollections helper methods

Chris2

Active 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:
B4X:
Sub AppStart (Args() As String)
    start
    StartMessageLoop
End Sub

Private Sub start
    Dim CopyList As CopyOnWriteList...

Erel

B4X founder
Staff member
Licensed User
Longtime User
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:
B4X:
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:
B4X:
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

Chris2

Active Member
Licensed User
Longtime User
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:
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

Erel

B4X founder
Staff member
Licensed User
Longtime User
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:
B4X:
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

LucaMs

Expert
Licensed User
Longtime User
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

stevel05

Expert
Licensed User
Longtime User
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

Erel

B4X founder
Staff member
Licensed User
Longtime User
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:
B4X:
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

LucaMs

Expert
Licensed User
Longtime User
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:
B4X:
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

Chris2

Active Member
Licensed User
Longtime User
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:
B4X:
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:
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

Alessandro71

Well-Known Member
Licensed User
Longtime User
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

Erel

B4X founder
Staff member
Licensed User
Longtime User
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.

B4X:
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

Chris2

Active Member
Licensed User
Longtime User
B4X:
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:
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
Top