Articles

Java-kokoelman suodattaminen luettelolla

yleiskatsaus

kokoelman suodattaminen luettelolla on yleinen liiketoimintaloginen skenaario. On monia tapoja saavuttaa tämä. Osa voi kuitenkin johtaa alisuorittaviin ratkaisuihin, jos niitä ei tehdä kunnolla.

tässä opetusohjelmassa vertaamme joitakin suodatustoteutuksia ja keskustelemme niiden eduista ja haitoista.

käyttämällä For-Each Loop

aloitamme klassisimmalla syntaksilla, for-each Loopilla.

tässä ja kaikissa muissa tämän artikkelin esimerkeissä käytetään seuraavaa luokkaa:

public class Employee { private Integer employeeNumber; private String name; private Integer departmentId; //Standard constructor, getters and setters.}

käytämme yksinkertaisuuden vuoksi myös seuraavia menetelmiä kaikissa esimerkeissä:

private List<Employee> buildEmployeeList() { return Arrays.asList( new Employee(1, "Mike", 1), new Employee(2, "John", 1), new Employee(3, "Mary", 1), new Employee(4, "Joe", 2), new Employee(5, "Nicole", 2), new Employee(6, "Alice", 2), new Employee(7, "Bob", 3), new Employee(8, "Scarlett", 3));}private List<String> employeeNameFilter() { return Arrays.asList("Alice", "Mike", "Bob");}

esimerkissämme suodatamme ensimmäisen listan työntekijöistä toisen listan perusteella työntekijöiden nimet löytääksemme vain työntekijät, joilla on nämä tietyt nimet.

nyt nähdään perinteinen lähestymistapa – loopataan molempien listojen läpi hakien osumia:

@Testpublic void givenEmployeeList_andNameFilterList_thenObtainFilteredEmployeeList_usingForEachLoop() { List<Employee> filteredList = new ArrayList<>(); List<Employee> originalList = buildEmployeeList(); List<String> nameFilter = employeeNameFilter(); for (Employee employee : originalList) { for (String name : nameFilter) { if (employee.getName().equals(name)) { filteredList.add(employee); // break; } } } assertThat(filteredList.size(), is(nameFilter.size()));}

Tämä on yksinkertainen syntaksi, mutta melko monisanainen ja itse asiassa melko tehoton. Yksinkertaisesti sanottuna, se iterates kautta kartesiolainen tuote kaksi sarjaa, jotta saamme vastauksen.

jopa lisäämällä tauko poistumiseen aikaisin iteroidaan edelleen samassa järjestyksessä kuin Karteesinen tuote keskimääräisessä tapauksessa.

jos kutsumme työntekijälistan kokoa n, niin nameFilter on tilauksessa yhtä suuri, mikä antaa meille O(n2) – luokituksen.

käyttäen streameja ja List#sisältää

nyt uudistamme edellisen menetelmän käyttämällä lambdoja yksinkertaistamaan syntaksia ja parantamaan luettavuutta. Käytetään myös listaa#sisältää menetelmää lambda-suodattimena:

@Testpublic void givenEmployeeList_andNameFilterList_thenObtainFilteredEmployeeList_usingLambda() { List<Employee> filteredList; List<Employee> originalList = buildEmployeeList(); List<String> nameFilter = employeeNameFilter(); filteredList = originalList.stream() .filter(employee -> nameFilter.contains(employee.getName())) .collect(Collectors.toList()); assertThat(filteredList.size(), is(nameFilter.size()));}

Stream API: n avulla luettavuus on parantunut huomattavasti, mutta koodimme on edelleen yhtä tehoton kuin edellinen menetelmämme, koska se iteroituu edelleen karteesisen tuotteen läpi sisäisesti. Näin ollen meillä on sama O(n2) – luokitus.

käyttämällä striimejä, joissa on HashSet

suorituskyvyn parantamiseksi on käytettävä HashSet#sisältää-menetelmää. Tämä menetelmä eroaa listasta#Sisältää, koska se suorittaa hash-koodin haun, antaen meille vakioaikaisen määrän operaatioita:

@Testpublic void givenEmployeeList_andNameFilterList_thenObtainFilteredEmployeeList_usingLambdaAndHashSet() { List<Employee> filteredList; List<Employee> originalList = buildEmployeeList(); Set<String> nameFilterSet = employeeNameFilter().stream().collect(Collectors.toSet()); filteredList = originalList.stream() .filter(employee -> nameFilterSet.contains(employee.getName())) .collect(Collectors.toList()); assertThat(filteredList.size(), is(nameFilterSet.size()));}

käyttämällä Hashsetiä, kooditehokkuutemme on parantunut huomattavasti vaikuttamatta luettavuuteen. Koska HashSet#sisältää juoksuja jatkuvassa ajassa, olemme parantaneet luokitteluamme O(n): ksi.

johtopäätös

tässä nopeassa opetusohjelmassa opimme suodattamaan kokoelman arvolistan ja yksinkertaisimmalta vaikuttavan menetelmän käytön haittapuolet.

meidän on aina harkittava tehokkuutta, koska koodimme saattaa päätyä toimimaan valtavissa tietokokonaisuuksissa, ja suorituskykyongelmilla voi olla katastrofaalisia seurauksia tällaisissa ympäristöissä.

kaikki tässä artikkelissa esitetyt koodit ovat saatavilla GitHubissa.

Aloita kevät 5: llä ja Kevätsotku 2: lla, Opi kevät-kurssin kautta:

>> tsekkaa kurssi