• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  */
17 
18 package java.util;
19 
20 import java.io.IOException;
21 import java.io.ObjectInputStream;
22 import java.io.ObjectOutputStream;
23 import java.io.ObjectStreamField;
24 import java.io.Serializable;
25 import libcore.icu.ICU;
26 
27 /**
28  * {@code Locale} represents a language/country/variant combination. Locales are used to
29  * alter the presentation of information such as numbers or dates to suit the conventions
30  * in the region they describe.
31  *
32  * <p>The language codes are two-letter lowercase ISO language codes (such as "en") as defined by
33  * <a href="http://en.wikipedia.org/wiki/ISO_639-1">ISO 639-1</a>.
34  * The country codes are two-letter uppercase ISO country codes (such as "US") as defined by
35  * <a href="http://en.wikipedia.org/wiki/ISO_3166-1_alpha-3">ISO 3166-1</a>.
36  * The variant codes are unspecified.
37  *
38  * <p>Note that Java uses several deprecated two-letter codes. The Hebrew ("he") language
39  * code is rewritten as "iw", Indonesian ("id") as "in", and Yiddish ("yi") as "ji". This
40  * rewriting happens even if you construct your own {@code Locale} object, not just for
41  * instances returned by the various lookup methods.
42  *
43  * <a name="available_locales"><h3>Available locales</h3></a>
44  * <p>This class' constructors do no error checking. You can create a {@code Locale} for languages
45  * and countries that don't exist, and you can create instances for combinations that don't
46  * exist (such as "de_US" for "German as spoken in the US").
47  *
48  * <p>Note that locale data is not necessarily available for any of the locales pre-defined as
49  * constants in this class except for en_US, which is the only locale Java guarantees is always
50  * available.
51  *
52  * <p>It is also a mistake to assume that all devices have the same locales available.
53  * A device sold in the US will almost certainly support en_US and es_US, but not necessarily
54  * any locales with the same language but different countries (such as en_GB or es_ES),
55  * nor any locales for other languages (such as de_DE). The opposite may well be true for a device
56  * sold in Europe.
57  *
58  * <p>You can use {@link Locale#getDefault} to get an appropriate locale for the <i>user</i> of the
59  * device you're running on, or {@link Locale#getAvailableLocales} to get a list of all the locales
60  * available on the device you're running on.
61  *
62  * <a name="locale_data"><h3>Locale data</h3></a>
63  * <p>Note that locale data comes solely from ICU. User-supplied locale service providers (using
64  * the {@code java.text.spi} or {@code java.util.spi} mechanisms) are not supported.
65  *
66  * <p>Here are the versions of ICU (and the corresponding CLDR and Unicode versions) used in
67  * various Android releases:
68  * <table BORDER="1" WIDTH="100%" CELLPADDING="3" CELLSPACING="0" SUMMARY="">
69  * <tr><td>Cupcake/Donut/Eclair</td> <td>ICU 3.8</td> <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-5">CLDR 1.5</a></td>   <td><a href="http://www.unicode.org/versions/Unicode5.0.0/">Unicode 5.0</a></td></tr>
70  * <tr><td>Froyo</td>                <td>ICU 4.2</td> <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-7">CLDR 1.7</a></td>   <td><a href="http://www.unicode.org/versions/Unicode5.1.0/">Unicode 5.1</a></td></tr>
71  * <tr><td>Gingerbread/Honeycomb</td><td>ICU 4.4</td> <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-8">CLDR 1.8</a></td>   <td><a href="http://www.unicode.org/versions/Unicode5.2.0/">Unicode 5.2</a></td></tr>
72  * <tr><td>Ice Cream Sandwich</td>   <td>ICU 4.6</td> <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-9">CLDR 1.9</a></td>   <td><a href="http://www.unicode.org/versions/Unicode6.0.0/">Unicode 6.0</a></td></tr>
73  * <tr><td>Jelly Bean</td>           <td>ICU 4.8</td> <td><a href="http://cldr.unicode.org/index/downloads/cldr-2-0">CLDR 2.0</a></td>   <td><a href="http://www.unicode.org/versions/Unicode6.0.0/">Unicode 6.0</a></td></tr>
74  * <tr><td>Jelly Bean MR2</td>       <td>ICU 50</td>  <td><a href="http://cldr.unicode.org/index/downloads/cldr-21-1">CLDR 22.1</a></td> <td><a href="http://www.unicode.org/versions/Unicode6.2.0/">Unicode 6.2</a></td></tr>
75  * </table>
76  *
77  * <a name="default_locale"><h3>Be wary of the default locale</h3></a>
78  * <p>Note that there are many convenience methods that automatically use the default locale, but
79  * using them may lead to subtle bugs.
80  *
81  * <p>The default locale is appropriate for tasks that involve presenting data to the user. In
82  * this case, you want to use the user's date/time formats, number
83  * formats, rules for conversion to lowercase, and so on. In this case, it's safe to use the
84  * convenience methods.
85  *
86  * <p>The default locale is <i>not</i> appropriate for machine-readable output. The best choice
87  * there is usually {@code Locale.US}&nbsp;&ndash; this locale is guaranteed to be available on all
88  * devices, and the fact that it has no surprising special cases and is frequently used (especially
89  * for computer-computer communication) means that it tends to be the most efficient choice too.
90  *
91  * <p>A common mistake is to implicitly use the default locale when producing output meant to be
92  * machine-readable. This tends to work on the developer's test devices (especially because so many
93  * developers use en_US), but fails when run on a device whose user is in a more complex locale.
94  *
95  * <p>For example, if you're formatting integers some locales will use non-ASCII decimal
96  * digits. As another example, if you're formatting floating-point numbers some locales will use
97  * {@code ','} as the decimal point and {@code '.'} for digit grouping. That's correct for
98  * human-readable output, but likely to cause problems if presented to another
99  * computer ({@link Double#parseDouble} can't parse such a number, for example).
100  * You should also be wary of the {@link String#toLowerCase} and
101  * {@link String#toUpperCase} overloads that don't take a {@code Locale}: in Turkey, for example,
102  * the characters {@code 'i'} and {@code 'I'} won't be converted to {@code 'I'} and {@code 'i'}.
103  * This is the correct behavior for Turkish text (such as user input), but inappropriate for, say,
104  * HTTP headers.
105  */
106 public final class Locale implements Cloneable, Serializable {
107 
108     private static final long serialVersionUID = 9149081749638150636L;
109 
110     /**
111      * Locale constant for en_CA.
112      */
113     public static final Locale CANADA = new Locale(true, "en", "CA");
114 
115     /**
116      * Locale constant for fr_CA.
117      */
118     public static final Locale CANADA_FRENCH = new Locale(true, "fr", "CA");
119 
120     /**
121      * Locale constant for zh_CN.
122      */
123     public static final Locale CHINA = new Locale(true, "zh", "CN");
124 
125     /**
126      * Locale constant for zh.
127      */
128     public static final Locale CHINESE = new Locale(true, "zh", "");
129 
130     /**
131      * Locale constant for en.
132      */
133     public static final Locale ENGLISH = new Locale(true, "en", "");
134 
135     /**
136      * Locale constant for fr_FR.
137      */
138     public static final Locale FRANCE = new Locale(true, "fr", "FR");
139 
140     /**
141      * Locale constant for fr.
142      */
143     public static final Locale FRENCH = new Locale(true, "fr", "");
144 
145     /**
146      * Locale constant for de.
147      */
148     public static final Locale GERMAN = new Locale(true, "de", "");
149 
150     /**
151      * Locale constant for de_DE.
152      */
153     public static final Locale GERMANY = new Locale(true, "de", "DE");
154 
155     /**
156      * Locale constant for it.
157      */
158     public static final Locale ITALIAN = new Locale(true, "it", "");
159 
160     /**
161      * Locale constant for it_IT.
162      */
163     public static final Locale ITALY = new Locale(true, "it", "IT");
164 
165     /**
166      * Locale constant for ja_JP.
167      */
168     public static final Locale JAPAN = new Locale(true, "ja", "JP");
169 
170     /**
171      * Locale constant for ja.
172      */
173     public static final Locale JAPANESE = new Locale(true, "ja", "");
174 
175     /**
176      * Locale constant for ko_KR.
177      */
178     public static final Locale KOREA = new Locale(true, "ko", "KR");
179 
180     /**
181      * Locale constant for ko.
182      */
183     public static final Locale KOREAN = new Locale(true, "ko", "");
184 
185     /**
186      * Locale constant for zh_CN.
187      */
188     public static final Locale PRC = new Locale(true, "zh", "CN");
189 
190     /**
191      * Locale constant for the root locale. The root locale has an empty language,
192      * country, and variant.
193      *
194      * @since 1.6
195      */
196     public static final Locale ROOT = new Locale(true, "", "");
197 
198     /**
199      * Locale constant for zh_CN.
200      */
201     public static final Locale SIMPLIFIED_CHINESE = new Locale(true, "zh", "CN");
202 
203     /**
204      * Locale constant for zh_TW.
205      */
206     public static final Locale TAIWAN = new Locale(true, "zh", "TW");
207 
208     /**
209      * Locale constant for zh_TW.
210      */
211     public static final Locale TRADITIONAL_CHINESE = new Locale(true, "zh", "TW");
212 
213     /**
214      * Locale constant for en_GB.
215      */
216     public static final Locale UK = new Locale(true, "en", "GB");
217 
218     /**
219      * Locale constant for en_US.
220      */
221     public static final Locale US = new Locale(true, "en", "US");
222 
223     /**
224      * The current default locale. It is temporarily assigned to US because we
225      * need a default locale to lookup the real default locale.
226      */
227     private static Locale defaultLocale = US;
228 
229     static {
230         String language = System.getProperty("user.language", "en");
231         String region = System.getProperty("user.region", "US");
232         String variant = System.getProperty("user.variant", "");
233         defaultLocale = new Locale(language, region, variant);
234     }
235 
236     private transient String countryCode;
237     private transient String languageCode;
238     private transient String variantCode;
239     private transient String cachedToStringResult;
240 
241     /**
242      * There's a circular dependency between toLowerCase/toUpperCase and
243      * Locale.US. Work around this by avoiding these methods when constructing
244      * the built-in locales.
245      *
246      * @param unused required for this constructor to have a unique signature
247      */
Locale(boolean unused, String lowerCaseLanguageCode, String upperCaseCountryCode)248     private Locale(boolean unused, String lowerCaseLanguageCode, String upperCaseCountryCode) {
249         this.languageCode = lowerCaseLanguageCode;
250         this.countryCode = upperCaseCountryCode;
251         this.variantCode = "";
252     }
253 
254     /**
255      * Constructs a new {@code Locale} using the specified language.
256      */
Locale(String language)257     public Locale(String language) {
258         this(language, "", "");
259     }
260 
261     /**
262      * Constructs a new {@code Locale} using the specified language and country codes.
263      */
Locale(String language, String country)264     public Locale(String language, String country) {
265         this(language, country, "");
266     }
267 
268     /**
269      * Constructs a new {@code Locale} using the specified language, country,
270      * and variant codes.
271      */
Locale(String language, String country, String variant)272     public Locale(String language, String country, String variant) {
273         if (language == null || country == null || variant == null) {
274             throw new NullPointerException("language=" + language +
275                                            ",country=" + country +
276                                            ",variant=" + variant);
277         }
278         if (language.isEmpty() && country.isEmpty()) {
279             languageCode = "";
280             countryCode = "";
281             variantCode = variant;
282             return;
283         }
284 
285         languageCode = language.toLowerCase(Locale.US);
286         // Map new language codes to the obsolete language
287         // codes so the correct resource bundles will be used.
288         if (languageCode.equals("he")) {
289             languageCode = "iw";
290         } else if (languageCode.equals("id")) {
291             languageCode = "in";
292         } else if (languageCode.equals("yi")) {
293             languageCode = "ji";
294         }
295 
296         countryCode = country.toUpperCase(Locale.US);
297 
298         // Work around for be compatible with RI
299         variantCode = variant;
300     }
301 
clone()302     @Override public Object clone() {
303         try {
304             return super.clone();
305         } catch (CloneNotSupportedException e) {
306             throw new AssertionError(e);
307         }
308     }
309 
310     /**
311      * Returns true if {@code object} is a locale with the same language,
312      * country and variant.
313      */
equals(Object object)314     @Override public boolean equals(Object object) {
315         if (object == this) {
316             return true;
317         }
318         if (object instanceof Locale) {
319             Locale o = (Locale) object;
320             return languageCode.equals(o.languageCode)
321                     && countryCode.equals(o.countryCode)
322                     && variantCode.equals(o.variantCode);
323         }
324         return false;
325     }
326 
327     /**
328      * Returns the system's installed locales. This array always includes {@code
329      * Locale.US}, and usually several others. Most locale-sensitive classes
330      * offer their own {@code getAvailableLocales} method, which should be
331      * preferred over this general purpose method.
332      *
333      * @see java.text.BreakIterator#getAvailableLocales()
334      * @see java.text.Collator#getAvailableLocales()
335      * @see java.text.DateFormat#getAvailableLocales()
336      * @see java.text.DateFormatSymbols#getAvailableLocales()
337      * @see java.text.DecimalFormatSymbols#getAvailableLocales()
338      * @see java.text.NumberFormat#getAvailableLocales()
339      * @see java.util.Calendar#getAvailableLocales()
340      */
getAvailableLocales()341     public static Locale[] getAvailableLocales() {
342         return ICU.getAvailableLocales();
343     }
344 
345     /**
346      * Returns the country code for this locale, or {@code ""} if this locale
347      * doesn't correspond to a specific country.
348      */
getCountry()349     public String getCountry() {
350         return countryCode;
351     }
352 
353     /**
354      * Returns the user's preferred locale. This may have been overridden for
355      * this process with {@link #setDefault}.
356      *
357      * <p>Since the user's locale changes dynamically, avoid caching this value.
358      * Instead, use this method to look it up for each use.
359      */
getDefault()360     public static Locale getDefault() {
361         return defaultLocale;
362     }
363 
364     /**
365      * Equivalent to {@code getDisplayCountry(Locale.getDefault())}.
366      */
getDisplayCountry()367     public final String getDisplayCountry() {
368         return getDisplayCountry(getDefault());
369     }
370 
371     /**
372      * Returns the name of this locale's country, localized to {@code locale}.
373      * Returns the empty string if this locale does not correspond to a specific
374      * country.
375      */
getDisplayCountry(Locale locale)376     public String getDisplayCountry(Locale locale) {
377         if (countryCode.isEmpty()) {
378             return "";
379         }
380         String result = ICU.getDisplayCountryNative(toString(), locale.toString());
381         if (result == null) { // TODO: do we need to do this, or does ICU do it for us?
382             result = ICU.getDisplayCountryNative(toString(), Locale.getDefault().toString());
383         }
384         return result;
385     }
386 
387     /**
388      * Equivalent to {@code getDisplayLanguage(Locale.getDefault())}.
389      */
getDisplayLanguage()390     public final String getDisplayLanguage() {
391         return getDisplayLanguage(getDefault());
392     }
393 
394     /**
395      * Returns the name of this locale's language, localized to {@code locale}.
396      * If the language name is unknown, the language code is returned.
397      */
getDisplayLanguage(Locale locale)398     public String getDisplayLanguage(Locale locale) {
399         if (languageCode.isEmpty()) {
400             return "";
401         }
402 
403         // http://b/8049507 --- frameworks/base should use fil_PH instead of tl_PH.
404         // Until then, we're stuck covering their tracks, making it look like they're
405         // using "fil" when they're not.
406         String localeString = toString();
407         if (languageCode.equals("tl")) {
408             localeString = toNewString("fil", countryCode, variantCode);
409         }
410 
411         String result = ICU.getDisplayLanguageNative(localeString, locale.toString());
412         if (result == null) { // TODO: do we need to do this, or does ICU do it for us?
413             result = ICU.getDisplayLanguageNative(localeString, Locale.getDefault().toString());
414         }
415         return result;
416     }
417 
418     /**
419      * Equivalent to {@code getDisplayName(Locale.getDefault())}.
420      */
getDisplayName()421     public final String getDisplayName() {
422         return getDisplayName(getDefault());
423     }
424 
425     /**
426      * Returns this locale's language name, country name, and variant, localized
427      * to {@code locale}. The exact output form depends on whether this locale
428      * corresponds to a specific language, country and variant.
429      *
430      * <p>For example:
431      * <ul>
432      * <li>{@code new Locale("en").getDisplayName(Locale.US)} -> {@code English}
433      * <li>{@code new Locale("en", "US").getDisplayName(Locale.US)} -> {@code English (United States)}
434      * <li>{@code new Locale("en", "US", "POSIX").getDisplayName(Locale.US)} -> {@code English (United States,Computer)}
435      * <li>{@code new Locale("en").getDisplayName(Locale.FRANCE)} -> {@code anglais}
436      * <li>{@code new Locale("en", "US").getDisplayName(Locale.FRANCE)} -> {@code anglais (États-Unis)}
437      * <li>{@code new Locale("en", "US", "POSIX").getDisplayName(Locale.FRANCE)} -> {@code anglais (États-Unis,informatique)}.
438      * </ul>
439      */
getDisplayName(Locale locale)440     public String getDisplayName(Locale locale) {
441         int count = 0;
442         StringBuilder buffer = new StringBuilder();
443         if (!languageCode.isEmpty()) {
444             String displayLanguage = getDisplayLanguage(locale);
445             buffer.append(displayLanguage.isEmpty() ? languageCode : displayLanguage);
446             ++count;
447         }
448         if (!countryCode.isEmpty()) {
449             if (count == 1) {
450                 buffer.append(" (");
451             }
452             String displayCountry = getDisplayCountry(locale);
453             buffer.append(displayCountry.isEmpty() ? countryCode : displayCountry);
454             ++count;
455         }
456         if (!variantCode.isEmpty()) {
457             if (count == 1) {
458                 buffer.append(" (");
459             } else if (count == 2) {
460                 buffer.append(",");
461             }
462             String displayVariant = getDisplayVariant(locale);
463             buffer.append(displayVariant.isEmpty() ? variantCode : displayVariant);
464             ++count;
465         }
466         if (count > 1) {
467             buffer.append(")");
468         }
469         return buffer.toString();
470     }
471 
472     /**
473      * Returns the full variant name in the default {@code Locale} for the variant code of
474      * this {@code Locale}. If there is no matching variant name, the variant code is
475      * returned.
476      */
getDisplayVariant()477     public final String getDisplayVariant() {
478         return getDisplayVariant(getDefault());
479     }
480 
481     /**
482      * Returns the full variant name in the specified {@code Locale} for the variant code
483      * of this {@code Locale}. If there is no matching variant name, the variant code is
484      * returned.
485      */
getDisplayVariant(Locale locale)486     public String getDisplayVariant(Locale locale) {
487         if (variantCode.length() == 0) {
488             return variantCode;
489         }
490         String result = ICU.getDisplayVariantNative(toString(), locale.toString());
491         if (result == null) { // TODO: do we need to do this, or does ICU do it for us?
492             result = ICU.getDisplayVariantNative(toString(), Locale.getDefault().toString());
493         }
494         return result;
495     }
496 
497     /**
498      * Returns the three-letter ISO 3166 country code which corresponds to the country
499      * code for this {@code Locale}.
500      * @throws MissingResourceException if there's no 3-letter country code for this locale.
501      */
getISO3Country()502     public String getISO3Country() {
503         String code = ICU.getISO3CountryNative(toString());
504         if (!countryCode.isEmpty() && code.isEmpty()) {
505             throw new MissingResourceException("No 3-letter country code for locale: " + this, "FormatData_" + this, "ShortCountry");
506         }
507         return code;
508     }
509 
510     /**
511      * Returns the three-letter ISO 639-2/T language code which corresponds to the language
512      * code for this {@code Locale}.
513      * @throws MissingResourceException if there's no 3-letter language code for this locale.
514      */
getISO3Language()515     public String getISO3Language() {
516         String code = ICU.getISO3LanguageNative(toString());
517         if (!languageCode.isEmpty() && code.isEmpty()) {
518             throw new MissingResourceException("No 3-letter language code for locale: " + this, "FormatData_" + this, "ShortLanguage");
519         }
520         return code;
521     }
522 
523     /**
524      * Returns an array of strings containing all the two-letter ISO 3166 country codes that can be
525      * used as the country code when constructing a {@code Locale}.
526      */
getISOCountries()527     public static String[] getISOCountries() {
528         return ICU.getISOCountries();
529     }
530 
531     /**
532      * Returns an array of strings containing all the two-letter ISO 639-1 language codes that can be
533      * used as the language code when constructing a {@code Locale}.
534      */
getISOLanguages()535     public static String[] getISOLanguages() {
536         return ICU.getISOLanguages();
537     }
538 
539     /**
540      * Returns the language code for this {@code Locale} or the empty string if no language
541      * was set.
542      */
getLanguage()543     public String getLanguage() {
544         return languageCode;
545     }
546 
547     /**
548      * Returns the variant code for this {@code Locale} or an empty {@code String} if no variant
549      * was set.
550      */
getVariant()551     public String getVariant() {
552         return variantCode;
553     }
554 
555     @Override
hashCode()556     public synchronized int hashCode() {
557         return countryCode.hashCode() + languageCode.hashCode()
558                 + variantCode.hashCode();
559     }
560 
561     /**
562      * Overrides the default locale. This does not affect system configuration,
563      * and attempts to override the system-provided default locale may
564      * themselves be overridden by actual changes to the system configuration.
565      * Code that calls this method is usually incorrect, and should be fixed by
566      * passing the appropriate locale to each locale-sensitive method that's
567      * called.
568      */
setDefault(Locale locale)569     public synchronized static void setDefault(Locale locale) {
570         if (locale == null) {
571             throw new NullPointerException("locale == null");
572         }
573         defaultLocale = locale;
574     }
575 
576     /**
577      * Returns the string representation of this {@code Locale}. It consists of the
578      * language code, country code and variant separated by underscores.
579      * If the language is missing the string begins
580      * with an underscore. If the country is missing there are 2 underscores
581      * between the language and the variant. The variant cannot stand alone
582      * without a language and/or country code: in this case this method would
583      * return the empty string.
584      *
585      * <p>Examples: "en", "en_US", "_US", "en__POSIX", "en_US_POSIX"
586      */
587     @Override
toString()588     public final String toString() {
589         String result = cachedToStringResult;
590         if (result == null) {
591             result = cachedToStringResult = toNewString(languageCode, countryCode, variantCode);
592         }
593         return result;
594     }
595 
toNewString(String languageCode, String countryCode, String variantCode)596     private static String toNewString(String languageCode, String countryCode, String variantCode) {
597         // The string form of a locale that only has a variant is the empty string.
598         if (languageCode.length() == 0 && countryCode.length() == 0) {
599             return "";
600         }
601         // Otherwise, the output format is "ll_cc_variant", where language and country are always
602         // two letters, but the variant is an arbitrary length. A size of 11 characters has room
603         // for "en_US_POSIX", the largest "common" value. (In practice, the string form is almost
604         // always 5 characters: "ll_cc".)
605         StringBuilder result = new StringBuilder(11);
606         result.append(languageCode);
607         if (countryCode.length() > 0 || variantCode.length() > 0) {
608             result.append('_');
609         }
610         result.append(countryCode);
611         if (variantCode.length() > 0) {
612             result.append('_');
613         }
614         result.append(variantCode);
615         return result.toString();
616     }
617 
618     private static final ObjectStreamField[] serialPersistentFields = {
619         new ObjectStreamField("country", String.class),
620         new ObjectStreamField("hashcode", int.class),
621         new ObjectStreamField("language", String.class),
622         new ObjectStreamField("variant", String.class),
623     };
624 
writeObject(ObjectOutputStream stream)625     private void writeObject(ObjectOutputStream stream) throws IOException {
626         ObjectOutputStream.PutField fields = stream.putFields();
627         fields.put("country", countryCode);
628         fields.put("hashcode", -1);
629         fields.put("language", languageCode);
630         fields.put("variant", variantCode);
631         stream.writeFields();
632     }
633 
readObject(ObjectInputStream stream)634     private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
635         ObjectInputStream.GetField fields = stream.readFields();
636         countryCode = (String) fields.get("country", "");
637         languageCode = (String) fields.get("language", "");
638         variantCode = (String) fields.get("variant", "");
639     }
640 }
641