JavaRush /Java Blog /Random-TL /Coffee break #146. 5 pagkakamali na ginagawa ng 99% ng mg...

Coffee break #146. 5 pagkakamali na ginagawa ng 99% ng mga developer ng Java. Mga string sa Java - view sa loob

Nai-publish sa grupo

5 pagkakamali na ginagawa ng 99% ng mga developer ng Java

Source: Medium Sa post na ito, matututunan mo ang tungkol sa mga pinakakaraniwang pagkakamali na ginagawa ng maraming developer ng Java. Coffee break #146.  5 pagkakamali na ginagawa ng 99% ng mga developer ng Java.  Mga String sa Java - view sa loob - 1Bilang isang Java programmer, alam ko kung gaano masama ang gumugol ng maraming oras sa pag-aayos ng mga bug sa iyong code. Minsan ito ay tumatagal ng ilang oras. Gayunpaman, maraming mga error ang lumilitaw dahil sa katotohanan na binabalewala ng developer ang mga pangunahing panuntunan - iyon ay, ang mga ito ay napakababang antas ng mga error. Ngayon ay titingnan natin ang ilang karaniwang pagkakamali sa coding at pagkatapos ay ipaliwanag kung paano ayusin ang mga ito. Umaasa ako na makakatulong ito sa iyo na maiwasan ang mga problema sa iyong pang-araw-araw na gawain.

Paghahambing ng mga bagay gamit ang Objects.equals

Ipinapalagay ko na pamilyar ka sa pamamaraang ito. Madalas itong ginagamit ng maraming developer. Ang diskarteng ito, na ipinakilala sa JDK 7, ay tumutulong sa iyong mabilis na paghambingin ang mga bagay at epektibong maiwasan ang nakakainis na null pointer checking. Ngunit ang pamamaraang ito ay minsan ginagamit nang hindi tama. Narito ang ibig kong sabihin:
Long longValue = 123L;
System.out.println(longValue==123); //true
System.out.println(Objects.equals(longValue,123)); //false
Bakit ang pagpapalit ng == sa Objects.equals() ay magbubunga ng maling resulta? Ito ay dahil kukunin ng == compiler ang pinagbabatayan na uri ng data na naaayon sa uri ng packaging ng longValue at pagkatapos ay ikumpara ito sa pinagbabatayan na uri ng data na iyon. Ito ay katumbas ng compiler na awtomatikong nagko-convert ng mga constant sa pinagbabatayan na uri ng data ng paghahambing. Pagkatapos gamitin ang Objects.equals() method , ang default na base data type ng compiler constant ay int . Nasa ibaba ang source code para sa Objects.equals() kung saan ang a.equals(b) ay gumagamit ng Long.equals() at tinutukoy ang uri ng object. Nangyayari ito dahil ipinapalagay ng compiler na ang pare-pareho ay nasa uri int , kaya dapat na mali ang resulta ng paghahambing.
public static boolean equals(Object a, Object b) {
        return (a == b) || (a != null && a.equals(b));
    }

  public boolean equals(Object obj) {
        if (obj instanceof Long) {
            return value == ((Long)obj).longValue();
        }
        return false;
    }
Alam ang dahilan, ang pag-aayos ng error ay napakasimple. Ideklara lang ang uri ng data ng mga constant, tulad ng Objects.equals(longValue,123L) . Ang mga problema sa itaas ay hindi lilitaw kung ang lohika ay mahigpit. Ang kailangan nating gawin ay sundin ang malinaw na mga panuntunan sa programming.

Maling format ng petsa

Sa pang-araw-araw na pag-unlad, madalas mong kailangang baguhin ang petsa, ngunit maraming tao ang gumagamit ng maling format, na humahantong sa mga hindi inaasahang bagay. Narito ang isang halimbawa:
Instant instant = Instant.parse("2021-12-31T00:00:00.00Z");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss")
.withZone(ZoneId.systemDefault());
System.out.println(formatter.format(instant));//2022-12-31 08:00:00
Ginagamit nito ang format na YYYY-MM-dd para baguhin ang petsa mula 2021 hanggang 2022. Hindi mo dapat ginawa iyon. Bakit? Ito ay dahil ang Java DateTimeFormatter “YYYY” pattern ay batay sa ISO-8601 standard, na tumutukoy sa taon bilang Huwebes ng bawat linggo. Ngunit ang Disyembre 31, 2021 ay bumagsak sa isang Biyernes, kaya ang programa ay hindi wastong nagsasaad ng 2022. Upang maiwasan ito, dapat mong gamitin ang format na yyyy-MM-dd upang i-format ang petsa . Ang error na ito ay madalang na nangyayari, sa pagdating lamang ng bagong taon. Ngunit sa aking kumpanya ay nagdulot ito ng pagkabigo sa produksyon.

Paggamit ng ThreadLocal sa ThreadPool

Kung gagawa ka ng ThreadLocal variable , ang thread na nag-a-access sa variable na iyon ay gagawa ng thread local variable. Sa ganitong paraan maiiwasan mo ang mga isyu sa kaligtasan ng thread. Gayunpaman, kung gumagamit ka ng ThreadLocal sa isang thread pool , kailangan mong mag-ingat. Ang iyong code ay maaaring makagawa ng mga hindi inaasahang resulta. Para sa isang simpleng halimbawa, sabihin nating mayroon kaming platform ng e-commerce at kailangan ng mga user na magpadala ng email upang kumpirmahin ang isang nakumpletong pagbili ng mga produkto.
private ThreadLocal<User> currentUser = ThreadLocal.withInitial(() -> null);

    private ExecutorService executorService = Executors.newFixedThreadPool(4);

    public void executor() {
        executorService.submit(()->{
            User user = currentUser.get();
            Integer userId = user.getId();
            sendEmail(userId);
        });
    }
Kung gagamitin namin ang ThreadLocal upang i-save ang impormasyon ng user, may lalabas na nakatagong error. Dahil ang isang pool ng mga thread ay ginagamit, at ang mga thread ay maaaring gamitin muli, kapag gumagamit ng ThreadLocal upang makakuha ng impormasyon ng user, maaari itong maling magpakita ng impormasyon ng ibang tao. Upang malutas ang problemang ito, dapat mong gamitin ang mga session.

Gamitin ang HashSet para alisin ang duplicate na data

Kapag nagco-coding, madalas kailangan natin ng deduplication. Kapag iniisip mo ang deduplication, ang unang bagay na iniisip ng maraming tao ay ang paggamit ng HashSet . Gayunpaman, ang walang ingat na paggamit ng HashSet ay maaaring maging sanhi ng pagbagsak ng deduplication.
User user1 = new User();
user1.setUsername("test");

User user2 = new User();
user2.setUsername("test");

List<User> users = Arrays.asList(user1, user2);
HashSet<User> sets = new HashSet<>(users);
System.out.println(sets.size());// the size is 2
Ang ilang matulungin na mambabasa ay dapat na mahulaan ang dahilan ng pagkabigo. Gumagamit ang HashSet ng hash code para ma-access ang hash table at ginagamit ang equals method para matukoy kung pantay ang mga object. Kung hindi na-override ng object na tinukoy ng user ang hashcode method at katumbas ng method , ang paraan ng hashcode at equals na paraan ng parent na object ay gagamitin bilang default. Magiging sanhi ito ng HashSet na ipagpalagay na sila ay dalawang magkaibang mga bagay, na nagiging sanhi upang mabigo ang deduplication.

Pag-aalis ng "kinain" na thread ng pool

ExecutorService executorService = Executors.newFixedThreadPool(1);
        executorService.submit(()->{
            //do something
            double result = 10/0;
        });
Ginagaya ng code sa itaas ang isang senaryo kung saan ang isang exception ay itinapon sa thread pool. Ang code ng negosyo ay dapat maglagay ng iba't ibang sitwasyon, kaya malaki ang posibilidad na magtapon ito ng RuntimeException sa ilang kadahilanan . Ngunit kung walang espesyal na paghawak dito, ang pagbubukod na ito ay "kakainin" ng thread pool. At hindi ka magkakaroon ng paraan upang suriin ang sanhi ng pagbubukod. Samakatuwid, pinakamahusay na mahuli ang mga pagbubukod sa pool ng proseso.

Mga string sa Java - view sa loob

Pinagmulan: Medium Nagpasya ang may-akda ng artikulong ito na tingnan nang detalyado ang paggawa, pagpapagana at mga tampok ng mga string sa Java. Coffee break #146.  5 pagkakamali na ginagawa ng 99% ng mga developer ng Java.  Mga string sa Java - view sa loob - 2

Paglikha

Ang isang string sa Java ay maaaring gawin sa dalawang magkaibang paraan: implicitly, bilang literal na string, at tahasang, gamit ang bagong keyword . Ang mga string literal ay mga character na nakapaloob sa double quotes.
String literal   = "Michael Jordan";
String object    = new String("Michael Jordan");
Bagama't ang parehong mga deklarasyon ay lumikha ng isang string object, may pagkakaiba sa kung paano matatagpuan ang parehong mga bagay na ito sa memorya ng heap.

Panloob na representasyon

Dati, ang mga string ay inimbak sa anyong char[] , ibig sabihin, ang bawat karakter ay isang hiwalay na elemento sa array ng character. Dahil ang mga ito ay kinakatawan sa UTF-16 na format ng pag-encode ng character , nangangahulugan ito na ang bawat karakter ay umabot ng dalawang byte ng memorya. Hindi ito masyadong tama, dahil ipinapakita ng mga istatistika ng paggamit na karamihan sa mga string object ay binubuo ng Latin-1 na mga character lamang . Ang mga Latin-1 na character ay maaaring katawanin gamit ang isang byte ng memorya, na maaaring makabuluhang bawasan ang paggamit ng memorya—hanggang sa 50%. Ang isang bagong tampok na panloob na string ay ipinatupad bilang bahagi ng paglabas ng JDK 9 batay sa JEP 254 na tinatawag na Compact Strings. Sa release na ito, ang char[] ay ginawang byte[] at isang encoder flag field ang idinagdag upang kumatawan sa ginamit na encoding (Latin-1 o UTF-16). Pagkatapos nito, nangyayari ang pag-encode batay sa mga nilalaman ng string. Kung ang value ay naglalaman lamang ng Latin-1 na character, ang Latin-1 na encoding ay ginagamit (ang StringLatin1 class ) o ang UTF-16 encoding ay ginagamit (ang StringUTF16 class ).

Paglalaan ng memorya

Gaya ng nasabi kanina, may pagkakaiba sa paraan ng paglalaan ng memorya para sa mga bagay na ito sa heap. Ang paggamit ng tahasang bagong keyword ay medyo prangka dahil ang JVM ay lumilikha at naglalaan ng memorya para sa variable sa heap. Samakatuwid, ang paggamit ng literal na string ay sumusunod sa prosesong tinatawag na interning. Ang string interning ay ang proseso ng paglalagay ng mga string sa isang pool. Gumagamit ito ng paraan ng pag-iimbak lamang ng isang kopya ng bawat indibidwal na halaga ng string, na dapat ay hindi nababago. Ang mga indibidwal na halaga ay nakaimbak sa String Intern pool. Ang pool na ito ay isang Hashtable na tindahan na nag-iimbak ng reference sa bawat string object na ginawa gamit ang mga literal at hash nito. Bagama't nasa heap ang string value, makikita ang reference nito sa internal pool. Madali itong ma-verify gamit ang eksperimento sa ibaba. Narito mayroon kaming dalawang variable na may parehong halaga:
String firstName1   = "Michael";
String firstName2   = "Michael";
System.out.println(firstName1 == firstName2);             //true
Sa panahon ng pagpapatupad ng code, kapag nakatagpo ng JVM ang firstName1 , hinahanap nito ang halaga ng string sa panloob na string pool Michael . Kung hindi ito mahanap, pagkatapos ay isang bagong entry ay nilikha para sa bagay sa panloob na pool. Kapag naabot ng execution ang firstName2 , uulit muli ang proseso at sa pagkakataong ito ang value ay makikita sa pool batay sa firstName1 variable . Sa ganitong paraan, sa halip na duplicate at gumawa ng bagong entry, ibinabalik ang parehong link. Samakatuwid, ang kondisyon ng pagkakapantay-pantay ay nasiyahan. Sa kabilang banda, kung ang isang variable na may value na Michael ay ginawa gamit ang bagong keyword, walang interning na magaganap at ang kondisyon ng pagkakapantay-pantay ay hindi nasiyahan.
String firstName3 = new String("Michael");
System.out.println(firstName3 == firstName2);           //false
Maaaring gamitin ang interning gamit ang firstName3 intern() na paraan , bagama't hindi ito karaniwang ginusto.
firstName3 = firstName3.intern();                      //Interning
System.out.println(firstName3 == firstName2);          //true
Maaari ding mangyari ang interning kapag pinagsasama-sama ang dalawang literal na string gamit ang + operator .
String fullName = "Michael Jordan";
System.out.println(fullName == "Michael " + "Jordan");     //true
Dito makikita natin na sa oras ng pag-compile, idinaragdag ng compiler ang parehong mga literal at inaalis ang + operator mula sa expression upang bumuo ng isang string tulad ng ipinapakita sa ibaba. Sa runtime, parehong fullName at ang "idinagdag na literal" ay interned at ang kondisyon ng pagkakapantay-pantay ay nasiyahan.
//After Compilation
System.out.println(fullName == "Michael Jordan");

Pagkakapantay-pantay

Mula sa mga eksperimento sa itaas, makikita mo na ang mga string literal lang ang naka-interne bilang default. Gayunpaman, ang isang Java application ay tiyak na hindi magkakaroon lamang ng mga literal na string, dahil maaari itong makatanggap ng mga string mula sa iba't ibang mga mapagkukunan. Samakatuwid, ang paggamit ng equality operator ay hindi inirerekomenda at maaaring magdulot ng hindi kanais-nais na mga resulta. Ang pagsusuri sa pagkakapantay-pantay ay dapat lamang gawin sa pamamagitan ng equals na pamamaraan . Nagsasagawa ito ng pagkakapantay-pantay batay sa halaga ng string sa halip na sa memory address kung saan ito nakaimbak.
System.out.println(firstName1.equals(firstName2));       //true
System.out.println(firstName3.equals(firstName2));       //true
Mayroon ding bahagyang binagong bersyon ng equals method na tinatawag na equalsIgnoreCase . Maaari itong maging kapaki-pakinabang para sa mga layuning case-insensitive.
String firstName4 = "miCHAEL";
System.out.println(firstName4.equalsIgnoreCase(firstName1));  //true

Kawalang pagbabago

Ang mga string ay hindi nababago, ibig sabihin, ang kanilang panloob na estado ay hindi mababago kapag sila ay nilikha. Maaari mong baguhin ang halaga ng isang variable, ngunit hindi ang halaga ng string mismo. Ang bawat paraan ng klase ng String na tumatalakay sa pagmamanipula ng isang bagay (halimbawa, concat , substring ) ay nagbabalik ng bagong kopya ng halaga sa halip na i-update ang umiiral na halaga.
String firstName  = "Michael";
String lastName   = "Jordan";
firstName.concat(lastName);

System.out.println(firstName);                       //Michael
System.out.println(lastName);                        //Jordan
Gaya ng nakikita mo, walang pagbabagong nagaganap sa alinman sa mga variable: ni firstName o lastName . Ang mga pamamaraan ng klase ng string ay hindi nagbabago ng panloob na estado, gumagawa sila ng bagong kopya ng resulta at ibinabalik ang resulta tulad ng ipinapakita sa ibaba.
firstName = firstName.concat(lastName);

System.out.println(firstName);                      //MichaelJordan
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION