JavaRush /Java Blog /Random-TL /Coffee break #130. Paano gumana nang tama sa mga array ng...

Coffee break #130. Paano gumana nang tama sa mga array ng Java - mga tip mula sa Oracle

Nai-publish sa grupo
Source: Oracle Working with arrays can include reflection, generics, and lambda expressions. Kamakailan ay nakikipag-usap ako sa isang kasamahan na nag-develop sa C. Ang pag-uusap ay naging array at kung paano gumagana ang mga ito sa Java kumpara sa C. Nalaman kong medyo kakaiba ito, dahil ang Java ay itinuturing na isang wikang tulad ng C. Talagang marami silang pagkakatulad, ngunit mayroon ding mga pagkakaiba. Magsimula tayo sa simple. Coffee break #130.  Paano gumana nang tama sa mga array ng Java - mga tip mula sa Oracle - 1

Array Deklarasyon

Kung susundin mo ang Java tutorial, makikita mo na mayroong dalawang paraan upang magdeklara ng array. Ang una ay diretso:
int[] array; // a Java array declaration
Makikita mo kung paano ito naiiba sa C, kung saan ang syntax ay:
int array[]; // a C array declaration
Balik tayo ulit sa Java. Pagkatapos magdeklara ng array, kailangan mong ilaan ito:
array = new int[10]; // Java array allocation
Posible bang ideklara at simulan ang isang array nang sabay-sabay? Sa totoo lang hindi:
int[10] array; // NOPE, ERROR!
Gayunpaman, maaari mong ideklara at simulan kaagad ang array kung alam mo na ang mga halaga:
int[] array = { 0, 1, 1, 2, 3, 5, 8 };
Paano kung hindi mo alam ang kahulugan? Narito ang code na pinakamadalas mong makikita para sa pagdedeklara, paglalaan, at paggamit ng int array :
int[] array;
array = new int[10];
array[0] = 0;
array[1] = 1;
array[2] = 1;
array[3] = 2;
array[4] = 3;
array[5] = 5;
array[6] = 8;
...
Tandaan na tinukoy ko ang isang int array , na isang array ng Java primitive na uri ng data . Tingnan natin kung ano ang mangyayari kung susubukan mo ang parehong proseso sa isang hanay ng mga bagay sa Java sa halip na mga primitive:
class SomeClass {
    int val;
    // …
}
SomeClass[] array = new SomeClass[10];
array[0].val = 0;
array[1].val = 1;
array[2].val = 1;
array[3].val = 2;
array[4].val = 3;
array[5].val = 5;
array[6].val = 8;
Kung patakbuhin namin ang code sa itaas, makakakuha kami kaagad ng exception pagkatapos subukang gamitin ang unang elemento ng array. Bakit? Kahit na ang array ay inilalaan, ang bawat segment ng array ay naglalaman ng mga walang laman na object reference. Kung ilalagay mo ang code na ito sa iyong IDE, awtomatiko nitong pupunan ang .val para sa iyo, kaya maaaring nakakalito ang error. Upang ayusin ang bug, sundin ang mga hakbang na ito:
SomeClass[] array = new SomeClass[10];
for ( int i = 0; i < array.length; i++ ) {  //new code
    array[i] = new SomeClass();             //new code
}                                           //new code
array[0].val = 0;
array[1].val = 1;
array[2].val = 1;
array[3].val = 2;
array[4].val = 3;
array[5].val = 5;
array[6].val = 8;
Ngunit hindi ito elegante. Nagtaka ako kung bakit hindi ako madaling maglaan ng isang array at ang mga bagay sa loob ng array na may mas kaunting code, marahil lahat sa isang linya. Upang mahanap ang sagot, nagsagawa ako ng ilang mga eksperimento.

Paghahanap ng nirvana sa mga array ng Java

Ang aming layunin ay mag-code nang elegante. Kasunod ng mga panuntunan ng "malinis na code", nagpasya akong lumikha ng magagamit na code upang linisin ang pattern ng paglalaan ng array. Narito ang unang pagsubok:
public class MyArray {

    public static Object[] toArray(Class cls, int size)
      throws Exception {
        Constructor ctor = cls.getConstructors()[0];
        Object[] objects = new Object[size];
        for ( int i = 0; i < size; i++ ) {
            objects[i] = ctor.newInstance();
        }

        return objects;
    }

    public static void main(String[] args) throws Exception {
        SomeClass[] array1 = (SomeClass[])MyArray.toArray(SomeClass.class, 32); // see this
        System.out.println(array1);
    }
}
Ang linya ng code na may markang "tingnan ito" ay mukhang eksakto sa paraang gusto ko, salamat sa pagpapatupad ng toArray . Gumagamit ang diskarteng ito ng reflection upang mahanap ang default na constructor para sa ibinigay na klase at pagkatapos ay tatawagin ang constructor na iyon upang i-instantiate ang isang object ng klase na iyon. Tinatawag ng proseso ang constructor nang isang beses para sa bawat elemento ng array. Fabulous! Sayang lang at hindi gumana. Ang code ay nag-compile nang maayos, ngunit nagtatapon ng isang error sa ClassCastException kapag tumakbo. Upang magamit ang code na ito, kailangan mong lumikha ng isang hanay ng mga elemento ng Bagay, at pagkatapos ay i-cast ang bawat elemento ng array sa isang klase ng SomeClass tulad nito:
Object[] objects = MyArray.toArray(SomeClass.class, 32);
SomeClass scObj = (SomeClass)objects[0];
...
Hindi ito elegante! Pagkatapos ng higit pang eksperimento, gumawa ako ng ilang solusyon gamit ang reflection, generics, at lambda expression.

Solusyon 1: Gumamit ng repleksyon

Dito ginagamit namin ang klase ng java.lang.reflect.Array para i-instantiate ang array ng klase na iyong tinukoy sa halip na gamitin ang base na java.lang.Object class . Ito ay mahalagang pagbabago ng isang linyang code:
public static Object[] toArray(Class cls, int size) throws Exception {
    Constructor ctor = cls.getConstructors()[0];
    Object array = Array.newInstance(cls, size);  // new code
    for ( int i = 0; i < size; i++ ) {
        Array.set(array, i, ctor.newInstance());  // new code
    }
    return (Object[])array;
}
Maaari mong gamitin ang diskarteng ito upang makakuha ng isang hanay ng nais na klase at pagkatapos ay magtrabaho kasama ito tulad nito:
SomeClass[] array1 = (SomeClass[])MyArray.toArray(SomeClass.class, 32);
Bagama't hindi ito kinakailangang pagbabago, binago ang pangalawang linya upang magamit ang klase ng Array reflection upang itakda ang mga nilalaman ng bawat elemento ng array. Ito ay kamangha-manghang! Ngunit may isa pang detalye na mukhang hindi masyadong tama: ang cast sa SomeClass[] ay mukhang hindi masyadong maganda. Sa kabutihang palad, may solusyon sa generics.

Solusyon 2: Gumamit ng generics

Gumagamit ang balangkas ng Collections ng mga generic para sa uri ng pagbubuklod at inaalis ang mga cast sa mga ito sa marami sa mga pagpapatakbo nito. Maaari ding gamitin dito ang mga generic. Kunin natin ang java.util.List bilang halimbawa .
List list = new ArrayList();
list.add( new SomeClass() );
SomeClass sc = list.get(0); // Error, needs a cast unless...
Ang ikatlong linya sa snippet sa itaas ay magbibigay ng error maliban kung i-update mo ang unang linya tulad nito:
List<SomeClass> = new ArrayList();
Maaari mong makamit ang parehong resulta sa pamamagitan ng paggamit ng mga generic sa klase ng MyArray . Narito ang bagong bersyon:
public class MyArray<E> {
    public <E> E[] toArray(Class cls, int size) throws Exception {
        E[] array = (E[])Array.newInstance(cls, size);
        Constructor ctor = cls.getConstructors()[0];
        for ( int element = 0; element < array.length; element++ ) {
            Array.set(array, element, ctor.newInstance());
        }
        return arrayOfGenericType;
    }
}
// ...
MyArray<SomeClass> a1 = new MyArray(SomeClass.class, 32);
SomeClass[] array1 = a1.toArray();
Mukhang maganda. Sa pamamagitan ng paggamit ng mga generic at pagsasama ng target na uri sa deklarasyon, ang uri ay maaaring mahinuha sa iba pang mga operasyon. Bilang karagdagan, ang code na ito ay maaaring bawasan sa isang linya sa pamamagitan ng paggawa nito:
SomeClass[] array = new MyArray<SomeClass>(SomeClass.class, 32).toArray();
Mission accomplished, tama ba? Well, hindi naman. Ito ay mainam kung wala kang pakialam kung aling tagabuo ng klase ang tawagan mo, ngunit kung nais mong tumawag sa isang tiyak na tagabuo, kung gayon ang solusyon na ito ay hindi gagana. Maaari mong patuloy na gumamit ng pagmuni-muni upang malutas ang problemang ito, ngunit pagkatapos ay magiging kumplikado ang code. Sa kabutihang palad, may mga lambda expression na nag-aalok ng isa pang solusyon.

Solusyon 3: Gumamit ng mga expression ng lambda

Aaminin ko, hindi ako partikular na nasasabik tungkol sa mga expression ng lambda noon, ngunit natutunan kong pahalagahan ang mga ito. Sa partikular, nagustuhan ko ang java.util.stream.Stream interface , na humahawak ng mga koleksyon ng mga bagay. Tinulungan ako ng Stream na maabot ang Java array nirvana. Narito ang aking unang pagtatangka sa paggamit ng lambdas:
SomeClass[] array =
    Stream.generate(() -> new SomeClass())
    .toArray(SomeClass[]::new);
Hinati ko ang code na ito sa tatlong linya para sa mas madaling pagbabasa. Makikita mo na nilagyan nito ng tsek ang lahat ng mga kahon: simple at eleganteng ito, lumilikha ng isang punong hanay ng mga bagay na instance, at nagbibigay-daan sa iyong tumawag sa isang partikular na constructor. Bigyang-pansin ang toArray method parameter : SomeClass[]::new . Ito ay isang generator function na ginagamit upang maglaan ng array ng tinukoy na uri. Gayunpaman, tulad ng nakatayo, ang code na ito ay may maliit na problema: lumilikha ito ng isang hanay ng walang katapusang laki. Ito ay hindi masyadong optimal. Ngunit ang problema ay maaaring malutas sa pamamagitan ng pagtawag sa limit method :
SomeClass[] array =
    Stream.generate(() -> new SomeClass())
    .limit(32)   // calling the limit method
    .toArray(SomeClass[]::new);
Ang array ay limitado na ngayon sa 32 elemento. Maaari ka ring magtakda ng mga partikular na halaga ng object para sa bawat elemento ng array, tulad ng ipinapakita sa ibaba:
SomeClass[] array = Stream.generate(() -> {
    SomeClass result = new SomeClass();
    result.val = 16;
    return result;
    })
    .limit(32)
    .toArray(SomeClass[]::new);
Ipinapakita ng code na ito ang kapangyarihan ng mga expression ng lambda, ngunit hindi maayos o compact ang code. Sa aking opinyon, ang pagtawag sa isa pang tagabuo upang itakda ang halaga ay magiging mas mahusay.
SomeClass[] array6 = Stream.generate( () -> new SomeClass(16) )
    .limit(32)
    .toArray(SomeClass[]::new);
Gusto ko ang lambda expression based na solusyon. Ito ay perpekto kapag kailangan mong tumawag sa isang partikular na constructor o magtrabaho sa bawat elemento ng isang array. Kapag kailangan ko ng mas simple, kadalasang gumagamit ako ng generics-based na solusyon dahil mas simple ito. Gayunpaman, makikita mo mismo na ang mga expression ng lambda ay nagbibigay ng elegante at nababaluktot na solusyon.

Konklusyon

Ngayon natutunan namin kung paano gumawa sa pagdedeklara at paglalaan ng mga array ng primitives, paglalaan ng mga array ng Object elements , gamit ang reflection, generics, at lambda expression sa Java.
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION