Android Question Using .put on a map affects length of a null string?

CoderMaan

Member
Licensed User
Longtime User
I'm having trouble understanding the behavior exhibited in the attached code. If you really understand how to use the "map" type, perhaps you could help. My question is embedded in the second bullet below. Here's the situation:

* Although all three subs (Sub2, 3, and 4) are similar in structure, please notice that each also has its own set of similarly defined variables. So they are not using the variables from another sub.

* Sub3 is virtually identical in structure to Sub2, EXCEPT for the addition of a .put statement that sets a value in the (supposedly empty/clear) map to "" (or null?). To see this notice that line 33 is commented out while line 57 is not. The question is, why does the addition of this statement affect the log entries that follow this statement, as compared to the corresponding log entries from Sub2? That is, if the map was already "cleared," why aren't log lines 303 and 304 identical to log lines 203 and 204. After all, the .put statement is supposed to return a null (not the string "null"), when I "put" a new key-value pair in an empty map, isn't it?

* Sub4 is similar to Sub3, but the two .put statements have been swapped. The logs for Sub4 are not surprising, at least not in light of Subs2 and 3. I included it for comparison purposes only.

Here is the code and the resulting log. The subroutine CallOtherSubs gets called from Main; and that's really all that Main does (e.g., no variables are dim'd in Main). The code is also attached (although it seems that it includes only Main? --- in which case I hope you can copy the other module's code from what you see below, and just give it the name that you see in Main).

B4X:
'Code module --- Subs in this code module will be accessible from all modules.
Sub Process_Globals
    Dim ggMap2 As Map
    Dim ggStr_Old2 As String
    Dim ggStr_New2 As String

    Dim ggMap3 As Map
    Dim ggStr_Old3 As String
    Dim ggStr_New3 As String

    Dim ggMap4 As Map
    Dim ggStr_Old4 As String
    Dim ggStr_New4 As String
End Sub


Sub CallOtherSubs
    Sub2
    Sub3
    Sub4
End Sub


Sub Sub2
    ggMap2.Initialize
    ggMap2.Clear
   
    ggStr_Old2 = ""
    ggStr_New2 = "1234567"
   
    Log("201: ggStr_Old2 = " & ggStr_Old2)
    Log("202: Length of ggStr_Old2 = " & ggStr_Old2.Length & CRLF & CRLF)

'''    ggStr_Old2 = ggMap2.Put("Key1", "")   

    Log("203: ggStr_Old2 = " & ggStr_Old2)
    Log("204: Length of ggStr_Old2 = " & ggStr_Old2.Length & CRLF & CRLF)

    ggStr_Old2 = ggMap2.Put("Key1", ggStr_New2)
   
    Log("205: ggStr_Old2 = " & ggStr_Old2)
    Log("206: Length of ggStr_Old2 = " & ggStr_Old2.Length & CRLF & CRLF & CRLF & CRLF)

End Sub


Sub Sub3
    ggMap3.Initialize
    ggMap3.Clear
   
    ggStr_Old3 = ""
    ggStr_New3 = "1234567"
   
    Log("301: ggStr_Old3 = " & ggStr_Old3)
    Log("302: Length of ggStr_Old3 = " & ggStr_Old3.Length & CRLF & CRLF)

    ggStr_Old3 = ggMap3.Put("Key1", "")   

    Log("303: ggStr_Old3 = " & ggStr_Old3)
    Log("304: Length of ggStr_Old3 = " & ggStr_Old3.Length & CRLF & CRLF)

    ggStr_Old3 = ggMap3.Put("Key1", ggStr_New3)
   
    Log("305: ggStr_Old3 = " & ggStr_Old3)
    Log("306: Length of ggStr_Old3 = " & ggStr_Old3.Length & CRLF & CRLF & CRLF & CRLF)

End Sub


Sub Sub4
    ggMap4.Initialize
    ggMap4.Clear
   
    ggStr_Old4 = ""
    ggStr_New4 = "1234567"
   
    Log("401: ggStr_Old4 = " & ggStr_Old4)
    Log("402: Length of ggStr_Old4 = " & ggStr_Old4.Length & CRLF & CRLF)

    ggStr_Old4 = ggMap4.Put("Key1", ggStr_New4)

    Log("403: ggStr_Old4 = " & ggStr_Old4)
    Log("404: Length of ggStr_Old4 = " & ggStr_Old4.Length & CRLF & CRLF)

    ggStr_Old4 = ggMap4.Put("Key1", "")   
   
    Log("405: ggStr_Old4 = " & ggStr_Old4)
    Log("406: Length of ggStr_Old4 = " & ggStr_Old4.Length & CRLF & CRLF & CRLF & CRLF)

End Sub


Log results:

** Activity (main) Create, isFirst = true **

201: ggStr_Old2 =
202: Length of ggStr_Old2 = 0

203: ggStr_Old2 =
204: Length of ggStr_Old2 = 0

205: ggStr_Old2 = null
206: Length of ggStr_Old2 = 4



301: ggStr_Old3 =
302: Length of ggStr_Old3 = 0

303: ggStr_Old3 = null
304: Length of ggStr_Old3 = 4

305: ggStr_Old3 =
306: Length of ggStr_Old3 = 0



401: ggStr_Old4 =
402: Length of ggStr_Old4 = 0

403: ggStr_Old4 = null
404: Length of ggStr_Old4 = 4

405: ggStr_Old4 = 1234567
406: Length of ggStr_Old4 = 7
 

Attachments

  • m140810_UsingDotPutOnaMap.zip
    6.9 KB · Views: 145

LucaMs

Expert
Licensed User
Longtime User
The question is for headaches :confused: :)

I venture an answer.

The Value of a Map is of type Object. When you initialize the Map, and then "Clear" it, it does not "know" that must contain a string. Its previous value, which does not exist, is of type object (which probably occupies 4 bytes).

Erel can give a better answer.

The point is that, in this specific case, it is useless to use Clear immediately after initialization and, above all, you could check the existence of an element with that key, before using the Put.
 
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
--------- beginning of /dev/log/system
** Activity (main) Create, isFirst = true **
201: ggStr_Old2 =
202: Length of ggStr_Old2 = 0
203: ggStr_Old2 =
204: Length of ggStr_Old2 = 0
205: ggStr_Old2 = 1234567
206: Length of ggStr_Old2 = 7
301: ggStr_Old3 =
302: Length of ggStr_Old3 = 0
303: ggStr_Old3 =
304: Length of ggStr_Old3 = 0
305: ggStr_Old3 = 1234567
306: Length of ggStr_Old3 = 7
401: ggStr_Old4 =
402: Length of ggStr_Old4 = 0
403: ggStr_Old4 = 1234567
404: Length of ggStr_Old4 = 7
405: ggStr_Old4 =
406: Length of ggStr_Old4 = 0

B4X:
'Code module --- Subs in this code module will be accessible from all modules.
Sub Process_Globals
    Dim ggMap2 As Map
    Dim ggStr_Old2 As String
    Dim ggStr_New2 As String

    Dim ggMap3 As Map
    Dim ggStr_Old3 As String
    Dim ggStr_New3 As String

    Dim ggMap4 As Map
    Dim ggStr_Old4 As String
    Dim ggStr_New4 As String
End Sub


Sub CallOtherSubs
    Sub2
    Sub3
    Sub4
End Sub

Sub Sub2
    ggMap2.Initialize
    ggMap2.Clear
  
    ggStr_Old2 = ""
    ggStr_New2 = "1234567"
  
    Log("201: ggStr_Old2 = " & ggStr_Old2)
    Log("202: Length of ggStr_Old2 = " & ggStr_Old2.Length & CRLF & CRLF)

    ggStr_Old2 = "" ' The old line was splitted into two lines. Set the string to X
    ggMap2.Put("Key1", "")    ' Write the string to Key1. This is changed on all lines where you used .put!

    Log("203: ggStr_Old2 = " & ggStr_Old2)
    Log("204: Length of ggStr_Old2 = " & ggStr_Old2.Length & CRLF & CRLF)

    ggStr_Old2 = ggStr_New2
    ggMap2.Put("Key1", ggStr_New2)
  
    Log("205: ggStr_Old2 = " & ggStr_Old2)
    Log("206: Length of ggStr_Old2 = " & ggStr_Old2.Length & CRLF & CRLF & CRLF & CRLF)

End Sub


Sub Sub3
    ggMap3.Initialize
    ggMap3.Clear
  
    ggStr_Old3 = ""
    ggStr_New3 = "1234567"
  
    Log("301: ggStr_Old3 = " & ggStr_Old3)
    Log("302: Length of ggStr_Old3 = " & ggStr_Old3.Length & CRLF & CRLF)

    ggStr_Old3 = ""
    ggMap3.Put("Key1", "")  

    Log("303: ggStr_Old3 = " & ggStr_Old3)
    Log("304: Length of ggStr_Old3 = " & ggStr_Old3.Length & CRLF & CRLF)

    ggStr_Old3 = ggStr_New3
    ggMap3.Put("Key1", ggStr_New3)
  
    Log("305: ggStr_Old3 = " & ggStr_Old3)
    Log("306: Length of ggStr_Old3 = " & ggStr_Old3.Length & CRLF & CRLF & CRLF & CRLF)

End Sub


Sub Sub4
    ggMap4.Initialize
    ggMap4.Clear
  
    ggStr_Old4 = ""
    ggStr_New4 = "1234567"
  
    Log("401: ggStr_Old4 = " & ggStr_Old4)
    Log("402: Length of ggStr_Old4 = " & ggStr_Old4.Length & CRLF & CRLF)

    ggStr_Old4 = ggStr_New4
    ggMap4.Put("Key1", ggStr_New4)

    Log("403: ggStr_Old4 = " & ggStr_Old4)
    Log("404: Length of ggStr_Old4 = " & ggStr_Old4.Length & CRLF & CRLF)

    ggStr_Old4 = ""  
    ggMap4.Put("Key1", "")  
  
    Log("405: ggStr_Old4 = " & ggStr_Old4)
    Log("406: Length of ggStr_Old4 = " & ggStr_Old4.Length & CRLF & CRLF & CRLF & CRLF)

End Sub

Usually better is a contruct like

B4X:
    If ggMap2.ContainsKey("Key1") Then
        ggStr_Old2 = ggMap2.Get"Key1")
    Else
        ggStr_Old2 = ""
    End If
 
Last edited:
Upvote 0

CoderMaan

Member
Licensed User
Longtime User
Thanks for your thoughts and the idea for a different construct, but my concern is that the .put method is telling me that something that I expect to be a "null string" has length 4 rather than length 0.

Here is a much simpler program that illustrates the problem. Sub1 and Sub2 are "identical" except for one extra line in Sub2. The results are the same whether or not the .Clear statements are used.

The zip file is also attached. Can someone explain why the extra statement causes the outputs of the two subs to differ?

B4X:
#Region  Project Attributes
    #ApplicationLabel: Example1
    #VersionCode: 1
    #VersionName:
    'SupportedOrientations possible values: unspecified, landscape or portrait.
    #SupportedOrientations: unspecified
    #CanInstallToExternalStorage: False
#End Region

#Region  Activity Attributes
    #FullScreen: False
    #IncludeTitle: True
#End Region

Sub Process_Globals
End Sub

Sub Globals
End Sub

Sub Activity_Create(FirstTime As Boolean)
    Sub1
    Sub2
End Sub

Sub Activity_Resume
End Sub

Sub Activity_Pause (UserClosed As Boolean)
End Sub


Sub Sub1
    Dim StrX As String
    Dim MapX As Map
   
    StrX = ""
    MapX.Initialize
    MapX.Clear
   
    StrX = MapX.put("Key1", "AnyString")
    Log("StrX.Length = " & StrX.Length)        'This line results in the number 4.  Shouldn't it print a 0 instead of a 4?  
End Sub


Sub Sub2
    Dim StrY As String
    Dim MapY As Map
   
    StrY = ""
    MapY.Initialize
    MapY.Clear
   
    MapY.Put("Key1", "")                                   'This line does not appear in Sub1, but it does affect the output of the Log statement.  Why? 
    StrY = MapY.put("Key1", "AnyString")
    Log("StrY.Length = " & StrY.Length)        'This line results in the number 0. 
End Sub
 

Attachments

  • ForumQuestion140811_1_UsingMapsAndPut.zip
    6.1 KB · Views: 134
Upvote 0

LucaMs

Expert
Licensed User
Longtime User
I have never used the previous value (and I do not see the usefulness of it: if I want to know its value, I check it).

My opinion is that, BECAUSE THE VALUE IS AN OBJECT AND NOT A STRING, the previous value to... nothing returns an "anomalous" value, IF IT IS CONVERTED TO STRING.

But is all of this in practice an insurmountable problem ???

What is the purpose?
 
Upvote 0

keirS

Well-Known Member
Licensed User
Longtime User
You are casting a null value to a string. When you do this you will get a string value of "null". Add Log(StrX) to Sub1 and I bet you will see "null" as the value for StrX. Put returns a null if it is the first time you have mapped that key.
 
Upvote 0

CoderMaan

Member
Licensed User
Longtime User
I have never used the previous value (and I do not see the usefulness of it: if I want to know its value, I check it).

My opinion is that, BECAUSE THE VALUE IS AN OBJECT AND NOT A STRING, the previous value to... nothing returns an "anomalous" value, IF IT IS CONVERTED TO STRING.

But is all of this in practice an insurmountable problem ???

What is the purpose?


LucaMs,

Yes, it may indeed have to do with the difference between storing an object and storing a string, or at least between expecting an object instead of expecting nothing more than a string, so maybe I'll leave it at that. Nonetheless, it still seems very strange that it says the size is 4. It makes me think that it is actually returning "null" as a string rather than Null or "". But as you imply, it may not be a real issue since it is not often that you would use the return value (especially prior to putting something in the map).

I do think, however, that there are times when the returned value (i.e., the old value), might be wanted, so I am glad that Erel (et al?) included that option. It's one of those things you seldom need, but when you do need it it's nice to be able to say, oh yes, it can do that ; )

My real concern in starting this conversation has to do with using the .Get method on a map, after that map has been written to a file and then read back from the file. The issue appears when the values being stored (under multiple keys in a single map) are a combination of strings and (non-primitive) objects; in particular it's the objects that are giving me a headache. That is, although I can retrieve the objects, and actually see that their contents are as expected, the program crashes when I try to access a field within an object. But I'll save the details for another thread...

Thanks to you and DonManfred for the feedback!
 
Upvote 0

CoderMaan

Member
Licensed User
Longtime User
You are casting a null value to a string. When you do this you will get a string value of "null". Add Log(StrX) to Sub1 and I bet you will see "null" as the value for StrX. Put returns a null if it is the first time you have mapped that key.

Yes, I had noticed that it returns "null" in my original post. But "null" and Null have different meanings, don't they? The first is a proper string and the latter is simply a word describing the empty string "" (isn't it?). I'm still thinking that the method should return Null, not "null".

Thanks.
 
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
If you (or B4A) convert an "Object" to a String; and with
B4X:
StrX = MapX.put("Key1", "AnyString")
Log("StrX.Length = " & StrX.Length)
it needs a number to return for length... You first put "AnyString" into Key1. Key1 does not exists at this point so it is actually null.
The inserted object is a string. So b4a will try to return a string too for the "old" value . Remember; it is actually null, not "null"

A object of null converted to an "string" will return "null", a string ;)

It is usually better to do your own check if the key exists and if not create the key. If it exist then get the old key to put it in a variable and then rewrite the new value to the Key. See my first answer
 
Last edited:
Upvote 0

CoderMaan

Member
Licensed User
Longtime User
If you (or B4A) convert an "Object" to a String; and with
B4X:
StrX = MapX.put("Key1", "AnyString")
Log("StrX.Length = " & StrX.Length)
it needs a number to return for length... You first put "AnyString" into Key1. Key1 does not exists at this point so it is actually null.
The inserted object is a string. So b4a will try to return a string too for the "old" value . Remember; it is actually null, not "null"

A object of null converted to an "string" will return "null", a string ;)

It is usually better to do your own check if the key exists and if not create the key. If it exist then get the old key to put it in a variable and then rewrite the new value to the Key. See my first answer


Oh, that makes a lot more sense, especially your explanation that "A object of null converted to an "string" ...

I

THANK YOU!
 
Upvote 0
Top