Збирка решени задачи по предметот напредно програмирање Ѓорѓи Маџаров Стефан Андонов Факултет за информатички науки и компjутерско инженерство Универзитет Св. Кирил и Методиj, Скопjе Прво изgание, Jуни 2021 Содржина 1 Обjектно-ориентирано програмирање во Jава . . . . . . . . . . . . . . . . . . . . . . 5 1.1 Класи и обjекти 1.2 Наследување и композициjа 2 Исклучоци . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 2.1 Вградени исклучоци 24 2.2 Кориснички дефинирани исклучоци 25 3 Читање и запишување на податоци . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 3.1 Користење на класата Scanner за читање на податоци 3.1.1 Користење на класата PrintWriter за запишување на податоци . . . . . . . . . . . . 39 3.2 Користење на класата BufferedReader за читање од влезен поток 4 Генерици . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 4.1 Генерички класи, интерфеjси и методи 4.1.1 4.1.2 Генерички класи . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 Генерички методи и интефеjси . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 4.2 Генерички податочни структури 4.2.1 4.2.2 Колекции во Jава (листи и множества) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 Мапи . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 5 Stream API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 6 Вовед во шаблони за дизаjн на софтвер . . . . . . . . . . . . . . . . . . . . . . . . . 121 5 10 35 42 47 58 Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 1. Обjектно-ориентирано програмирање во Jава Обjектно-ориентираното програмирање или скратено ООП е доминантна програмска парадигма што ги заменува структурираните или процедуралните техники на програми-рање, развиени во 1970-тите. Обjектно-ориентираната програма е претставена преку обjекти кои меѓусебно комуницираат со испраќање на пораки. Секоj обjект има одредена специфична функционалност, изложена пред корисниците и скриена имплементациjа. Многу обjекти кои се користат во рамки на програмите се веќе дефинирани во одредени библиотеки и се користат согласно нивната постоечка дефинициjа и имплементациjа. Обjектите кои сеуште не се дефинирани или имплементирани, корисникот треба да се погрижи за нивната дефинициjа и имплементациjа. Дали кориникот ќе креира своj обjект или истиот ќе го превземе од некоjа постоечка билиотека зависи од повеќе фактори, како на пример време или буџет со кои располага. Но, во основа, сè додека некоj обjект ги задоволува корисничките спецификации, не е значаjно коj и како jа имплементира неговата функцоиналност. 1.1 Класи и обjекти Во рамките на ова поглавjе е дадено решение на едноставен проблем од обjектноориентирано програмирање. Претставени се основните концепти за креирање на класа, дефинирање на неjзините членови (атрибути и методи) како и начинот на инстанцирање на обjекти од соодветната класа. Опишани се основните критериуми за енкапсулациjа и интеракциjа. Проблем 1.1 Да се напише класа коjа чува низа на цели броеви IntegerArray . Класата треба да е immutable . Тоа значи дека, откако еднаш ќе се инстанцира, не може да се менува состоjбата на обjектот, односно не може да се менуваат податоците зачувани во него и не може да се наследува од неа final . За потребите на класата треба да се имплементираат следните методи: 6 Глава 1. Обjектно-ориентирано програмирање во Jава • IntegerArray(int a[]) - конструктор коj прима низа од цели броеви • length():int - метод коj jа враќа должината на низата • getElementAt(int i):int - го враќа елементот на позициjа i , може да претпоставите дека индексот i секогаш ќе има вредност во интервалот [0,length()-1] • sum():int - метод коj jа враќа сумата на сите елемeнти во низата • average():double - метод коj jа враќа средната вредност на елементите во низата • getSorted():IntegerArray - враќа нов обjект од истата класа коj ги содржи истите вредности од тековниот обjект, но сортирани во растечки редослед • concat(IntegerArray ia):IntegerArray - враќа нов обjект од истата класа во коj се содржат сите елементи од this обjектот и по нив сите елементи од ia обjектот, притоа запазуваjќи го нивниот редослед • toString():String - враќа текстуална репрезентациjа на обjектот каде елементите се одделени со запиркa и едно празно место после запирката и на почетокот и краjот на стрингот има средни загради. Пример за низа коjа ги содржи боревите 2,1 и 4 враќа "[2, 1, 4]" *Забелешка сите методи треба да се public Покраj класата IntegerArray треба да се напише дополнително уште една класа коjа ќе служи за вчитување на низа од цели броеви од влезен тек на податоци. Оваа класа треба да се вика ArrayReader и во неа треба да се имплементира public static метод за вчитување на низа од цели броеви од InputStream . • readIntegerArray(InputStream input):IntegerArray - вчитува низа од цели броеви од input зададена во следниот формат: Во првата линиjа има еден цел борj коj кажува колку елементи има во низата, а во наредниот ред се дадени елементите на низата одделени со едно или повеќе празни места. 1 2 3 4 5 6 import import import import import import java . io . ByteArrayInputStream ; java . io . IOException ; java . io . InputStream ; java . util . Arrays ; java . util . Random ; java . util . Scanner ; 7 8 9 class IntegerArray { 10 11 private int a []; 12 13 14 15 public IntegerArray ( int a []) { this . a = Arrays . copyOf (a , a . length ) ; } 16 17 18 19 20 21 22 private IntegerArray ( int a [] , boolean to_copy ) { if ( to_copy ) this . a = a ; else this . a = Arrays . copyOf (a , a . length ) ; } 23 24 public int length () { 1.1 Класи и обjекти 7 return a . length ; 25 } 26 27 public int getElementAt ( int i ) { return a [ i ]; } 28 29 30 31 public int sum () { int sum = 0; for ( int e : a ) sum += e ; return sum ; } 32 33 34 35 36 37 public double average () { return sum () * 1.0 / length () ; } 38 39 40 41 public IntegerArray getSorted () { int sorted_a [] = Arrays . copyOf (a , length () ) ; Arrays . sort ( sorted_a ) ; return new IntegerArray ( sorted_a ) ; } 42 43 44 45 46 47 public IntegerArray concat ( IntegerArray ia ) { int res_a [] = new int [ a . length + ia . a . length ]; System . arraycopy (a , 0 , res_a , 0 , a . length ) ; System . arraycopy ( ia .a , 0 , res_a , a . length , ia . a . length ) ; return new IntegerArray ( res_a , true ) ; } 48 49 50 51 52 53 54 public String toString () { return Arrays . toString ( a ) ; } 55 56 57 58 @Override public int hashCode () { final int prime = 31; int result = 1; result = prime * result + Arrays . hashCode ( a ) ; return result ; } 59 60 61 62 63 64 65 66 @Override public boolean equals ( Object obj ) { if ( this == obj ) return true ; if ( obj == null ) return false ; if ( getClass () != obj . getClass () ) return false ; IntegerArray other = ( IntegerArray ) obj ; if (! Arrays . equals (a , other . a ) ) return false ; return true ; } 67 68 69 70 71 72 73 74 75 76 77 78 79 80 } 81 82 class ArrayReader { 83 84 85 public static IntegerArray readIntegerArray ( InputStream input ) { Scanner jin = new Scanner ( input ) ; 8 Глава 1. Обjектно-ориентирано програмирање во Jава int n = jin . nextInt () ; int a [] = new int [ n ]; for ( int i = 0 ; i < n ; ++ i ) { a [ i ] = jin . nextInt () ; } jin . close () ; return new IntegerArray ( a ) ; 86 87 88 89 90 91 92 } 93 94 } 95 96 public class IntegerArrayTester { 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 public static void main ( String [] args ) { Scanner scanner = new Scanner ( System . in ) ; String s = scanner . nextLine () ; IntegerArray ia = null ; switch ( s ) { case " testSimpleMethods " : ia = new IntegerArray ( generateRandomArray ( scanner . nextInt () )); testSimpleMethods ( ia ) ; break ; case " testConcat " : testConcat ( scanner ) ; break ; case " testEquals " : testEquals ( scanner ) ; break ; case " testSorting " : testSorting ( scanner ) ; break ; case " testReading " : testReading ( new ByteArrayInputStream ( scanner . nextLine () . getBytes () ) ) ; break ; case " testImmutability " : int a [] = generateRandomArray ( scanner . nextInt () ) ; ia = new IntegerArray ( a ) ; testSimpleMethods ( ia ) ; testSimpleMethods ( ia ) ; IntegerArray sorted_ia = ia . getSorted () ; testSimpleMethods ( ia ) ; testSimpleMethods ( sorted_ia ) ; sorted_ia . getSorted () ; testSimpleMethods ( sorted_ia ) ; testSimpleMethods ( ia ) ; a [0] += 2; testSimpleMethods ( ia ) ; ia = ArrayReader . readIntegerArray ( new ByteArrayInputStream ( i n t egerArrayToString ( ia ) . getBytes () ) ) ; testSimpleMethods ( ia ) ; break ; } scanner . close () ; } 138 139 140 141 142 143 static void testReading ( InputStream in ) { IntegerArray read = ArrayReader . readIntegerArray ( in ) ; System . out . println ( read ) ; } 1.1 Класи и обjекти 144 145 146 147 148 static void testSorting ( Scanner scanner ) { int [] a = readArray ( scanner ) ; IntegerArray ia = new IntegerArray ( a ) ; System . out . println ( ia . getSorted () ) ; } 149 150 151 152 153 154 155 156 157 158 159 160 static void testEquals ( Scanner scanner ) { int [] a = readArray ( scanner ) ; int [] b = readArray ( scanner ) ; int [] c = readArray ( scanner ) ; IntegerArray ia = new IntegerArray ( a ) ; IntegerArray ib = new IntegerArray ( b ) ; IntegerArray ic = new IntegerArray ( c ) ; System . out . println ( ia . equals ( ib ) ) ; System . out . println ( ia . equals ( ic ) ) ; System . out . println ( ib . equals ( ic ) ) ; } 161 162 163 164 165 166 167 168 169 static void testConcat ( Scanner scanner ) { int [] a = readArray ( scanner ) ; int [] b = readArray ( scanner ) ; IntegerArray array1 = new IntegerArray ( a ) ; IntegerArray array2 = new IntegerArray ( b ) ; IntegerArray concatenated = array1 . concat ( array2 ) ; System . out . println ( concatenated ) ; } 170 171 172 173 174 175 176 static void testSimpleMethods ( IntegerArray ia ) { System . out . print ( integerArrayToString ( ia ) ) ; System . out . println ( ia ) ; System . out . println ( ia . sum () ) ; System . out . printf ( " %.2 f \ n " , ia . average () ) ; } 177 178 179 180 181 182 183 184 185 186 static String integerArrayToString ( IntegerArray ia ) { StringBuilder sb = new StringBuilder () ; sb . append ( ia . length () ) . append ( ’\ n ’) ; for ( int i = 0; i < ia . length () ; ++ i ) sb . append ( ia . getElementAt ( i ) ) . append ( ’ ’) ; sb . append ( ’\ n ’) ; return sb . toString () ; } 187 188 189 190 191 192 193 194 195 static int [] readArray ( Scanner scanner ) { int n = scanner . nextInt () ; int [] a = new int [ n ]; for ( int i = 0; i < n ; ++ i ) { a [ i ] = scanner . nextInt () ; } return a ; } 196 197 198 199 200 201 202 203 204 static int [] generateRandomArray ( int k ) { Random rnd = new Random ( k ) ; int n = rnd . nextInt (8) + 2; int a [] = new int [ n ]; for ( int i = 0; i < n ; ++ i ) { a [ i ] = rnd . nextInt (20) - 5; } 9 10 Глава 1. Обjектно-ориентирано програмирање во Jава return a ; 205 } 206 207 208 } Прво се дефинира класата IntegerArray во коjа се чува само една променлива на инстанца - низа од цели броеви a . Со цел класата да се прогласи за immutable , потребно е класата да се декларира како final . За класата е потребно да се дефинира конструктор со еден аргумент - низа од цели броеви чиjа содржина ќе биде ископирана во низата од цели броеви што се чува како променлива на инстанца. За таа цел се користи готовата функционалност Arrays.copyOf(...) . Методите length() , getElementAt(i) , sum() и average() имплементираат тривиjални операции со низи од цели броеви. Методот IntegerArray getSorted() соодветно треба да врати нов обjект од класата IntegerArray во коj низата со цели броеви ќе биде со истите вредности од низата од this обjектот, но истите ќе бидат сортирани. Тука е важно да се креира копиjа од низата што се чува во this обjектот, копиjата да се подреди (со готовата функционалност Arrays.sort()) и да се прати како аргумент во конструктор при креирање на нов обjект од класата IntegerArray . Честа грешка при решавање на ова задача може да биде повикот на Arrays.sort() на низата што се чува во this обjектот, што ќе допринесе до промена на низата во this обjектот и во обjектот што е резултат од методот. Во методот IntegerArray concat (IntegerArray ia) потребно е да се врати нов обjект коj во себе ќе има низа коjа ќе се добие како резултат на споjување на низата од this обjектот и низата од обjектот ia . За да не се случи промена на низата во this обjектот, прво е потребно да се инициjализира низа од цели броеви со должина еднаква на сумата на должините на двете низи. Потоа, со функционалноста System.arraycopy() се копира содржината на низата од this обjектот, а веднаш по неа и содржината на низата во ia обjектот. Вака добиената низа се праќа како аргумент во конструкторот при креирање на резултантиот обjект. Функционалностите со влезни и излезни потоци на податоци, што се потребни во класата ArrayReader , ќе бидат подетално обjаснети во следните поглавjа. 1.2 Наследување и композициjа Во рамките на ова поглавjе ќе бидат разгледани два примери кои укажуваат на начинот на коj правилно се имплементира наследување, полиморфиразм и композициjа во Jава. Во првата задача е ставен акцент на наследување и полиморфизам, додека во втората задача е претставен концептот на композициjа на обjекти. Проблем 1.2 Треба да се креира апликациjа за банка коjа ќе управуваа со сметките на повеќе корисниците и ќе врши трансакции помеѓу нив. Банката работи со долари. За потребите на ваквата апликациjа треба да се напишат класите Account , Transaction и Bank . Класата Account претставува една сметка на еден корисник и треба да ги чува следните податоци: • Име на корисникот, • единствен идентификационен броj ( long ) • тековното салдо на сметката. 1.2 Наследување и композициjа 11 Оваа класа исто така треба да ги имплементира и следниве методи • Account(String name, double balance) – конструктор со параметри (id-то треба да се генерира со помош на класата java.util.Random ) • getBalance():double • getName():String • getId():long • setBalance(double balance) • toString():String – враќа стринг во следниот формат Name:FirstName LastName Balance:20.00\$ Класата Transaction претставува трансакциjа (префрлување пари од една на друга сметка), од страна на банката за што честопати се наплаќа провизиjа. За почеток треба да се напише класата Transaction со податочни членови за идентификационите броеви на две сметки, едната од коjа се одземаат парите и друга на коjа се додаваат парите, текстуален опис и износ на трансакциjата. За оваа класа треба да се имплементираат методите: • Transaction(long fromId, long toId, Stirng desc, double amount) • getAmount():double • getFromId():long • getToId():long Оваа класа треба да апстрактна бидеjќи не е наменета директно да се користи туку само како основна класа за изведување на други класи. Како што беше претходно напоменато, банката наплаќа провизиjа за одредени трансакции. Има два типа на провизиjа, фискна сума и процент. Каj фиксна сума за било коjа трансакциjа без разлика на износот на трансакциjата се наплаќа исто провизиjа (пример 10$). Каj процент за секоj еден долар од трансакциjата банката наплаќа одреден процент провизиjа (на пример 5%, или 5 центи на секоj долар – процентите секогаш се целоброjни и провизиjа се наплаќа само на цели долари). За да се прави разлика меѓу различните типови на провизиjа, треба да се напишат уште две класи кои ќе наследуваат од Transaction кои треба да се именуваат како FlatAmountProvisionTransaction и FlatPercentProvisionTransaction . Првата класа FlatAmountProvisionTransaction треба да содржи: • FlatAmountProvisionTransaction(long fromId, long toId, double amount, double flatProvision) - соодветен конструктор коj го инициjализира полето за опис на FlatAmount и • getFlatAmount():double . Слично, класата FlatPercentProvisionTransaction треба да има: • FlatPercentProvisionTransaction (long fromId, long toId, double amount, int centsPerDolar) - конструктор коj го инициjализира полето за опис на FlatPercent и • getPercent():int . 12 Глава 1. Обjектно-ориентирано програмирање во Jава Исто така треба да се препокрие equals(Object o):boolean методот и за двете класи. За краj треба да се имплементира класата Bank коjа ги чува сметките од своите корисници и дополнително врши трансакции. Класата освен сметките на своите корисници, треба да ги чува и сопственото име и вкупната сума на трансфери како и вкупната наплатена провизиjа од страна на банката за сите трансакции. Класата Bank треба да ги има следните методи: • Bank(String name, Account accounts[]) – конструктор со соодветните параметри (направете сопствена копиjа на низата од сметки) • makeTransaction(Transaction t):boolean – врши проверка дали корисникот ги има потребните средства на сметка и дали и двете сметки на кои се однесува трансакциjата се нависитина во банката и ако и двата услови се исполнето jа извршува трансакциjата и враќа true, во спротивно враќа false • totalTransfers():double – jа дава вкупната сума на пари кои се префрлени во сите трансакции до сега • totalProvision():double – jа дава вкупната провизиjа наплатена од банката за сите извршени трансакции до сега • toString():String - го враќа името на банката во посебна линиjа во формат Name: Sopanska Banka A.D. Skopje по што следат податоците за сите корисници. Провизиjата се наплаќа така што на основната сума на трансакциjата се додава вредноста не провизиjата и таа сума се одзема од првата сметка. За сите класи да се напишат соодветни equals и hashCode методи. За задачите во кои се очекува да се користат концептите на наследување и полиморфизам, наjважно е на почетокот да се идентификуваат врските помеѓу податоците и соодветно да се дизаjнираат класите и нивните зависности. Во оваа задача, особено важни се дефинициите на класите за трансакции. Прво, се дефинира класата Transaction коjа е апстрактна класа и од неа се изведуваат две класи за конкретните типови на трансакции. Во задачата не е побарано, но, за класата Transaction да биде апстрактна потребно е да се дефинира наjмалку еден апстрактен метод. Апстрактни методи се методите кои имаат дефинициjа, но, не и имплементациjа. Во овоj случаj, тоа би бил методот коj треба да jа врати провизиjата за дадената трансакциjа ( double getProvision() ). Овоj метод треба соодветно да биде препокриен во изведените класи согласно барањатa на задачата. На овоj начин, со употреба на полиморфизам, сите обjекти инстанцирани од изведените класи на Transacation би имале различно однесување согласно имплементациjата на методот getProvision . 1 import java . util .*; 2 3 4 5 class Bank { private String name ; private Account [] accounts ; 6 7 8 private double totalTransfers ; private double totalProvision ; 9 10 11 12 public Bank ( String name , Account [] accounts ) { this . name = name ; this . accounts = Arrays . copyOf ( accounts , accounts . length ) ; 1.2 Наследување и композициjа this . totalTransfers = 0.0; this . totalProvision = 0.0; 13 14 15 } 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public boolean makeTransaction ( Transaction transaction ) { Optional < Account > from = findAccount ( transaction . getFromId () ) ; Optional < Account > to = findAccount ( transaction . getToId () ) ; if ( from . isPresent () && to . isPresent () ) { Account fromAccount = from . get () ; Account toAccount = to . get () ; double fromBalance = fromAccount . getBalance () ; double toBalance = toAccount . getBalance () ; double amount = transaction . getAmount () ; double provision = transaction . getProvision () ; if ( provision + amount <= fromBalance ) { if ( fromAccount . getId () == toAccount . getId () ) { fromBalance = toBalance = ( fromBalance - provision ) ; } else { fromBalance -= provision + amount ; toBalance += amount ; } fromAccount . setBalance ( fromBalance ) ; toAccount . setBalance ( toBalance ) ; updateTotals ( amount , provision ) ; return true ; } } return false ; } 43 44 45 46 47 void updateTotals ( double amount , double provision ) { this . totalTransfers += amount ; this . totalProvision += provision ; } 48 49 50 51 52 53 Optional < Account > findAccount ( long id ) { return Arrays . stream ( accounts ) . filter ( each -> each . getId () == id ) . findAny () ; } 54 55 56 57 public static String toString ( long amount ) { return String . format ( " %.2 f$ " , amount / 100.) ; } 58 59 60 61 62 63 64 65 66 67 68 @override public String toString () { StringBuilder result = new StringBuilder () ; result . append ( " Name : " ) ; result . append ( name ) ; result . append ( " \ n \ n " ) ; Arrays . stream ( accounts ) . forEach ( each -> result . append ( each . toString () ) ) ; return result . toString () ; } 69 70 71 72 73 @override public boolean equals ( Object o ) { if ( this == o ) return true ; if ( o == null || getClass () != o . getClass () ) return false ; 13 14 Глава 1. Обjектно-ориентирано програмирање во Jава Bank bank = ( Bank ) o ; return Objects . equals ( name , bank . name ) && Arrays . equals ( accounts , bank . accounts ) && Objects . equals ( totalTransfers , bank . totalTransfers ) && Objects . equals ( totalProvision , bank . totalProvision ) ; 74 75 76 77 78 } 79 80 @override public int hashCode () { return Objects . hash ( name , accounts , totalTransfers , totalProvision ) ; } 81 82 83 84 85 86 public double totalProvision () { return totalProvision ; } 87 88 89 90 public double totalTransfers () { return totalTransfers ; } 91 92 93 94 public Account [] getAccounts () { return accounts ; } 95 96 97 98 } 99 100 101 102 103 class Account { private String name ; private long id ; private double balance ; 104 105 106 107 108 109 public Account ( String name , double balance ) { this . name = name ; this . balance = balance ; this . id = new Random () . nextLong () ; } 110 111 112 113 public String getName () { return name ; } 114 115 116 117 public void setName ( String name ) { this . name = name ; } 118 119 120 121 public long getId () { return id ; } 122 123 124 125 public void setId ( long id ) { this . id = id ; } 126 127 128 129 public double getBalance () { return balance ; } 130 131 132 133 134 public void setBalance ( double balance ) { this . balance = balance ; } 1.2 Наследување и композициjа @override public String toString () { return String . format ( " Name : % s \ nBalance : % s \ n " , name , balance ) ; } 135 136 137 138 139 140 @override public boolean equals ( Object o ) { if ( this == o ) return true ; if ( o == null || getClass () != o . getClass () ) return false ; Account account = ( Account ) o ; return Objects . equals ( id , account . id ) ; } 141 142 143 144 145 146 147 148 @override public int hashCode () { return Objects . hash ( id ) ; } 149 150 151 152 153 } 154 155 class F l a t A m o u n t P r o v i s i o n T r a n s a c t i o n extends Transaction { 156 private final double flatAmount ; 157 158 public F l a t A m o u n t P r o v i s i o n T r a n s a c t i o n ( long fromId , long toId , double amount , double flatAmount ) { super ( fromId , toId , amount , " FlatAmount " ) ; this . flatAmount = flatAmount ; } 159 160 161 162 163 164 165 @override public double getProvision () { return flatAmount ; } 166 167 168 169 170 public double getFlatAmount () { return flatAmount ; } 171 172 173 174 } 175 176 177 class F l a t P e r c e n t P r o v i s i o n T r a n s a c t i o n extends Transaction { private final int percent ; 178 public F l a t P e r c e n t P r o v i s i o n T r a n s a c t i o n ( long fromId , long toId , double amount , int percent ) { super ( fromId , toId , amount , " FlatPercent " ) ; this . percent = percent ; } 179 180 181 182 183 184 185 @override public double getProvision () { long amount = ( long ) this . amount / 100; return percent * amount ; } 186 187 188 189 190 191 public int getPercent () { return percent ; } 192 193 194 195 } 15 16 Глава 1. Обjектно-ориентирано програмирање во Jава 196 197 198 199 200 201 202 abstract class Transaction { final long fromId ; final long toId ; final double amount ; final String description ; 203 public Transaction ( long fromId , long toId , double amount , String description ) { this . fromId = fromId ; this . toId = toId ; this . amount = amount ; this . description = description ; } 204 205 206 207 208 209 210 211 public long getFromId () { return fromId ; } 212 213 214 215 public long getToId () { return toId ; } 216 217 218 219 public double getAmount () { return amount ; } 220 221 222 223 public abstract double getProvision () ; 224 225 @override public boolean equals ( Object o ) { if ( this == o ) return true ; if ( o == null || getClass () != o . getClass () ) return false ; Transaction that = ( Transaction ) o ; return Objects . equals ( fromId , that . fromId ) && Objects . equals ( toId , that . toId ) && Objects . equals ( amount , that . amount ) && Objects . equals ( description , that . description ) ; } 226 227 228 229 230 231 232 233 234 235 236 @override public int hashCode () { return Objects . hash ( fromId , toId , amount , description ) ; } 237 238 239 240 241 public String getDescription () { return description ; } 242 243 244 245 } 246 247 public class BankTester { 248 249 250 251 252 253 254 255 256 public static void main ( String [] args ) { Scanner jin = new Scanner ( System . in ) ; String bank_name = jin . nextLine () ; int num_accounts = jin . nextInt () ; jin . nextLine () ; Account accounts [] = new Account [ num_accounts ]; for ( int i = 0; i < num_accounts ; ++ i ) accounts [ i ] = new Account ( jin . nextLine () , 1.2 Наследување и композициjа 17 Double . parseDouble ( jin . nextLine () ) ) ; Bank bank = new Bank ( bank_name , accounts ) ; while ( true ) { String line = jin . nextLine () ; switch ( line ) { case " stop " : return ; case " transaction " : String descrption = jin . nextLine () ; double amount = Double . parseDouble ( jin . nextLine () ) ; double parameter = Double . parseDouble ( jin . nextLine () ) ; int from_idx = jin . nextInt () ; int to_idx = jin . nextInt () ; jin . nextLine () ; Transaction t = getTransaction ( descrption , from_idx , to_idx , amount , parameter , bank ) ; System . out . println ( " Transaction amount : " + t . getAmount () ) ; System . out . println ( " Transaction description : " + t . getDescription () ) ; System . out . println ( " Transaction successful ? " + bank . makeTransaction ( t ) ) ; break ; case " print " : System . out . println ( bank . toString () ) ; System . out . println ( " Total provisions : " + bank . totalProvision () ) ; System . out . println ( " Total transfers : " + bank . totalTransfers () ) ; System . out . println () ; break ; } } 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 } 293 294 private static Transaction getTransaction ( String description , int from_idx , int to_idx , double amount , double o , Bank bank ) { switch ( description ) { case " FlatAmount " : return new F l a t A m o u n t P r o v i s i o n T r a n s a c t i o n ( bank . getAccounts () [ from_idx ]. getId () , bank . getAccounts () [ to_idx ]. getId () , amount , o); case " FlatPercent " : return new F l a t P e r c e n t P r o v i s i o n T r a n s a c t i o n ( bank . getAccounts () [ from_idx ]. getId () , bank . getAccounts () [ to_idx ]. getId () , amount , ( int ) o ) ; } return null ; } 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 } Методот makeTransacation(Transaction t) во класата Bank е наjкомплексен за имплементациjа. Во истиот, потребно e да се извршат неколку валидации пред да 18 Глава 1. Обjектно-ориентирано програмирање во Jава се реализира самата трансакциjа. Дополнително, потребно е да се внимава методот да не стане предолг, а со тоа и тежок за разбирање и тестирање. За таа цел, за првата валидациjа (дали постоjат сметките на праќачот и примачот) се користи помошен приватен метод findAccount(long id) . Доколку се наjдат и двете сметки, се проверува дали праќачот има средства за да го покрие износот на трансакциjа, но, и провизиjата за истата. Доколку тоj услов е исполнет, од сметката на праќачот се одзема износот и провизиjата на трансакциjата, а на сметката на примачот се додава износот на трансакциjата. Соодветно се ажурираат и записите на банката со методот updateTotals(long amount, long provision) . Проблем 1.3 Да се имплементира класа Canvas на коjа ќе чуваат различни форми. За секоjа форма се чува: • id:String • color:Color (enum дадена) Притоа сите форми треба да имплментираат два интерфеjси: • Scalable - дефиниран со еден метод void scale(float scaleFactor) за соодветно зголемување/намалување на формата за дадениот фактор • Stackable - дефиниран со еден метод float weight() коj враќа тежина на формата (се пресметува како плоштина на соодветната форма) Во класата Canvas да се имплементираат следните методи: • void add(String id, Color color, float radius) за додавање круг • void add(String id, Color color, float width, float height) за додавање правоаголник – При додавањето на нова форма, во листата со форми таа треба да се смести на соодветното место според неjзината тежина. Елементите постоjано се подредени според тежината во опаѓачки редослед. • void scale(String id, float scaleFactor) - метод коj jа скалира формата со даденото id за соодветниот scaleFactor . Притоа ако има потреба, треба да се изврши преместување на соодветните форми, за да се задржи подреденоста на елементите. Не смее да се користи сортирање на листата. • toString() - враќа стринг составен од сите фигури во нов ред. За секоjа фигура се додава: – C: [id:\%5s] [color:\%10s] [weight:\%10.2f] ако е круг – R: [id:\%5s] [color:\%10s] [weight:\%10.2f] ако е правоаголник Во оваа задача треба да постоjат два типа на форми (кругови и правоаголници), при што и двете форми ќе бидат скалабилни и ќе може да се измери нивната тежина. Скалабилноста на формите и мерењето на нивната тежина е овозможено преку два интерфеjси Scalable и Stackable што треба да ги имплементираат и двете форми. Бидеjќи круговите и правоаголниците имаат два заеднички податоци (ИД и боjа), се дефинира основна класа Shape во коjа се дефинирани овие полиња. Класите Circle и Rectangle соодтвено наследуваат од класата Shape . Бидеjќи сите типови на форми треба да ги имплементираат интерфеjсите Scalable и Stackable , нивната имплементациjа се декларира директно во класата Shape . Во оваа класа, методите нема да се имплементираат, па со тоа класата станува апстрактна класа. 1.2 Наследување и композициjа 19 Класите Rectangle и Circle наследуваат од Shape и ги имплементираат методите дефинирани во рамки на интерфеjсите. Во методот scale(float scaleFactor) се множат сите димензии на формата со scaleFactor , додека пак во методот weight() се пресметува плоштината на формата според соодветната математичка формула. Во класата Canvas се чува листа од форми (обjекти инстанцирани од изведените класи на класата Shape ) кои се референцирани од референци од класата Shape . Во ова задача е претставен и концептот на преопотоварување (анг. overloading) на методи, преку имплементациjата на методите со име add и нивниот различен потпис. 1 2 3 import java . util . ArrayList ; import java . util . List ; import java . util . Scanner ; 4 5 6 7 enum Color { RED , GREEN , BLUE } 8 9 10 11 abstract class Shape implements Scaleble , Stackable { protected String id ; protected Color color ; 12 public Shape ( String id , Color color ) { this . id = id ; this . color = color ; } 13 14 15 16 17 } 18 19 20 21 interface Stackable { public float weight () ; } 22 23 24 25 interface Scaleble { public void scale ( float scaleFactor ) ; } 26 27 28 class Circle extends Shape { float r ; 29 30 31 32 33 public Circle ( String id , Color color , float r ) { super ( id , color ) ; this . r = r ; } 34 35 36 37 38 @Override public void scale ( float scaleFactor ) { r *= scaleFactor ; } 39 40 41 42 43 @Override public float weight () { return ( float ) ( Math . PI * r * r ) ; } 44 45 46 47 48 49 50 @Override public String toString () { return String . format ( " C : % -5 s % -10 s %10.2 f \ n " , id , color , weight () ) ; } 20 51 Глава 1. Обjектно-ориентирано програмирање во Jава } 52 53 54 class Rectangle extends Shape { float width , height ; 55 public Rectangle ( String id , Color color , float width , float height ) { super ( id , color ) ; this . width = width ; this . height = height ; } 56 57 58 59 60 61 62 @Override public void scale ( float scaleFactor ) { width *= scaleFactor ; height *= scaleFactor ; } 63 64 65 66 67 68 @Override public float weight () { return width * height ; } 69 70 71 72 73 @Override public String toString () { return String . format ( " R : % -5 s % -10 s %10.2 f \ n " , id , color , weight () ) ; } 74 75 76 77 78 79 80 } 81 82 83 class Canvas { List < Shape > shapes ; 84 85 86 87 public Canvas () { shapes = new ArrayList < Shape >() ; } 88 89 90 91 92 93 94 95 96 int find ( float weight ) { for ( int i = 0; i < shapes . size () ; ++ i ) { if ( shapes . get ( i ) . weight () < weight ) { return i ; } } return shapes . size () ; } 97 98 99 100 101 102 public void add ( String id , Color color , float radius ) { Circle c = new Circle ( id , color , radius ) ; int index = find ( c . weight () ) ; this . shapes . add ( index , c ) ; } 103 104 105 106 107 108 109 public void add ( String id , Color color , float width , float height ) { Rectangle rect = new Rectangle ( id , color , width , height ) ; int index = find ( rect . weight () ) ; this . shapes . add ( index , rect ) ; } 110 111 public void scale ( String id , float scaleFactor ) { 1.2 Наследување и композициjа Shape s = null ; for ( int i = shapes . size () - 1; i >= 0; i - -) { if ( shapes . get ( i ) . id . equals ( id ) ) { s = shapes . get ( i ) ; shapes . remove ( i ) ; break ; } } s . scale ( scaleFactor ) ; int index = find ( s . weight () ) ; shapes . add ( index , s ) ; 112 113 114 115 116 117 118 119 120 121 122 } 123 124 @Override public String toString () { StringBuilder sb = new StringBuilder () ; for ( Shape shape : shapes ) { sb . append ( shape ) ; } return sb . toString () ; } 125 126 127 128 129 130 131 132 133 } 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 public class ShapesTest { public static void main ( String [] args ) { Scanner scanner = new Scanner ( System . in ) ; Canvas canvas = new Canvas () ; while ( scanner . hasNextLine () ) { String line = scanner . nextLine () ; String [] parts = line . split ( " " ) ; int type = Integer . parseInt ( parts [0]) ; String id = parts [1]; if ( type == 1) { Color color = Color . valueOf ( parts [2]) ; float radius = Float . parseFloat ( parts [3]) ; canvas . add ( id , color , radius ) ; } else if ( type == 2) { Color color = Color . valueOf ( parts [2]) ; float width = Float . parseFloat ( parts [3]) ; float height = Float . parseFloat ( parts [4]) ; canvas . add ( id , color , width , height ) ; } else if ( type == 3) { float scaleFactor = Float . parseFloat ( parts [2]) ; System . out . println ( " ORIGNAL : " ) ; System . out . print ( canvas ) ; canvas . scale ( id , scaleFactor ) ; System . out . printf ( " AFTER SCALING : % s %.2 f \ n " , id , scaleFactor ) ; System . out . print ( canvas ) ; } } } } 21 2. Исклучоци Вообичаено, при решавање на одредени проблеми потребно е да се изградат две одделни стратегии. Првата стратегиjа се однесува на посакуваниот тек на настани, кога решавањето на проблемот се одвива согласно направениот план и реализациjата на парциjалните активности е според предвидените очекувања. Втората стратегиjа се однесува на предвидување и решавање на ситуациите кои не се во согласност со посакуваниот тек на настани. Наjчесто, оваа стратегиjа се однесува на справување со исклучителните ситуации во кои можеме да се наjдеме при решавање на проблемите. Овоj пристап, целосно може да се преслика при решавање на софтверските задачи. Повеќе програмски jазици нудат директна поддршка за справување со т.н. исклучителни ситуации во кои може да се наjде нашата програма. Тоа се прави со помош на маханизамот исклучок. Исклучоците се настани што се случуваат за време на извршувањето на програмите и истите го менуваат неjзиниот нормален тек (на пр. делење со нула, пристап до низа од елементи надвор од неjзините граници, итн.). Во Jава, исклучок е обjект што енкапсулира настан за грешка што се случил во методот и содржи: • информации за грешката, вклучуваjќи го и неговиот тип • состоjбата на програмата пред поjавата на грешката • други дополнителни информации Исклучокот може да биде фрлен во даден метод и истиот да биде фатен, се со цел правилно и навремено да се справиме со него. Исклучоците се користат за опишување на многу различни типови на состоjби на грешка. Исклучоците го одделуваат кодот за справување со настанатите грешки од регуларниот код. Користењето на исклучоците обезбедува почист и почитлив код. Со помош на исклучоците, грешките се пропагираат, односно вгнездените методи не мора експлицитно да се справуваат со грешките што доведува до помалку работа и посигурен код. Дефинирањето на класи за исклучоци овозможува групирање на типовите на грешки според нивната родител класа и/или нивна идентификациjа според нивната вистинска класа. Со други зборови, исклучоците го стандардизираат справувањето со 24 Глава 2. Исклучоци грешки во рамките на програмите. Во програмскиот jазик Jава, исклучок обjектот е секогаш инстанца на класа изведена од Throwable . Jава дефинира неколку типови на исклучоци во рамки на постоечките библиотеки. Исто така, Jава им овозможува на корисниците да дефинираат сопствени типови на исклучоци, ако вградените исклучоци не одговараат на нивните потреби. 2.1 Вградени исклучоци Вградените исклучоци се достапни во стандардните библиотеки на Jава. Овие исклучоци се погодни за обjаснување на одредени ситуации во кои се генерираат грешки. Во продолжение, даден е списокот на важни вградени исклучоци во Jава. • ArithmeticException - се фрла при поjава на исклучителна состоjба во аритметичка операциjа. • ArrayIndexOutOfBoundsException - се фрла за да се покаже дека пристапот до елемент од низата се прави со невалиден индекс. Индексот е негативен, поголем од или еднаков на големината на низата. • ClassNotFoundException - овоj исклучок се фрла кога се обидуваме да пристапиме до класа чиjа дефинициjа не е пронаjдена • FileNotFoundException - овоj исклучок се фрла кога датотеката не е достапна или истата не се отвора. • IOException - се фрла кога операциjата за влез-излез нема да успее или истата ќе биде прекината. • InterruptedException - се фрла кога конкретна нитка чека, спие или процесира, и истата е прекината. • NoSuchFieldException - се фрла кога дадена класа не го содржи специфицираното поле (или променлива). • NoSuchMethodException - се фрла при обид за пристап до метод што не постои. • NullPointerException - овоj исклучок се фрла при пристап до празен (анг. null) обjект. • NumberFormatException - овоj исклучок се фрла кога методот не може да конвертира низа од знаци во нумерички формат. • RuntimeException - претставува било каков исклучок што се случува за време на извршување на програмата. Проблем 2.1 Да се напише пример програма за користење на вградената имплемен- тациjа на исклучокот ArrayIndexOutOfBoundException 1 2 3 4 5 6 7 8 9 10 11 12 13 class A r r a y I n d e x O u t O f B o u n d T e s t e r { public static void main ( String args []) { try { int a [] = new int [10]; a [15] = 1; } catch ( A r r a y I n d e x O u t O f B o u n d s E x c e p t i o n e ) { System . out . println ( " Array Index is Out Of Bounds " ) ; } } } 2.2 Кориснички дефинирани исклучоци 25 Проблем 2.2 Да се напише пример програма за користење на вградената имплемен- тациjа на исклучокот FileNotFoundException 1 2 3 4 import java . io . File ; import java . io . F ileNot Found Except ion ; import java . io . FileReader ; class File_notFound_Demo { 5 public static void main ( String args []) { try { // Following file does not exist File file = new File ( " E :// file . txt " ) ; FileReader fr = new FileReader ( file ) ; } catch ( Fil eNotF oundEx ceptio n e ) { System . out . println ( " File does not exist " ) ; } } 6 7 8 9 10 11 12 13 14 15 } 2.2 Кориснички дефинирани исклучоци Кодот што се извршува може да наиде на проблем што не може да се опише од стандардните (вградени) класи на исклучоци. Во овоj случаj, потребно е корисникот да дефинира своj тип на исклучок преку наследување на класата Exception или било коjа друга класа коjа директно или индиректно наследува од класата Exception . Вообичаено, се имплементира стандардниот конструктор и преотоварен конструктор со сите детали за проблемот што корисникот сака да ги пренесе. Проблем 2.3 Треба да се развие систем за електронска нарачка од пицериjа. Менито на пицериjата се состои од следново: • Pizza: – Standard: 10$ – Pepperoni: 12$ – Vegetarian: 8$ • Extra – Ketchup 3$ – Coke 5$ За да се претстави менито, секоjа ставка треба да имплементира интерфеjс Item коj опишува една ставка од менито и ги дефинира следниве методи: • int getPrice() - jа дава цената за конкретната ставка Следно, потребно е да се дефинираат две класи ExtraItem и PizzaItem за да може да правите разлика меѓу пици и останатите работи во нарачката. И двете класи треба да имаат еден конструктор коj прима еден String аргумент. • ExtraItem(String type) - валидни вредности за type се Coke, и Ketchup • PizzaItem(String type) - валидни вредности за type се Standard , Pepperoni и Vegetarian Ако за type се проследи некоjа невалидна вредност (коjа jа нема на менито) треба да се фрли исклучок InvalidExtraTypeException , односно 26 Глава 2. Исклучоци InvalidPizzaTypeException . Последно имплементираjте jа класата Order . Таа треба да ги нуди следните функционалности: • Order() - креира нова празна нарачка • addItem(Item item, int count) - соодветната ставка се додава во нарачката ( count означува колку примероци сакаме од дадената ставка). Aко count е поголем од 10 се фрла исклучок ItemOutOfStockException(item) . Доколку во нарачката веќе jа има соодветната ставка Item тогаш истата се заменува со нова. Следниот код резултира со нарачка со една стандардна пица: Order order = new Order(); order.addItem(new PizzaItem("Standard"), 2); order.addItem(new PizzaItem("Standard"), 1); • getPrice():int - jа враќа вкупната цена на нарачката • displayOrder() - jа печати содржината на нарачката со соодветни редни броеви пред секоjа ставка, името, количината и збирна сума на ставката, како и вкупна сума за целата нарачка. За редниот броj се резервирани 3 места порамнети во десно, за имињата на ставките се резервирани 15 места со порам-нување од лево, за кардиналноста две места порамнети во десно и за цената на една ставка 5 места порамнети во десно. За “Total:” се резервирани 22 места со порамнување од лево и за вкупната цена 5 места порамнети во десно. Пример: 1.Standard 2.Vegetarian 3.Coke Total: x 2 x 1 x 3 20$ 8$ 15$ 43$ Редоследот по коj се печатат ставките е оноj по коj тие се внесувани во нарачката. Доколку некоjа ставка се внесе повторно неjзиното место не се менува. • removeItem(int idx) - се отстранува нарачката со даден индекс (сите нарачки со поголеми индекси се поместуваат во лево). Доколку не постои нарачка со таков индекс треба да се фрли исклучок ArrayIndexOutOfBoundsException(idx) • lock() - jа заклучува нарачката. За да може нарачката да се заклучи треба истата да има барем една ставка, во спротивно фрлете исклучок EmptyOrderException . Откако ќе се заклучи нарачката треба веќе да не може да се менува со методите removeItem , addItem . Повикот на овие методи резултира со исклучок од типот OrderLockedException . При решавање на задачи во кои има можност да се случи неочекуван тек на настани, потребно е да се употреби механизмот за фрлање и справување на исклучоци. При решавање на вакви задачи важно е: 1. Да се идентификува каде во кодот би можел да се поjави неочекуваниот настан, па соодветно таму да се креира и фрли исклучок. 2.2 Кориснички дефинирани исклучоци 27 2. Да се опфати повикот кон потенциjално ризичниот код (метод, конструктор) со try/catch блок за да се дефинира однесување при исклучок. Треба да се има во предвид дека интерпретерите на повеќето околини за развоj сами даваат сугестиjа во коj дел од кодот треба да се додаде овоj блок. 1 2 3 4 import import import import java . util . ArrayList ; java . util . Optional ; java . util . Scanner ; java . util . stream . IntStream ; 5 6 7 interface Item { boolean isPizza () ; 8 int getPrice () ; 9 10 String getType () ; 11 12 } 13 14 15 enum ExtraType { Coke (0) , OrangeJuice (1) , Ketchup (2) ; 16 private int value ; private int cost ; 17 18 19 ExtraType ( int value ) { this . value = value ; if ( value == 0) cost = 5; else if ( value == 1) cost = 8; else if ( value == 2) cost = 3; } 20 21 22 23 24 25 26 public int getCost () { return cost ; } 27 28 29 30 public int getValue () { return value ; } 31 32 33 34 } 35 36 37 38 39 enum PizzaType { Standard (0) , Pepperoni (1) , Extra_cheese (2) , Vegetarian (3) ; private int value ; private int cost ; 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 PizzaType ( int value ) { this . value = value ; switch ( value ) { case 0: cost = 10; break ; case 1: cost = 12; break ; case 2: cost = 15; break ; case 3: cost = 8; break ; } 28 Глава 2. Исклучоци } 57 58 public int getCost () { return cost ; } 59 60 61 62 public int getValue () { return value ; } 63 64 65 66 } 67 68 69 70 71 class EmptyOrder extends Exception { public EmptyOrder () { this ( " The order must not be empty " ) ; } 72 public EmptyOrder ( String message ) { super ( message ) ; } 73 74 75 76 } 77 78 79 80 class ExtraItem implements Item { private ExtraType type ; 81 public ExtraItem ( String type ) throws I n v al i d E x t r a T y pe E x c e p t i o n { try { this . type = ExtraType . valueOf ( type ) ; } catch ( Il l e ga l A rg u m en t E xc e p ti o n e ) { throw new I n va l i d E x t r a T y pe E x c e p t i o n ( type ) ; } } 82 83 84 85 86 87 88 89 public ExtraItem ( ExtraType type ) { this . type = type ; } 90 91 92 93 public int getPrice () { return type . getCost () ; } 94 95 96 97 public boolean isPizza () { return false ; } 98 99 100 101 @Override public String getType () { return type . name () ; } 102 103 104 105 106 } 107 108 109 class PizzaItem implements Item { private PizzaType type ; 110 111 112 113 114 115 116 117 public PizzaItem ( String type ) throws I n v al i d P i z z a T y pe E x c e p t i o n { try { this . type = PizzaType . valueOf ( type ) ; } catch ( Il l e ga l A rg u m en t E xc e p ti o n e ) { throw new I n va l i d P i z z a T y pe E x c e p t i o n () ; } } 2.2 Кориснички дефинирани исклучоци 118 @Override public int getPrice () { return type . getCost () ; } 119 120 121 122 123 public boolean isPizza () { return true ; } 124 125 126 127 @Override public String getType () { return type . name () ; } 128 129 130 131 132 } 133 134 135 136 137 class I n v a l i dE x t r a T y p e E xc e p t i o n extends Exception { public In v a l i d E x t r a Ty p e E x c e p t i on ( String type ) { super ( type ) ; } 138 public In v a l i d E x t r a Ty p e E x c e p t i on () { this ( " Nepoznat tip " ) ; } 139 140 141 142 } 143 144 145 class I t em O ut Of St o ck Ex c ep ti on extends Exception { private Item item ; 146 public I te m Ou tO fS t oc kE xc e pt io n ( Item item ) { this . item = item ; } 147 148 149 150 public I te m Ou tO fS t oc kE xc e pt io n () { this ( " Unknown item is out of stock " ) ; } 151 152 153 154 public I te m Ou tO fS t oc kE xc e pt io n ( String message ) { super ( message ) ; } 155 156 157 158 public Item getItem () { return item ; } 159 160 161 162 } 163 164 165 166 167 class Order { private class OrderItem { private final Item item ; private int count ; 168 169 170 171 public Item getItem () { return item ; } 172 173 174 175 public int getCount () { return count ; } 176 177 178 public OrderItem ( Item item , int count ) { this . item = item ; 29 30 Глава 2. Исклучоци this . count = count ; 179 } 180 181 public void setCount ( int count ) { this . count = count ; } 182 183 184 185 public int getPrice () { return getItem () . getPrice () * getCount () ; } 186 187 188 189 190 } 191 192 private ArrayList < OrderItem > items ; 193 194 private boolean locked ; 195 196 197 198 199 public Order () { items = new ArrayList < >() ; locked = false ; } 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 public void addItem ( Item item , int count ) throws OrderLockedException , I te mO ut O fS to c kE xc ep t io n { if ( locked ) { throw new OrderLockedException () ; } if ( count > 10) { throw new I t em Ou tO f St oc kE x ce pt i on ( item ) ; } Optional < OrderItem > orderItem = items . stream () . filter ( each -> each . getItem () . getType () . equals ( item . getType () ) ) . findFirst () ; if ( orderItem . isPresent () ) { orderItem . ifPresent ( oi -> oi . setCount ( count ) ) ; return ; } items . add ( new OrderItem ( item , count ) ) ; } 219 220 221 222 223 224 225 public void removeItem ( int index ) throws OrderLockedException { if ( locked ) { throw new OrderLockedException () ; } items . remove ( index ) ; } 226 227 228 229 230 231 public int getPrice () { return items . stream () . mapToInt ( OrderItem :: getPrice ) . sum () ; } 232 233 234 235 236 237 238 239 public void displayOrder () { IntStream . range (0 , items . size () ) . forEach ( i -> { OrderItem order = items . get ( i ) ; System . out . printf ( " %3 d .% -15 sx %2 d %5 d$ \ n " , i + 1 , order . getItem () . getType () , order . getCount () , order . getPrice () ) ; 2.2 Кориснички дефинирани исклучоци }) ; System . out . printf ( " % -22 s %5 d$ \ n " , " Total : " , getPrice () ) ; 240 241 } 242 243 public void lock () throws EmptyOrder { if ( items . size () == 0) { throw new EmptyOrder () ; } locked = true ; } 244 245 246 247 248 249 250 } 251 252 253 254 255 class I n v a l i dP i z z a T y p e E xc e p t i o n extends Exception { public In v a l i d P i z z a Ty p e E x c e p t i on () { } } 256 257 258 class O rderLockedException extends Exception { private static final long serialVersionUID = 1 L ; 259 public OrderLockedException () { } 260 261 262 } 263 264 public class PizzaOrderTest { 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 public static void main ( String [] args ) { Scanner jin = new Scanner ( System . in ) ; int k = jin . nextInt () ; if ( k == 0) { // test order with removing Order order = new Order () ; while ( true ) { try { String type = jin . next () ; String name = jin . next () ; Item item = null ; if ( type . equals ( " Pizza " ) ) item = new PizzaItem ( name ) ; else item = new ExtraItem ( name ) ; if (! jin . hasNextInt () ) break ; order . addItem ( item , jin . nextInt () ) ; } catch ( Exception e ) { System . out . println ( e . getClass () . getSimpleName () ) ; } } jin . next () ; System . out . println ( order . getPrice () ) ; order . displayOrder () ; while ( jin . hasNextInt () ) { try { int idx = jin . nextInt () ; order . removeItem ( idx ) ; } catch ( Exception e ) { System . out . println ( e . getClass () . getSimpleName () ) ; } } System . out . println ( order . getPrice () ) ; order . displayOrder () ; } if ( k == 1) { // test locking & exceptions Order order = new Order () ; try { 31 32 Глава 2. Исклучоци order . lock () ; } catch ( Exception e ) { System . out . println ( e . getClass () . getSimpleName () ) ; } try { order . addItem ( new ExtraItem ( " Coke " ) , 1) ; } catch ( Exception e ) { System . out . println ( e . getClass () . getSimpleName () ) ; } try { order . lock () ; } catch ( Exception e ) { System . out . println ( e . getClass () . getSimpleName () ) ; } try { order . removeItem (0) ; } catch ( Exception e ) { System . out . println ( e . getClass () . getSimpleName () ) ; } 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 } 320 } 321 322 323 } Во оваа задача можат да се идентификуваат неколку ситуации во кои може да се наруши очекуваниот тек на извршување на настаните: 1. При креирање на обjекти од тип ExtraItem или PizzaItem , во конструкторите на овие две класи, текстот што се добива како аргуент потребно е да се конвертира во обjект од соодветната енумерациjа. Овоj дел од кодот е опфатен со try/catch блок. Доколку успешно се креира обjектот од тип enum , ќе се креира и обjект од тип ExtraItem/PizzaItem . Во спротивно, методот valueOf од соодветните енумерации ќе фрли исклучок од тип InvalidArgumentException и програмата ќе продолжи во catch делот дефиниран за овоj тип на исклучок. Во catch блокот, дополнително ќе биде фрлен кориснички дефинираниот исклучок од тип InvalidPizzaTypeException , односно InvalidExtraTypeException . Ова ќе направи прекин на креирањето на обjектите во соодветните конструктори, односно, ќе спречи креирање на невалиден обjект. За овоj исклучок треба да се обезбеди механизам за справување со исклучоци ( try/catch блок) каде има опасност исклучокот да биде фрлен, а тоа е секаде каде што има повици до конструкторите на класите ExtraItem и PizzaItem . Вообичаено, не е добра пракса конструкторите да фрлаат исклучок. Валидациjата на вредностите на аргументите вообичаено се прави претходно во соодветни валидатори, но заради поедноставување на решението, истото е направено во рамки на конструкторот 2. Во методот addItem се фрла исклучок од тип ItemOutOfStockException при обид да се додаде ставка со количина поголема од 10. Соодветно, се обезбедува механизам за справување со исклучокот ( try/catch блок) секаде каде се повикува методот addItem . 3. Во класата нарачка се чува bool променлива locked коjа инициjално е поставена на false (нарачката е отворена), но со повик на методот lock() може да се заклучи нарачката, односно променливата locked да се постави на true . Во методите addItem и removeItem потребно е да се фрли исклучок од тип OrderLockedException доколку нарачката е веќе заклучена. За справување со овоj исклучок потребно е повиците на методите addItem и removeItem да бидат 2.2 Кориснички дефинирани исклучоци 33 опфатени try/catch блок. 4. Во методот lock() треба да се направи проверка дали нарачката има барем една ставка. Во случаj кога листата со ставки е празна треба да се фрли исклучок од тип EmptyOrderException . 5. При обид за бришење на нарачка на позициjа што е надвор од граници на низата. Во овоj случаj постоjат две опции: • Доколку структурата за чување на податоци за нарачки е обична низа, во кодот на методот removeItem треба да се направи проверка дали индексот idx e во граници од 0 до големината на низата со ставки. Доколку ова не е исполнето, да се фрли исклучок од тип ArrayIndexOutOfBounds(idx) . • Доколку се користи класата ArrayList за чување на нарачки, неjзините методи за пристап до елементите преку нивната позициjа во рамки на листата директно фрлаат исклучок од тип ArrayIndexOutOfBounds (овоj исклучок е веќе дефиниран во пакетот java.lang ). Справувањето со овоj исклучок е единствен за двата пристапи, односно, употреба на try/catch блок секаде каде се повикува методот removeItem . 3. Читање и запишување на податоци Во оваа глава ќе бидат презентирани класите кои се поставени во java.util пакетот и се наменети за читање од влезен поток од податоци и запишување на излезен поток од податоци. Овие класи наjчесто се употребуваат за читање од тастатура или влезна датотека, односно запишување на екран или во излезна датотека. Во заедницата се познати и како класи за влез/излез (анг. I/O). Во задачите се покажува како правилно да се користат класите наменети за читање од влезен поток на податоци и како правилно да се форматираат и постават податоци на излезен поток. 3.1 Користење на класата Scanner за читање на податоци Класата Scanner наjчесто се користи за читање на податоци од тастатура и текстуална датотека. Едноставна замена на аргументот System.in (во конструкторот на класата Scanner ) со соодветен обjект за репрезентациjа на поток на податоци коj е поврзан со текстуалната датотека овозможува читање на текстуалната датотека. Scanner StreamObject = new Scanner(new FileInputStream(FileName)); Методите на Scanner класата овозможуваат читање од било коj влезeн поток на податоци (во случаjот читање од тастатура и текстуална датотека). Класата Scanner го дели влезот од податоци на токени користеjќи сепаратор (празно место како предефинирана вредност). Добиените токени можат да се конвертораат во вредности од различни типови со користење на неговите интерфеjсни методи. Проблем 3.1 Да се имплементира класа Subtitles коjа ќе чита од влезен тек (стандарден влез, датотека, ...) превод во стандарден srt формат. Секоj еден елемент од преводот се состои од реден броj, време на почеток на прикажување, време на краj на прикажување и текст и е во следниот формат (пример): 2 00:00:48,321 --> 00:00:50,837 36 Глава 3. Читање и запишување на податоци Let’s see a real bet. Делот со текстот може да има повеќе редови. Сите елементи се разделени со еден нов ред. Ваша задача е да ги имплементирате методите: • Subtitles() - предефиниран конструктор • int loadSubtitles(InputStream inputStream) - метод за читање на преводот (враќа резултат колку елементи се прочитани) • void print() - го печати вчитаниот превод во истиот формат како и при читањето. • void shift(int ms) - ги поместува времињата на сите елементи од преводот за броjот на милисекунди кои се проследува како аргумент (може да биде негативен, со што се поместуваат времињата наназад). При решавање на задачи во кои е потребно да се прочитаат податоци од влезен поток од податоци, многу е важно прво да се согледа форматот на запишаните влезни податоци. Тоj може да биде структуриран и неструктуриран. Стратегиjата и начинот на читање на тие податоци можат да бидат различни: • да се чита влезниот поток на податоци ред по ред • да се чита знак по знак (баjт по баjт) • влезниот поток на податоци директно да се токенизира доколку е познат форматот на запишување • да се обезбеди директна конверзиjа на податоците доколку однапред jа знаеме структурата на запис (пример: вчитување на вредности на матрица, добро позната податочна структураж, и.т.н.). Откако ќе се согледа форматот на запишаните податоци, може да се донесе одлука со кои класи и методи од тие класи ќе бидат вчитани податоците. 1 2 3 4 import import import import java . io . InputStream ; java . util . ArrayList ; java . util . List ; java . util . Scanner ; 5 6 7 class Subtitles { List < Element > elements ; 8 9 10 11 public Subtitles () { elements = new ArrayList < Element >() ; } 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public int loadSubtitles ( InputStream inputStream ) { Scanner scanner = new Scanner ( inputStream ) ; while ( scanner . hasNextLine () ) { String line = scanner . nextLine () ; int number = Integer . parseInt ( line ) ; String time = scanner . nextLine () ; StringBuilder text = new StringBuilder () ; while ( true ) { if (! scanner . hasNextLine () ) break ; line = scanner . nextLine () ; if ( line . trim () . length () == 0) break ; text . append ( line ) ; text . append ( " \ n " ) ; } 3.1 Користење на класата Scanner за читање на податоци Element element = new Element ( number , time , text . toString () ) ; elements . add ( element ) ; 28 29 30 } return elements . size () ; 31 32 } 33 34 public void shift ( int ms ) { for ( Element e : elements ) { e . shift ( ms ) ; } } 35 36 37 38 39 40 public void find ( String text ) { for ( Element e : elements ) { if ( e . findText ( text ) ) { System . out . println ( e . number ) ; } } } 41 42 43 44 45 46 47 48 public void print () { for ( Element e : elements ) { System . out . println ( e ) ; } } 49 50 51 52 53 54 } 55 56 57 58 59 60 class Element { public int timeFrom ; public int timeTo ; public String text ; public int number ; 61 62 63 64 65 66 67 68 public Element ( int number , String time , String text ) { this . number = number ; String [] parts = time . split ( " -->" ) ; timeFrom = stringToTime ( parts [0]. trim () ) ; timeTo = stringToTime ( parts [1]. trim () ) ; this . text = text ; } 69 70 71 72 73 public void shift ( int ms ) { timeFrom += ms ; timeTo += ms ; } 74 75 76 77 public boolean findText ( String someText ) { return text . contains ( someText ) ; } 78 79 80 81 82 83 84 85 86 87 88 static int stringToTime ( String time ) { String [] parts = time . split ( " ," ) ; int res = Integer . parseInt ( parts [1]) ; parts = parts [0]. split ( " : " ) ; int sec = Integer . parseInt ( parts [2]) ; int min = Integer . parseInt ( parts [1]) ; int h = Integer . parseInt ( parts [0]) ; res += sec * 1000; res += min * 60 * 1000; res += h * 60 * 60 * 1000; 37 38 Глава 3. Читање и запишување на податоци return res ; 89 } 90 91 static String timeToString ( int time ) { int h = time / (60 * 60 * 1000) ; time = time % (60 * 60 * 1000) ; int m = time / (60 * 1000) ; time = time % (60 * 1000) ; int s = time / 1000; int ms = time % 1000; return String . format ( " %02 d :%02 d :%02 d ,%03 d " , h , m , s , ms ) ; } 92 93 94 95 96 97 98 99 100 101 @Override public String toString () { return String . format ( " % d \ n % s --> % s \ n % s " , number , timeToString ( timeFrom ) , timeToString ( timeTo ) , text ) ; } 102 103 104 105 106 107 } 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 public class SubtitlesTest { public static void main ( String [] args ) { Subtitles subtitles = new Subtitles () ; int n = subtitles . loadSubtitles ( System . in ) ; System . out . println ( " +++++ ORIGINIAL SUBTITLES +++++ " ) ; subtitles . print () ; int shift = n * 37; shift = ( shift % 2 == 1) ? - shift : shift ; System . out . println ( String . format ( " SHIFT FOR % d ms " , shift ) ) ; subtitles . shift ( shift ) ; System . out . println ( " +++++ SHIFTED SUBTITLES +++++ " ) ; subtitles . print () ; } } Во оваа задача, лесно може да се согледа дека податоците за еден превод се организирани во наjмалку три редови. При тоа, во првиот ред е запишан редниот броj, во вториот ред е запишан временскиот опсег во коj треб да се прикаже преводот и во наредните редови е запишан преводот. Кога се чита ред по ред (како во оваа ситуациjа) може да се искористат класите Scanner и BufferedReader . Оваа задача е решена со помош на класата Scanner . Прво се креира обjект од класата Scanner преку предавање на обjектот од тип InputStream во конструкторот на класата Scanner . Бидеjќи влезот треба да се прочита ред по ред, прво потребно e да се напише циклус што ќе се извршува се додека има редови во датотеката за читање. За проверка на условот дали сме стигнале до краjот на датотеката се користи методот scanner.hasNextLine() . Во рамки на циклусот, прво се вчитува еден ред и истиот се парсира во цел броj (бидеjќи тоj ред го означува редниот броj на преводот). Потоа се чита уште еден ред и истиот се зачувува како текстуална низа што подоцна се трансформира во два временски интервали. Следно, со помош на вгнезден циклус се читаат редовите во кои е запишан преводот, се додека не доjдеме до празен ред (преводите се одделени со празен ред). Секоj од прочитаните редови се додава во StringBuilder со цел да се избегне постоjано креирање на нови тестуачни низи. Како краен резултат од извршувањето на вгнездениот циклус е вчитувањето на целосниот превод во една текстуална низа што се добива со повикувањето на методот toString() од StringBuilder обjектот. За читање од влезниот поток од податоци ред по ред, се 3.1 Користење на класата Scanner за читање на податоци 39 користи методот nextLine() од класата Scanner . Дополнително, во задачата потребно е да се обработи и прочитаниот запис што се однесува на почетното и краjното време на преводот. За таа цел, во рамки на конструкторот на класата Element потребно е редот во коj е запишано почетното и краjното време на преводот да се раздели на двe текстуални низи со сепаратор „стрелки“. Добиените две текстуални низи се конвертираат во милисекунди изминати од почетокот на филмот. 3.1.1 Користење на класата PrintWriter за запишување на податоци Класата PrintWriter се користи за форматирано печатње на податоци во излезен поток. Проблем 3.2 Да се имплементира класа CheckIn коjа ќе чита од влезен тек (стандар- ден влез, датотека, ...) податоци за приjавување/одjавување на корисник. Податоците содржат (Име - единствено), почетен и краен датум (dd.mm.YYYY) со време (во 24-часовен формат) на приjавување и одjавување. Сите податоци се разделени со едно празно место, датумот и времето се разделени со - , во датумот денот, месецот и годината може да се разделени со . или / , а во самото време часот и минутите може да бидат разделени со : или . . Пример за форматот на податоците: John 03.06.2015-11:15 03/06/2015-20.45 За потребите на класата потребно е да се имплементираат следните методи: • CheckIn() - предефиниран конструктор • void readTimes(InputStream inputStream) - метод за читање на податоците • void writeTimes(OutputStream outputStream) - метод коj ги печати сите времиња на приjавување сортирани во растечки редослед според времето на приjавуавање на корисникот во растечки редослед. При печатањето името се печати со 15 места порамнето во лево, датумот во формат dd.mm.YYYY (само еднаш), па следуваат времињата на приjавување/одjавување и вкупно времетраење на присуство во минути. Методот за читање readTimes фрла исклучок од тип ако датумот на приjавување и одjавување е различен или пак времето на приjавување е после времето на одjавување треба да се фрли исклучок од тип InvalidCheckInTimes . Исклучокот во пораката getMessage() треба да го врати влезниот податок коj го предизвикал исклучокот. Сите приjавувања/одjавувања до моментот кога ќе се фрли некоj исклучок треба да си останат вчитани. Слично како и во претходната задача, така и во оваа, читањето на податоци наjдобро е да се направи ред по ред во кои се запишани информациите за работните времиња на вработените. Прво се креира обjект од класата Scanner , а потоа со помош на циклус за повторување се вчитува ред по ред од влезниот поток. Секоjа од вчитаните текстуални низи се дели на три низи користеjќи го празното место како сепаратор. Во првата текстуална низа е запишано името на вработениот, во втората текстуална низа почетокот на смената, а во третата текстуална низа е запишан краjот на смената. На втората и третата текстуална низа дополнително се прави поделба каде како сепаратор се користи „—“. Ова резултира со добивање на две текстуални низи со должина 2, каде првата текстуална низа е датумот, а втората текстуална низа е времето. Откако ќе се добиjат датумите и времињата за почетокот и краjот на смената, потребно е истите 40 Глава 3. Читање и запишување на податоци да се форматираат. Бидеjќи денот, месецот и годината во датумите може да бидат разделени со „/“ или „.“, а во излезот на програмата се очекува да бидат разделени со „.“, со помош на методот replace од класата String секаде каде што има „/“ се заменува со „.“. За парсирање на времето, прво се обидуваме да jа поделиме текстуалната низа со „:“, а доколку тоа не успее (не врати низа од текстуални низи со големина 2), се прави делење на текстуалната низа со „.“. Во оваа задача, освен читањето од влезен поток, потребно е резултатите да се запишат во излезен поток (обjект од класата outputStream ) со помош на методот writeTimes . За таа цел, се креира обjект од класата PrintWriter , така што на конструкторот на класата се предава обjектот од тип OutputStream . Податоците за смените (обjекти од класата CheckInTime ) се подредуваат и се печатат со методот println() од класа PrintWriter . При работа со PrintWriter важно е да се повика методот flush со цел креираниот податочен поток во самиот PrintWriter обjект да се запише на излезниот поток. На краj, секоj отворен поток на податоци треба да се затвори со повик на методот close . 1 2 3 4 5 6 7 import import import import import import import java . io . InputStream ; java . io . OutputStream ; java . io . PrintWriter ; java . util . ArrayList ; java . util . Collections ; java . util . List ; java . util . Scanner ; 8 9 10 class CheckIn { List < CheckInTime > elements ; 11 12 13 14 public CheckIn () { elements = new ArrayList < CheckInTime >() ; } 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public void loadTimes ( InputStream inputStream ) throws InvalidCheckInTimes { Scanner scanner = new Scanner ( inputStream ) ; while ( scanner . hasNextLine () ) { String line = scanner . nextLine () ; String [] parts = line . split ( " " ) ; String name = parts [0]; String [] time1 = parts [1]. split ( " -" ) ; String [] time2 = parts [2]. split ( " -" ) ; CheckInTime time = new CheckInTime ( name , CheckInTime . setDateFormat ( time1 [0]) , CheckInTime . setDateFormat ( time2 [0]) , CheckInTime . strToTime ( time1 [1]) , CheckInTime . strToTime ( time2 [1]) ) ; if (! time . valid () ) throw new InvalidCheckInTimes ( line ) ; elements . add ( time ) ; } } 34 35 36 37 38 39 40 41 public void printTimes ( OutputStream outputStream ) { PrintWriter writer = new PrintWriter ( outputStream ) ; Collections . sort ( elements ) ; for ( CheckInTime checkInTime : elements ) { writer . println ( checkInTime ) ; } writer . flush () ; 3.1 Користење на класата Scanner за читање на податоци writer . close () ; 42 } 43 44 45 46 } 47 48 49 50 51 52 53 class CheckInTime implements Comparable < CheckInTime > { private String name ; private String dateFrom ; private String dateTo ; private int timeFrom ; private int timeTo ; 54 public CheckInTime ( String name , String dateFrom , String dateTo , int timeFrom , int timeTo ) { this . name = name ; this . dateFrom = dateFrom ; this . dateTo = dateTo ; this . timeFrom = timeFrom ; this . timeTo = timeTo ; } 55 56 57 58 59 60 61 62 63 public boolean valid () { return dateFrom . equals ( dateTo ) && timeFrom <= timeTo ; } 64 65 66 67 public static int strToTime ( String time ) { String [] t = time . split ( " : " ) ; if ( t . length == 1) { t = time . split ( " \\. " ) ; } return Integer . parseInt ( t [0]) * 60 + Integer . parseInt ( t [1]) ; } 68 69 70 71 72 73 74 75 public static String setDateFormat ( String date ) { date = date . replace ( " / " , " . " ) ; return date ; } 76 77 78 79 80 @Override public String toString () { return String . format ( " % -15 s % s %02 d :%02 d - %02 d :%02 d (% d ) " , name , dateFrom , timeFrom / 60 , timeFrom % 60 , timeTo / 60 , timeTo % 60 , timeTo - timeFrom ) ; } 81 82 83 84 85 86 87 88 @Override public int compareTo ( CheckInTime o ) { return Integer . compare ( timeFrom , o . timeFrom ) ; } 89 90 91 92 93 } 94 95 96 class U n s u p p o r t e d F o r m a t E x c e p t i o n extends Exception { String data ; 97 98 99 100 public U n s u p p o r t e d F o r m a t E x c e p t i o n ( String data ) { this . data = data ; } 101 102 @Override 41 42 public String getMessage () { return data ; } 103 104 105 106 Глава 3. Читање и запишување на податоци } 107 108 109 class I nvalidCheckInTimes extends Exception { String data ; 110 public InvalidCheckInTimes ( String data ) { this . data = data ; } 111 112 113 114 @Override public String getMessage () { return data ; } 115 116 117 118 119 } 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 public class CheckInTest { public static void main ( String [] args ) { CheckIn checkIns = new CheckIn () ; try { checkIns . loadTimes ( System . in ) ; } catch ( InvalidCheckInTimes e2 ) { System . out . println ( " InvalidCheckInTimes : " + e2 . getMessage () ) ; } System . out . println ( String . format ( " ====== PRINT CHECK INS ====== " ) ) ; checkIns . printTimes ( System . out ) ; } } 3.2 Користење на класата BufferedReader за читање од влезен поток Класата BufferedReader може да се користи за читање од влезен поток на податоци (вклучително и текстуална датотека). Обjект од класата BufferedReader ги има методите read и readLine . За користење на класата BufferedReader за читање од текстуална датотека, потребно е да бидат внесени следниве библиотеки: import import import import java.io.BufferedReader; java.io.FileReader; java.io.FileNotFoundException; java.io.IOException; Класата BufferedReader како аргумент во конструкторот користи обjект од класата FileReader . Поток на податоци од класата BufferedReader се креира и поврзува со текстуална датотека на следниов начин: BufferedReader readerObject; readerObject = new BufferedReader(new FileReader(FileName)); По дефинирањето на обjектот од класата BufferedReader , методите read и readLIne можат да се користат за читање од датотеката со име FileName . Методот readLine е ист како и методот за читање од тастатура, со таа разлика што овде се чита од датотека. Методот read чита еден знак и враќа вредност (од тип int ) коj соодветствува на прочитаниот знак. Поради тоа, наjчесто е потребно и дополнително кастирање: 3.2 Користење на класата BufferedReader за читање од влезен поток 43 char next = (char)(readerObject.read()); Дополнително, започнуваjќи од Jава 8 постои и методот lines() што враќа обjект Stream<String> , што претставува поток од текстуални низи каде секоjа текстуална низа е еден ред од потокот на податоци. Проблем 3.3 Да се дефинира класа CakeShopApplication во коjа ќе се чуваат информации за сите тековни нарачки на торти и пити. За класата да се дефинира: • CakeShopApplication() - конструктор • int readCakeOrders(InputStream inputStream) - метод коj од влезен поток на податоци ќе прочита информации за повеќе нарачки. Во секоj еден ред се наоѓа информациjа за една нарачка во формат: orderId item1Name item1Price item2Name item2Price .... itemNName itemNPrice, каде што orderId е ID на нарачката, а по неа следуваат паровите нарачан производ и цена. Методот треба да врати цел броj коj означува колку точно производи се нарачани во рамки на сите нарачки кои се успешно прочитани. • void printLongestOrder(OutputStream outputStream) - метод коj на излезен поток ќе jа испечати нарачката со наjголем броj на нарачани продукти, во формат: orderId totalOrderItems, каде totalOrderItems е броjот на сите нарачани продукти. Од барањата на задачата во кои jасно е наведено дека информациите за нарачките се дадени во посебни редови, може да се заклучи дека читањето на влезниот поток треба да биде реализирано ред по ред. Иако задачата може да се реши и со употреба на Scanner, овде ќе биде прикажан начинот на употреба на BufferedReader класата за читање од влезен поток на податоци. Во функциjата int readCakeOrders(InputStream inputStream) прво се креира обjект од тип BufferedReader. Во конструкторот на класата BufferedReader се предава обjект од тип InputStreamReader, додека во конструкторот на InputStreamReader се предава обjект од тип InputStream. Оваа е генерално решение што може да функционира за читање од било каков InputStream (од класата InputStream се изведени стотина поткласи). Потоа, со помош на циклус за повторување се чита ред по ред, се додека методот readLine() oд класата BufferedReader не врати null. За секоj прочитан ред се повикува статичкиот методот createOrder од класата Order коj ги парсира сите информации за нарачката и ставките што се дел од неа. Релевантните податоци за една нарачка запишани во еден ред од влезниот поток се одделени со празно место, при што на нултата позициjа се наоѓа ИД-то на нарачката, а потоа на секоjа непарна позициjа (1, 3, итн.) се наоѓа името на ставката. На секоjа парна позициjа (2, 4, итн.) се наоѓа цената на ставката. Соодветно, се креира листа од сите ставки и истата се предава како аргумент во конструкторот на класата Order, заедно со ИД-то на нарачката што се наоѓа на нултата позициjа. На краjот од читањето се повикува методот close, за да се затвори читањето од влезниот поток на податоци. Во коментари е дадено решение со употреба на методот lines() од класата BufferedReader. 1 2 3 4 import import import import java . io .*; java . util . ArrayList ; java . util . Arrays ; java . util . Comparator ; 44 5 6 Глава 3. Читање и запишување на податоци import java . util . List ; import java . util . stream . Collectors ; 7 8 9 10 class Item { private String name ; private int price ; 11 public Item ( String name ) { this . name = name ; this . price = 0; } 12 13 14 15 16 public void setPrice ( int price ) { this . price = price ; } 17 18 19 20 } 21 22 23 24 class Order implements Comparable < Order > { private int id ; private List < Item > items ; 25 26 27 28 29 public Order () { this . id = -1; items = new ArrayList < >() ; } 30 31 32 33 34 public Order ( int id , List < Item > items ) { this . id = id ; this . items = items ; } 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public static Order createOrder ( String line ) { String [] parts = line . split ( " \\ s + " ) ; int orderId = Integer . parseInt ( parts [0]) ; List < Item > items = new ArrayList < >() ; Item item = null ; for ( int i = 1; i < parts . length ; i ++) { if ( i % 2 == 1) { // item name is here item = new Item ( parts [ i ]) ; } else { item . setPrice ( Integer . parseInt ( parts [ i ]) ) ; items . add ( item ) ; } } return new Order ( orderId , items ) ; } 51 52 53 54 public List < Item > getItems () { return items ; } 55 56 57 58 public int getNumberOfItems () { return items . size () ; } 59 60 61 62 63 $@override public int compareTo ( Order o ) { return Integer . compare ( this . items . size () , o . items . size () ) ; } 64 65 $@override 3.2 Користење на класата BufferedReader за читање од влезен поток public String toString () { return this . id + " " + this . items . size () ; } 66 67 68 69 } 70 71 72 class C akeShopApplication { private List < Order > orders ; 73 74 75 76 public CakeShopApplication () { this . orders = new ArrayList < >() ; } 77 78 79 80 81 public int readCakeOrders ( InputStream inputStream ) throws IOException { BufferedReader br = new BufferedReader ( new InputStreamReader ( inputStream ) ) ; 82 /* JAVA 8 Stream API solution this . orders = br . lines () . map ( Order :: createOrder ) . collect ( Collectors . toList () ) ; 83 84 85 86 87 br . close () 88 89 return this . orders . stream () . mapToInt ( order -> order . getNumberOfItems () ) . sum () ; */ 90 91 92 93 String line ; while (( line = br . readLine () ) != null ) { orders . add ( Order . createOrder ( line ) ) ; } 94 95 96 97 98 int sum = 0; for ( Order order : orders ) { sum += order . getNumberOfItems () ; } 99 100 101 102 103 br . close () ; return sum ; 104 105 106 } 107 108 109 110 public void printLongestOrder ( OutputStream outputStream ) { PrintWriter printWriter = new PrintWriter ( outputStream ) ; Order longestOrder = orders . get (0) ; 111 112 113 114 115 116 /* JAVA 8 Stream API solution Order longestOrder = this . orders . stream () . max ( Comparator . naturalOrder () ) . orElseGet ( Order :: new ) ; */ 117 118 119 120 121 122 for ( Order order : orders ) { if ( order . compareTo ( longestOrder ) > 0) { longestOrder = order ; } } 123 124 printWriter . println ( longestOrder ) ; 125 126 printWriter . flush () ; 45 46 Глава 3. Читање и запишување на податоци printWriter . close () ; 127 } 128 129 } 130 131 public class C ak e S h op A p pl i c at i o nT e s t1 { 132 public static void main ( String [] args ) { CakeShopApplication cakeShopApplication = new CakeShopApplication () ; 133 134 135 136 System . out . println ( " --- READING FROM INPUT STREAM ---" ) ; try { System . out . println ( cakeShopApplication . readCakeOrders ( System . in ) ) ; } catch ( IOException e ) { e . printStackTrace () ; } 137 138 139 140 141 142 143 144 System . out . println ( " --- PRINTING LARGEST ORDER TO OUTPUT STREAM ---" ) ; cakeShopApplication . printLongestOrder ( System . out ) ; 145 146 147 } 148 149 } 4. Генерици Започнуваjќи со верзиjата 5.0, Jава дозволува дефинирање на класи и методи што вклучуваат параметри за типови. Таквите дефиниции се нарекуваат генерици. Воведувањето на параметарски тип овозможува креирање на класи, методи и интерфеjси независни од конкретни податочни типови. Ова значи дека една класа, метод или интерфеjс може да се употребува со повеке податочни типови без да се пушува конкретна, специфична имплементциjа. Иако Object е класа предок на сите други класи чиjа референца може да се однесува на било коj тип на обjект, сепак на таков начин не може да се обезбеди безбведност на тип. Со воведувањето на параметарските типови успешно можеме да превенираме поjава на грешки настанати како резултат на кастирање при извршување на програмата. 4.1 Генерички класи, интерфеjси и методи 4.1.1 Генерички класи Класата коjа е дефинирана со параметар како тип се нарекува генеричка класа или параметризирана класа. Параметарскиот тип се дава во аглести загради по името на класата во неjзиното заглавие. Секоj не-клучен збор може да биде употребен како идентификатор за параметарски тип. По конвенциjа параметарот стартува со голема буква. Параметарскиот тип може да се употребува како и секоj друг тип во дефинициjата на класата. Проблем 4.1 Да се имплементира генеричка класа Triple (троjка) од нумерички вредности (кои се подтип на класата Number ). За класата да се имплементираат: • конструктор со 3 аргументи, • double max() - го враќа наjголемиот од трите броjа • double average() - коj враќа просек на трите броjа • void sort() - коj ги сортира елементите во растечки редослед 48 Глава 4. Генерици • да се преоптовари методот toString() коj враќа форматиран стринг со две децимални места за секоj елемент и празно место помеѓу нив. Во оваа задача потребно е да се чуваат три променливи на инстанца во класата Triple и сите три да се од ист тип. Типот на овие променливи треба да биде подтип на класата Number . Со употреба на генерици, се дефинираат овие три променливи од генерички тип T , додека пак во дефинициjата на класата се додава <T extends Number> , што означува дека Т мора да биде тип што наследува од класата Number . Со ова се овозможува класата Triple да се користи за троjки од тип Short , Integer , Float , Double , Long итн. 1 2 class Triple < T extends Number > { T first , second , third ; 3 4 5 6 7 8 public Triple ( T first , T second , T third ) { this . first = first ; this . second = second ; this . third = third ; } 9 10 11 12 13 14 15 16 17 18 19 public double max () { double max = first . doubleValue () ; if ( second . doubleValue () > max ) { max = second . doubleValue () ; } if ( third . doubleValue () > max ) { max = third . doubleValue () ; } return max ; } 20 21 22 23 24 25 public double avarage () { double sum = first . doubleValue () + second . doubleValue () + third . doubleValue () ; return sum / 3; } 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public void sort () { if ( first . doubleValue () > second . doubleValue () ) { T temp = second ; second = first ; first = temp ; } if ( second . doubleValue () > third . doubleValue () ) { T temp = third ; third = second ; second = temp ; } if ( first . doubleValue () > second . doubleValue () ) { T temp = second ; second = first ; first = temp ; } } 44 45 46 @Override public String toString () { 4.1 Генерички класи, интерфеjси и методи 49 return String . format ( " %.2 f %.2 f %.2 f " , first . doubleValue () , second . doubleValue () , third . doubleValue () ) ; 47 48 } 49 50 public static void main ( String [] args ) { Scanner scanner = new Scanner ( System . in ) ; int a = scanner . nextInt () ; int b = scanner . nextInt () ; int c = scanner . nextInt () ; 51 52 53 54 55 56 Triple < Integer > tInt = new Triple < Integer >( a , b , c ) ; 57 58 System . out . printf ( " %.2 f \ n " , tInt . max () ) ; System . out . printf ( " %.2 f \ n " , tInt . avarage () ) ; 59 60 61 tInt . sort () ; System . out . println ( tInt ) ; 62 63 64 float fa = scanner . nextFloat () ; float fb = scanner . nextFloat () ; float fc = scanner . nextFloat () ; 65 66 67 68 Triple < Float > tFloat = new Triple < Float >( fa , fb , fc ) ; 69 70 System . out . printf ( " %.2 f \ n " , tFloat . max () ) ; System . out . printf ( " %.2 f \ n " , tFloat . avarage () ) ; 71 72 73 tFloat . sort () ; System . out . println ( tFloat ) ; 74 75 76 double da = scanner . nextDouble () ; double db = scanner . nextDouble () ; double dc = scanner . nextDouble () ; 77 78 79 80 Triple < Double > tDouble = new Triple < Double >( da , db , dc ) ; 81 82 System . out . printf ( " %.2 f \ n " , tDouble . max () ) ; System . out . printf ( " %.2 f \ n " , tDouble . avarage () ) ; 83 84 85 tDouble . sort () ; System . out . println ( tDouble ) ; 86 87 } 88 89 } Проблем 4.2 Да се имплементира класа за генеричка табела GenericTable со генерички имиња на редовите кои се споредливи вредности ( Comparable ) и генерички вредности кои мора да се подкласа на Number . Во класата треба да се имплементираат следните методи: • void addRow(RowKey key, Value... values) - за додавање на низа од вредности values за редот со даден клуч key • double max(RowKey key) - jа враќа вредноста ( double ) на елементот со максимална вредност за даден ред со клуч key • String toString() - враќа String репрезентациjа на табелата со формат на секоj ред key: v1 v2 . . . vn каде што елементите v се печатат со 6 места од кои две децимални и се разделени со таб \t . 50 Глава 4. Генерици Од барањето специфицирано за методот addRow(RowKey rowKey, Value ... values) треба да се забележи дека за решавање на оваа задача ќе бидат потребни два генерички параметри RowKey и Value. Дополнително, може да се согледа дека клучевите на редицте треба да бидат споредливи обjекти, односно генеричкиот тип RowKey треба да наследува (односно имплементира) од Comparable (RowKey extends Comparable) интерфеjсот. Вредностите треба да бидат нумерички типови, односно класите кои ќе се користат на местото на генеричкиот тип Value треба да се подтип на класата Number (Value extends Number). Согласно тоа, дефинициjата на генеричката класа GenericTable ќе биде class GenericTable<RowKey extends Comparable, Value extends Number>. Дефинициjата на оваа класа може да биде и class GenericTable<RowKey extends Comparable<RowKey>, Value extends Number>. Практично тоа значи, наместо да го имплементираме методот compareTo(Object obj), ќе биде потребно да го имплементираме методот compareTo (RowKey rowKey). Ако не се наметнат ограничувањета на типовите RowKey и Value, тогаш е невозможно генеричката табела да биде претставена преку TreeMap, бидеjќи елементите од RowKey класата нема да можат да се споредуваат меѓусебно. Исто така, методот double max (RowKey key) ќе биде невозможно да се импелементира од истите причини (класата Number го имплементира интерфеjсот Comparable<Number> и обjектите од таа класа ќе можат да се споредуваат меѓусебно). 1 import java . util .*; 2 3 4 5 class Key implements Comparable < Key > { int index ; String name ; 6 public Key ( int index , String name ) { this . index = index ; this . name = name ; } 7 8 9 10 11 @Override public int compareTo ( Key o ) { return Integer . compare ( index , o . index ) ; } 12 13 14 15 16 @Override public String toString () { return String . format ( " % d (% s ) " , index , name ) ; } 17 18 19 20 21 } 22 23 24 class GenericTable < RowKey extends Comparable , Value extends Number > { Map < RowKey , List < Value > > rows ; 25 26 27 28 public GenericTable () { rows = new TreeMap < >() ; } 29 30 31 32 33 34 public void addRow ( RowKey key , Value ... values ) { List < Value > row = rows . get ( key ) ; if ( row == null ) { row = new ArrayList < >() ; } 4.1 Генерички класи, интерфеjси и методи row . addAll ( Arrays . asList ( values ) ) ; rows . put ( key , row ) ; 35 36 37 51 } 38 39 40 41 42 43 44 45 46 public double max ( RowKey key ) { List < Value > row = rows . get ( key ) ; double max = Double . MIN_VALUE ; for ( Number number : row ) { max = Math . max ( max , number . doubleValue () ) ; } return max ; } 47 48 49 50 51 52 53 54 55 56 57 58 59 60 @Override public String toString () { StringBuilder res = new StringBuilder () ; for ( RowKey key : rows . keySet () ) { List < Value > row = rows . get ( key ) ; res . append ( String . format ( " % s : " , key ) ) ; for ( Value value : row ) { res . append ( String . format ( " %6.2 f \ t " , value . doubleValue () ) ) ; } res . append ( ’\ n ’) ; } return res . toString () ; } 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 public static void main ( String [] args ) { Scanner scanner = new Scanner ( System . in ) ; GenericTable < String , Integer > stringTable = new GenericTable < >() ; GenericTable < Integer , Double > integerTable = new GenericTable < >() ; GenericTable < Key , Float > keyTable = new GenericTable < >() ; int n = scanner . nextInt () ; scanner . nextLine () ; for ( int i = 0; i < n ; ++ i ) { String [] parts = scanner . nextLine () . split ( " \\ s + " ) ; Integer [] values = new Integer [ parts . length - 1]; for ( int j = 0; j < values . length ; ++ j ) { values [ j ] = Integer . parseInt ( parts [ j + 1]) ; } stringTable . addRow ( parts [0] , values ) ; } System . out . println ( " === STRING TABLE === " ) ; System . out . println ( stringTable ) ; String k = String . format ( " row % d " , n / 2) ; System . out . printf ( " MAX (% s ) : %.2 f \ n " , k , stringTable . max ( k ) ) ; n = scanner . nextInt () ; scanner . nextLine () ; for ( int i = 0; i < n ; ++ i ) { String [] parts = scanner . nextLine () . split ( " \\ s + " ) ; Double [] values = new Double [ parts . length - 1]; for ( int j = 0; j < values . length ; ++ j ) { values [ j ] = Double . parseDouble ( parts [ j + 1]) ; } integerTable . addRow ( Integer . parseInt ( parts [0]) , values ) ; } System . out . println ( " === INTEGER TABLE === " ) ; System . out . println ( integerTable ) ; System . out . printf ( " MAX (% d ) : %.2 f \ n " , n / 2 , integerTable . max ( n / 2) ) ; n = scanner . nextInt () ; 52 Глава 4. Генерици scanner . nextLine () ; for ( int i = 0; i < n ; ++ i ) { String [] parts = scanner . nextLine () . split ( " \\ s + " ) ; Float [] values = new Float [ parts . length - 1]; for ( int j = 0; j < values . length ; ++ j ) { values [ j ] = Float . parseFloat ( parts [ j + 1]) ; } String [] keys = parts [0]. split ( " : " ) ; Key key = new Key ( Integer . parseInt ( keys [0]) , keys [1]) ; keyTable . addRow ( key , values ) ; } System . out . println ( " === KEY TABLE === " ) ; System . out . println ( keyTable ) ; Key key = new Key (1 , " a " ) ; System . out . printf ( " MAX (% s ) : %.2 f \ n " , key , keyTable . max ( key ) ) ; scanner . close () ; 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 } 112 113 } Проблем 4.3 Да се имплементира GenericCounter<T> за броење на исти елементи. Класата треба да имплементира два методи: • void count(T element) - метод коj прима елемент за броење • String toString() - коj враќа стринг репрезентациjа на изброените елементи [елемент]|[повторување на знакот * онолку пати колку што е изброjан дадениот елемент] Решението треба да има мемориска комплексност O(n) и комплексност O(n) за броење на елементите (линеарно пребарување) Во оваа задача класата GenericCounter се дефинира со генеричкиот параметар T . Дополнително, се креира помошна генеричка класа CountableElement<T> коjа во себе содржи елемент од тип T и броjач на поjавување на тоj елемент. При броењето на елементите во методот void count(T element) , ако се поjави нов елемент, истиот се додава во листата на обjекти од тип CountableElement<T> , а ако елементот се поjавил претходно, броjачот за неговото поjавување се зголемува за еден. 1 2 3 import java . util . ArrayList ; import java . util . List ; import java . util . Scanner ; 4 5 6 class GenericCounter <T > { List < CountableElement <T > > elements ; 7 8 9 10 GenericCounter () { elements = new ArrayList < CountableElement <T > >() ; } 11 12 13 14 15 16 17 18 19 public void count ( T element ) { for ( int i = 0; i < elements . size () ; ++ i ) { CountableElement <T > el = elements . get ( i ) ; if ( el . isEqual ( element ) ) { el . increment () ; return ; } } 4.1 Генерички класи, интерфеjси и методи elements . add ( new CountableElement <T >( element ) ) ; 20 } 21 22 @Override public String toString () { StringBuilder sb = new StringBuilder () ; for ( int i = 0; i < elements . size () ; ++ i ) { CountableElement <T > el = elements . get ( i ) ; sb . append ( String . format ( " % s |% s \ n " , el . element . toString () , countToStar ( el . count ) ) ) ; } return sb . toString () ; } 23 24 25 26 27 28 29 30 31 32 33 static String countToStar ( int c ) { StringBuilder sb = new StringBuilder () ; for ( int i = 0; i < c ; ++ i ) { sb . append ( " * " ) ; } return sb . toString () ; } 34 35 36 37 38 39 40 41 } 42 43 44 45 class CountableElement <T > { int count ; T element ; 46 public CountableElement ( T element ) { this . element = element ; count = 1; } 47 48 49 50 51 public void increment () { ++ count ; } 52 53 54 55 public boolean isEqual ( T element ) { return this . element . equals ( element ) ; } 56 57 58 59 } 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 public class GenericCounterTest { public static void main ( String [] args ) { Scanner scanner = new Scanner ( System . in ) ; int n = scanner . nextInt () ; GenericCounter < Integer > counterInt = new GenericCounter < Integer >() ; scanner . nextLine () ; for ( int i = 0; i < n ; ++ i ) { int x = scanner . nextInt () ; counterInt . count ( x ) ; } System . out . println ( " ===== INTEGERS ===== " ) ; System . out . println ( counterInt ) ; n = scanner . nextInt () ; scanner . nextLine () ; GenericCounter < String > counterString = new GenericCounter < String >() ; for ( int i = 0; i < n ; i ++) { String s = scanner . nextLine () ; counterString . count ( s ) ; 53 54 Глава 4. Генерици } System . out . println ( " ===== STRINGS ===== " ) ; System . out . println ( counterString ) ; n = scanner . nextInt () ; scanner . nextLine () ; GenericCounter < Float > counterFloat = new GenericCounter < Float >() ; for ( int i = 0; i < n ; i ++) { float f = scanner . nextFloat () ; counterFloat . count ( f ) ; } System . out . println ( " ===== FLOATS ===== " ) ; System . out . println ( counterFloat ) ; scanner . close () ; 81 82 83 84 85 86 87 88 89 90 91 92 93 94 } 95 96 } Проблем 4.4 Да се развие генеричка класа GenericFraction за работа со дропки. Класата има два генерички параметри T и U кои мора да бидат од некоjа класа коjа наследува од класата Number . GenericFraction има две променливи: • numerator - броител • denominator - именител. Во класата да се имплементираат следните методи: • GenericFraction(T numerator, U denominator) - конструктор коj ги инициjализира броителот и именителот на дропката. Ако се обидиме да инициjализираме дропка со 0 вредност за именителот треба да се фрли исклучок од тип ZeroDenominatorException • GenericFraction<Double, Double> add(GenericFraction<? extends Number,? extends Number> gf) - собирање на две дропки • double toDouble() - враќа вредност на дропката како реален броj • String toString() - jа печати дропката во следниот формат [numerator] / [denominator] , скратена (нормализирана) и секоj со две децимални места. Согласно барањето дефинирано во задачата, генеричката класа за дропка потребно е да има два генерички параметри кои наследуваат од класата Number . Барањето е дефинирано на овоj начин, со цел броителот и именителот на дропката да можат да бидат претставени со различна прецизност (пр. броителот да биде обjект од тип Short , а именителот да биде обjект од тип Double ). Иако броителот и именителот можат да бидат репрезентирани со различна прецизност, сепак наследувањето од класата Number гарантира конверзиjа на нивната вредност во Double преку имплементациjата методот doubleValue() . Ова е покажано во методите toDouble() и gcd() . Употребата на џокер знакот ( ? ) во методот add означува дека типот на соодветниот податок може да биде било коjа класа што наследува од класата Number . Ова овозможува на да се направи сума на две дропки каj кои броителот и именителот не се инстанцирани од иста класа (пр. дропка со броител од тип Short и именител од тип Long и друга дропка со броител од тип Long и именител од тип Integer ). Ако се употребат генеричките параметри T и V наместо џокер знакот ? , тогаш методот add ќе може да се користи само за дропки со исти типови на броител и именител. 4.1 Генерички класи, интерфеjси и методи 1 import java . util . Scanner ; 2 3 4 5 class GenericFraction < T extends Number , U extends Number > { private T numerator ; private U denominator ; 6 public GenericFraction ( T numerator , U denominator ) throws Z e r oD e n om i n at o r Ex c e pt i o n { if ( denominator . doubleValue () == 0) { throw new Ze r o De n o mi n a to r E xc e p ti o n () ; } this . numerator = numerator ; this . denominator = denominator ; 7 8 9 10 11 12 13 14 } 15 16 double toDouble () { return this . numerator . doubleValue () / this . denominator . doubleValue () ; } 17 18 19 20 21 static double gcd ( double a , double b ) { if ( b == 0) return a ; if ( a < b ) return gcd (a , b - a ) ; else return gcd (b , a - b ) ; } 22 23 24 25 26 27 28 29 30 public double gcd () { return gcd ( this . numerator . doubleValue () , this . denominator . doubleValue () ) ; } 31 32 33 34 35 public GenericFraction < Double , Double > add ( GenericFraction <? extends Number , ? extends Number > gf ) throws Z e r oD e n om i n at o r Ex c e pt i o n { return new GenericFraction < Double , Double >( this . numerator . doubleValue () * gf . denominator . doubleValue () + this . denominator . doubleValue () * gf . numerator . doubleValue () , this . denominator . doubleValue () * gf . denominator . doubleValue () ) ; 36 37 38 39 40 41 42 43 44 45 46 } 47 48 @Override public String toString () { double gcd = this . gcd () ; return String . format ( " %.2 f / %.2 f " , this . numerator . doubleValue () / gcd , this . denominator . doubleValue () / gcd ) ; } 49 50 51 52 53 54 55 56 } 57 58 59 60 61 class Z er o D en o m in a t or E x ce p t io n extends Exception { public Z e r oD e n om i n at o r Ex c e pt i o n () { super ( " Denominator cannot be zero " ) ; } 55 56 62 Глава 4. Генерици } 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 public class GenericFractionTest { public static void main ( String [] args ) { Scanner scanner = new Scanner ( System . in ) ; double n1 = scanner . nextDouble () ; double d1 = scanner . nextDouble () ; float n2 = scanner . nextFloat () ; float d2 = scanner . nextFloat () ; int n3 = scanner . nextInt () ; int d3 = scanner . nextInt () ; try { GenericFraction < Double , Double > gfDouble = new GenericFraction < Double , Double >( n1 , d1 ) ; GenericFraction < Float , Float > gfFloat = new GenericFraction < Float , Float >( n2 , d2 ) ; GenericFraction < Integer , Integer > gfInt = new GenericFraction < Integer , Integer >( n3 , d3 ) ; System . out . printf ( " %.2 f \ n " , gfDouble . toDouble () ) ; System . out . println ( gfDouble . add ( gfFloat ) ) ; System . out . println ( gfInt . add ( gfFloat ) ) ; System . out . println ( gfDouble . add ( gfInt ) ) ; gfInt = new GenericFraction < Integer , Integer >( n3 , 0) ; } catch ( Ze r o De n o mi n a to r E xc e p ti o n e ) { System . out . println ( e . getMessage () ) ; } scanner . close () ; } } 4.1.2 Генерички методи и интефеjси Интерфеjсите може да имаат еден или повеќе параметарски типови. Деталите и нотациjата се исти како и каj класите со параметарски типови. Параметарскиот тип може да се користи и каj методите од генеричка класа. Како дополнување, генеричкиот метод може да има своj параметарски тип, различен од оноj на класата. Генеричкиот метод може да биде член на обична или на генеричка класа и да има своj параметарски тип. Параметарскиот тип на генеричкиот метод е локален и важи за самиот метод, не и за класата. Проблем 4.5 Да се имплементира статички генерички метод за печатење на низа од елементи static <E> void printArray(E[] inputArray) . Методот треба да поддржува печатење на низи од различни податочни типови. Кога работиме со генерички функции потребно е сите декларирани генерички типови во аргументите на генеричката функциjа и во типот на резултатот од генеричката функциjа, да се наведат пред типот на резултатот од функциjата во <> . Во оваа задача е употребен само еден генерички тип ( E ) и истиот е аргумент на функциjата (пред void се декларира само <E> ). 1 public class PrintArray { 2 3 4 5 6 7 public static <E > void printArray ( E [] inputArray ) { for ( E element : inputArray ) { System . out . printf ( " % s " , element ) ; } System . out . println () ; 4.1 Генерички класи, интерфеjси и методи 57 } 8 9 public static void main ( String args []) { Integer [] intArray = { 1 , 2 , 3 , 4 , 5 }; Double [] doubleArray = { 1.1 , 2.2 , 3.3 , 4.4 }; Character [] charArray = { ’H ’ , ’E ’ , ’L ’ , ’L ’ , ’O ’ }; 10 11 12 13 14 System . out . println ( " Array integerArray contains : " ) ; printArray ( intArray ) ; 15 16 17 System . out . println ( " \ nArray doubleArray contains : " ) ; printArray ( doubleArray ) ; 18 19 20 System . out . println ( " \ nArray characterArray contains : " ) ; printArray ( charArray ) ; 21 22 } 23 24 } Проблем 4.6 Доколку во претходната задача за генеричка дропка има дополнително барање кое гласи: „Да се напише статичка генеричка функциjа create коjа има два аргументи (именител и броител), обjекти кои се од класа коjа наследува од класата Number а враќа обjект од тип генеричка дропка потписот и имплементациjата на таа функциjа би бил: 1 2 3 4 5 static <T extends Number , U extends Number > GenericFraction <T ,U > create ( T numerator , U denominator ) throws Z e r oD e n om i n at o r Ex c e pt i o n { return new GenericFraction ( numerator , denominator ) ; } Проблем 4.7 Да се имплементира статички генерички метод за наоѓање на максимум од три елементи static <T extends Comparable<T> > T maximum(T x, T y, T z) . Методот треба да поддржува споредување на елементи од различни податочни типови. Во оваа функциjа имаме три аргументи кои се од генерички тип Т. Затоа во декларациjата на генеричките параметри на почетокот на функциjата се наоѓа само генеричкиот параметар T. Целта на оваа функциjа е да наjде максимум од три елементи. За да може да се наjде максимумот, мора генеричкиот тип T да биде споредлив, па затоа генеричкиот параметар е деклариран како <T extends Comparable<T>> во дефинициjата на методот. 1 2 3 public class Max { public static <T extends Comparable <T > > T maximum ( T x , T y , T z ) { T max = x ; 4 if ( y . compareTo ( max ) > 0) { max = y ; } 5 6 7 8 if ( z . compareTo ( max ) > 0) { max = z ; } 9 10 11 12 return max ; 13 14 15 } 58 Глава 4. Генерици public static void main ( String args []) { System . out . printf ( " Max of %d , % d and % d is % d \ n \ n " , 3 , 4 , 5 , maximum ( 3 , 4 , 5 ) ) ; 16 17 18 19 System . out . printf ( " Max of %.1 f ,%.1 f and %.1 f is %.1 f \ n \ n " , 6.6 , 8.8 , 7.7 , maximum ( 6.6 , 8.8 , 7.7 ) ) ; 20 21 22 System . out . printf ( " Max of %s , % s and % s is % s \ n " , " pear " , " apple " , " orange " , maximum ( " pear " , " apple " , " orange " ) ) ; 23 24 25 } 26 27 } 4.2 Генерички податочни структури 4.2.1 Колекции во Jава (листи и множества) Jава колекциjа е секоjа класа коjа организирано чува обjекти и го имплементира Collection интерфеjсот. На пример ArrayList<T> е Jава колекциска класа и ги имплементира сите методи на Collection интерфеjсот. Collection интерфеjсот е наjвисоко ниво на рамката за колекциски класи (класи колекции) во Jава. Сите класи колекции се наоѓаат во пакетот java.util . Проблем 4.8 Да се имплементира класа BodyForm коjа од влезен тек (стандарден влез, датотека, ...) ќе чита податоци за мерења на тежината на неколку луѓе. Податоците за луѓето и нивната тежина (секоj човек може да има различен броj на мерења) се дадени во следниот формат: name weight1 weight2 weight3... Пример: James 81.3 90.8 87.9 Ваша задача е да ги имплементирате методите: • BodyForm() - default конструктор • void readData(InputStream inputStream) - метод за читање на податоците • void printByWeight(OutputStream outputStream, int type) - метод коj ги печати сите луѓе сортирани според тежината во растечки редослед. Притоа ако аргументот type има вредност: – 1 се подредуваат според максималната измерена тежина – 2 се подредуваат според просечната измерена тежина (просек од сите измерени тежини). Печатењето на луѓето е во следниот формат: [name] MAX : [max.0] kg, AVG : [avg.0] kg Во оваа задача, во класата BodyForm потребно е да се чува колекциjа од мерењата направени за секоjа од личностите (обjекти од класата Person). Бидеjќи нема никакви барања за групирање на личностите или пак за нивно чување во колекциjа коjа овозможува конкретен начин на управување, обjектите од класата Person се чуваат во генеричка податочна структура - листа (List<Person>). Конкретната имплементациjа 4.2 Генерички податочни структури 59 на оваа генеричка податочна структура коjа се користисти во решението е класата ArrayList<Person>. Со вчитување на податоците за личностите (за секоjа личност информациите се дадени во еден ред), се креира обjект од класа Person и истиот се додава во листата од обjекти од тип Person. Во методот printByWeight треба да се овозможи подредување на елементите во листата по два критериуми. Доколку се бара подредување по само еден критериум, наjчесто тоа се решава со имплементациjа на генеричкиот интерфеjс Comparable <Person>. Кога се бара подредување по повеќе од еден критериум наjдобро е да се имплементираат компаратори (имплементациjа на генеричкиот интерфеjс Comparator <Person>) кои би се користеле при повик на методот Collections.sort(). Во задачата се имплементирани два компаратори: MaxComparator (споредува според максимална измерена тежина) и AvgComparator (споредува според просечна измерена тежина). Бидеjќи компараторот е функциски интерфеjс, истиот може да биде имплементиран со ламбда израз. Како и секоj друг интерфеjс, може да биде имплементиран и со анонимни класи. 1 2 import java . io .*; import java . util .*; 3 4 5 class BodyForm { private List < Person > persons ; 6 public BodyForm () { persons = new ArrayList < Person >() ; } 7 8 9 10 void readData ( InputStream inputStream ) { Scanner scanner = new Scanner ( inputStream ) ; while ( scanner . hasNext () ) { String line = scanner . nextLine () ; String [] parts = line . split ( " " ) ; String name = parts [0]; Person person = new Person ( name ) ; for ( int i = 1; i < parts . length ; ++ i ) { person . addMeasurment ( Float . parseFloat ( parts [ i ]) ) ; } persons . add ( person ) ; } scanner . close () ; } 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void printByWeight ( OutputStream outputStream , int type ) { PrintWriter pw = new PrintWriter ( outputStream ) ; Collections . sort ( persons , ( type == 1) ? new MaxComparator () : new AvgComparator () ) ; 26 27 28 29 30 for ( Person person : persons ) { pw . println ( person ) ; } pw . flush () ; 31 32 33 34 } 35 36 37 } 38 39 40 41 class Person { String name ; List < Float > measurements ; 60 Глава 4. Генерици float max ; float sum ; float avg ; 42 43 44 45 public Person ( String name ) { this . name = name ; measurements = new ArrayList < Float >() ; sum = 0; max = Float . MIN_VALUE ; } 46 47 48 49 50 51 52 public void addMeasurment ( float weightMeasurement ) { measurements . add ( weightMeasurement ) ; sum += weightMeasurement ; if ( weightMeasurement > max ) { max = weightMeasurement ; } avg = sum / measurements . size () ; } 53 54 55 56 57 58 59 60 61 @Override 62 63 public String toString () { return String . format ( " % s MAX : %.1 f kg , " + " AVG : %.1 f kg " , name , max , avg ) ; } 64 65 66 67 68 69 } 70 71 72 class MaxComparator implements Comparator < Person > { @Override 73 public int compare ( Person p1 , Person p2 ) { if ( p1 . max < p2 . max ) return -1; else if ( p1 . max > p2 . max ) return 1; else return 0; } 74 75 76 77 78 79 } 80 81 82 class AvgComparator implements Comparator < Person > { @Override 83 public int compare ( Person p1 , Person p2 ) { if ( p1 . avg < p2 . avg ) return -1; else if ( p1 . avg > p2 . avg ) return 1; else return 0; } 84 85 86 87 88 89 } 90 91 92 93 94 95 96 97 98 99 100 public class BodyFormTest { public static void main ( String [] args ) { BodyForm bodyForm = new BodyForm () ; bodyForm . readData ( System . in ) ; System . out . println ( " BY MAX " ) ; bodyForm . printByWeight ( System . out , 1) ; System . out . println ( " BY AVG " ) ; bodyForm . printByWeight ( System . out , 2) ; } } 4.2 Генерички податочни структури 61 Проблем 4.9 Да се имплементира класа ArchiveStore во коjа се чува листа на архиви (елементи за архивирање). Секоj елемент за архивирање Archive има: • id - цел броj • dateArchived - датум на архивирање. Постоjат два видови на елементи за архивирање, LockedArchive за коj дополнително се чува датум до коj не смее да се отвори dateToOpen и SpecialArchive за коj се чуваат максимален броj на дозволени отварања maxOpen . За елементите за архивирање треба да се обезбедат следните методи: • LockedArchive(int id, LocalDate dateToOpen) - конструктор за заклучена архива • SpecialArchive(int id, int maxOpen) - конструктор за специjална архива За класата ArchiveStore да се обезбедат следните методи: • ArchiveStore() - предефиниран конструктор • void archiveItem(Archive item, LocalDate date) - метод за архивирање елемент item на одреден датум date • void openItem(int id, LocalDate date) - метод за отварање елемент од архивата со зададен id и одреден датум date. Ако не постои елемент со даденото id треба да се фрли исклучок од тип NonExistingItemException со порака Item with id [id] doesn’t exist . • String getLog() - враќа стринг со сите пораки запишани при архивирањето и отварањето архиви во посебен ред. За секоjа акциjа на архивирање во текст треба да се додаде следната порака Item [id] archived at [date] , додека за секоjа акциjа на отварање архива треба да се додаде Item [id] opened at [date] . При отварање ако се работи за LockedArhive и датумот на отварање е пред датумот кога може да се отвори, да се додаде порака Item [id] cannot be opened before [date] . Ако се работи за SpecialArhive и се обидеме да jа отвориме повеќе пати од дозволениот броj ( maxOpen ) да се додаде порака Item [id] cannot be opened more than [maxOpen] times . Во решението на оваа задача потребно да се употребат концептите на наследување и полиморфизам. Прво треба да се дефинира апстрактната класа Archive со еден апстрактен метод void open (Date date) . Од оваа класа треба да се изведат класи за двата типа на архиви: LockedArchive и SpecialArchive . Во овие две класи треба соодветно да се препокрие методот open() согласно барањата на задачата. Слично како и во претходната задача, архивите (обjекти од класата Archive ) ќе се чувааат во генеричката податочната структура ArrayList<Archive> во рамки на класата ArchiveStore . 1 2 3 import java . time . LocalDate ; import java . util . ArrayList ; import java . util . Scanner ; 4 5 6 7 8 class ArchiveStore { private ArrayList < Archive > items ; private StringBuilder log ; 62 Глава 4. Генерици public ArchiveStore () { items = new ArrayList < Archive >() ; log = new StringBuilder () ; } 9 10 11 12 13 public void archiveItem ( Archive item , LocalDate date ) { item . archive ( date ) ; items . add ( item ) ; log . append ( String . format ( " Item % d archived at % s \ n " , item . getId () , date . toString () ) ) ; } 14 15 16 17 18 19 20 public Archive openItem ( int id , LocalDate date ) throws N o n Ex i s ti n g It e m Ex c e pt i o n { for ( Archive item : items ) { if ( item . getId () == id ) { try { item . open ( date ) ; } catch ( I n v a l i d A r c h i v e O p e n E x c e p t i o n e ) { log . append ( e . getMessage () ) ; log . append ( " \ n " ) ; return item ; } log . append ( String . format ( " Item % d opened at % s \ n " , item . getId () , date ) ) ; return item ; } } throw new No n E xi s t in g I te m E xc e p ti o n ( id ) ; } 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public String getLog () { return log . toString () ; } 40 41 42 43 } 44 45 46 47 48 49 class N on E x is t i ng I t em E x ce p t io n extends Exception { public N o n Ex i s ti n g It e m Ex c e pt i o n ( int id ) { super ( String . format ( " Item with id % d doesn ’t exist " , id ) ) ; } } 50 51 52 53 abstract class Archive { protected int id ; protected LocalDate dateArchived ; 54 55 56 57 public void archive ( LocalDate date ) { this . dateArchived = date ; } 58 59 60 public abstract LocalDate open ( LocalDate date ) throws I n v a l i d A r c h i v e O p e n E x c e p t i o n ; 61 62 63 64 public int getId () { return id ; } 65 66 67 68 69 @override public boolean equals ( Object obj ) { Archive archive = ( Archive ) obj ; return this . id == archive . id ; 4.2 Генерички податочни структури } 70 71 } 72 73 74 class LockedArchive extends Archive { private LocalDate dateToOpen ; 75 public LockedArchive ( int id , LocalDate dateToOpen ) { this . id = id ; this . dateToOpen = dateToOpen ; } 76 77 78 79 80 @override public LocalDate open ( LocalDate date ) throws I n v a l i d A r c h i v e O p e n E x c e p t i o n { if ( date . isBefore ( dateToOpen ) ) throw new I n v a l i d A r c h i v e O p e n E x c e p t i o n ( String . format ( " Item % d cannot be opened before % s " , id , dateToOpen ) ) ; return date ; 81 82 83 84 85 86 87 88 89 } 90 91 } 92 93 94 95 class SpecialArchive extends Archive { private int countOpened ; private int maxOpen ; 96 public SpecialArchive ( int id , int maxOpen ) { this . id = id ; countOpened = 0; this . maxOpen = maxOpen ; } 97 98 99 100 101 102 @override public LocalDate open ( LocalDate date ) throws I n v a l i d A r c h i v e O p e n E x c e p t i o n { if ( countOpened >= maxOpen ) throw new I n v a l i d A r c h i v e O p e n E x c e p t i o n ( String . format ( " Item % d cannot be opened more than % d times " , id , maxOpen ) ) ; ++ countOpened ; return date ; } 103 104 105 106 107 108 109 110 111 112 113 114 } 115 116 117 118 119 120 class I n v a l i d A r c h i v e O p e n E x c e p t i o n extends Exception { public I n v a l i d A r c h i v e O p e n E x c e p t i o n ( String message ) { super ( message ) ; } } 121 122 123 124 125 126 127 128 129 130 public class ArchiveStoreTest { public static void main ( String [] args ) { ArchiveStore store = new ArchiveStore () ; LocalDate date = LocalDate . of (2013 , 10 , 7) ; Scanner scanner = new Scanner ( System . in ) ; scanner . nextLine () ; int n = scanner . nextInt () ; scanner . nextLine () ; scanner . nextLine () ; 63 64 Глава 4. Генерици int i ; for ( i = 0; i < n ; ++ i ) { int id = scanner . nextInt () ; long days = scanner . nextLong () ; 131 132 133 134 135 LocalDate dateToOpen = date . atStartOfDay () . plusSeconds ( days * 24 * 60 * 60) . toLocalDate () ; LockedArchive lockedArchive = new LockedArchive ( id , dateToOpen ) ; store . archiveItem ( lockedArchive , date ) ; 136 137 138 139 140 } scanner . nextLine () ; scanner . nextLine () ; n = scanner . nextInt () ; scanner . nextLine () ; scanner . nextLine () ; for ( i = 0; i < n ; ++ i ) { int id = scanner . nextInt () ; int maxOpen = scanner . nextInt () ; SpecialArchive specialArchive = new SpecialArchive ( id , maxOpen ) ; store . archiveItem ( specialArchive , date ) ; } scanner . nextLine () ; scanner . nextLine () ; while ( scanner . hasNext () ) { int open = scanner . nextInt () ; try { store . openItem ( open , date ) ; } catch ( No n E xi s t in g I te m E xc e p ti o n e ) { System . out . println ( e . getMessage () ) ; } } System . out . println ( store . getLog () ) ; 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 } 165 166 } Во методот openItem(int id, LocalDate date) , потребно е да се измине листата од архиви за да се наjде архивата со соодветно id . За пребарување на елементи според некоj критериум (како што е побарано во рамки на оваа задача), наjчесто се препорачува прво да се индексираат елементите за да се овозможи побрзо пребарување. Во продолжение се дадени примери во кои се користат податочни структури кои овозможуваат индексирање на елементите. Проблем 4.10 Да се напише класа за книга Book во коjа се чува: Наслов, категориjа и цена. За оваа класа, да се имплементира преоптоварен конструктор со следните аргументи Book(String title, String category, float price) . Потоа да се напише класа BookCollection во коjа се чува колекциjа од книги. Во оваа класа треба да се имплементираат следните методи: • public void addBook(Book book) - додавање книга во колекциjата • public void printByCategory(String category) - ги печати сите книги од проследената категориjа (се споредува стрингот без разлика на мали и големи букви), сортирани според насловот на книгата (ако насловот е ист, се сортираат според цената). • public List<Book> getCheapestN(int n) - враќа листа на наjевтините N 4.2 Генерички податочни структури 65 книги (ако има помалку од N книги во колекциjата, ги враќа сите). Во оваа задача, потребно е да се подредат книгите по два критериуми. Затоа се креираат два компаратори (класи што го имплементираат интерфеjсот Comparator): TitleComparator и PriceComparator. Оваа задача е решена со користење на податочната структура множество коjа овозможува чување само на уникатни елементи. Со користење на TreeSet имплементациjата, книгите директно се подредуваат при нивното додавање во множеството според употребениот компаратор. На овоj начин се гарантира елементите во податочната структура TreeSet секогаш да бидат подредени согласно зададениот критериум. Компараторот (PriceComparator) се користи при креирање множеството од тип TreeSet. Компараторот TitleComparator се користи во методот printByCategory (String category) за да се подредат книгите според категориjата во коjа припаѓаат. Подредувањето на книгите според категориjа е реализирано со помошно множество од тип TreeSet. 1 import java . util .*; 2 3 4 5 6 class Book { private String title ; private String category ; private float price ; 7 public Book ( String title , String category , float price ) { this . title = title ; this . category = category ; this . price = price ; } 8 9 10 11 12 13 public String getTitle () { return title ; } 14 15 16 17 public String getCategory () { return category ; } 18 19 20 21 public float getPrice () { return price ; } 22 23 24 25 @override public String toString () { return String . format ( " % s (% s ) %.2 f " , title , category , price ) ; } 26 27 28 29 30 } 31 32 class TitleComparator implements Comparator < Book > { 33 @override public int compare ( Book o1 , Book o2 ) { int result = o1 . getTitle () . compareTo ( o2 . getTitle () ) ; if ( result != 0) return result ; return Float . compare ( o1 . getPrice () , o2 . getPrice () ) ; 34 35 36 37 38 39 40 } 41 42 } 66 Глава 4. Генерици 43 44 class PriceComparator implements Comparator < Book > { 45 @override public int compare ( Book o1 , Book o2 ) { int result = Float . compare ( o1 . getPrice () , o2 . getPrice () ) ; if ( result != 0) { return result ; } return o1 . getTitle () . compareTo ( o2 . getTitle () ) ; } 46 47 48 49 50 51 52 53 54 } 55 56 57 class BookCollection { private TreeSet < Book > books ; 58 public BookCollection () { books = new TreeSet < >( new PriceComparator () ) ; } 59 60 61 62 63 public void addBook ( Book book ) { books . add ( book ) ; } 64 65 66 67 public void printByCategory ( String category ) { Set < Book > booksByTitle = new TreeSet < >( new TitleComparator () ) ; booksByTitle . addAll ( books ) ; for ( Book book : booksByTitle ) { if ( book . getCategory () . equalsIgnoreCase ( category ) ) { System . out . println ( book ) ; } } } 68 69 70 71 72 73 74 75 76 77 78 public List < Book > getCheapestN ( int n ) { List < Book > firstNBooks = new ArrayList < >() ; int i = 0; for ( Book b : books ) { if ( i < n ) { firstNBooks . add ( b ) ; } ++ i ; } return firstNBooks ; } 79 80 81 82 83 84 85 86 87 88 89 90 } 91 92 93 94 95 96 97 98 99 100 101 102 103 public class BooksTest { public static void main ( String [] args ) { Scanner scanner = new Scanner ( System . in ) ; int n = scanner . nextInt () ; scanner . nextLine () ; BookCollection booksCollection = new BookCollection () ; Set < String > categories = fillCollection ( scanner , booksCollection ) ; System . out . println ( " === PRINT BY CATEGORY === " ) ; for ( String category : categories ) { System . out . println ( " CATEGORY : " + category ) ; booksCollection . printByCategory ( category ) ; 4.2 Генерички податочни структури 67 } System . out . println ( " === TOP N BY PRICE === " ) ; print ( booksCollection . getCheapestN ( n ) ) ; 104 105 106 } 107 108 static void print ( List < Book > books ) { for ( Book book : books ) { System . out . println ( book ) ; } } 109 110 111 112 113 114 static TreeSet < String > fillCollection ( Scanner scanner , BookCollection collection ) { TreeSet < String > categories = new TreeSet < String >() ; while ( scanner . hasNext () ) { String line = scanner . nextLine () ; String [] parts = line . split ( " : " ) ; Book book = new Book ( parts [0] , parts [1] , Float . parseFloat ( parts [2]) ) ; collection . addBook ( book ) ; categories . add ( parts [1]) ; } return categories ; } 115 116 117 118 119 120 121 122 123 124 125 126 127 128 } При користење на множества од типот ( TreeSet ) треба да се внимава на имплементациjата на компараторите односно на имплементациjата на compareTo() методот во самите класи бидеjќи множествата чуваат уникатни елементи. Во овоj случаj, доколку компараторот за цена споредбата jа прави само по цената на книгите, во множество ќе биде додадена само една книга од сите книги со иста цена. Тоа ќе биде книгата коjа последно сме се обиделе да jа внесеме во множеството. Иако не е наведено во барањата на задачата, во компараторите покраj споредба на цена/наслов се прави и споредба на преостанатите полиња со цел да се избегне оваа ситуациjа. Проблем 4.11 Да се дефинира класа Component за коjа се чуваат боjа, тежина и колекциjа од внатрешни компоненти (референци од класата Component ). Во оваа класа да се дефинираат методите: • Component(String color, int weight) - конструктор со аргументи боjа и тежина • void addComponent(Component component) - за додавање нова компонента во внатрешната колекциjа (во оваа колекциjа компонентите секогаш се подредени според тежината во растечки редослед, ако имаат иста тежина подредени се алфабетски според боjата). • void changeColor(int weight, String color) - jа менува боjата на сите компоненти со тежина помала од проследената. • String toString() - враќа стринг репрезентациjа на обjектот, во следниот формат: weight1:color1 ---weight2:color2 ------weight3:color3 ------weight4:color4 ... 68 Глава 4. Генерици ---weight5:color5 ------weight6:color6 ------weight7:color7 ---------weight8:color8 Во оваа задача, класата коjа треба да се имплементира треба да чува (управува со) референци од обjекти од самата таа класа. Овоj концепт овозможува креирање на структура од обjекти во форма на дрво или граф. Во поглавjето „Шаблони за развоj на софтвер“ се воведува шаблон што користи слична парадигма за решавање на конкретен тип на проблеми. За да се овозможи чување на подредени елементи согласно одреден критериум, колекциjата од компоненти е множество од тип TreeSet. Изборот на овоj тип на множество наметнува имплементациjа на compareTo методот во рамките на класата Component или имплементациjа на соодветен Comparator. При имплементациjа на конкретни функционалности каj овие класи, наjчесто се употребува рекурзиjа со цел да се овозможи изминување (посетување) на елементите од сите нивоа на податочната структура. Во решението на задачата, два методи се имплементирани со помош на рекурзиjа: • changeColor(int weight , String color) - Во овоj метод прво се проверува тежината на компонента. Ако таа е помала од аргументот тежина (weight) се менува боjата на компонентата. Потоа, рекурзивно се посетуваат сите внатрешни компоненти со помош на статичкиот рекурзивен метод change(Component component, int weight, String color) коj исто така jа менува боjата на сите компоненти ако нивната тежина е помала од аргументот тежина (weight). • toString() - Во овоj метод, при креирањето на текстуалната репрезентациjа на компонентите треба да се овозможи додавање на екстра црти за секое следно ниво во хиерархиjата од компоненти. За таа цел е имплементиран помошниот статички рекурзивен метод createString(StringBuilder sb, Component component, int level) што рекурзивно ги изминува внатрешните компоненти на секоjа компонента. При процесирањето на внатрешните компоненти се менува и нивото (level). Оваа променлива jа чува информациjата за броjот на црти кои треба да се додадат при креирањето на текстуалната репрезентациjа. При повик на toString методот на една компонента, се повикува помошниот метод createString со празен StringBuilder на коjшто ќе се додаваат текстуалните репрезентации на сите внатрешни компоненти. 1 2 3 import java . util . Scanner ; import java . util . Set ; import java . util . TreeSet ; 4 5 6 7 8 9 class Component implements Comparable < Component > { String color ; int weight ; Set < Component > inner ; 10 11 12 13 14 15 16 public Component ( String color , int weight ) { this . color = color ; this . weight = weight ; this . inner = new TreeSet < Component >() ; } 4.2 Генерички податочни структури public void addComponent ( Component component ) { inner . add ( component ) ; } 17 18 19 20 public void changeColor ( int weight , String color ) { if ( this . weight < weight ) { this . color = color ; } for ( Component composite : inner ) { change ( composite , weight , color ) ; } } 21 22 23 24 25 26 27 28 29 static void change ( Component component , int weight , String color ) { if ( component . weight < weight ) { component . color = color ; } for ( Component c : component . inner ) { change (c , weight , color ) ; } } 30 31 32 33 34 35 36 37 38 static void createString ( StringBuilder sb , Component component , int level ) { for ( int i = 0; i < level ; ++ i ) sb . append ( " ---" ) ; sb . append ( String . format ( " % d :% s \ n " , component . weight , component . color ) ) ; for ( Component c : component . inner ) { createString ( sb , c , level + 1) ; } } 39 40 41 42 43 44 45 46 47 48 49 @Override 50 51 public int compareTo ( Component o ) { int w = this . weight - o . weight ; if ( w == 0) return this . color . compareTo ( o . color ) ; return w ; } 52 53 54 55 56 57 @Override 58 59 public String toString () { StringBuilder sb = new StringBuilder () ; Component . createString ( sb , this , 0) ; return sb . toString () ; } 60 61 62 63 64 65 } 66 67 68 69 70 71 72 73 74 75 76 77 public class ComponentTest { public static void main ( String [] args ) { Scanner scanner = new Scanner ( System . in ) ; String name = scanner . nextLine () ; Component prev = null ; while ( true ) { int what = scanner . nextInt () ; scanner . nextLine () ; if ( what == 1) { String color = scanner . nextLine () ; int weight = scanner . nextInt () ; 69 70 Глава 4. Генерици Component component = new Component ( color , weight ) ; prev = component ; } else if ( what == 2) { String color = scanner . nextLine () ; int weight = scanner . nextInt () ; Component component = new Component ( color , weight ) ; prev . addComponent ( component ) ; prev = component ; } else if ( what == 3) { String color = scanner . nextLine () ; int weight = scanner . nextInt () ; Component component = new Component ( color , weight ) ; prev . addComponent ( component ) ; } else if ( what == 4) { break ; } scanner . nextLine () ; 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 } 95 } 96 97 } 4.2.2 Мапи Мапа е генеричка податочна структура коjа чува парови од клуч и вредност. Клучевите во мапата се уникатни и еден клуч може да биде поврзан единствено со една вредност. Мапите како податочни структури се корисни при пребарување, ажурирање или бришење на елементи врз основа на даден клуч. Интерфеjсот Map вклучува методи за основните операции како додавање на пар од клуч и вредност, читање на вредност преку неговиот клуч, отстранување на пар од клуч и вредност, тестирање на присуство на клуч или вредност, броj на парови во структурата и тестирање дали податочната структура е празна или не. Проблем 4.12 Да се имплементира податочната структура Trie . Trie , или како што се нарекува префикс дрво (анг. prefix tree) (бидеjќи може да се пребарува преку префикси), претставува вид на подредено пребарувачко дрво во кое позициите на jазлите ги дефинираат клучевите со кои истите се асоцирани. Сите наследници на даден jазел имаат ист (заеднички) префикс на зборот (текстуалната низа) асоциран со тоj jазел. Коренот е асоциран со празна текстуална низа. 4.2 Генерички податочни структури 71 Пример: Во Trie податочната структура прикажана на сликата се додадени зборовите (клучевите): A , to , tea , ted , ten , i , in , и inn . Trie податочната структурa коjа треба да се имплемментира треба да има две методи: • Методот void addString(String word) - метод за додавање на нов збор во индексираната структура. • List<String> complete(String word) - метод коj треба да ги врати сите зборови внесени во податочната структура кои започнуваат со зборот (имаат префикс) word . Зборовите содржани во други зборови не се земаат во предвид. Пример: Ако зборовите accord и according се внесени во податочната структура, се враќа само зборот according . Од цртежот даден во описот на задачата, може да се согледа дека ќе треба да дефинираме податочна структура слична на дрво. За да се дефинира дрво податочната структура, прво потребно е да се дефинира класа jазел (Node) коjа претставува основен градбен елемент на дрво податочната структура. Jазелот е дефиниран со класата Node во коjа се чува вредност на jазелот (знакот, value) и референци кон неговите деца jазли. За чување на референците на деца jазлите се користи мапа во коjа клуч е знакот запишан во дете jазелот, а вредност е обjект од класата Node што го претставува соодветниот дете jазел. Мапата е од тип TreeMap со цел клучевите да бидат лексикографски подредени. На тоj начин како резултат од изминувањето на дрвото во методот complete се добие листа од лексикографски подредени зборови. Податочната структура Trie е претставена само преку jазелот корен. 1. void addString(String word) - За да се имплементира овоj метод креиран е помошниот метод void addStringRecursive(Node node, String word, int position) со следните аргументи: • Првиот аргумент се однесува на jазeлот коj моментално се проверува и овоj аргумент се менува постоjано во секоj рекурзивен повик. • Вториот аргумент го означува зборот коj се додава во структурата Trie и истиот е константен, односно не се менува низ рекурзивните повици. • Третиот аргумент е позициjата на буквата од зборот коjа се процесира. Позициjата се менува низ секоj рекурзивен повик. 72 Глава 4. Генерици Прво, методот се повикува за коренот на дрвото и за позициjа 0 (првата буква). Методот рекурзивно се извршува се додека позициjата не се изедначи со броjот на букви во зборот, односно, се додека не се изминати сите букви од зборот. Доколку jазелот node има дете со вредност иста како буквата што се наоѓа на позициjа position, тогаш методот се повикува рекурзивно за детето jазел. Доколку jазелот node нема дете jазел со вредност иста како буквата на позициjа position, тогаш методот се повикува рекурзивно на ново-креирано дете jазел со вредност еднаква на буквата на позициjа position. На овоj начин дрвото од тип Trie рекурзивно се полни за секоj додаден збор. 2. List<String> complete(String word) - Во методот complete прво се бара jазелот во коj се наоѓа последната буква од зборот word, проверуваjќи ги сите претходни букви од зборот. За таа цел jазелот prefixNode се инициjализира првично на коренот на дрвото. Потоа, се итерираат сите букви од зборот и се бара детето на prefixNode со вредност како таа буква. Доколку не се наjде дете jазел со таква вредност (методот getChid врати null), тоа значи дека во дрвото нема запишано збор со префикс word и се враќа празна листа како резултат. Доколку се врати jазел различен од null тоj jазел се запишува во prefixNode и постапката продолжува со следната буква од зборот. Откако е наjден jазелот во коj се наоѓа последната буква од зборот word, се повикува помошниот рекурзивен метод searchWordsFromNode(String word, Node node, List<String> results) со следните аргументи: • Првиот аргумент е зборот коj го пребаруваме. Тоа е почетниот префикс за сите зборови кои се додадени во структурата. Истиот се менува низ секоj рекурзивен повик на тоj начин што му се додава буквата коjа е запишана во jазелот коj се процесира. Зборот целосно се оформува кога ќе стигнеме до jазел лист во дрвото. • Вториот аргумент е jазелот коj се процесира • Третиот аргумент е листата од сите зборови со префикс word кои се дел од изградената Trie структурата. Методот searchWordsFromNode е помошен метод коj се повикува во complete методот. Истиот овозможува креирање на зборовите кои се дел од Trie структурата. 1 import java . util .*; 2 3 4 5 class Node { Character value ; Map < Character , Node > children ; 6 7 8 9 10 public Node () { value = ’ ’; children = new TreeMap < >() ; } 11 12 13 14 15 public Node ( Character value ) { this () ; this . value = value ; } 16 17 18 19 public Node getChild ( Character c ) { return children . get ( c ) ; } 20 21 public char getValue () { 4.2 Генерички податочни структури return value ; 22 } 23 24 public Node addChild ( char c ) { Node node = new Node ( c ) ; children . put (c , node ) ; return node ; } 25 26 27 28 29 30 @Override public String toString () { return toString ( this , 0) ; } 31 32 33 34 35 public static String toString ( Node node , int level ) { StringBuilder sb = new StringBuilder () ; for ( int i = 0; i < level ; i ++) sb . append ( " -" ) ; sb . append ( node . value ) . append ( " \ n " ) ; node . children . values () . forEach ( children -> sb . append ( Node . toString ( children , level + 1) ) ) ; return sb . toString () ; } 36 37 38 39 40 41 42 43 44 45 public boolean hasChildren () { return children . size () != 0; } 46 47 48 49 public boolean hasChild ( char c ) { return ( children . get ( c ) != null ) ; } 50 51 52 53 } 54 55 class Trie { 56 57 Node root ; 58 59 60 61 public Trie () { root = new Node () ; } 62 63 64 65 public void addString ( String word ) { addStringRecursive ( root , word , 0) ; } 66 67 68 69 70 71 72 73 74 75 76 77 78 79 public static void addStringRecursive ( Node node , String word , int position ) { if ( position != word . length () ) { char c = word . charAt ( position ) ; if ( node . hasChild ( c ) ) { addStringRecursive ( node . getChild ( c ) , word , position + 1) ; } else { addStringRecursive ( node . addChild ( c ) , word , position + 1) ; } } } 80 81 82 List < String > complete ( String word ) { Node prefixNode = root ; 73 74 Глава 4. Генерици List < String > results = new ArrayList < >() ; for ( int i = 0; i < word . length () ; i ++) { if (! prefixNode . hasChild ( word . charAt ( i ) ) ) return new ArrayList < >() ; prefixNode = prefixNode . getChild ( word . charAt ( i ) ) ; } searchWordsFromNode ( word , prefixNode , results ) ; return results ; 83 84 85 86 87 88 89 90 } 91 92 public void searchWordsFromNode ( String word , Node node , List < String > results ) { if (! node . hasChildren () ) { results . add ( word ) ; } else { for ( char c : node . children . keySet () ) { searchWordsFromNode ( word + c , node . getChild ( c ) , results ) ; } } } 93 94 95 96 97 98 99 100 101 102 103 104 @Override public String toString () { return root . toString () ; } 105 106 107 108 109 } 110 111 public class TrieTest { 112 public static void main ( String [] args ) { 113 114 Trie trie = new Trie () ; 115 116 List < String > completeWords = new ArrayList < >() ; boolean complete = false ; 117 118 119 Scanner scanner = new Scanner ( System . in ) ; while ( scanner . hasNext () ) { String word = scanner . next () ; if ( word . equals ( " COMPLETE : " ) ) { complete = true ; continue ; } if ( complete ) { completeWords . add ( word ) ; } else { trie . addString ( word ) ; } } scanner . close () ; 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 for ( String word : completeWords ) { System . out . println ( word ) ; List < String > completedWords = trie . complete ( word ) ; System . out . println ( completedWords ) ; } 135 136 137 138 139 } 140 141 } 4.2 Генерички податочни структури 75 Проблем 4.13 Да се имплементира класа UserGroups во коjа ќе се чуваат информаци- ите за корисници (имиња) и групите во кои тие се членови. Во класата да се имплементираат следните методи: • void addUsers(String[] users, String[] groups) - метод за додавање на корисниците (низите users и groups се со иста должина и за секоj корисник на истиот индекс во низата groups е групата во коjа припаѓа. • void setParentGroups(String[] groups, String[] parentGroups) - во овоj метод на група од низата groups се поставува родител група од parentGroups . Ова означува дека сите корисници кои се членови на некоjа група group треба да бидат членови и на евентуалниот родител на оваа група (ако го има). • void printUsers() - се печатат сите корисници подредени според броjот во името во растечки редослед и групите во кои припаѓа, разделени со запирка • void printGroups() - се печатат сите групи подредени лексикографски и сите корисници во групата разделени со запирка. Напомена: Имињата на корисниците се уникатни. При решавање на овоj проблем (како и за сите други), прво треба да одлучиме кои податочни структури ќе се користат за чување и менаџирање со корисниците на системот и групите заедно со нивните членови. Со цел да се покаже употребата на мапа податочната структура, корисниците независно се чуваат во една мапа ( users , каде клучот е името на корисникот, а вредност е обjект од класата User ), а корисниците по групи се чуваат во друга мапа ( groups , каде клучот е името на групата, а вредност е множество од сите корисници кои се дел од таа група). Имплементациjата коjа се користи за мапата groups е TreeMap со цел паровите од клуч и вредност да се чуваат подредени според вредноста на клуч, односно според името на групата (согласно поставеното барање во функциjата printGroups() . Дополнително, за секоj корисник се чува информациjа за групите во кои корисникот припаѓа. Групите на корисникот се чуваат во подредено множество ( TreeSet ), со цел истите да бидат лексикографски подредени за потребите на методот за печатење printGroups() . Во методот printUsers() сите корисници од мапата се додаваат во подредено множество ( TreeSet ) за да се подредат според името на корисникот. 1 import java . util .*; 2 3 4 5 class UserGroups { Map < String , User > users ; Map < String , Set < User > > groups ; 6 7 8 9 10 public UserGroups () { users = new HashMap < >() ; groups = new TreeMap < >() ; } 11 12 13 14 15 16 17 18 19 20 public void addUsers ( String [] users , String [] groups ) { for ( int i = 0; i < users . length ; ++ i ) { User user = new User ( users [ i ]) ; user . addGroup ( groups [ i ]) ; this . users . put ( users [ i ] , user ) ; Set < User > groupUsers = this . groups . get ( groups [ i ]) ; if ( groupUsers == null ) { groupUsers = new TreeSet < >() ; } 76 Глава 4. Генерици groupUsers . add ( user ) ; this . groups . put ( groups [ i ] , groupUsers ) ; 21 22 } 23 } 24 25 public void setParentGroups ( String [] groups , String [] parentGroups ) { for ( int i = 0; i < groups . length ; ++ i ) { Set < User > users = this . groups . get ( groups [ i ]) ; Set < User > pgUsers = this . groups . get ( parentGroups [ i ]) ; if ( users != null && pgUsers != null ) { pgUsers . addAll ( users ) ; for ( User user : users ) { user . groups . add ( parentGroups [ i ]) ; } } } } 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public void printUsers () { TreeSet < User > users = new TreeSet < >( this . users . values () ) ; for ( User user : users ) { System . out . println ( user ) ; } } 40 41 42 43 44 45 46 public void printGroups () { for ( String group : groups . keySet () ) { System . out . print ( group ) ; System . out . print ( " : " ) ; int i = 0; Set < User > users = groups . get ( group ) ; for ( User user : users ) { System . out . print ( user . name ) ; if (++ i != users . size () ) { System . out . print ( " , " ) ; } } System . out . println () ; } } 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 } 63 64 65 66 67 class User implements Comparable < User > { String name ; int number ; Set < String > groups ; 68 69 70 71 72 73 public User ( String name ) { this . name = name ; number = Integer . parseInt ( name . split ( " _ " ) [1]) ; this . groups = new TreeSet < >() ; } 74 75 76 77 public void addGroup ( String name ) { this . groups . add ( name ) ; } 78 79 80 81 @Override public String toString () { StringBuilder res = new StringBuilder () ; 4.2 Генерички податочни структури 77 res . append ( name ) ; res . append ( " : " ) ; for ( String g : groups ) { res . append ( g ) ; res . append ( " , " ) ; } res . delete ( res . length () - 2 , res . length () ) ; return res . toString () ; 82 83 84 85 86 87 88 89 } 90 91 @Override public int compareTo ( User o ) { return Integer . compare ( number , o . number ) ; } 92 93 94 95 96 } 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 public class UserGroupsTest { public static void main ( String [] args ) { Scanner scanner = new Scanner ( System . in ) ; UserGroups userGroups = new UserGroups () ; int n = scanner . nextInt () ; scanner . nextLine () ; String [] users = new String [ n ]; String [] groups = new String [ n ]; for ( int i = 0; i < n ; ++ i ) { String [] parts = scanner . nextLine () . split ( " : " ) ; users [ i ] = parts [0]; groups [ i ] = parts [1]; } userGroups . addUsers ( users , groups ) ; n = scanner . nextInt () ; scanner . nextLine () ; String [] groups1 = new String [ n ]; String [] parentGroups = new String [ n ]; for ( int i = 0; i < n ; ++ i ) { String [] parts = scanner . nextLine () . split ( " : " ) ; groups1 [ i ] = parts [0]; parentGroups [ i ] = parts [1]; } userGroups . setParentGroups ( groups1 , parentGroups ) ; System . out . println ( " --- USERS ---" ) ; userGroups . printUsers () ; System . out . println ( " --- GROUPS ---" ) ; userGroups . printGroups () ; scanner . close () ; } } Проблем 4.14 Да се имплементира класа за именик PhoneBook со следните методи: • void addContact(String name, String number) - додава нов контакт со име и броj во именикот. Ако се обидеме да додадеме контакт со веќе постоечки броj, треба да се фрли исклучок од класа DuplicateNumberException со порака Duplicate number: [number] . Комплексноста на овоj метод не треба да надминува O(logN) за N контакти. • void contactsByNumber(String number) - ги печати сите контакти кои во броjот го содржат броjот пренесен како аргумент во методот (минимална 78 Глава 4. Генерици должина на броjот [number] е 3). Комплексноста на овоj метод не треба да надминува O(NlogN) за N контакти. • void contactsByName(String name) - ги печати сите контакти кои даденото име. Комплексноста на пристапот до контактите со име name треба да биде O(1). Во двата методи контактите се печатат сортирани лексикографски според името, а оние со исто име потоа според броjот. За да се избегне додавање на дупликати телефонски броеви со методот addContact( String name, String number) од класата PhoneBook , телефонските броеви треба да се чуваат во множество. Бидеjќи немаме барање за подредување на телефонските броеви во функционалните барања, телефонските броеви ќе бидат чувани во HashSet , чиjа комплексноста за пристап до елемент е O(1). Од барањата за методот contactsByNumber(String number) може да се заклучи дека телефонските броеви треба да се чуваат во посебна податочна структура, каде истите ќе бидат групирани според сите можни комбинации од 3-цифрени до 9цифрени броеви кои се дел од самите броеви (пр. телефонскиот броj 076123456 ги содржи следните комбинации на цифри од 3-цифрени до 9-цифрени броеви: 076, 761, 612, 123, 234, 345, 456, 0761, 7612, ..., 07612345, 76123456, 076123456). За таа цел, телефонските броеви/контактите се чуваат во мапа во коjа клуч ќе бидат сите 3-9 цифрени комбинации на броеви од броевите што се додаваат во методот addContact , а вредност ќе биде подредено множество од обjекти од класата Contact чиj телефонски броj го содржи клучот. Мапата е од тип HashMap , што гарантира комплексноста од O(1) за пристап до броевите што го содржат броjот number . Бидеjќи множеството е подредено ( TreeSet ), комплексноста за печатење на контактите е O() каде е броjот на пронаjдени контакти и <= N (контактите се веќе индексирани според пребарувачкиот критериум). За потребите на методот contactsByName(String name) контактите треба да се индексираат според нивното име. Индексирачката податочна структура е мапа во коjа клуч е името на контактот, а вредност е подредено множество од сите контакти со име name . Мапата е од тип HashMap коjа гарантира комплексност од O(1) за пристап до контактите со име name . За да се постигне целосна функционалност при додавање на нови контакти со методот addContact(String name, String number) , сите податочни структури мора да се пополнат и обноват согласно новодобиените податоци. 1 2 3 4 5 6 7 8 import import import import import import import import java . util . ArrayList ; java . util . HashMap ; java . util . HashSet ; java . util . List ; java . util . Scanner ; java . util . Set ; java . util . TreeMap ; java . util . TreeSet ; 9 10 11 12 13 class PhoneBook { TreeMap < String , Set < Contact > > contactsNumber ; HashMap < String , Set < Contact > > contactsName ; HashSet < String > numbers ; 14 15 16 17 18 public PhoneBook () { contactsNumber = new TreeMap < String , Set < Contact > >() ; contactsName = new HashMap < String , Set < Contact > >() ; numbers = new HashSet < String >() ; 4.2 Генерички податочни структури } 19 20 public void addContact ( String name , String number ) throws D u p li c a te N u mb e r Ex c e pt i o n { if ( numbers . contains ( number ) ) { throw new Du p l ic a t eN u m be r E xc e p ti o n ( number ) ; } numbers . add ( number ) ; Contact contact = new Contact ( name , number ) ; List < String > keys = contact . getPhoneKeys () ; for ( String key : keys ) { Set < Contact > contacts = contactsNumber . get ( key ) ; if ( contacts == null ) { contacts = new TreeSet < Contact >() ; contactsNumber . put ( key , contacts ) ; } contacts . add ( contact ) ; } Set < Contact > nameContacts = contactsName . get ( name ) ; if ( nameContacts == null ) { nameContacts = new TreeSet < Contact >() ; contactsName . put ( name , nameContacts ) ; } nameContacts . add ( contact ) ; } 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public void contactsByNumber ( String number ) { Set < Contact > contacts = contactsNumber . get ( number ) ; if ( contacts == null ) { System . out . println ( " NOT FOUND " ) ; return ; } for ( Contact contact : contacts ) { System . out . println ( contact ) ; } } 45 46 47 48 49 50 51 52 53 54 55 public void contactsByName ( String name ) { Set < Contact > contacts = contactsName . get ( name ) ; if ( contacts == null ) { System . out . println ( " NOT FOUND " ) ; return ; } for ( Contact contact : contacts ) { System . out . println ( contact ) ; } } 56 57 58 59 60 61 62 63 64 65 66 } 67 68 69 70 class Contact implements Comparable < Contact > { String name ; String number ; 71 72 73 74 75 public Contact ( String name , String number ) { this . name = name ; this . number = number ; } 76 77 78 79 @Override public String toString () { return String . format ( " % s % s " , name , number ) ; 79 80 Глава 4. Генерици } 80 81 public List < String > getPhoneKeys () { List < String > keys = new ArrayList < String >() ; int len = number . length () ; for ( int i = 0; i <= len - 3; ++ i ) { for ( int j = i + 3; j <= len ; ++ j ) { String key = number . substring (i , j ) ; keys . add ( key ) ; } } return keys ; } 82 83 84 85 86 87 88 89 90 91 92 93 @Override public int compareTo ( Contact o ) { if ( name . equals ( o . name ) ) { return number . compareTo ( o . number ) ; } return name . compareTo ( o . name ) ; } 94 95 96 97 98 99 100 101 102 } 103 104 105 106 107 108 class D up l i ca t e Nu m b er E x ce p t io n extends Exception { public D u p li c a te N u mb e r Ex c e pt i o n ( String number ) { super ( String . format ( " Duplicate number : % s " , number ) ) ; } } 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 public class PhoneBookTest { public static void main ( String [] args ) { PhoneBook phoneBook = new PhoneBook () ; Scanner scanner = new Scanner ( System . in ) ; int n = scanner . nextInt () ; scanner . nextLine () ; for ( int i = 0; i < n ; ++ i ) { String line = scanner . nextLine () ; String [] parts = line . split ( " : " ) ; try { phoneBook . addContact ( parts [0] , parts [1]) ; } catch ( Du p l ic a t eN u m be r E xc e p ti o n e ) { System . out . println ( e . getMessage () ) ; } } while ( scanner . hasNextLine () ) { String line = scanner . nextLine () ; System . out . println ( line ) ; String [] parts = line . split ( " : " ) ; if ( parts [0]. equals ( " NUM " ) ) { phoneBook . contactsByNumber ( parts [1]) ; } else { phoneBook . contactsByName ( parts [1]) ; } } } } 4.2 Генерички податочни структури 81 Проблем 4.15 Да се имплементира паркинг системот на еден град. За таа цел треба да се имплементираат класите: • ParkingSector во коjа се чуват информации за: – кодот на секторот String – броjот на паркинг места int – сите автомобили (регистрации) во овоj сектор ? • CityParking во коjа се чуваат информации за: – името на градот String – и сите паркинг сектори во тоj град ? Во класата CityParking треба да се имплементираат следните методи: • CityParking(String name) конструктор со аргумент име на градот • void createSectors(String[] sectorNames, int[] counts) креирање на паркинг сектори со имиња String[] sectorNames и броj на паркинг места int[] counts (двете низи се со иста големина) • void addCar(String sectorName, int spotNumber, String rNumber) за додавање автомобил со регистрациjа rNumber на позициjа spotNumber (притоа ако позициjата не е во опсегот од 1 ≤ spotNumber ≤ броj-на-паркинг-места треба да се фрли исклучок од тип InvalidSpotNumberException , ако позициjата е зафатена од некоj друг автомобил тогаш се фрла исклучок од тип SpotTakenException , ако не постои сектор со даденото име се фрла исклучок од тип NoSuchSectorException ) • void findCar(String registrationNumber) за печатење на секторот и броjот на местото каде што е паркиран автомобилот со дадената регистрациjа (ако не се пронаjде таков автомобил се фрла исклучок од тип CarNotFoundException ) Забелешка: комплексноста на методот не треба да биде поголема од O(logN) • toString() враќа стринг во формат: Ime_na_grad Kod_na_sektor : zafateni_mesta/parking_mesta //za site sektori vo nov red Во класата ParkingSector треба да се имплементираат следните методи: • ParkingSector(String code, int count) конструктор со аргументи код на секторот и броj на слободни места • и останати потребни методи за имплементациjа на претходната класа... Во класата ParkingSector автомобилите/регистрациите се чуваат во мапа ( spots ) во коjа клуч е редниот броj на паркинг местото (од 1 до броjот на паркинг места), а вредност е регистрациjата на паркираното возило на тоа паркинг место. Мапата е од тип HashMap што гарантира комплексност од O(1) при проверка за пополнетост на паркинг место. За проверка дали возило со дадена регистрациjа е паркирано во секторот, дополнително се користи инверзна мапа ( index ) во коjа клуч е регистрациjата на возилото, а вредност е редниот броj на паркинг местото). Во класата CityParking , паркинг секторите се чуваат во мапа ( sectors ) во коjа клуч е кодот на секторот, а вредност е обjект од класата ParkingSector . Оваа мапа е од 82 Глава 4. Генерици тип TreeMap со цел паровите (клуч, вредност) да бидат подредени според вредноста на клучот, односно според кодот на паркинг секторот. Во оваа класа дополнително се чува и мапа ( index ) во коjа клуч е броjот на регистрациjата, а вредност е паркинг секторот каде што е паркирано возилото со регистрациjа иста како клучот (обjект од тип ParkingSector ). Мапата index е од тип HashMap и истата гарантира комплексност од O(1) за пристап до паркинг секторот во коj е паркирано возилото. Бидеjќи комплексноста за пристап до паркинг секторот каде е паркирано некое возило е O(1) и комплексноста за пристап до редниот броj на паркинг местото во секторот каде е паркирано возилото е O(1), вкупната комплексност на методот findCar(String registrationNumber) изнесува исто така O(1). 1 2 3 4 5 import import import import import java . util . HashMap ; java . util . Map ; java . util . Map . Entry ; java . util . Scanner ; java . util . TreeMap ; 6 7 8 9 10 11 class ParkingSector { private String code ; private int count ; private Map < Integer , String > spots ; private Map < String , Integer > index ; 12 13 14 15 16 17 18 public ParkingSector ( String code , int count ) { this . code = code ; this . count = count ; this . spots = new HashMap < Integer , String >() ; this . index = new HashMap < String , Integer >() ; } 19 20 21 22 23 24 25 26 27 28 29 30 public void addCar ( int spotNumber , String registrationNumber ) throws InvalidSpotNumberException , SpotTakenException { if ( spotNumber <= 0 || spotNumber > count ) { throw new I n v a l i d S p o t N u m b e r E x c e p t i o n () ; } if ( spots . containsKey ( spotNumber ) ) { throw new SpotTakenException () ; } spots . put ( spotNumber , registrationNumber ) ; index . put ( registrationNumber , spotNumber ) ; } 31 32 33 34 35 36 37 38 public int findSpotNumber ( String registrationNumber ) throws CarNotFoundException { if ( index . containsKey ( registrationNumber ) ) { return index . get ( registrationNumber ) ; } throw new CarNotFoundException ( registrationNumber ) ; } 39 40 41 42 public void clearSpot ( int spotNumber ) { spots . remove ( spotNumber ) ; } 43 44 45 46 public String getCode () { return this . code ; } 47 48 public Map < Integer , String > getSpots () { 4.2 Генерички податочни структури return spots ; 49 } 50 51 @Override public String toString () { return String . format ( " % s : % d /% d " , code , spots . size () , count ) ; } 52 53 54 55 56 57 } 58 59 60 61 62 63 class I n v a l i d S p o t N u m b e r E x c e p t i o n extends Exception { public I n v a l i d S p o t N u m b e r E x c e p t i o n () { super ( " Invalid spot number " ) ; } } 64 65 66 67 68 69 class SpotTakenException extends Exception { public SpotTakenException () { super ( " Spot is taken already ! " ) ; } } 70 71 72 73 74 class CityParking { private String name ; private Map < String , ParkingSector > sectors ; private Map < String , ParkingSector > index ; 75 76 77 78 79 80 public CityParking ( String name ) { this . name = name ; this . sectors = new TreeMap < String , ParkingSector >() ; this . index = new HashMap < String , ParkingSector >() ; } 81 82 83 84 85 86 87 88 89 public void createSectors ( String [] sectorNames , int [] counts ) { for ( int i = 0; i < sectorNames . length ; ++ i ) { ParkingSector parkingSector = new ParkingSector ( sectorNames [ i ] , counts [ i ]) ; sectors . put ( parkingSector . getCode () , parkingSector ) ; } } 90 91 92 93 94 95 96 97 98 99 100 101 102 public void addCar ( String sectorName , int spotNumber , String registrationNumber ) throws InvalidSpotNumberException , SpotTakenException , NoSuc hSect orExce ption { ParkingSector parkingSector = sectors . get ( sectorName ) ; if ( parkingSector != null ) { parkingSector . addCar ( spotNumber , registrationNumber ) ; index . put ( registrationNumber , parkingSector ) ; } else { throw new NoS uchSec torEx ceptio n ( sectorName ) ; } } 103 104 105 106 107 108 109 public void findCar ( String registrationNumber ) throws CarNotFoundException { if ( index . containsKey ( registrationNumber ) ) { ParkingSector parkingSector = index . get ( registrationNumber ) ; int spotNumber = 83 84 Глава 4. Генерици parkingSector . findSpotNumber ( registrationNumber ) ; System . out . println ( String . format ( " % s : % d " , parkingSector . getCode () , spotNumber ) ) ; } else { throw new CarNotFoundException ( registrationNumber ) ; } 110 111 112 113 114 115 } 116 117 @Override public String toString () { StringBuilder sb = new StringBuilder () ; sb . append ( name ) ; sb . append ( " \ n " ) ; for ( Entry < String , ParkingSector > sector : sectors . entrySet () ) { sb . append ( sector . getValue () . toString () ) ; sb . append ( " \ n " ) ; } return sb . toString () ; } 118 119 120 121 122 123 124 125 126 127 128 129 130 } 131 132 133 134 135 136 class N o SuchS ectorE xcept ion extends Exception { public NoSu chSect orExce ption ( String sectorName ) { super ( String . format ( " No sector with name % s " , sectorName ) ) ; } } 137 138 139 140 141 142 143 class C arNotFoundException extends Exception { public CarNotFoundException ( String registrationNumber ) { super ( String . format ( " Car with RN % s not found " , registrationNumber ) ) ; } } 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 public class ParkingTest { public static void main ( String [] args ) { Scanner scanner = new Scanner ( System . in ) ; int n = scanner . nextInt () ; scanner . nextLine () ; String [] sectorNames = new String [ n ]; int [] counts = new int [ n ]; for ( int i = 0; i < n ; ++ i ) { String [] parts = scanner . nextLine () . split ( " " ) ; sectorNames [ i ] = parts [0]; counts [ i ] = Integer . parseInt ( parts [1]) ; } String name = scanner . nextLine () ; CityParking cityParking = new CityParking ( name ) ; cityParking . createSectors ( sectorNames , counts ) ; n = scanner . nextInt () ; scanner . nextLine () ; for ( int i = 0; i < n ; ++ i ) { String [] parts = scanner . nextLine () . split ( " " ) ; String sectorName = parts [0]; int spotNumber = Integer . parseInt ( parts [1]) ; String registrationNumber = parts [2]; try { cityParking . addCar ( sectorName , spotNumber , registrationNumber ) ; } catch ( I n v a l i d S p o t N u m b e r E x c e p t i o n e ) { 4.2 Генерички податочни структури System . out . println ( e . getMessage () ) ; } catch ( SpotTakenException e ) { System . out . println ( e . getMessage () ) ; } catch ( NoS uchSe ctorEx ceptio n e ) { System . out . println ( e . getMessage () ) ; } 171 172 173 174 175 176 } n = scanner . nextInt () ; scanner . nextLine () ; for ( int i = 0; i < n ; ++ i ) { String registrationNumber = scanner . nextLine () ; try { cityParking . findCar ( registrationNumber ) ; } catch ( CarNotFoundException e ) { System . out . println ( e . getMessage () ) ; } } System . out . println ( cityParking ) ; 177 178 179 180 181 182 183 184 185 186 187 188 } 189 190 } 85 5. Stream API Апликацискиот програмски интерфеjс за потоци (Stream API) е додаден во Jава 8 за да се олесни последователното и паралелното извршување на операциите. Овоj интерфеjс обезбедува две клучни апстракции: поток од податоци, што претставува конечна или бесконечна низа на елементи и поточни операции за процесирање на потокот од елементи. Како Потокот од елементи може да биде креиран на повеќе различни начини. Вообичаено потоците од елементи се креираат од колекции, низи, датотеки, генератори на псевдо-случаjни броеви и други. Елементите во потокот може да бидат инстанци на примитивни податочни типови или референцни типови. Од примитивните податочни типови поддржани се int , long и double . Процесирачките операции може да бидат класични и терминални. Класичните операции го трансформираат влезниот потокот од елементи во друг поток од елементи од ист или различен тип (пропуштање на одредени елементи кои задоволуваат одреден услов или мапирање на елементи со помош на некоjа функциjа). Терминалните операции се последните операции кои се спроведуваат на потокот од елементи добиен како резултат од последната класична операциjа (агрегациjа на сите елементи од потокот во единствена вредност односно колекциjа од елементи или печатење на сите елементи). Евалуациjата на класичните операции не започнува додека не биде поставена терминална операциjа. Проблем 5.1 Да се имплементира апликациjа за евидентирање на оценките на студентите на еден факултет. Студентите на факултетот може да бидат запишани на тригодишни или четиригодишни студии. Во текот на студиите, студентите имаат два семестри во секоjа година и во секоj од семестрите имаат по наjмногу три предмети. За таа цел, дефинираjте класа Faculty во коjа што ќе чувате информации за студентите и нивните оценки во сите семестри. За класата да се имплементираат: • Faculty() - предефиниран конструктор • метод void addStudent(String id, int yearsOfStudies) за додавање на студент на факултетот со индекс ID и години на студии yearsOfStudies . 88 Глава 5. Stream API • метод void addGradeToStudent(String studentId, int term, String courseName, int grade) - за додавање на оцена по предметот courseName на студентот со индекс studentId во семестар term . • Со помош на исклучок од тип OperationNotAllowedException да се спречи додавање на повеќе од 3 оценки по семестар. Во таков случаj да се испечати порака од формат: Student [studentID] already has 3 grades in term [term] Со истиот тип на исклучок да се спречи додавање на оценка во семестар поголем од 6 за тригодишни студии односно во семестар поголем од 8 за четиригодишни студии. Во овоj случаj да се испечати порака: Term [term] is not possible for student with ID [studentId] • Да се детектира дипломирање на студентот. Студентот дипломира тогаш кога ќе положи 18 или 24 предмети во зависност од тоа колку години студира. Во моментот на дипломирање на студентот истиот треба да се избрише од евиденциjата и да се зачува лог за него во формат: Student with ID [studentID] graduated with average grade [averageGrade] in [yearsOfStudies] years • Метод String getFacultyLogs () - што ќе ги врати логовите за дипломираните студенти. • Метод String getDetailedReportForStudent (String id) - метод што ќе врати детален извештаj студентот со индекс id. Пристапот до студентот со индекс ИД да има комплексност O(1) ! Деталниот извештаj е во формат: Student: [id] Term 1: Courses for term: [count] Average grade for term: [average] ... ..... Term n: Courses: [count] Average grade for term: [average] Average grade: [average grade for student] Courses attended: [all-attended-courses, comma-separated] • Метод void printFirstNStudents (int n) - метод коj ќе испечати краток извештаj за наjдобрите n студенти (според броjот на положени предмети, а доколку е ист броjот на положени предмети според просечната оцена), подредени во опаѓачки редослед. Краткиот извештаj треба да го има следниот формат: Student: [id] Courses passed: [coursesPassed] Average grade: [averageGrade] 89 • Метод void printCourses () - метод коjшто ќе ги испечати сите предмети во следниот формат: [course_name] [count_of_students] [average_grade]} Предметите треба да бидат подредени според броjот на слушатели, а доколку тоj броj е ист, според просечната оцена. Во решението на оваа задача потребно е да се употреби наследување и полиморфизам со цел да се имплементира барањето за постоење на студент од тригодишни или четиригодишни студии (дадена е дефинициjа на класите StudentOnThreeYearStudies и StudentOnFourYearStudies ). За да се овозможи групирање на оцените по семестар, како и подредување на семестрите според редниот броj во растечки редослед, оцените на секоj студент се чуваат во колекциjа TreeMap , каде клучот е цел броj што означува реден броj на семестарот, а вредноста е листа од цели броеви што ги претставува сите оцени на студентот во соодветниот семестар. За да се овозможи лексикографско подредување на предметите што секоj студент ги има слушано, имињата на предметите се чуваат во подредено множество TreeSet . Во продолжение се обjаснети дел од методите со посебен акцент на stream операторите: • Методот double averageGrade() во класата Student - Во овоj метод прво се креира поток од вредностите од мапата со оцени по семестри gradesByTerm . Потоа се користи операторот flatMap(Collections::stream) за да се постигне израмнување на потокот од листи од цели броеви во поток од цели броеви. Како резултат од овие два оператори се добива Stream<Integer> . За да се искористи постоечкиот оператор за пресметување на средна вредност average() потребно е потокот од податоци да биде конвертиран во IntStream со помош на методот mapToInt . • Методот double averageGrade() во класата Student - Во овоj метод се користи аналоген пристап како и во претходниот метод со единствена разлика што тука се земаат само оцените за конкретен семестар од мапата gradesByTerm . На тоj начин директно се добива поток од цели броеви и нема потреба од повикување на операторот flatMap за израмнување на потокот. • Методот printFirstNStudents(int n) во класата Faculty - Во овоj метод прво се креира компаратор за студенти што се искористи за подредување на студентите. Потоа, сите студенти се додаваат во подередено множество TreeSet со цел студентите да се чуваат подредени според дефинираниот компаратор. Oд подреденото множество се креира поток на студенти и од истиот со помош на операторот limit се земаат првите N . Со операторот forEach студентите кои се добиваат како резултат од претходната операциjа се печатат во нов ред. • Методот printCourses() во класата Faculty - Овоj метод jа следи истата парадигма како и претходниот метод ( printFirstNStudents(int n) ). Прво се креира компаратор за споредување и подредување на курсевите во резултантното множество. Потоа, со операторот forEach секоj курс директно се печати во нов ред. 1 2 3 import java . util .*; import java . util . function . Function ; import java . util . stream . Collectors ; 90 4 Глава 5. Stream API import java . util . stream . IntStream ; 5 6 7 8 9 10 class O p e r a t i o n N o t A l l o w e d E x c e p t i o n extends Exception { O p e r a t i o n N o t A l l o w e d E x c e p t i o n ( String message ) { super ( message ) ; } } 11 12 13 14 15 abstract class Student { String id ; Map < Integer , List < Integer > > gradesByTerm ; Set < String > courses ; 16 17 18 19 20 21 public Student ( String id ) { this . id = id ; gradesByTerm = new TreeMap < >() ; courses = new TreeSet < >() ; } 22 23 24 25 26 27 String getGraduationLog () { return String . format ( " Student with ID % s graduated with average " + " grade %.2 f " , id , averageGrade () ) ; } 28 29 30 31 32 33 34 35 double averageGrade () { return gradesByTerm . values () . stream () . flatMap ( Collection :: stream ) . mapToInt ( i -> i ) . average () . orElse (5.0) ; } 36 37 38 39 40 41 42 double averageGradeForTerm ( int term ) { return gradesByTerm . get ( term ) . stream () . mapToInt ( i -> i ) . average () . orElse (5.0) ; } 43 44 45 abstract boolean addGrade ( int term , String courseName , int grade ) throws O p e r a t i o n N o t A l l o w e d E x c e p t i o n ; 46 47 48 49 50 51 52 53 54 55 56 57 void validate ( int term ) throws O p e r a t i o n N o t A l l o w e d E x c e p t i o n { if (! gradesByTerm . containsKey ( term ) ) throw new O p e r a t i o n N o t A l l o w e d E x c e p t i o n ( String . format ( " Term % d is not possible for student with ID % s " , term , id ) ) ; if ( gradesByTerm . get ( term ) . size () == 3) throw new O p e r a t i o n N o t A l l o w e d E x c e p t i o n ( String . format ( " Student % s already has 3 grades in term % d " , id , term ) ) ; } 58 59 60 61 62 63 64 int countOfCoursesPassed () { return gradesByTerm . values () . stream () . mapToInt ( List :: size ) . sum () ; } 91 public String getDetailedReport () { StringBuilder sb = new StringBuilder () ; sb . append ( String . format ( " Student : % s \ n " , id ) ) ; gradesByTerm . keySet () . forEach ( term -> sb . append ( getTermReport ( term ) ) . append ( " \ n " ) ) ; sb . append ( String . format ( " Average grade : %.2 f \ nCourses attended : % s " , averageGrade () , String . join ( " ," , courses ) )); return sb . toString () ; } 65 66 67 68 69 70 71 72 73 74 75 76 77 78 public String getShortReport () { return String . format ( " Student : % s Courses passed : " + " % d Average grade : %.2 f " , id , countOfCoursesPassed () , averageGrade () ) ; } 79 80 81 82 83 84 85 86 87 String getTermReport ( int term ) { return String . format ( " Term % d \ nCourses : " + " % d \ nAverage grade for term : %.2 f " , term , gradesByTerm . get ( term ) . size () , averageGradeForTerm ( term ) ); } 88 89 90 91 92 93 94 95 96 97 String getId () { return id ; } 98 99 100 101 } 102 103 class S t u d e n t O n T h r e e Y e a r s S t u d i e s extends Student { 104 public S t u d e n t O n T h r e e Y e a r s S t u d i e s ( String id ) { super ( id ) ; IntStream . range (1 , 7) . forEach ( i -> gradesByTerm . putIfAbsent (i , new ArrayList < >() ) ) ; } 105 106 107 108 109 110 @Override boolean addGrade ( int term , String courseName , int grade ) throws O p e r a t i o n N o t A l l o w e d E x c e p t i o n { validate ( term ) ; gradesByTerm . get ( term ) . add ( grade ) ; courses . add ( courseName ) ; return countOfCoursesPassed () == 18; } 111 112 113 114 115 116 117 118 119 @Override String getGraduationLog () { return super . getGraduationLog () + " in 3 years . " ; } 120 121 122 123 124 125 } 92 126 Глава 5. Stream API class S t u d e n tO n F o u r Y e a r sS t u d i e s extends Student { 127 public St u d e n t O n F o u rY e a r s S t u d i es ( String id ) { super ( id ) ; IntStream . range (1 , 9) . forEach ( i -> gradesByTerm . putIfAbsent (i , new ArrayList < >() ) ) ; } 128 129 130 131 132 133 @Override boolean addGrade ( int term , String courseName , int grade ) throws O p e r a t i o n N o t A l l o w e d E x c e p t i o n { validate ( term ) ; gradesByTerm . get ( term ) . add ( grade ) ; courses . add ( courseName ) ; return countOfCoursesPassed () == 24; } 134 135 136 137 138 139 140 141 142 @Override String getGraduationLog () { return super . getGraduationLog () + " in 4 years . " ; } 143 144 145 146 147 } 148 149 150 151 class Course { String courseName ; I n t SummaryStatistics statistics ; 152 public Course ( String courseName ) { this . courseName = courseName ; statistics = new IntSummaryStatistics () ; } 153 154 155 156 157 void addGrade ( int grade ) { statistics . accept ( grade ) ; } 158 159 160 161 @Override public String toString () { return String . format ( " % s % d %.2 f " , courseName , statistics . getCount () , statistics . getAverage () ) ; } 162 163 164 165 166 167 168 169 int getStudentsCount () { return ( int ) statistics . getCount () ; } 170 171 172 173 double getC ourseA verage Grade () { return statistics . getAverage () ; } 174 175 176 177 public String getCourseName () { return courseName ; } 178 179 180 181 } 182 183 184 185 186 class Faculty { Map < String , Student > studentsById ; Map < String , Course > coursesByName ; StringBuilder logs ; 93 187 public Faculty () { studentsById = new HashMap < >() ; coursesByName = new HashMap < >() ; logs = new StringBuilder () ; } 188 189 190 191 192 193 void addStudent ( String id , int yearsOfStudies ) { studentsById . put ( id , yearsOfStudies == 3 ? new S t u d e n t O n T h r e e Y e a r s S t u d i e s ( id ) : new S t u d e nt O n F o u r Y e a rs S t u d i e s ( id ) ) ; } 194 195 196 197 198 199 200 void addGradeToStudent ( String studentId , int term , String courseName , int grade ) throws O p e r a t i o n N o t A l l o w e d E x c e p t i o n { Student student = studentsById . get ( studentId ) ; boolean graduated = student . addGrade ( term , courseName , grade ) ; coursesByName . putIfAbsent ( courseName , new Course ( courseName ) ) ; coursesByName . get ( courseName ) . addGrade ( grade ) ; if ( graduated ) { logs . append ( student . getGraduationLog () ) . append ( " \ n " ) ; studentsById . remove ( studentId ) ; } } 201 202 203 204 205 206 207 208 209 210 211 212 213 214 String getFacultyLogs () { return logs . deleteCharAt ( logs . length () - 1) . toString () ; } 215 216 217 218 String g e t D e t a i l e d R e p o r t F o r S t u d e n t ( String id ) { return studentsById . get ( id ) . getDetailedReport () ; } 219 220 221 222 void printFirstNStudents ( int n ) { Comparator < Student > studentComparator = Comparator . comparing ( Student :: countOfCoursesPassed ) . thenComparing ( Student :: averageGrade ) . thenComparing ( Student :: getId ) . reversed () ; TreeSet < Student > students = new TreeSet < >( studentComparator ) ; students . addAll ( studentsById . values () ) ; students . stream () . limit ( n ) . forEach ( student -> System . out . println ( student . getShortReport () ) ) ; } 223 224 225 226 227 228 229 230 231 232 233 234 235 void printCourses () { Comparator < Course > courseComparator = Comparator . comparing ( Course :: getStudentsCount ) . thenComparing ( Course :: getCou rseAv erageG rade ) . thenComparing ( Course :: getCourseName ) ; TreeSet < Course > coursesSet = new TreeSet < >( courseComparator ) ; coursesSet . addAll ( coursesByName . values () ) ; coursesSet . forEach ( System . out :: println ) ; } 236 237 238 239 240 241 242 243 244 245 } 246 247 public class FacultyTest { 94 Глава 5. Stream API 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 public static void main ( String [] args ) { Scanner sc = new Scanner ( System . in ) ; Faculty faculty = new Faculty () ; for ( int i = 1; i <= 10; i ++) { faculty . addStudent ( " student " + i , (( i % 2) == 1 ? 3 : 4) ) ; int courseCounter = 1; for ( int j = 1; j <= (( i % 2 == 1) ? 6 : 8) ; j ++) { for ( int k = 1; k <= (( j % 2 == 1) ? 2 : 3) ; k ++) { int grade = sc . nextInt () ; try { faculty . addGradeToStudent ( " student " + i , j, ( " course " + courseCounter ) , grade ) ; } catch ( O p e r a t i o n N o t A l l o w e d E x c e p t i o n e ) { System . out . println ( e . getMessage () ) ; } ++ courseCounter ; } } 271 272 } 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 for ( int i = 11; i < 15; i ++) { faculty . addStudent ( " student " + i , (( i % 2) == 1 ? 3 : 4) ) ; int courseCounter = 1; for ( int j = 1; j <= (( i % 2 == 1) ? 6 : 8) ; j ++) { for ( int k = 1; k <= 3; k ++) { int grade = sc . nextInt () ; try { faculty . addGradeToStudent ( " student " + i , j, ( " course " + courseCounter ) , grade ) ; } catch ( O p e r a t i o n N o t A l l o w e d E x c e p t i o n e ) { System . out . println ( e . getMessage () ) ; } ++ courseCounter ; } } } System . out . println ( " LOGS " ) ; System . out . println ( faculty . getFacultyLogs () ) ; System . out . println ( " DETAILED REPORT FOR STUDENT " ) ; System . out . println ( faculty . g e t D e t a i l e d R e p o r t F o r S t u d e n t ( " student2 " ) ) ; try { System . out . println ( faculty . g e t D e t a i l e d R e p o r t F o r S t u d e n t ( " student11 " ) ) ; System . out . println ( " The graduated students should be deleted !!! " ) ; } catch ( NullPointerException e ) { System . out . println ( " The graduated students are really deleted " ) ; } System . out . println ( " FIRST N STUDENTS " ) ; 95 faculty . printFirstNStudents (10) ; System . out . println ( " COURSES " ) ; faculty . printCourses () ; 309 310 311 } 312 313 } Проблем 5.2 Да се имплементира систем за чување на пораки според определени теми. За секоjа порака во системот се чува: - временски печат ( timestamp ), обjект од класата LocalDateTime - содржина на пораката String - на коjа партициjа треба да биде зачувана пораката ( Integer коjшто може да биде и null). - клуч на поракта ( String ) Системот за чување на овие пораки функционира на следниов начин: • Пораките се чуваат во рамки на еден Broker . Во брокерот може да има повеќе теми ( topics ). • Во секоjа тема ( Topic ) има одреден броj на партиции каде што се чуваат пораки. Пораките се чуваат сортирани според датумот на креирање. На секоjа од партициите има исто ограничување за тоа кои пораки може да се чуваат и тоа: • може да се чуваат максимум limit пораки. Доколку пристигне порака кога капацитетот е исполнет, се бриши наjстарата порака, а се додава новата. • не може да се чуваат пораки постари од startDate . • На секоjа тема може да му биде зголемен броjот на партиции, но не смее да биде намален! За да се имплементира системот потребно е да се имплементираат следните класи со соодветни функционалности: • Message - класа за репрезентациjа на порака, • Topic - класа за чување на пораки по теми. За секоjа тема се чува името на темата, како и колекциjа од сите пораки распределени по партиции. Оваа класа треба да ги има следните методи: – Topic(String topicName, int partitionsCount) конструктор – void addMessage(Message message) throws PartitionDoesNotExistException - метод за додавање на нова порака во оваа тема. Додавањето се прави во соодветна партициjа коjа се чува како информациjа во пораката message . Доколку таму не е специфицирана, треба да се искористи методот assignPartition од класата PartitionAssigner . Доколку не постои партициjата коjа е наведена во пораката да се фрли исклучок од тип PartitionDoesNotExistException . – void changeNumberOfPartitions(int newPartitionsNumber) throws UnsupportedOperationException - метод за промена на броjот на партиции. Доколку се проба да се намали броjот на партиции да се фрли исклучок како во потписот на функциjата. – String toString() - метод коjшто ќе дава toString репрезентациjа на темата такашто за него ќе се испечати неговото име, броjот на партиции и содржината на соодветните партиции. Партициите да се сортирани според реден броj. • MessageBroker - класа за чување на повеќе теми. Дополнително во оваа класа се чуваат и стартниот датум, како и максималниот капацитет на секоjа 96 Глава 5. Stream API партициjа за сите теми во овоj брокер. Да се имплементираат следните методи: – MessageBroker(LocalDateTime minimumDate, Integer capacityPerTopic) - конструктор – void addTopic (String topic, int partitionsCount) - метод за додавање на нова тема со одреден броj на партиции во неа. Не смее да се додаде тема со исто име. – void addMessage (String topic, Message message) - метод за додавање на порака на определена тема. – void changeTopicSettings (String topic, int partitionsCount) метод за промена на броjот на партиции на определена тема. – String toString() - метод што ќе дава текстуална репрезентациjа на брокерот. Наjпрво ќе се испечати колку теми има, а потоа за сите теми ќе се даде нивната текстуална репрезентациjа. Темите да се подредени според нивното име. Во решението на оваа задача пораките се чуваат во различни партиции на различни теми. За да се овозможи групирање на пораките според партициjа во рамките на една тема, како и подредување на партициите според нивниот реден броj, пораките се чуваат во мапа ( TreeMap ) каде клучот претставува броjот на партициjата, а вредноста претставува подредено множество од пораки TreeSet . Вредноста на мапата е од тип TreeSet за да се овозможи подредување на пораките според нивниот временски печат. Сите теми во брокерот се чуваат во една мапа. Клучот е текстуална низа коjа го означува името на темата, а вредноста е обjект од класа Topic . Во оваа задача на неколку места се искористени stream оператори и тоа: • Конструкторот Topic(String topicName, int partitionsCount) ) - Во конструкторот се користи статичкиот метод range(start,end) од класата IntStream за да се генерира поток од цели броеви во опсег од 1 до броjот на партиции. Потоа секоj броj од овоj поток со помош на операторот forEach се користи за да се додаде празна колекциjа од пораки за соодветните партиции. • Mетодот toString() во класата Topic - Во овоj метод покраj користењето на stream оператори, дополнително се користат вгнездени stream оператори (на елемент од потокот на податоци). Прво се креира поток од Map.Entry< Integer, TreeSet<Message> > , кадешто секоj елемент од потокот претставува една партициjа со сите неjзини пораки. Секоj од паровите од мапата со помош на операторот map прво се трансформира во текстуална низа, а потоа со помош на операторот collect , текстуалните низи се споjуваат во една текстуална низа разделени со празен ред меѓу нив. Во рамките на map оператот се користат вгнездени stream оператори. Прво се креира поток од пораките во партициjата, истите се мапираат во нивната текстуална репрезентациjа, а потоа со користење на collect) се споjуваат со нов ред помеѓу нив. 1 2 3 4 import import import import java . time . LocalDateTime ; java . util .*; java . util . stream . Collectors ; java . util . stream . IntStream ; 5 6 7 8 class P a r t i t i o n D o e s N o t E x i s t E x c e p t i o n extends Exception { P a r t i t i o n D o e s N o t E x i s t E x c e p t i o n ( String topic , int partition ) { super ( String . format ( " The topic % s does not have " + 97 " a partition with number % d " , topic , partition ) ) ; 9 } 10 11 } 12 13 class U n s u p p o r t e d O p e r a t i o n E x c e p t i o n extends Exception { 14 public U n s u p p o r t e d O p e r a t i o n E x c e p t i o n ( String s ) { super ( s ) ; } 15 16 17 18 } 19 20 21 22 23 24 class Message implements Comparable < Message > { LocalDateTime timestamp ; String message ; Integer partition ; String key ; 25 public Message ( LocalDateTime timestamp , String message , Integer partition , String key ) { this . timestamp = timestamp ; this . message = message ; this . partition = partition ; this . key = key ; } 26 27 28 29 30 31 32 33 public Message ( LocalDateTime timestamp , String message , String key ) { this . timestamp = timestamp ; this . message = message ; this . key = key ; } 34 35 36 37 38 39 40 41 @Override public int compareTo ( Message message ) { return this . timestamp . compareTo ( message . timestamp ) ; } 42 43 44 45 46 @Override public String toString () { final StringBuilder sb = new StringBuilder ( " Message { " ) ; sb . append ( " timestamp = " ) . append ( timestamp ) ; sb . append ( " , message = ’ " ) . append ( message ) . append ( ’\ ’ ’) ; sb . append ( ’} ’) ; return sb . toString () ; } 47 48 49 50 51 52 53 54 55 } 56 57 58 59 60 class Topic { String topicName ; Map < Integer , TreeSet < Message > > messagesByPartition ; int partitionsCount ; 61 62 63 64 65 66 67 68 69 public Topic ( String topicName , int partitionsCount ) { this . topicName = topicName ; messagesByPartition = new TreeMap < >() ; this . partitionsCount = partitionsCount ; IntStream . range (1 , partitionsCount + 1) . forEach ( i -> messagesByPartition . put (i , new TreeSet < >() ) ) ; } 98 Глава 5. Stream API void addMessage ( Message message ) throws P a r t i t i o n D o e s N o t E x i s t E x c e p t i o n { Integer messagePartition = message . partition ; if ( messagePartition == null ) { messagePartition = ( Math . abs ( message . key . hashCode () ) % this . partitionsCount ) + 1; } 70 71 72 73 74 75 76 77 if (! messagesByPartition . containsKey ( messagePartition ) ) throw new P a r t i t i o n D o e s N o t E x i s t E x c e p t i o n ( topicName , messagePartition ) ; 78 79 80 81 messagesByPartition . computeIfPresent ( messagePartition , (k , v ) -> { if ( v . size () == MessageBroker . capacityPerTopic ) v . remove ( v . first () ) ; v . add ( message ) ; return v ; }) ; 82 83 84 85 86 87 88 } 89 90 void c h a n ge N u mb e r Of P a rt i t io n s ( int newPartitionsNumber ) throws U n s u p p o r t e d O p e r a t i o n E x c e p t i o n { if ( newPartitionsNumber < this . partitionsCount ) throw new U n s u p p o r t e d O p e r a t i o n E x c e p t i o n ( " Partitions number cannot be decreased ! " ) ; 91 92 93 94 95 96 else { int diff = newPartitionsNumber - this . partitionsCount ; int size = this . messagesByPartition . size () ; for ( int i = 1; i <= diff ; i ++) this . messagesByPartition . putIfAbsent ( size + i , new TreeSet < >() ) ; this . partitionsCount = newPartitionsNumber ; } 97 98 99 100 101 102 103 104 } 105 106 public String toString () { return String . format ( " Topic : %10 s Partitions : %5 d \ n % s " , topicName , partitionsCount , messagesByPartition . entrySet () . stream () . map ( entry -> String . format ( " %2 d : Count of messages : %5 d \ n % s " , entry . getKey () , entry . getValue () . size () , ! entry . getValue () . isEmpty () ? " Messages :\ n " + entry . getValue () . stream () . map ( Message :: toString ) . collect ( Collectors . joining ( " \ n " ) ) : "") ) . collect ( Collectors . joining ( " \ n " ) ) ); } 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 } 129 130 class MessageBroker { 99 Map < String , Topic > topicMap ; static LocalDateTime minimumDate ; static Integer capacityPerTopic ; 131 132 133 134 public MessageBroker ( LocalDateTime minimumDate , Integer capacityPerTopic ) { topicMap = new TreeMap < >() ; MessageBroker . minimumDate = minimumDate ; MessageBroker . capacityPerTopic = capacityPerTopic ; 135 136 137 138 139 140 } 141 142 public void addTopic ( String topic , int partitionsCount ) { topicMap . put ( topic , new Topic ( topic , partitionsCount ) ) ; } 143 144 145 146 public void addMessage ( String topic , Message message ) throws UnsupportedOperationException , PartitionDoesNotExistException { if ( message . timestamp . isBefore ( minimumDate ) ) return ; 147 148 149 150 151 152 topicMap . get ( topic ) . addMessage ( message ) ; 153 } 154 155 public void changeTopicSettings ( String topic , int partitionsCount ) throws U n s u p p o r t e d O p e r a t i o n E x c e p t i o n { topicMap . get ( topic ) . c h a n ge N u mb e r Of P a rt i t io n s ( partitionsCount ) ; } 156 157 158 159 160 public String toString () { return String . format ( " Broker with %2 d topics :\ n % s " , topicMap . size () , topicMap . values () . stream () . map ( Topic :: toString ) . collect ( Collectors . joining ( " \ n " ) ) ); } 161 162 163 164 165 166 167 168 } 169 170 public class MessageBrokersTest { 171 172 173 public static void main ( String [] args ) { Scanner sc = new Scanner ( System . in ) ; 174 175 176 177 178 179 180 String date = sc . nextLine () ; LocalDateTime localDateTime = LocalDateTime . parse ( date ) ; Integer partitionsLimit = Integer . parseInt ( sc . nextLine () ) ; MessageBroker broker = new MessageBroker ( localDateTime , partitionsLimit ) ; int topicsCount = Integer . parseInt ( sc . nextLine () ) ; 181 182 183 184 185 186 187 188 189 // Adding topics for ( int i = 0; i < topicsCount ; i ++) { String line = sc . nextLine () ; String [] parts = line . split ( " ; " ) ; String topicName = parts [0]; int partitionsCount = Integer . parseInt ( parts [1]) ; broker . addTopic ( topicName , partitionsCount ) ; } 190 191 // Reading messages 100 192 Глава 5. Stream API int messagesCount = Integer . parseInt ( sc . nextLine () ) ; 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 System . out . println ( " === ADDING MESSAGES TO TOPICS === " ) ; for ( int i = 0; i < messagesCount ; i ++) { String line = sc . nextLine () ; String [] parts = line . split ( " ; " ) ; String topic = parts [0]; LocalDateTime timestamp = LocalDateTime . parse ( parts [1]) ; String message = parts [2]; if ( parts . length == 4) { String key = parts [3]; try { broker . addMessage ( topic , new Message ( timestamp , message , key ) ) ; } catch ( U n s u p p o r t e d O p e r a t i o n E x c e p t i o n | PartitionDoesNotExistException e) { System . out . println ( e . getMessage () ) ; } } else { Integer partition = Integer . parseInt ( parts [3]) ; String key = parts [4]; try { broker . addMessage ( topic , new Message ( timestamp , message , partition , key ) ) ; } catch ( U n s u p p o r t e d O p e r a t i o n E x c e p t i o n | PartitionDoesNotExistException e) { System . out . println ( e . getMessage () ) ; } } } 223 224 225 226 System . out . println ( " === BROKER STATE AFTER ADDITION OF MESSAGES === " ) ; System . out . println ( broker ) ; 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 System . out . println ( " === CHANGE OF TOPICS CONFIGURATION === " ) ; // topics changes int changesCount = Integer . parseInt ( sc . nextLine () ) ; for ( int i = 0; i < changesCount ; i ++) { String line = sc . nextLine () ; String [] parts = line . split ( " ; " ) ; String topicName = parts [0]; Integer partitions = Integer . parseInt ( parts [1]) ; try { broker . changeTopicSettings ( topicName , partitions ) ; } catch ( U n s u p p o r t e d O p e r a t i o n E x c e p t i o n e ) { System . out . println ( e . getMessage () ) ; } } 242 243 244 245 246 247 248 249 250 251 252 System . out . println ( " === ADDING NEW MESSAGES TO TOPICS === " ) ; messagesCount = Integer . parseInt ( sc . nextLine () ) ; for ( int i = 0; i < messagesCount ; i ++) { String line = sc . nextLine () ; String [] parts = line . split ( " ; " ) ; String topic = parts [0]; LocalDateTime timestamp = LocalDateTime . parse ( parts [1]) ; String message = parts [2]; if ( parts . length == 4) { String key = parts [3]; 101 try { broker . addMessage ( topic , new Message ( timestamp , message , key ) ) ; } catch ( U n s u p p o r t e d O p e r a t i o n E x c e p t i o n | PartitionDoesNotExistException e) { System . out . println ( e . getMessage () ) ; } } else { Integer partition = Integer . parseInt ( parts [3]) ; String key = parts [4]; try { broker . addMessage ( topic , new Message ( timestamp , message , partition , key ) ) ; } catch ( U n s u p p o r t e d O p e r a t i o n E x c e p t i o n | PartitionDoesNotExistException e) { System . out . println ( e . getMessage () ) ; } } 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 } 272 273 System . out . println ( " === BROKER STATE AFTER CONFIGURATION CHANGE === " ) ; System . out . println ( broker ) ; 274 275 276 277 278 } 279 280 } Проблем 5.3 Да се напише класа Canvas во коjа што ќе се чува колекциjа од форми од различен тип. Секоjа форма треба да може да се добиjат информации за ИД на корисникот што jа додал во колекциjата, колкава плоштина и периметар има, како и да се овозможи формата да биде скалирана за некоj коефициент. Во класата Canvas да се имплементираат: • предефиниран конструктор • void readShapes (InputStream is) - метод за вчитување на информации за формите од влезен поток. – Информациите за секоjа форма се дадени во секоj ред. При вчитување на формите прво се вчитува броj (1 = круг/2 = квадрат/3 = правоаголник), па потоа се чита ИД-то на корисникот што jа креирал формата, па потоа доколку станува збор за круг/квадрат се вчитува броj за радиусот/страната на кругот/квадартот, а доколку е правоаголник се вчитуваат два броjа за должина и висина на правоаголнкот. – ИД на корисникот мора да биде стринг со должина од 6 знаци, при што не се дозволени специjални знаци (само букви и броjки). Доколку некоj ИД не е во ред да се фрли исклучок од тип InvalidIDException при креирањето на формата, а со истиот справете се во рамки на функциjата readShapes, односно неправилно ИД да не повлече прекинување на вчитувањето на формите. – Димензиjа на форма не смее да биде 0. Во таков случаj да се фрли исклучок од тип InvalidDimensionException . Овоj исклучок треба да го прекине понатамошното читање на останатите форми. • void scaleShapes (String userID, double coef) - метод што ќе ги скалира 102 Глава 5. Stream API сите форми креирани од корисникот userID со коефициентот coef (ќе ги помножи сите димензии на формата со тоj коефициент). • void printAllShapes (OutputStream os, boolean ascending) - метод што ќе ги испечати формите на излезен поток сортирани според нивната плоштина во растечки/опаѓачки редослед според вредноста на променливата ascending . • void printByUserId (OutputStream os) - метод што ќе ги испечати формите групирани според корисникот, при што корисниците ќе се подредени според броjот на форми што ги имаат креирани (доколку тоj броj е ист, тогаш според сумата на плоштините на формите). Формите на даден корисник треба да се подредени според периметарот во опаѓачки редослед. • void statistics (OutputStream os) - метод што ќе испечати статистики за плоштините на сите форми во колекциjата (min, max, average, sum, count). Бидеjќи постоjат три различни типови на форми во здачата, прво се дефинира IShape интерфеjсот во коj се декларирани три методи за основните функционалности на било коjа форма. Потоа, се дефинирана апстрактната класа Shape коjа го имплементира интерфеjсот IShape и во неа се чуваат сите заеднички полиња на различните форми. Од оваа класа се изведени класите Circle , Square и Rectangle за соодветните типови на форми. Формите се чуваат како листа од обjекти од тип IShape во рамки на класата Canvas . Во класата Canvas , stream операторите се употребени во: • Методот scaleShapes(String userId, double coef) - Прво, се креира поток од сите форми и истите се филтираат според наметнатиот критериум (вредноста на променливата ID е еднаква на вредноста на првиот аргумент на методот. Потоа со операторот forEach , секоjа од пропуштените форми се скалира со коефициентот даден како втор аргумент на овоj метод. • Методот printByUserId(OutputStream os) - Во овоj метод со помош на колекторот groupingBy , прво се креира мапа во коjа клуч е ID -то на корисникот, а вредноста на мапата е множество ( TreeSet ) од сите форми креирани од страна на тоj корисник. Потоа, сите форми согласно креираниот компаратор се подредуваат и со помош на операторот foreach се печатат потребните информации за секоjа форма. • Методот statistics(OutputStream os) - Со помош на операторот mapToDouble , потокот на форми во овоj метод се конвертира во поток од децимални броеви (секоj децимален броj jа претставува плоштината на соодветната форма). На резултантниот поток, со помош на терминалниот оператор summaryStatistics се добива информациjа за минимумот, максимумот, просекот, сумата и броjот на децималните броеви (запишани во обjект од класата DoubleSummaryStatistcs ). 1 2 3 import java . io .*; import java . util .*; import java . util . stream . Collectors ; 4 5 6 interface IShape { String getID () ; 7 8 double getArea () ; 9 10 11 double getPerimeter () ; 103 void scale ( double coef ) ; 12 13 } 14 15 16 17 18 19 class I n v a l i dD i m e n s i o n E xc e p t i o n extends Exception { I n v a l i dD i m e n s i o n E xc e p t i o n () { super ( " Dimension 0 is not allowed ! " ) ; } } 20 21 class InvalidIDException extends Exception { 22 public InvalidIDException ( String id ) { super ( String . format ( " ID % s is not valid " , id ) ) ; } 23 24 25 26 } 27 28 29 abstract class Shape implements IShape { String id ; 30 public Shape ( String id ) { this . id = id ; } 31 32 33 34 @Override public String getID () { return id ; } 35 36 37 38 39 @Override public String toString () { return id ; } 40 41 42 43 44 } 45 46 class Circle extends Shape { 47 48 private double radius ; 49 50 51 52 53 public Circle ( String id , double radius ) { super ( id ) ; this . radius = radius ; } 54 55 56 57 58 @Override public double getArea () { return Math . pow ( radius , 2) * Math . PI ; } 59 60 61 62 63 @Override public double getPerimeter () { return 2 * radius * Math . PI ; } 64 65 66 67 68 @Override public void scale ( double coef ) { radius *= coef ; } 69 70 71 72 @Override public String toString () { return String 104 Глава 5. Stream API . format ( " Circle : % s Radius : %.2 f Area : " + " %.2 f Perimeter : %.2 f " , id , radius , getArea () , getPerimeter () ) ; 73 74 75 } 76 77 } 78 79 class Square extends Shape { 80 double a ; 81 82 public Square ( String id , double a ) { super ( id ) ; this . a = a ; } 83 84 85 86 87 @Override public double getArea () { return Math . pow (a , 2) ; } 88 89 90 91 92 @Override public double getPerimeter () { return 4 * a ; } 93 94 95 96 97 @Override public void scale ( double coef ) { a *= coef ; } 98 99 100 101 102 @Override public String toString () { return String . format ( " Square : % s Side : %.2 f Area : " + " %.2 f Perimeter : %.2 f " , id , a , getArea () , getPerimeter () ) ; } 103 104 105 106 107 108 109 110 } 111 112 class Rectangle extends Square { 113 114 double b ; 115 116 117 118 119 public Rectangle ( String id , double a , double b ) { super ( id , a ) ; this . b = b ; } 120 121 122 123 124 @Override public double getArea () { return a * b ; } 125 126 127 128 129 @verride public double getPerimeter () { return 2 * a + 2 * b ; } 130 131 132 133 @Override public void scale ( double coef ) { super . scale ( coef ) ; 105 b *= coef ; 134 } 135 136 @Override public String toString () { return String . format ( " Rectangle : % s Sides : %.2 f , %.2 f Area : " + " %.2 f Perimeter : %.2 f " , id , a , b , getArea () , getPerimeter () ) ; } 137 138 139 140 141 142 143 144 } 145 146 class ShapeFactory { 147 private static boolean checkId ( String id ) { if ( id . length () != 6) return false ; 148 149 150 151 for ( char c : id . toCharArray () ) { if (! Character . isLetterOrDigit ( c ) ) return false ; } 152 153 154 155 156 return true ; 157 } 158 159 public static IShape createShape ( String line ) throws InvalidDimensionException , InvalidIDException { String [] parts = line . split ( " \\ s + " ) ; int type = Integer . parseInt ( parts [0]) ; String id = parts [1]; if (! checkId ( id ) ) throw new InvalidIDException ( id ) ; double firstDimension = Double . parseDouble ( parts [2]) ; if ( firstDimension == 0.0) throw new I n va l i d D i m e n s i on E x c e p t i o n () ; 160 161 162 163 164 165 166 167 168 169 170 if ( type == 1) { return new Circle ( id , firstDimension ) ; } else if ( type == 2) { return new Square ( id , firstDimension ) ; } else { double secondDimension = Double . parseDouble ( parts [3]) ; if ( secondDimension == 0.0) throw new I n va l i d D i m e n s i on E x c e p t i o n () ; return new Rectangle ( id , firstDimension , secondDimension ) ; } 171 172 173 174 175 176 177 178 179 180 } 181 182 } 183 184 185 class Canvas { List < IShape > shapes ; 186 187 188 189 public Canvas () { shapes = new ArrayList < >() ; } 190 191 192 193 194 public void readShapes ( InputStream is ) throws In v a l i d D i m e ns i o n E x c e p t i on { Scanner sc = new Scanner ( is ) ; while ( sc . hasNextLine () ) { 106 Глава 5. Stream API String line = sc . nextLine () ; try { shapes . add ( ShapeFactory . createShape ( line ) ) ; } catch ( InvalidIDException e ) { System . out . println ( e . getMessage () ) ; } 195 196 197 198 199 200 } 201 202 } 203 204 205 206 207 public void scaleShapes ( String userID , double coef ) { shapes . stream () . filter ( shape -> shape . getID () . equals ( userID ) ) . forEach ( shape -> shape . scale ( coef ) ) ; } 208 209 210 211 212 213 214 public void printAllShapes ( OutputStream os , boolean ascending ) { Comparator < IShape > shapeComparator = Comparator . comparing ( IShape :: getArea ) . thenComparing ( IShape :: getID ) ; if (! ascending ) shapeComparator = shapeComparator . reversed () ; 215 PrintWriter pw = new PrintWriter ( os ) ; shapes . stream () . sorted ( shapeComparator ) . forEach ( pw :: println ) ; pw . flush () ; 216 217 218 219 220 221 } 222 223 224 225 226 227 228 229 230 231 232 233 public void printByUserId ( OutputStream os ) { PrintWriter pw = new PrintWriter ( os ) ; Comparator < IShape > shapeComparator = Comparator . comparing ( IShape :: getPerimeter ) . thenComparing ( IShape :: getID ) . reversed () ; Map < String , Set < IShape > > result = shapes . stream () . collect ( Collectors . groupingBy ( IShape :: getID , Collectors . toCollection ( () -> new TreeSet < >( shapeComparator ) ) )); 234 Comparator < Map . Entry < String , Set < IShape > > > entryComparator = Comparator . comparing ( entry -> entry . getValue () . size () ) ; 235 236 237 238 result . entrySet () . stream () . sorted ( entryComparator . reversed () . thenComparing ( entry -> entry . getValue () . stream () . mapToDouble ( IShape :: getArea ) . sum () ) ) . forEach ( entry -> { pw . println ( " Shapes of user : " + entry . getKey () ) ; entry . getValue () . forEach ( pw :: println ) ; }) ; 239 240 241 242 243 244 245 246 247 pw . flush () ; 248 249 } 250 251 252 253 254 255 public void statistics ( OutputStream os ) { PrintWriter pw = new PrintWriter ( os ) ; pw . println ( shapes . stream () . mapToDouble ( IShape :: getArea ) . summaryStatistics () ) ; pw . flush () ; 107 } 256 257 } 258 259 public class CanvasTest { 260 public static void main ( String [] args ) { Canvas canvas = new Canvas () ; 261 262 263 System . out . println ( " READ SHAPES AND EXCEPTIONS TESTING " ) ; try { canvas . readShapes ( System . in ) ; } catch ( I n v al i d D i m e n s i o nE x c e p t i o n e ) { System . out . println ( e . getMessage () ) ; } 264 265 266 267 268 269 270 System . out . println ( " BEFORE SCALING " ) ; canvas . printAllShapes ( System . out , true ) ; canvas . scaleShapes ( " 123456 " , 1.5) ; System . out . println ( " AFTER SCALING " ) ; canvas . printAllShapes ( System . out , false ) ; 271 272 273 274 275 276 System . out . println ( " PRINT BY USER ID TESTING " ) ; canvas . printByUserId ( System . out ) ; 277 278 279 System . out . println ( " PRINT STATISTICS " ) ; canvas . statistics ( System . out ) ; 280 281 } 282 283 } Проблем 5.4 За потребите на Министерството за здравство потребно е да се направи апликациjа коjа ќе ги менаџира корисниците на апликациjата и нивните контакти со кои биле во близина. Да се дефинира класа StopCorona и за истата да се имплементираат: • предефиниран конструктор • метод void addUser(String name, String id) - што ќе регистрира нов корисник на апликациjата. Доколку веќе постои корисник со такво id , методот треба да фрли исклучок од тип UserIdAlreadyExistsException . • Метод void addLocations (String id, List<ILocation> iLocations) - што за корисникот со ИД исто како првиот аргумент, ќе ги регистрира сите негови детектирани локации. ILocation e интерфеjс и истиот обезбедува информации за должината и ширината на локациjата, како и времето кога е детектирана таа локациjа. • Метод void detectNewCase (String id, LocalDateTime timestamp) - што симулира приjавување на даден корисник дека е носител на вирусот. Првиот аргумент е неговото ИД, а вториот е времето кога корисникот приjавил дека е носител. • Метод Map<User, Integer> getDirectContacts (User u) - што ќе враќа мапа во коjа клучеви се сите блиски контакти на корисикот u , а соодветните вредности во мапата се броjот на остварени блиски контакти со корисникот u. • Метод Collection<User> getIndirectContacts (User u) - што ќе враќа колекциjа од индиректните контакти на корисникот u . За индиректни контакти се сметаат блиските контакти на директните контакти на u , при што еден 108 Глава 5. Stream API корисник не може да биде и директен и индиректен контакт на некоj друг корисник. • Метод void createReport () - што ќе креира и испечати извештаj за МЗ во коj за сите корисници носители на вирусот ќе испечати информации во следниот формат: [user_name] [user_id] [timestamp_detected] Direct contacts: [contact1_name] [contact1_first_five_letters_of_id] [number_of_detected_contacts1] [contact2_name] [contact2_first_five_letters_of_id] [number_of_detected_contacts1] ... [contactN_name] [contactN_first_five_letters_of_id] [number_of_detected_contactsN] Count of direct contacts: [sum] Indirect contacts: [contact1_name] [contact1_first_five_letters_of_id] [contact2_name] [contact2_first_five_letters_of_id] ... [contactN_name] [contactN_first_five_letters_of_id] Count of indirect contacts: [count] • Дополнително на краjот на извештаjот да се испечати просечниот броj на директни и индиректни контакти на корисниците што се носители на Корона вирусот. Напомена: • Близок контакт се смета контактот на дваjца корисници кога евклидовото растоjание помеѓу некоjа од нивните локациите е <=2 , а временското растоjание на соодветно измерените локации е помало од 5 минути. • Носителите на вирусот да се сортирани според времето кога се детектирани дека се носители. Директните контакти на носителите да бидат сортирани според броjот на остварени блиски контакти во опаѓачки редослед. Индиректните контакти да се сортирани лексикографски според нивното име, а доколку се исти според ИД на корисникот. Во класата StopCoronaApp информациите за сите корисници на апликациjата се чуваат во мапата userByIdMap , во коjа клуч е ИД-то на корисникот, а вредност е обjект од класата User . Дополнително, во оваа класа чуваме уште една мапа за корисниците на апликациjата кои се инфицирани од вирусот КОВИД-19. Во оваа мапа, клучот е ИД-то на инфицираниот корисник, а вредноста е временски печат кога корисникот е приjавен како инфициран (обjект од класата LocalDateTime ). Stream оператори во класата User се користат во countCloseContacts(User otherUser) методот. Тоj како резултат го враќа броjот на блиски контакти на корисникот со otherUser . Прво се користи flatMapToInt операторот коj jа мапира секоjа i-та локациjа на this корисникот во поток од цели броеви ( IntStream ). Овоj поток е составен од броевите 0 и 1 и означува дали i-тата и j-тата локациjа ги исполнуваат критериумот за близок контакт ( j-тата локациjа ги претставува 109 локациите на корисникот otherUser ). На краj, откако е добиен IntStream се повикува терминалниот оператор sum коj враќа вкупен броj на сите блиски контакти меѓу двата корисници. Во класата StopCoronaApp , stream операторите се употребени во методите: • Mетодот Map<User, Integer> getDirectContacts(User u) - Во рамки на овоj метод, прво се креира поток од сите корисници на апликациjата (вредностите од мапата userByIdMap ). Од тоj поток се отстранува корисникот u како и сите корисници кои немаат отстварено близок контакт со корисникот u (со употреба на операторот filter . Потоа, со операторот forEach секоj од останатите корисници се сместува во мапа, кадешто клуч е корисникот, а вредност е броjот на блиски контакти со корисникот u . • Методот Collection<User> getIndirectContact(User u) - Согласно барањата наведени во задачата за индиректни контакти на еден корисник се сметаат сите блиски контакти на неговите директни контакти. За таа цел, резултатот од методот getDirectContacts со операторот flatMap се мапира секоj директен контакт на u во неговите директни контакти и сите тие се агрегураат во Stream<User> . Од овие корисници се острануваат тие што се веќе евидентирани како директни контакти, како и самиот корисник u со помош на операторот filter . Пропуштените корисници се собираат во подредено множество TreeSet со помош на колектор. • Методот void createReport() - Во овоj метод се креира поток на парови клучвредност од мапата на инфицирани корисници. Истиот поток се подредува со операторот sorted според вредноста на парот клуч-вредност. Потоа, со операторот forEach за секоj пар се повикува методот printInfectedUserEntry . 1 2 3 4 import import import import java . time . Duration ; java . util .*; java . time . LocalDateTime ; java . util . stream . Collectors ; 5 6 7 interface ILocation { double getLongitude () ; 8 double getLatitude () ; 9 10 LocalDateTime getTimestamp () ; 11 12 } 13 14 15 16 17 18 19 20 21 22 class LocationUtils { public static double distanceBetween ( ILocation location1 , ILocation location2 ) { return Math . sqrt ( Math . pow ( location1 . getLatitude () location2 . getLatitude () , 2) + Math . pow ( location1 . getLongitude () - location2 . getLongitude () , 2) ) ; } 23 24 25 26 27 28 public static double timeBetweenInSeconds ( ILocation location1 , ILocation location2 ) { return Math . abs ( Duration . between ( location1 . getTimestamp () , location2 . getTimestamp () ) . getSeconds () ) ; } 29 30 public static boolean isDanger ( ILocation location1 , 110 Глава 5. Stream API ILocation location2 ) { return distanceBetween ( location1 , location2 ) <= 2.0 && timeBetweenInSeconds ( location1 , location2 ) <= 300; 31 32 33 } 34 35 } 36 37 38 39 40 class User { String id ; String name ; List < ILocation > locations ; 41 public User ( String id , String name ) { this . id = id ; this . name = name ; locations = new ArrayList < >() ; } 42 43 44 45 46 47 public void addLocations ( List < ILocation > iLocations ) { locations . addAll ( iLocations ) ; } 48 49 50 51 public String complete () { return String . format ( " % s % s " , name , id ) ; } 52 53 54 55 public String hidden () { return String . format ( " % s % s *** " , name , id . substring (0 , 4) ) ; } 56 57 58 59 public int countCloseContacts ( User otherUser ) { return locations . stream () . flatMapToInt ( i -> otherUser . locations . stream () . mapToInt ( j -> LocationUtils . isDanger (i , j ) ? 1 : 0) ) . sum () ; } 60 61 62 63 64 65 66 67 @Override public boolean equals ( Object o ) { if ( this == o ) return true ; if ( o == null || getClass () != o . getClass () ) return false ; User user = ( User ) o ; return Objects . equals ( id , user . id ) ; } 68 69 70 71 72 73 74 75 @Override public int hashCode () { return Objects . hash ( id ) ; } 76 77 78 79 80 public String getId () { return id ; } 81 82 83 84 public String getName () { return name ; } 85 86 87 88 } 89 90 91 class U s e r A l re a d y E x i s t E xc e p t i o n extends Exception { String id ; 111 92 public Us e r A l r e a d y E xi s t E x c e p t i on ( String id ) { super ( String . format ( " User with id % s already exists " , id ) ) ; } 93 94 95 96 } 97 98 class StopCoronaApp { 99 100 101 Map < String , User > userByIdMap ; Map < String , LocalDateTime > infectedUsersByIdMap ; 102 103 104 105 106 StopCoronaApp () { userByIdMap = new HashMap < >() ; infectedUsersByIdMap = new HashMap < >() ; } 107 108 109 110 111 112 113 114 public void addUser ( String name , String id ) throws Us e r A l r e a d y Ex i s t E x c e p t i on { if ( userByIdMap . containsKey ( id ) ) throw new U s er A l r e a d y E x i st E x c e p t i o n ( id ) ; userByIdMap . put ( id , new User ( id , name ) ) ; } 115 116 117 118 public void addLocations ( String id , List < ILocation > locations ) { userByIdMap . get ( id ) . addLocations ( locations ) ; } 119 120 121 122 123 public void detectNewCase ( String id , LocalDateTime timestamp ) { infectedUsersByIdMap . put ( id , timestamp ) ; } 124 125 126 127 128 129 130 131 132 133 134 public Map < User , Integer > getDirectContacts ( User u ) { Map < User , Integer > result = new TreeMap < >( Comparator . comparing ( User :: getId ) ) ; userByIdMap . values () . stream () . filter ( user -> ! user . equals ( u ) ) . filter ( user -> user . countCloseContacts ( u ) != 0) . forEach ( user -> result . put ( user , u . countCloseContacts ( user ) ) ) ; return result ; } 135 136 137 138 139 140 141 142 143 144 145 146 147 public Collection < User > getIndirectContact ( User u ) { Comparator < User > comparator = Comparator . comparing ( User :: getName ) . thenComparing ( User :: getId ) ) ; Map < User , Integer > directContact = getDirectContacts ( u ) ; return directContact . keySet () . stream () . flatMap ( user -> getDirectContacts ( user ) . keySet () . stream () ) . filter ( user -> ! directContact . containsKey ( user ) && ! user . equals ( u ) ) . collect ( Collectors . toCollection ( () -> new TreeSet < >( comparator ) ) ) ; 148 149 } 150 151 152 public void createReport () { 112 Глава 5. Stream API 153 List < Integer > c ountO fDirec tConta cts = new ArrayList < >() ; List < Integer > co u nt Of In d ir ec tC o nt ac t s = new ArrayList < >() ; 154 155 156 infectedUsersByIdMap . entrySet () . stream () . sorted ( Map . Entry . comparingByValue () ) . forEach ( entry -> pr int In fec ted Us erE nt ry ( entry , countOfDirectContacts , c ou nt Of I nd ir e ct Co nt a ct s ) ) ; 157 158 159 160 161 162 System . out . printf ( " Average direct contacts : %.4 f \ n " , cou ntOfDi rectCo ntact s . stream () . mapToInt ( i -> i ) . average () . getAsDouble () ) ; System . out . printf ( " Average indirect contacts : %.4 f \ n " , c ou nt Of I nd ir e ct Co nt a ct s . stream () . mapToInt ( i -> i ) . average () . getAsDouble () ) ; 163 164 165 166 167 168 169 170 } 171 172 private void pr int Inf ec ted Us erE ntr y ( Map . Entry < String , LocalDateTime > entry , List < Integer > countsOfDirectContacts , List < Integer > c o un t s Of I n di r e ct C o nt a c ts ) { User user = userByIdMap . get ( entry . getKey () ) ; System . out . printf ( " % s % s \ n " , user . complete () , entry . getValue () ) ; System . out . println ( " Direct contacts : " ) ; 173 174 175 176 177 178 179 180 181 Map < User , Integer > directContacts = getDirectContacts ( user ) ; 182 183 directContacts . entrySet () . stream () . sorted ( Map . Entry . comparingByValue ( Comparator . reverseOrder () ) ) . forEach ( e -> System . out . printf ( " % s % d \ n " , e . getKey () . hidden () , e . getValue () ) ) ; 184 185 186 187 188 189 190 int countOfDirectContact = directContacts . values () . stream () . mapToInt ( i -> i ) . sum () ; System . out . printf ( " Count of direct contacts : % d \ n " , countOfDirectContact ) ; co un tsO fDi re ctC on tac ts . add ( countOfDirectContact ) ; System . out . println ( " Indirect contacts : " ) ; 191 192 193 194 195 196 197 198 Collection < User > indirectContacts = getIndirectContact ( user ) ; indirectContacts . forEach ( u -> System . out . println ( u . hidden () ) ) ; System . out . printf ( " Count of indirect contacts : % d \ n " , indirectContacts . size () ) ; c o un t s Of I n di r e ct C o nt a c ts . add ( indirectContacts . size () ) ; 199 200 201 202 203 } 204 205 } 206 207 public class StopCoronaTest { 208 209 210 211 212 213 public static double timeBetweenInSeconds ( ILocation location1 , ILocation location2 ) { return Math . abs ( Duration . between ( location1 . getTimestamp () , location2 . getTimestamp () ) . getSeconds () ) ; } 113 214 215 216 public static void main ( String [] args ) { Scanner sc = new Scanner ( System . in ) ; 217 StopCoronaApp stopCoronaApp = new StopCoronaApp () ; 218 219 while ( sc . hasNext () ) { String line = sc . nextLine () ; String [] parts = line . split ( " \\ s + " ) ; 220 221 222 223 switch ( parts [0]) { case " REG " : // register String name = parts [1]; String id = parts [2]; try { stopCoronaApp . addUser ( name , id ) ; } catch ( U s e rA l r e a d y E x i s tE x c e p t i o n e ) { System . out . println ( e . getMessage () ) ; } break ; case " LOC " : // add locations id = parts [1]; List < ILocation > locations = new ArrayList < >() ; for ( int i = 2; i < parts . length ; i += 3) { locations . add ( createLocationObject ( parts [ i ] , parts [ i + 1] , parts [ i + 2]) ) ; } stopCoronaApp . addLocations ( id , locations ) ; 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 break ; case " DET " : // detect new cases id = parts [1]; LocalDateTime timestamp = LocalDateTime . parse ( parts [2]) ; stopCoronaApp . detectNewCase ( id , timestamp ) ; 243 244 245 246 247 248 249 break ; case " REP " : // print report stopCoronaApp . createReport () ; break ; default : break ; 250 251 252 253 254 255 } 256 } 257 258 } 259 260 261 262 263 264 265 266 267 private static ILocation createLocationObject ( String lon , String lat , String timestamp ) { return new ILocation () { @Override public double getLongitude () { return Double . parseDouble ( lon ) ; } 268 269 270 271 272 @Override public double getLatitude () { return Double . parseDouble ( lat ) ; } 273 274 @Override 114 Глава 5. Stream API public LocalDateTime getTimestamp () { return LocalDateTime . parse ( timestamp ) ; } 275 276 277 }; 278 } 279 280 } Проблем 5.5 Да се дефинира класата GenericCollection во коjа што ќе се чуваат елементи кои треба да се споредливи и елементи кои имаат временски момент на креирање. Класата да ги овозможи следните методи: • void addGenericItem (String category, T element) - метод за додавање на нов елемент во дадена категориjа • Collection<T> findAllBetween (LocalDateTime from, LocalDateTime to) - метод коjшто ќе врати колекциjа од сите елементи што се наоѓаат во интервалот на датуми даден како аргументи на функциjата. • Collection<T> itemsFromCategories (List<String> categories) - метод што ќе врати броj на елементи што се наоѓаат во категориите дадени како аргумент на функциjата. • public Map< String, Set<T> > byMonthAndDay() - враќа мапа во коjа што елементите се групирани според нивниот timestamp (поточно месецот и денот конкатенирани со - измеѓу нив пр. 12-30 , без разлика на годината). Месецот се добива со повик на методот getMonth() , а денот getDayOfMonth() . • public Map<Integer, Long> countByYear() - враќа мапа во коjа што клучеви се сите години кога има креирани некоj елемент, а соодветната вредност е броjот на елементи креирани во таа година. Секаде каде што има колекциjа од елементи, истите треба да бидат подредени во опаѓачки редослед! Во решението на ова задача треба да се употребат генерици. За таа цел, прво треба да се утврди како точно да се дефинира генеричкиот тип T и кои се неговите ограничувања. Од барањата во задачата, произлегуваат две ограничувања: Елементите да може да се споредуваат и да имаат своj временски печат LocalDateTime . Од ограничувањето дека елементите треба да може да се споредуваат, може да се заклучи дека T треба да го имплементира генеричкиот интерфеjс Comparable<T> . Од второто ограничување, може да се заклучи дека типот T треба да имплементира интерфеjс со метод getTimestamp (интерфеjсот IHasTimestamp ). Од ова произлегува дека T extends Comparable<T> & IHasTimestamp е дефинициjата на генеричкиот тип T . Во класата GenericCollection се чуваат генеричките елементи групирани според нивната категориjа во мапа кадешто клучот е името на категориjата, а вредноста е множество од генеричките елементи што припаѓаат во соодветната категориjа. Во класата GenericCollection<T> на неколку места се употребени stream оператори: • Методот Collection<T> findAllBetween(LocalDateTime from, LocalDateTime to) - Во овоj метод, со помош на операторот flatMap прво се израмнува колекциjата од множества од генерички елементи во колекциjа од генерички елементи. По израмнувањето се филтрираат елементите чии временски печат е помеѓу временскиот печат from и to . На краj, со помош на операторот collect се собираат сите пропуштени елементи во подредено множество ( TreeSet ). • Методот Collection<T> itemsFromCategories (List<String> categories) 115 - Во овоj метод. прво се филтираат категориите од мапата што припаѓаат во листата categories . Потоа пропуштените категории се мапираат со помош на flatMap операторот во поток од генеричките елементи кои припаѓаат на соодветните категории. На краj, повторно како и во претходниот метод истите се агрегираат во подредено множество. • Методот Map< String, Set<T> > byMonthAndDay() - Во овоj метод, исто како и во првиот метод, прво се израмнуваат сите генерички елементи во поток од генерички елементи. Потоа, со помош на collect операторот и groupBy колек-торот се собираат сите елементи во мапа каде клучот е текстуална низа во формат месец-ден (првиот аргумент во groupBy повикот), а вредност е подредено множество од сите елементи чии временски печат е во тоj ден и месец (третиот аргумент во groupBy повикот). Резултантната мапа е од тип TreeMap (дефинирана преку вториот аргумент во groupBy повикот). • Методот Map<Integer,Long> countByYear() - Овоj метод е комплетно аналоген на претходниот и истиот се разликува само во однос на агрегациjата на елементите направена со groupBy операторот. Во овоj метод, елементите се групираат според годината од временскиот печат (првиот аргумент во groupBy повикот), а резултатот е вкупниот броj на елементи во рамки на таа година. 1 2 3 import java . time . LocalDateTime ; import java . util .*; import java . util . stream . Collectors ; 4 5 6 7 interface IHasTimestamp { LocalDateTime getTimestamp () ; } 8 9 10 class IntegerElement implements Comparable < IntegerElement > , IHasTimestamp { 11 12 13 int value ; LocalDateTime timestamp ; 14 15 16 17 18 19 public IntegerElement ( int value , LocalDateTime timestamp ) { this . value = value ; this . timestamp = timestamp ; } 20 21 22 23 24 @Override public LocalDateTime getTimestamp () { return timestamp ; } 25 26 27 28 29 @Override public int compareTo ( IntegerElement o ) { return Integer . compare ( this . value , o . value ) ; } 30 31 32 33 34 35 36 37 @Override public String toString () { return " IntegerElement { " + " value = " + value + " , timestamp = " + timestamp + ’} ’; } 116 38 Глава 5. Stream API } 39 40 41 class StringElement implements Comparable < StringElement > , IHasTimestamp { 42 String value ; LocalDateTime timestamp ; 43 44 45 46 public StringElement ( String value , LocalDateTime timestamp ) { this . value = value ; this . timestamp = timestamp ; } 47 48 49 50 51 @Override public LocalDateTime getTimestamp () { return timestamp ; } 52 53 54 55 56 @Override public int compareTo ( StringElement o ) { return this . value . compareTo ( o . value ) ; } 57 58 59 60 61 @Override public String toString () { return " StringElement { " + " value = ’ " + value + ’\ ’ ’ + " , timestamp = " + timestamp + ’} ’; } 62 63 64 65 66 67 68 69 } 70 71 72 class TwoIntegersElement implements Comparable < TwoIntegersElement > , IHasTimestamp { 73 74 75 76 int value1 ; int value2 ; LocalDateTime timestamp ; 77 78 79 80 81 82 83 public TwoIntegersElement ( int value1 , int value2 , LocalDateTime timestamp ) { this . value1 = value1 ; this . value2 = value2 ; this . timestamp = timestamp ; } 84 85 86 87 88 @Override public LocalDateTime getTimestamp () { return timestamp ; } 89 90 91 92 93 94 95 96 97 98 @Override public int compareTo ( TwoIntegersElement o ) { int cmp = Integer . compare ( this . value1 , o . value1 ) ; if ( cmp != 0) return cmp ; else return Integer . compare ( this . value2 , o . value2 ) ; } 117 @Override public String toString () { return " TwoIntegersElement { " + " value1 = " + value1 + " , value2 = " + value2 + " , timestamp = " + timestamp + ’} ’; } 99 100 101 102 103 104 105 106 107 } 108 109 class GenericCollection < T extends Comparable <T > & IHasTimestamp > { 110 111 Map < String , Set <T > > mapByCategory ; 112 113 114 115 GenericCollection () { mapByCategory = new HashMap < >() ; } 116 117 118 119 120 121 122 123 public void addGenericItem ( String category , T element ) { mapByCategory . putIfAbsent ( category , new TreeSet < >() ) ; mapByCategory . computeIfPresent ( category , (k , v ) -> { v . add ( element ) ; return v ; }) ; } 124 125 126 127 128 129 130 131 132 133 public Collection <T > findAllBetween ( LocalDateTime from , LocalDateTime to ) { return mapByCategory . values () . stream () . flatMap ( Collection :: stream ) . filter ( t -> t . getTimestamp () . isAfter ( from ) && t . getTimestamp () . isBefore ( to ) ) . collect ( Collectors . toCollection (() -> new TreeSet <T >( Comparator . reverseOrder () ) ) ) ; } 134 135 136 137 138 139 140 141 142 143 public Collection <T > itemsFromCategories ( List < String > categories ) { return mapByCategory . keySet () . stream () . filter ( categories :: contains ) . flatMap ( category -> mapByCategory . get ( category ) . stream () ) . collect ( Collectors . toCollection (() -> new TreeSet <T >( Comparator . reverseOrder () ) ) ) ; } 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 public Map < String , Set <T > > byMonthAndDay () { return mapByCategory . values () . stream () . flatMap ( Collection :: stream ) . collect ( Collectors . groupingBy ( element -> String . format ( " %02 d -%02 d " , element . getTimestamp () . getMonthValue () , element . getTimestamp () . getDayOfMonth () ) , TreeMap :: new , Collectors . toCollection (() -> new TreeSet <T >( Comparator . reverseOrder () ) ) )); } 118 Глава 5. Stream API public Map < Integer , Long > countByYear () { return mapByCategory . values () . stream () . flatMap ( Collection :: stream ) . collect ( Collectors . groupingBy ( element -> element . getTimestamp () . getYear () , TreeMap :: new , Collectors . counting () )); } 160 161 162 163 164 165 166 167 168 169 private String getDayAndMonth ( T element ) { return String . format ( " %02 d -%02 d " , element . getTimestamp () . getMonthValue () , element . getTimestamp () . getDayOfMonth () ) ; } 170 171 172 173 174 175 private Integer getYear ( T element ) { return element . getTimestamp () . getYear () ; } 176 177 178 179 180 } 181 182 public class G eneric Colle ctionT est { 183 184 public static void main ( String [] args ) { 185 186 187 188 189 190 191 192 193 int type1 , type2 ; GenericCollection < IntegerElement > integerCollection = new GenericCollection < IntegerElement >() ; GenericCollection < StringElement > stringCollection = new GenericCollection < StringElement >() ; GenericCollection < TwoIntegersElement > tw oInte gersCo llect ion = new GenericCollection < TwoIntegersElement >() ; Scanner sc = new Scanner ( System . in ) ; 194 195 type1 = sc . nextInt () ; 196 197 int count = sc . nextInt () ; 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 for ( int i = 0; i < count ; i ++) { if ( type1 == 1) { // integer element int value = sc . nextInt () ; LocalDateTime timestamp = LocalDateTime . parse ( sc . next () ) ; String category = sc . next () ; integerCollection . addGenericItem ( category , new IntegerElement ( value , timestamp ) ) ; } else if ( type1 == 2) { // string element String value = sc . next () ; LocalDateTime timestamp = LocalDateTime . parse ( sc . next () ) ; String category = sc . next () ; stringCollection . addGenericItem ( category , new StringElement ( value , timestamp ) ) ; } else { // two integer element int value1 = sc . nextInt () ; int value2 = sc . nextInt () ; LocalDateTime timestamp = LocalDateTime . parse ( sc . next () ) ; String category = sc . next () ; two Intege rsColl ectio n . addGenericItem ( category , 119 new TwoIntegersElement ( value1 , value2 , timestamp ) ) ; 221 222 } 223 } 224 225 type2 = sc . nextInt () ; 226 227 if ( type2 == 1) { // findAllBetween LocalDateTime start = LocalDateTime . of (2008 , 1 , 1 , 0 , 0) ; LocalDateTime end = LocalDateTime . of (2020 , 1 , 30 , 23 , 59) ; if ( type1 == 1) p r i n t R e s u l t s F r o m F i n d A l l B e t w e e n ( integerCollection , start , end ) ; else if ( type1 == 2) p r i n t R e s u l t s F r o m F i n d A l l B e t w e e n ( stringCollection , start , end ) ; else p r i n t R e s u l t s F r o m F i n d A l l B e t w e e n ( twoIntegersCollection , start , end ) ; } else if ( type2 == 2) { // itemsFromCategories List < String > categories = new ArrayList < >() ; int n = sc . nextInt () ; while ( n != 0) { categories . add ( sc . next () ) ; n - -; } if ( type1 == 1) p r i n t R e s u l t s F r o m I t e m s F r o m C a t e g o r i e s ( integerCollection , categories ) ; else if ( type1 == 2) p r i n t R e s u l t s F r o m I t e m s F r o m C a t e g o r i e s ( stringCollection , categories ) ; else printResultsFromItemsFromCategories ( twoIntegersCollection , categories ) ; } else if ( type2 == 3) { // byMonthAndDay if ( type1 == 1) p r i n t R e s u l t s F r o m B y M o n t h A n d D a y ( integerCollection ) ; else if ( type1 == 2) p r i n t R e s u l t s F r o m B y M o n t h A n d D a y ( stringCollection ) ; else p r i n t R e s u l t s F r o m B y M o n t h A n d D a y ( tw oInte gersCo llect ion ) ; } else { // countByYear if ( type1 == 1) p r i n t R e s u l t s F r o m C o u n t B y Y e a r ( integerCollection ) ; else if ( type1 == 2) p r i n t R e s u l t s F r o m C o u n t B y Y e a r ( stringCollection ) ; else p r i n t R e s u l t s F r o m C o u n t B y Y e a r ( twoInt egers Collec tion ) ; } 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 } 272 273 274 275 276 277 278 private static void p r i n t R e s u l t s F r o m I t e m s F r o m C a t e g o r i e s ( GenericCollection <? > collection , List < String > categories ) { collection . itemsFromCategories ( categories ) . forEach ( element -> System . out . println ( element . toString () ) ) ; } 279 280 281 private static void p r i n t R e s u l t s F r o m F i n d A l l B e t w e e n ( GenericCollection <? > collection , LocalDateTime start , 120 Глава 5. Stream API LocalDateTime end ) { collection . findAllBetween ( start , end ) . forEach ( element -> System . out . println ( element . toString () ) ) ; 282 283 284 } 285 286 private static void printSetOfElements ( Set <? > set ) { System . out . print ( " [ " ) ; System . out . print ( set . stream () . map ( Object :: toString ) . collect ( Collectors . joining ( " , " ) ) ) ; System . out . println ( " ] " ) ; } 287 288 289 290 291 292 293 private static void p r i n t R e s u l t s F r o m B y M o n t h A n d D a y ( GenericCollection <? > collection ) { collection . byMonthAndDay () . forEach (( key , value ) -> { System . out . print ( key + " -> " ) ; printSetOfElements ( value ) ; }) ; } 294 295 296 297 298 299 300 301 private static void p r i n t R e s u l t s F r o m C o u n t B y Y e a r ( GenericCollection <? > collection ) { collection . countByYear () . forEach (( key , value ) -> { System . out . println ( key + " -> " + value ) ; }) ; } 302 303 304 305 306 307 308 } 6. Вовед во шаблони за дизаjн на софтвер Дизаjнот на софтвер го опфаќа процесот на планирање и решавање на проблем при развоj на софтверско решение. По одредувањето на целите и спецификациjата на барањата, развивачите на софтвер (или дизаjнерите) треба да развиjат план за решавање на проблемот. Тоj вклучува имплементациjа на алгоритми, развоj на компоненти од ниско ниво и архитектонски поглед на решението. Во пракса, често пати се поjавуваат исти (слични) проблеми кои се решаваат на ист (сличен) начин. Идеjата е еднаш развиеното докажано решение повторно да се употреби Во софтверското инженерство, шаблоните за дизаjн претставуваат генерално, реупотребливо решение за вообичаени (повторливи) проблеми кои се поjавуваат во секоjдневието при дизаjнот на софтвер. Реупотребата на вакви шаблони може да го забрза процесот на развоj на софтвер бидеjќи би се применувале претходно докажани и испробани решениjа. Шаблонот не е готов, директно употребив дизаjн коj може директно да се трансформира во код. Шаблонот претставува само опис, рецепт, мостра за решавање на проблем коj може да се употреби во повеќе различни ситуации. Обjектно-ориентираните шаблони за дизаjн вообичаено ги прикажуваат релациите и интеракциите помеѓу класите или обjектите, без прецизна спецификациjа на конечните класи или обjекти кои ќе бидат вклучени во апликациjата. Алгоритмите не се сметаат за шаблони за дизаjн бидеjќи тие решаваат пресметковни а не дизаjнерски проблеми. Проблем 6.1 Да се напише класа за MP3Player во коjа се чуваат листа со песни ( List<Song> ) и песната коjа моментално се слуша (е на ред да се пушти). MP3Playerот има четири копчиња Play , Stop , FWD и REW . • Ако се притисне копчето Play се пушта моменталната песна (на екранот се испишува "Song i is playing" , каде i е редниот броj на моменталната песна, почнуваjќи од 0). • Ако се притисне копчето Stop : 122 Глава 6. Вовед во шаблони за дизаjн на софтвер – моменталната песна коj е пуштена се паузира (на екран се испишува "Song i is paused" каде i е моменталната песна коjа била пуштена). – листата целосно се ресетира од почеток, ако моменталната песна веќе била паузирана (на екран се испишува "Songs are stopped" ). • Ако се притисне копчето FWD песната се паузира и следната песна од листата станува моментална (да се земе во предвид кружното повторување на песните). • Ако се притисне копчето REW песната се паузира и претходната песна од листата станува моментална (да се земе во предвид кружното повторување на песните). За секоjа песна се чуваат насловот на песната (како String ) и изведувачот на песната (како String ). Задачата може да се реши на многу начини. Со цел да се обезбеди поголема флексибилност и робусност во решението треба да се употребат добри дизаjнерски практики. Без употреба на шаблон за развоj на софтвер, решението може да стане преобемно со користење на многу if-else услови (од кои голем дел ќе бидат вгнездени). За да се избегне сето тоа, пожелно е ова задача да биде решена со употреба на Состоjба (анг. State) шаблонот за развоj на софтвер. При решавање на оваа задача, прво треба да се идентификуваат сите можни состоjби. Состоjбите кои се идентификувани каj MP3 player-от се: пуштена песна (play), стопирано (stop), паузирано (pause), следна песна (fwd), претходна песна (rew). Откако ќе бидат идентификувани состоjбите, треба да се идентификуваат акциите за премин меѓу состоjбите. Во овоj случаj, премин меѓу состоjбите може да се реализира со притискање на некоj од четирите тастери: STOP, FWD, REW и Play. Наjдобар начин за визуелизациjа на состоjбите и премините меѓу состоjбите е со помош на конечен автомат (анг. finite state machine). Инициjално MP3 player-от е во состоjбата стопирано. Слика 6.1: Конечен автомат 123 Откако се идентифукувани сите состоjби и можните премини меѓу состоjбите, се дефинираат класи за состоjбите согласно шаблонот за развоj на софтвер. Прво се дефинира интерфеjс State коj како методи ги има сите можни акции со кои се дефинира конкретен премин меѓу различните состоjби (во случаjов тоа се пристикања на одредени тастери). Овоj интерфеjс го имплементира класата AbstractState во коjа се чува и инстанца од класата MP3Player . Потоа, од класата AbstractState се изведуваат класите за петте можни состоjби. Во секоjа од изведените класи се препокриваат методите од интерфеjсот State и истите се имплементираат согласно начинот на справување со акциите за конкретната состоjба. Во класата MP3Player освен потребните информации за песните, се чуваат и сите различни состоjби во кои MP3 player-от може да се наjде како и референца на моменталната активна состоjба (состоjбата во коjа се наоѓа MP3 player-от). 1 2 import java . util . ArrayList ; import java . util . List ; 3 4 5 6 7 8 9 10 11 12 public class PatternTest { public static void main ( String args []) { List < Song > listSongs = new ArrayList < Song >() ; listSongs . add ( new Song ( " first - title " , " first - artist " ) ) ; listSongs . add ( new Song ( " second - title " , " second - artist " ) ) ; listSongs . add ( new Song ( " third - title " , " third - artist " ) ) ; listSongs . add ( new Song ( " fourth - title " , " fourth - artist " ) ) ; listSongs . add ( new Song ( " fifth - title " , " fifth - artist " ) ) ; MP3Player player = new MP3Player ( listSongs ) ; 13 14 15 System . out . println ( player . toString () ) ; System . out . println ( " First test " ) ; 16 17 18 19 20 21 player . pressPlay () ; player . printCurrentSong () ; player . pressPlay () ; player . printCurrentSong () ; 22 23 24 25 26 player . pressPlay () ; player . printCurrentSong () ; player . pressStop () ; player . printCurrentSong () ; 27 28 29 30 31 player . pressPlay () ; player . printCurrentSong () ; player . pressFWD () ; player . printCurrentSong () ; 32 33 34 35 36 player . pressPlay () ; player . printCurrentSong () ; player . pressREW () ; player . printCurrentSong () ; 37 38 39 40 System . out . println ( player . toString () ) ; System . out . println ( " Second test " ) ; 41 42 43 44 45 46 player . pressStop () ; player . printCurrentSong () ; player . pressStop () ; player . printCurrentSong () ; 124 Глава 6. Вовед во шаблони за дизаjн на софтвер 47 player . pressStop () ; player . printCurrentSong () ; player . pressPlay () ; player . printCurrentSong () ; 48 49 50 51 52 player . pressStop () ; player . printCurrentSong () ; player . pressFWD () ; player . printCurrentSong () ; 53 54 55 56 57 player . pressStop () ; player . printCurrentSong () ; player . pressREW () ; player . printCurrentSong () ; 58 59 60 61 62 63 System . out . println ( player . toString () ) ; System . out . println ( " Third test " ) ; 64 65 66 67 player . pressFWD () ; player . printCurrentSong () ; player . pressFWD () ; player . printCurrentSong () ; 68 69 70 71 72 player . pressFWD () ; player . printCurrentSong () ; player . pressPlay () ; player . printCurrentSong () ; 73 74 75 76 77 player . pressFWD () ; player . printCurrentSong () ; player . pressStop () ; player . printCurrentSong () ; 78 79 80 81 82 player . pressFWD () ; player . printCurrentSong () ; player . pressREW () ; player . printCurrentSong () ; 83 84 85 86 87 88 System . out . println ( player . toString () ) ; 89 } 90 91 } 92 93 94 95 class Song { String title ; String artist ; 96 97 98 99 100 public Song ( String title , String artist ) { this . title = title ; this . artist = artist ; } 101 102 103 104 105 public String getTitle () { return title ; } 106 107 public void setTitle ( String title ) { 125 this . title = title ; 108 } 109 110 public String getArtist () { return artist ; } 111 112 113 114 public void setArtist ( String artist ) { this . artist = artist ; } 115 116 117 118 @Override public String toString () { return " Song { " + " title = " + title + " , artist = " + artist + ’} ’; } 119 120 121 122 123 124 125 } 126 127 128 129 class MP3Player { List < Song > songList ; int currentSong ; 130 131 132 133 134 135 State State State State State play ; pause ; stop ; fwd ; rew ; 136 137 138 139 140 141 142 143 144 final void createStates () { play = new PlayState ( this ) ; pause = new PauseState ( this ) ; stop = new StopState ( this ) ; fwd = new FWDState ( this ) ; rew = new REWState ( this ) ; state = stop ; } 145 146 147 148 149 150 public MP3Player ( List < Song > songList ) { this . songList = songList ; currentSong = 0; createStates () ; } 151 152 153 154 public State getPlay () { return play ; } 155 156 157 158 public void setPlay ( State play ) { this . play = play ; } 159 160 161 162 public State getPause () { return pause ; } 163 164 165 166 public void setPause ( State pause ) { this . pause = pause ; } 167 168 public State getStop () { 126 Глава 6. Вовед во шаблони за дизаjн на софтвер return stop ; 169 170 } 171 172 173 174 public void setStop ( State stop ) { this . stop = stop ; } 175 176 177 178 public State getFwd () { return fwd ; } 179 180 181 182 public void setFwd ( State fwd ) { this . fwd = fwd ; } 183 184 185 186 public State getRew () { return rew ; } 187 188 189 190 public void setRew ( State rew ) { this . rew = rew ; } 191 192 State state ; 193 194 195 196 public State getState () { return state ; } 197 198 199 200 public void setState ( State state ) { this . state = state ; } 201 202 203 204 public Song getCurrentSong () { return songList . get ( currentSong ) ; } 205 206 207 208 public void setSongIndex ( int currentSong ) { this . currentSong = currentSong % songList . size () ; } 209 210 211 212 public int getSongIndex () { return currentSong ; } 213 214 215 216 217 public void songFWD () { currentSong = ( currentSong + 1) % songList . size () ; } 218 219 220 221 222 public void songREW () { currentSong = ( currentSong + songList . size () - 1) % songList . size () ; } 223 224 225 226 public void pressPlay () { state . pressPlay () ; } 227 228 229 public void pressStop () { state . pressStop () ; 127 } 230 231 public void pressFWD () { state . pressFwd () ; state . forward () ; } 232 233 234 235 236 public void pressREW () { state . pressRew () ; state . reward () ; } 237 238 239 240 241 void printCurrentSong () { System . out . println ( getCurrentSong () ) ; } 242 243 244 245 @Override public String toString () { return " MP3Player { currentSong = " + currentSong + " , songList = " + songList + " } " ; } 246 247 248 249 250 251 252 253 } 254 255 256 interface State { void pressPlay () ; 257 void pressStop () ; 258 259 void pressFwd () ; 260 261 void pressRew () ; 262 263 void forward () ; 264 265 void reward () ; 266 267 } 268 269 270 271 abstract class AbstractState implements State { MP3Player mp3 ; 272 public AbstractState ( MP3Player mp3 ) { this . mp3 = mp3 ; } 273 274 275 276 277 } 278 279 280 281 282 class FWDState extends AbstractState { public FWDState ( MP3Player mp3 ) { super ( mp3 ) ; } 283 284 285 286 287 @Override public void pressPlay () { System . out . println ( " Illegal action " ) ; } 288 289 290 @Override public void pressStop () { 128 Глава 6. Вовед во шаблони за дизаjн на софтвер System . out . println ( " Illegal action " ) ; 291 } 292 293 @Override public void pressFwd () { System . out . println ( " Illegal action " ) ; } 294 295 296 297 298 @Override public void pressRew () { System . out . println ( " Illegal action " ) ; } 299 300 301 302 303 @Override public void forward () { mp3 . songFWD () ; mp3 . setState ( mp3 . getPause () ) ; } 304 305 306 307 308 309 @Override public void reward () { System . out . println ( " Illegal action " ) ; } 310 311 312 313 314 315 } 316 317 318 319 320 321 class REWState extends AbstractState { public REWState ( MP3Player mp3 ) { super ( mp3 ) ; } 322 323 324 325 326 @Override public void pressPlay () { System . out . println ( " Illegal action " ) ; } 327 328 329 330 331 @Override public void pressStop () { System . out . println ( " Illegal action " ) ; } 332 333 334 335 336 @Override public void pressFwd () { System . out . println ( " Illegal action " ) ; } 337 338 339 340 341 @Override public void pressRew () { System . out . println ( " Illegal action " ) ; } 342 343 344 345 346 @Override public void forward () { System . out . println ( " Illegal action " ) ; } 347 348 349 350 351 @Override public void reward () { mp3 . songREW () ; mp3 . setState ( mp3 . getPause () ) ; 129 } 352 353 354 } 355 356 357 class PlayState extends AbstractState { 358 public PlayState ( MP3Player mp3 ) { super ( mp3 ) ; } 359 360 361 362 @Override public void pressPlay () { System . out . println ( " Song is already playing " ) ; } 363 364 365 366 367 @Override public void pressStop () { System . out . println ( " Song " + mp3 . getSongIndex () + " is paused " ) ; mp3 . setState ( mp3 . getPause () ) ; } 368 369 370 371 372 373 374 @Override public void pressFwd () { System . out . println ( " Forward ... " ) ; mp3 . setState ( mp3 . getFwd () ) ; } 375 376 377 378 379 380 @Override public void pressRew () { System . out . println ( " Reward ... " ) ; mp3 . setState ( mp3 . getRew () ) ; } 381 382 383 384 385 386 @Override public void forward () { System . out . println ( " Illegal action " ) ; } 387 388 389 390 391 @Override public void reward () { System . out . println ( " Illegal action " ) ; } 392 393 394 395 396 397 } 398 399 400 class StopState extends AbstractState { 401 402 403 404 public StopState ( MP3Player mp3 ) { super ( mp3 ) ; } 405 406 407 408 409 410 411 412 @Override public void pressPlay () { System . out . println ( " Song " + mp3 . getSongIndex () + " is playing " ) ; mp3 . setState ( mp3 . getPlay () ) ; } 130 Глава 6. Вовед во шаблони за дизаjн на софтвер @Override public void pressStop () { System . out . println ( " Songs are already stopped " ) ; } 413 414 415 416 417 @Override public void pressFwd () { System . out . println ( " Forward ... " ) ; mp3 . setState ( mp3 . getFwd () ) ; } 418 419 420 421 422 423 @Override public void pressRew () { System . out . println ( " Reward ... " ) ; mp3 . setState ( mp3 . getRew () ) ; } 424 425 426 427 428 429 @Override public void forward () { System . out . println ( " Illegal action " ) ; } 430 431 432 433 434 @Override public void reward () { System . out . println ( " Illegal action " ) ; } 435 436 437 438 439 440 } 441 442 class PauseState extends AbstractState { 443 444 445 446 public PauseState ( MP3Player mp3 ) { super ( mp3 ) ; } 447 448 449 450 451 452 453 @Override public void pressPlay () { System . out . println ( " Song " + mp3 . getSongIndex () + " is playing " ) ; mp3 . setState ( mp3 . getPlay () ) ; } 454 455 456 457 458 459 460 @Override public void pressStop () { System . out . println ( " Songs are stopped " ) ; mp3 . setSongIndex (0) ; mp3 . setState ( mp3 . getStop () ) ; } 461 462 463 464 465 466 @Override public void pressFwd () { System . out . println ( " Forward ... " ) ; mp3 . setState ( mp3 . getFwd () ) ; } 467 468 469 470 471 472 473 @Override public void pressRew () { System . out . println ( " Reward ... " ) ; mp3 . setState ( mp3 . getRew () ) ; } 131 @Override public void forward () { System . out . println ( " Illegal action " ) ; } 474 475 476 477 478 @Override public void reward () { System . out . println ( " Illegal action " ) ; } 479 480 481 482 483 } Проблем 6.2 Да се имплементира класа пошта PostOffice во коjа е чуваат името на поштата, локациjата и листа од пратки. За пратките да се имплементира апстрактна класа Package во коjа се чуваат информации за пратка: name (String) за кого е наменета, address ( String ), trackingNumber ( int ) за следење и weight ( int ) изразена во грамови. Во системот постоjат интернационални пратки ( InternationalPackage ) за кои дополнително се чува за коj регион се пратени ( Africa , Asia , Europe и America ) и локални ( LocalPackage ) пратки за кои се чува дали пратката е со приоритет или без приоритет. Цената на интернационалните пратки се наплаќа според масата и тоа по 1.5 долари за грам, а локалните пратки со приоритет се наплаќаат по 5 долари, а по 3 долари пратките без приоритет. Дополнително, потои и групна пратка ( GroupPackage ) коjа се состои од една или повеќе пратки од било коj тип (интернационална, локална или групна пратка). За групните пратки треба да постои метод за додавање на нова пратка (во рамки на групната пратка). Цената на групните пратки се пресметува како сума од цените на сите пратки кои се дел од групната пратка и 2 долари како дополнителен трошок. Тежината на групните пратки се пресметува како сума од тежините на сите пратки кои се дел од групната пратка. Во класата PostOffice да се имплементираат следните методи: • void loadPackages(Scanner scanner) – метод преку коj се внесуваат пратките во следен формат: тип на пратка ( I или L ), име, адреса, броj, тежина, за локалните – true ако е со приритет false ако не е, за интернационалните – регион. Доколку записот на пратка не одговара со опишаниот формат (типот на пратката е различна од I и L , или тежината е помала или еднаква на нула) се фрла исклучок од класата InvalidPackageException во коj се проследува невалидниот запис. • boolean addPackage(Package p) - за додавање на пакет во листата со пакети • Package mostExpensive() - jа враќа наjскапата пратка • void printPackages(OutputStream out) – метод коj ги печати пратките според цената (од наjвисока кон наjниска) во следен формат: L, name, address, number, weight, true/false за приоритет – локална пратка I, name, address, number, weight, region – интернационална пратка G, number, weight – групна пратка Пратките кои се дел од групната пратка се печатат вовлечени за едно празно место во десно. Во оваа задача е потребно да се дефинираат и имплементираат класи за три 132 Глава 6. Вовед во шаблони за дизаjн на софтвер различни типа на поштенски пратки (локални, меѓународни и групни). Сите овие класи наследуваат од апстрактна класа Package . Локалните и меѓународните пратки имаат едноставна дефинициjа со мали разлики од основната класата, но групните пратки претставуваат композитна податочна структура. За групните пратки треба да се чува листа од пратки (локални, меѓународни и групни пратки). Групната пратка може во себе да содржи друга групна пратка и да формира композициjа од различните пратки. 1 2 3 4 5 6 import import import import import import java . io . OutputStream ; java . io . PrintWriter ; java . util . ArrayList ; java . util . Collections ; java . util . Comparator ; java . util . Scanner ; 7 8 9 10 11 class PostOffice { String name ; String location ; private ArrayList < Package > packages ; 12 13 14 15 16 17 public PostOffice ( String name , String location ) { this . name = name ; this . location = location ; this . packages = new ArrayList < >() ; } 18 19 20 21 public boolean addPackage ( Package p ) { return packages . add ( p ) ; } 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public void loadPackages ( Scanner scanner ) throws I nv a li dP ac k ag eE xc e pt io n { while ( scanner . hasNext () ) { String line = scanner . nextLine () ; String [] parts = line . split ( " \\ s + " ) ; if (! parts [0]. equals ( " I " ) && ! parts [0]. equals ( " L " ) ) { scanner . close () ; throw new I n va li dP a ck ag eE x ce pt i on ( line ) ; } int weight = Integer . parseInt ( parts [4]) ; if ( weight <= 0) { scanner . close () ; throw new I n va li dP a ck ag eE x ce pt i on ( line ) ; } if ( parts [0]. equals ( " I " ) ) { packages . add ( new InternationalPackage ( parts [1] , parts [2] , Integer . parseInt ( parts [3]) , Integer . parseInt ( parts [4]) , parts [5]) ) ; } if ( parts [0]. equals ( " L " ) ) { packages . add ( new LocalPackage ( parts [1] , parts [2] , Integer . parseInt ( parts [3]) , Integer . parseInt ( parts [4]) , parts [5]. equals ( " true " ) ) ) ; } } } 133 public Package mostExpensive () { return packages . stream () . max ( Comparator . naturalOrder () ) . get () ; } 53 54 55 56 public void printPackages ( OutputStream out ) { PrintWriter printWriter = new PrintWriter ( out ) ; packages . stream () . sorted () . forEach ( printWriter :: println ) ; printWriter . flush () ; } 57 58 59 60 61 62 } 63 64 65 66 67 68 abstract class Package implements Comparable < Package > { String name ; String address ; int trackingNumber ; int weight ; 69 public Package ( String name , String address , int trackingNumber , int weight ) { this . name = name ; this . address = address ; this . trackingNumber = trackingNumber ; this . weight = weight ; } 70 71 72 73 74 75 76 77 public int getWeight () { return weight ; } 78 79 80 81 abstract public double getPrice () ; 82 83 @Override public int compareTo ( Package o ) { return Double . compare ( o . getPrice () , getPrice () ) ; } 84 85 86 87 88 abstract protected String format ( String spaces ) ; 89 90 @Override public String toString () { return format ( " " ) ; } 91 92 93 94 95 96 } 97 98 99 class I nternationalPackage extends Package { String region ; 100 101 102 103 104 105 106 public InternationalPackage ( String name , String address , int trackingNumber , int weight , String region ) { super ( name , address , trackingNumber , weight ) ; this . region = region . toString () ; } 107 108 109 110 111 @Override public double getPrice () { return weight * 1.5; } 112 113 @Override 134 protected String format ( String spaces ) { return String . format ( spaces + "I , %s , %s , %d , %d , % s " , name , address , trackingNumber , getWeight () , region ) ; } 114 115 116 117 118 Глава 6. Вовед во шаблони за дизаjн на софтвер } 119 120 121 class LocalPackage extends Package { boolean priorityPackage ; 122 public LocalPackage ( String name , String address , int trackingNumber , int weight , boolean priorityPackage ) { super ( name , address , trackingNumber , weight ) ; this . priorityPackage = priorityPackage ; } 123 124 125 126 127 128 129 @Override public double getPrice () { return priorityPackage ? 5 : 3; } 130 131 132 133 134 @Override protected String format ( String spaces ) { return String . format ( spaces + "L , %s , %s , %d , %d , % s " , name , address , trackingNumber , getWeight () , priorityPackage ) ; } 135 136 137 138 139 140 141 } 142 143 144 class GroupPackage extends Package { ArrayList < Package > packages ; 145 146 147 148 149 150 public GroupPackage ( String name , String address , int trackingNumber ) { super ( name , address , trackingNumber , -1) ; packages = new ArrayList < >() ; } 151 152 153 154 155 @Override public double getPrice () { return packages . stream () . mapToDouble ( Package :: getPrice ) . sum () +2; } 156 157 158 159 public void addPackage ( Package p ) { packages . add ( p ) ; } 160 161 162 163 164 @Override public int getWeight () { return packages . stream () . mapToInt ( Package :: getWeight ) . sum () ; } 165 166 167 168 169 170 171 @Override protected String format ( String spaces ) { StringBuilder sBuilder = new StringBuilder () ; sBuilder . append ( String . format ( spaces + "G , %d , % d \ n " , trackingNumber , getWeight () ) ) ; 172 173 packages . forEach ( p -> sBuilder . append ( p . format ( spaces + " " ) ) . append ( " \ n " ) ) ; 135 174 sBuilder . deleteCharAt ( sBuilder . length () - 1) ; return sBuilder . toString () ; 175 176 } 177 178 } 179 180 class I n va l id Pa ck a ge Ex c ep ti on extends Exception { 181 public I nv a li dP ac k ag eE xc e pt io n ( String message ) { super ( message ) ; } 182 183 184 185 186 } 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 public class PostOfficeTester { public static void main ( String [] args ) { Scanner scanner = new Scanner ( System . in ) ; int test = Integer . parseInt ( scanner . nextLine () ) ; if ( test == 1) { // Group Package printing System . out . println ( " ====== Packages ====== " ) ; PostOffice office = new PostOffice ( " Poshta " , " Skopje " ) ; office . addPackage ( new InternationalPackage ( " John_Doe " , " Main_St_123 " , 111 , 4 , " America " ) ) ; GroupPackage groupPackage = new GroupPackage ( " John_Done " , " Main_St_123 " , 232) ; groupPackage . addPackage ( new LocalPackage ( " Jon_Snow " , " The_Wall " , 432 , 5 , true ) ) ; office . addPackage ( groupPackage ) ; office . printPackages ( System . out ) ; } if ( test == 2) { // Nested Group Package printing System . out . println ( " ====== Packages ====== " ) ; PostOffice office = new PostOffice ( " Poshta " , " Skopje " ) ; office . addPackage ( new InternationalPackage ( " Richard_Hendricks " , " Noble_Pathway " , 325 , 4 , " Africa " ) ) ; office . addPackage ( new LocalPackage ( " Jalisa_Acheson " , " Emerald_Harbour " , 600 , 14 , false ) ) ; 215 216 217 218 219 220 221 222 223 224 GroupPackage groupPackage = new GroupPackage ( " Meagan_Schuette " , " Westeros " , 232) ; groupPackage . addPackage ( new LocalPackage ( " Meagan_Schuette " , " Westeros " , 432 , 5 , true ) ) ; groupPackage . addPackage ( new InternationalPackage ( " Sansa_Stark " , " Westeros " , 332 , 3 , " Asia " ) ) ; 225 226 227 228 229 230 231 232 233 234 GroupPackage nestedGroupPackage = new GroupPackage ( " Marline_Bohling " , " Crystal_Hills " , 284) ; nestedGroupPackage . addPackage ( new InternationalPackage ( " Edie_Bramblett " , " Lazy_Treasure " , 382 , 7 , " Europe " ) ) ; nestedGroupPackage . addPackage ( new InternationalPackage ( " Cassaundra_Huff " , " Sleepy_Farms " , 696 , 1 , " Asia " ) ) ; 136 Глава 6. Вовед во шаблони за дизаjн на софтвер nestedGroupPackage . addPackage ( new InternationalPackage ( " German_Sabbagh " , " Tawny_Heath " , 963 , 12 , " Africa " ) ) ; 235 236 237 238 groupPackage . addPackage ( nestedGroupPackage ) ; office . addPackage ( groupPackage ) ; office . addPackage ( new LocalPackage ( " Clemmie_Reves " , " Little_Cloud " , 217 , 5 , true ) ) ; office . printPackages ( System . out ) ; 239 240 241 242 243 244 } if ( test == 3) { // Most expensive Group Package System . out . println ( " ====== Packages ====== " ) ; PostOffice office = new PostOffice ( " Poshta " , " Skopje " ) ; office . addPackage ( new InternationalPackage ( " Dohn_Joe " , " Main_St_321 " , 444 , 4 , " Europe " ) ) ; GroupPackage groupPackage = new GroupPackage ( " John_Jon " , " First_St_123 " , 232) ; groupPackage . addPackage ( new LocalPackage ( " Jon_Snow " , " Westeros " , 432 , 5 , true ) ) ; groupPackage . addPackage ( new InternationalPackage ( " Sansa_Stark " , " Westeros " , 332 , 3 , " Asia " ) ) ; office . addPackage ( groupPackage ) ; office . addPackage ( new LocalPackage ( " Littlefinger " , " The_Eyrie " , 987 , 7 , false ) ) ; office . printPackages ( System . out ) ; System . out . println () ; System . out . println ( " ====== MostExpensive ====== " ) ; System . out . println ( office . mostExpensive () ) ; } if ( test == 4) { // Most expensive International Package System . out . println ( " ====== Packages ====== " ) ; PostOffice office = new PostOffice ( " Poshta " , " Skopje " ) ; office . addPackage ( new InternationalPackage ( " Dohn_Joe " , " Main_St_321 " , 444 , 15 , " Europe " ) ) ; GroupPackage groupPackage = new GroupPackage ( " John_Jon " , " First_St_123 " , 232) ; groupPackage . addPackage ( new LocalPackage ( " Jon_Snow " , " Westeros " , 432 , 5 , true ) ) ; groupPackage . addPackage ( new InternationalPackage ( " Sansa_Stark " , " Westeros " , 332 , 3 , " Asia " ) ) ; office . addPackage ( groupPackage ) ; office . addPackage ( new LocalPackage ( " Littlefinger " , " The_Eyrie " , 987 , 7 , false ) ) ; office . printPackages ( System . out ) ; System . out . println () ; System . out . println ( " ====== MostExpensive ====== " ) ; System . out . println ( office . mostExpensive () ) ; } scanner . close () ; 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 } 293 294 } 137 Во вакви ситуации се препорачува употребата на Композициjа (анг. Composite) шаблонот за развоj на софтвер коj овозможува претставување на композитни податочни структури (во форма на дрво или граф). При користење на овоj шаблон за развоj на софтвер, наjчесто се jавува потреба од рекурзивно изминување на структурата за да се изврши некоjа операциjа. Во оваа задача, рекурзивното изминување на композициjата од елементи се прави при пресметувањето на цената на групната пратка, пресметувањето на тежината на групната пратка, како и при печатењето на пратките кои се составен дел од групната пратка. Проблем 6.3 Да се напише класа TaskManager што ќе служи за менаџирање на задачи tasks на даден корисник. За класата да се имплементираат методите: • readTasks (InputStream inputStream) - метод за вчитување на задачите на корисникот при што секоjа задача е во следниот формат: [kategorija][ime_na_zadaca],[opis],[rok_na_zadacata],[prioritet] . Рокот за задачата и приоритетот се опционални полиња. Не смее да се дозволи дадена задача да има рок коjшто е веќе поминат. Во ваков случаj да се фрли исклучот од тип DeadlineNotValidException . Да се фати исклучокот на соодветно место, така што нема да се попречи вчитувањето на останатите задачи!!! • void printTasks(OutputStream os, boolean includePriority, boolean includeCategory) - метод за печатење на задачите. – Доколку includeCategory e true да се испечатат задачите групирани според категории, во спротивно се печатат сите внесени задачи – Доколку includePriority e true да се испечатат задачите сортирани според приоритетот (при што 1 е наjвисок приоритет), a немаат приоритет или имаат ист приоритет се сортираат растечки според временското растоjание помеѓу рокот и моменталниот датум, односно задачите со рок наjблизок до денешниот датум се печатат први. – Доколку includePriority e false се печатат во растечки редослед според временското растоjание помеѓу рокот и моменталниот датум. – При печатењето на задачите се користи default опциjата за toString (доколку работите вo IntelliJ), со тоа што треба да внимавате на името на променливите. Од дефинициjата на методот readTasks може да се заклучи дека за секоjа задача се знае неjзината категориjата и неjзиното име. Дополнително, задачите имаат и опционални полиња за краен рок за извршување на задачата и приоритет. Ова значи дека во рамки на системот за менаџирање на задачи може да постоjат 4 типа на задачи: обична задача, задача со приоритет, задача со краен рок за извршување, задача со приоритет и краен рок за извршување. Бидеjќи постоjат неколку типови на задачи интуитивен пристап при решавање на овоj проблем би бил да се направи еден интерфеjс коj би го дефинирал однесувањето на задачите и да се дефинираат 4 класи што ќе го имплементираат тоj интерфеjс. Овоj пристап би работел за ова ситуациjа. Но, ако претпоставиме дека во иднина се очекува на задачите да се доделат уште 3 опционални полиња (пр. вработен на коj е доделена задачата, епоха во коjа припаѓа, проценето време за завршување). Во оваа ситуациjа би требало да се дефинираат 32 класи, односно уште дополнителни 28 класи. Во вакви ситуации се користи шаблонот за дизаjн на софтвер Декоратор (анг. 138 Глава 6. Вовед во шаблони за дизаjн на софтвер Decorator). Декораторот предвидува дефинирање на еден интефеjс ( Component ) коj ги дефинира сите своjства на обjектите. Дополнително се имплементира(ат) класа/класи за конкретни компоненти ConcreteComponent , како и една апстрактна класа за декоратор BaseDecorator . Во апстрактната класа за декораторот се чува инстанца од обjект од тип Component . Таа инстанца е обjектот што се декорира со дополнителни своjства/променливи. Во решението на оваа задача прво е дефиниран интерфеjсот ITask . Овоj интерфеjс има три методи: getCategory , getPriority , getDeadline . Потоа е дефинирана конкретна компонента - класа за обичната задача SimpleTask . TaskDecorator е апстрактна класа декоратор, а конкретните класи за двата типа на декорирање се PriorityTaskDecorator и TimeTaskDecorator . Согласно ваквиот дизаjн, креирањето на обjекти од тип ITask кои имаат приоритет и краен рок на завршување се прави на следниот начин: 1 2 ITask base = new SimpleTask ( category , name , description ) ; ITask result = new P riorit yTask Decora tor ( new TimeTaskDecorator ( base , deadline ) , priority ) ; Доколку се навратиме повторно на сценариото во кое една задача има 5 опционални полиња, со користење на Декоратор шаблонот, наместо да се креираат 32 класи, ќе се креираат само 5 класи за конкретните декоратори. 1 2 3 4 5 6 import import import import import import java . io .*; java . time . Duration ; java . time . LocalDateTime ; java . util .*; java . util . stream . Collectors ; java . util . stream . Stream ; 7 8 9 interface ITask { LocalDateTime getDeadline () ; 10 int getPriority () ; 11 12 String getCategory () ; 13 14 } 15 16 17 18 19 20 21 class D e a d l i ne N o t V a l i d E xc e p t i o n extends Exception { public De a d l i n e N o t V al i d E x c e p t i on ( LocalDateTime deadline ) { super ( String . format ( " The deadline % s has already passed " , deadline ) ) ; } } 22 23 24 25 26 class SimpleTask implements ITask { String category ; String name ; String description ; 27 28 29 30 31 32 33 public SimpleTask ( String category , String name , String description ) { this . category = category ; this . name = name ; this . description = description ; } 34 35 36 @Override public LocalDateTime getDeadline () { 139 return LocalDateTime . MAX ; 37 } 38 39 @Override public int getPriority () { return Integer . MAX_VALUE ; } 40 41 42 43 44 @Override public String getCategory () { return category ; } 45 46 47 48 49 @Override public String toString () { final StringBuilder sb = new StringBuilder ( " Task { " ) ; sb . append ( " name = ’ " ) . append ( name ) . append ( ’\ ’ ’) ; sb . append ( " , description = ’ " ) . append ( description ) . append ( ’\ ’ ’) ; sb . append ( ’} ’) ; return sb . toString () ; } 50 51 52 53 54 55 56 57 58 } 59 60 61 abstract class TaskDecorator implements ITask { ITask iTask ; 62 public TaskDecorator ( ITask iTask ) { this . iTask = iTask ; } 63 64 65 66 } 67 68 class P r iorit yTaskD ecora tor extends TaskDecorator { 69 70 int priority ; 71 72 73 74 75 public Prio rityTa skDeco rator ( ITask iTask , int priority ) { super ( iTask ) ; this . priority = priority ; } 76 77 78 79 80 @Override public LocalDateTime getDeadline () { return iTask . getDeadline () ; } 81 82 83 84 85 @Override public int getPriority () { return priority ; } 86 87 88 89 90 @Override public String getCategory () { return iTask . getCategory () ; } 91 92 93 94 95 96 97 @Override public String toString () { StringBuilder sb = new StringBuilder () ; sb . append ( iTask . toString () , 0 , iTask . toString () . length () - 1) ; sb . append ( " , priority = " ) . append ( priority ) ; sb . append ( ’} ’) ; 140 Глава 6. Вовед во шаблони за дизаjн на софтвер return sb . toString () ; 98 } 99 100 } 101 102 class TimeTaskDecorator extends TaskDecorator { 103 LocalDateTime deadline ; 104 105 public TimeTaskDecorator ( ITask iTask , LocalDateTime deadline ) { super ( iTask ) ; this . deadline = deadline ; } 106 107 108 109 110 @Override public LocalDateTime getDeadline () { return deadline ; } 111 112 113 114 115 @Override public int getPriority () { return iTask . getPriority () ; } 116 117 118 119 120 @Override public String getCategory () { return iTask . getCategory () ; } 121 122 123 124 125 @Override public String toString () { StringBuilder sb = new StringBuilder () ; sb . append ( iTask . toString () , 0 , iTask . toString () . length () - 1) ; sb . append ( " , deadline = " ) . append ( deadline ) ; sb . append ( ’} ’) ; return sb . toString () ; } 126 127 128 129 130 131 132 133 134 } 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 class TaskFactory { public static ITask createTask ( String line ) throws De a d l i n e N o t Va l i d E x c e p t i on { String [] parts = line . split ( " ," ) ; String category = parts [0]; String name = parts [1]; String description = parts [2]; SimpleTask base = new SimpleTask ( category , name , description ) ; if ( parts . length == 3) { return base ; } else if ( parts . length == 4) { try { int priority = Integer . parseInt ( parts [3]) ; return new P riori tyTask Decor ator ( base , priority ) ; } catch ( Exception e ) { // parsing failed , it ’s a date LocalDateTime deadline = LocalDateTime . parse ( parts [3]) ; checkDeadline ( deadline ) ; return new TimeTaskDecorator ( base , deadline ) ; } } else { LocalDateTime deadline = LocalDateTime . parse ( parts [3]) ; checkDeadline ( deadline ) ; 141 int priority = Integer . parseInt ( parts [4]) ; return new Priori tyTask Decor ator ( new TimeTaskDecorator ( base , deadline ) , priority ) ; 159 160 161 } 162 } 163 164 private static void checkDeadline ( LocalDateTime deadline ) throws De a d l i n e N o t Va l i d E x c e p t i on { if ( deadline . isBefore ( LocalDateTime . now () ) ) throw new D e ad l i n e N o t V a l id E x c e p t i o n ( deadline ) ; } 165 166 167 168 169 170 } 171 172 173 class TaskManager { Map < String , List < ITask > > tasks ; 174 175 176 177 public TaskManager () { tasks = new TreeMap < >() ; } 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 public void readTasks ( InputStream inputStream ) { tasks = new BufferedReader ( new InputStreamReader ( inputStream ) ) . lines () . map ( line -> { try { return TaskFactory . createTask ( line ) ; } catch ( D e a dl i n e N o t V a l i dE x c e p t i o n e ) { System . out . println ( e . getMessage () ) ; } return null ; }) . filter ( Objects :: nonNull ) . collect ( Collectors . groupingBy ( ITask :: getCategory , TreeMap :: new , Collectors . toList () ) ); } 197 198 199 200 201 202 203 204 205 public void addTask ( ITask iTask ) { tasks . computeIfAbsent ( iTask . getCategory () , k -> new ArrayList < >() ) ; tasks . computeIfPresent ( iTask . getCategory () , (k , v ) -> { v . add ( iTask ) ; return v ; }) ; } 206 207 208 209 public void printTasks ( OutputStream os , boolean includePriority , boolean byCategory ) { PrintWriter pw = new PrintWriter ( os ) ; 210 211 212 213 214 215 216 217 218 219 Comparator < ITask > priorityComparator = Comparator . comparing ( ITask :: getPriority ) . thenComparing ( task -> Duration . between ( LocalDateTime . now () , task . getDeadline () ) ) ; Comparator < ITask > simpleComparator = Comparator . comparing ( task -> Duration . between ( LocalDateTime . now () , task . getDeadline () ) ) ; 142 Глава 6. Вовед во шаблони за дизаjн на софтвер if ( byCategory ) { tasks . forEach (( category , t ) -> { pw . println ( category . toUpperCase () ) ; t . stream () . sorted ( includePriority ? priorityComparator : simpleComparator ) . forEach ( pw :: println ) ; }) ; } else { tasks . values () . stream () . flatMap ( Collection :: stream ) . sorted ( includePriority ? priorityComparator : simpleComparator ) . forEach ( pw :: println ) ; } 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 pw . flush () ; 236 } 237 238 } 239 240 241 242 public class TasksManagerTest { public static void main ( String [] args ) { TaskManager manager = new TaskManager () ; 243 System . out . println ( " Tasks reading " ) ; manager . readTasks ( System . in ) ; System . out . println ( " By categories with priority " ) ; manager . printTasks ( System . out , true , true ) ; System . out . println ( " - - - - - - - - - - - - - - - - - - - - - - - - - " ) ; System . out . println ( " By categories without priority " ) ; manager . printTasks ( System . out , false , true ) ; System . out . println ( " - - - - - - - - - - - - - - - - - - - - - - - - - " ) ; System . out . println ( " All tasks without priority " ) ; manager . printTasks ( System . out , false , false ) ; System . out . println ( " - - - - - - - - - - - - - - - - - - - - - - - - - " ) ; System . out . println ( " All tasks with priority " ) ; manager . printTasks ( System . out , true , false ) ; System . out . println ( " - - - - - - - - - - - - - - - - - - - - - - - - - " ) ; 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 } 259 260 } Проблем 6.4 Да се имплементира апликациjата за евиденциjа на работниот ангажман на вработени во една ИТ компаниjа. За таа цел да се имплементира класата PayrollSystem во коjа што ќе се чуваат информации за вработени во компаниjата. Постоjат два типа на вработени HourlyEmployee и FreelanceEmployee . HourlyEmployee добиваат плата базирана на вкупниот броj на изработени часови, додека пак FreelanceEmployee добиваат плата базирана на поените на тикетите што ги решиле. За класата PayrollSystem да се имплементираат:За класата PayrollSystem да се имплементираат: • PayrollSystem(Map<String,Double> hourlyRateByLevel, Map<String, Double> ticketRateByLevel) - конструктор со два аргументи. Првиот аргумент означува колку е саатницата за соодветно ниво за вработените што земаат плата по час работа, а вториот аргумент означува колку е платата по поен од тикет за соодветното ниво за фриленсерите. 143 • void readEmployeesData (InputStream is) - метод за вчитување на податоците за вработените во компаниjата, при што за секоj вработен податоците се дадени во нов ред. • Employee createEmployee (String line) - метод што врз основа на влезен стринг (еден вчитан ред) во коj се запишани информациите за даден вработен, ќе креира и врати обjект од класа Employee. Податоците за вработените се внесуваат во следниот формат: – Доколку вработениот е HourlyEmployee : H;ID;level;hours; bonus – Доколку вработениот е FreelanceEmployee : F;ID;level;ticketPoints1;ticketPoints2;...;ticketPointsN; bonus За вработените постоjат два типа на бонуси: – Фиксен паричен бонус (запишан како броjка). пр. H;ID;level;hours; 100 (во овоj случаj добива фиксен бонус на плата од 100$) – Процентуален паричен бонус (запишан како броj со знак процент). пр. F;ID;level;ticketPoints1;ticketPoints2;...;ticketPointsN; 10% (во овоj случаj добива процентуален бонус од 10% од неговата плата). • Во претходниот метод, со искчучок од тип BonusNotAllowedException да се спречи креирање на вработен на коjшто му е доделен фиксен бонус поголем од 1000$ или процентуален бонус поголем од 20 • Map<String, Double> getOvertimeSalaryForLevels () - метод коjшто ќе врати мапа каде што клучот е нивото на вработениот, а вредноста е вкупниот износ коjа што компаниjата го исплатила за прекувремена работа за вработените од тоа ниво. • void printStatisticsForOvertimeSalary () - метод коjшто ќе испечати статистки (минимум, максимум, сума, просек) на исплатените додатоци за прекувремена работа на сите вработени во компаниjата. • Map<String, Integer> ticketsDoneByLevel() - метод коjшто ќе врати мапа каде што клучот е нивото на вработените, а вредноста е броjот на поени за тикети што се сработени од вработените од соодветното ниво. • Collection<Employee> getFirstNEmployeesByBonus (int n) - метод коjшто ќе врати сортирана колекциjа од првите n вработени сортирани во опаѓачки редослед според бонусот коjшто го добиле на платата. Во оваа задача повторно се употребува шаблонот за дизаjн на софтвер Декоратор. Дефиниран е Employee интерфеjсот и истиот има две конкретни имплементации: HourlyEmployee и FreelanceEmployee . Целта е да се декорира обjект од тип Employee со дополнително своjство - бонус на плата. Бонусот може да биде фиксен износ коj се додава на платата или пак процентуален бонус коj се пресметува на основната плата. Се дефинира апстракната класа BonusDecorator што го имплементи-ра интерфеjсот Employee и чува инстанца од тип Employee , односно вработениот коj ќе биде декориран со соодветниот бонус. Во апстрактната класа BonusDecorator се имплементирани поголемиот дел од методите од интерфеjсот Employee , со исклучок на методите getBonus() и calculateSalary() бидеjќи нивната имплементациjа зависи од конкретниот декоратор. Од класата BonusDecorator се изведени декораторите за фиксни бонуси и процентуални бонуси ( FixedBonusDecorator и PercentageBonusDecorator ). Соодветно, во 144 Глава 6. Вовед во шаблони за дизаjн на софтвер овие два декоратори се имплементирани методите getBonus() и calculateSalary() . Креирањето на обjекти од тип Employee што се декорирани со бонус декоратор се прави на следниот начин: 1 2 3 Employee baseEmployee = new HourlyEmployee ( " testID " , " level1 " , 11 , 43) ; Employee decoratedEmployee = new FixedBonusDecorator ( baseEmployee , 100) ; Employee decoratedEmployee2 = new P e r ce n t ag e B on u s De c o ra t o r ( baseEmployee ,5) ; Целосното решение на задачата е дадено во продолжение. 1 2 import java . util .*; import java . util . stream . Collectors ; 3 4 5 6 7 class B on u s No t A ll o w ed E x ce p t io n extends Exception { B o n u s N ot A l lo w e dE x c ep t i on ( String bonus ) { super ( String . format ( " Bonus of % s is not allowed " , bonus ) ) ; } 8 9 } 10 11 12 interface Employee { double calculateSalary () ; 13 double getBonus () ; 14 15 double getOvertime () ; 16 17 int getTicketsCount () ; 18 19 void updateBonus ( double amount ) ; 20 21 String getLevel () ; 22 23 } 24 25 26 27 28 29 30 abstract class EmployeeBase implements Employee , Comparable < EmployeeBase > { String ID ; String level ; double rate ; double totalBonus ; 31 32 33 34 35 36 37 38 public EmployeeBase ( String ID , String level , double rate ) { this . ID = ID ; this . level = level ; this . rate = rate ; this . totalBonus = 0.0; } 39 40 41 42 public String getLevel () { return level ; } 43 44 45 46 47 48 49 @Override public int compareTo ( EmployeeBase o ) { return Comparator . comparing ( EmployeeBase :: calculateSalary ) . thenComparing ( EmployeeBase :: getLevel ) . compare ( this , o ) ; } 50 51 52 @Override public String toString () { 145 return String . format ( " Employee ID : % s Level : % s Salary : %.2 f " , ID , level , calculateSalary () + totalBonus ) ; 53 54 55 } 56 57 @Override public double getBonus () { return totalBonus ; } 58 59 60 61 62 @Override public void updateBonus ( double amount ) { this . totalBonus += amount ; } 63 64 65 66 67 } 68 69 70 71 72 class HourlyEmployee extends EmployeeBase { double hours ; double overtime ; double regular ; 73 public HourlyEmployee ( String ID , String level , double rate , double hours ) { super ( ID , level , rate ) ; this . hours = hours ; overtime = Math . max (0 , hours - 40) ; regular = hours - overtime ; } 74 75 76 77 78 79 80 81 @Override public double calculateSalary () { return regular * rate + overtime * rate * 1.5; } 82 83 84 85 86 @Override public double getOvertime () { return overtime * rate * 1.5; } 87 88 89 90 91 @Override public int getTicketsCount () { return -1; } 92 93 94 95 96 @Override public String toString () { return super . toString () + String . format ( " Regular hours : %.2 f Overtime hours : %.2 f " , regular , overtime ) ; } 97 98 99 100 101 102 103 } 104 105 106 class FreelanceEmployee extends EmployeeBase { List < Integer > ticketPoints ; 107 108 109 110 111 112 113 public FreelanceEmployee ( String ID , String level , double rate , List < Integer > ticketPoints ) { super ( ID , level , rate ) ; this . ticketPoints = ticketPoints ; } 146 Глава 6. Вовед во шаблони за дизаjн на софтвер @Override public double calculateSalary () { return ticketPoints . stream () . mapToInt ( tp -> tp ) . sum () * rate ; } 114 115 116 117 118 @Override public double getOvertime () { return -1; } 119 120 121 122 123 @Override public int getTicketsCount () { return ticketPoints . size () ; } 124 125 126 127 128 @Override public String toString () { return super . toString () + String . format ( " Tickets count : % d Tickets points : % d " , ticketPoints . size () , ticketPoints . stream () . mapToInt ( i -> i ) . sum () ); } 129 130 131 132 133 134 135 136 137 } 138 139 140 abstract class BonusDecorator implements Employee { Employee employee ; 141 public BonusDecorator ( Employee employee ) { this . employee = employee ; } 142 143 144 145 @Override public double getOvertime () { return employee . getOvertime () ; } 146 147 148 149 150 @Override public int getTicketsCount () { return employee . getTicketsCount () ; } 151 152 153 154 155 @Override public void updateBonus ( double amount ) { employee . updateBonus ( amount ) ; } 156 157 158 159 160 @Override public String getLevel () { return employee . getLevel () ; } 161 162 163 164 165 @Override public String toString () { return employee . toString () + String . format ( " Bonus : %.2 f " , getBonus () ) ; } 166 167 168 169 170 171 } 172 173 174 class F ixedBonusDecorator extends BonusDecorator { 147 double fixedAmount ; 175 176 public FixedBonusDecorator ( Employee employee , double fixedAmount ) { super ( employee ) ; this . fixedAmount = fixedAmount ; employee . updateBonus ( fixedAmount ) ; } 177 178 179 180 181 182 183 @Override public double calculateSalary () { double salaryWithoutBonus = employee . calculateSalary () ; return salaryWithoutBonus + fixedAmount ; } 184 185 186 187 188 189 @Override public double getBonus () { return fixedAmount ; } 190 191 192 193 194 } 195 196 197 198 199 class P er c e nt a g eB o n us D e co r a to r extends BonusDecorator { double percent ; double bonus ; 200 public P e r ce n t ag e B on u s De c o ra t o r ( Employee employee , double percent ) { super ( employee ) ; this . percent = percent ; bonus = employee . calculateSalary () * percent / 100.0; employee . updateBonus ( bonus ) ; } 201 202 203 204 205 206 207 208 @Override public double calculateSalary () { double salaryWithoutBonus = employee . calculateSalary () ; return salaryWithoutBonus + bonus ; } 209 210 211 212 213 214 @Override public double getBonus () { return bonus ; } 215 216 217 218 219 } 220 221 222 223 224 225 226 class EmployeeFactory { public static Employee createEmployee ( String line , Map < String , Double > hourlyRate , Map < String , Double > ticketRate ) throws B o n us N o tA l l ow e d Ex c e pt i o n { String [] partsBySpace = line . split ( " \\ s + " ) ; 227 228 229 Employee e = createSimpleEmployee ( partsBySpace [0] , hourlyRate , ticketRate ) ; 230 231 232 233 234 235 if ( partsBySpace . length > 1) { if ( partsBySpace [1]. contains ( " % " ) ) { // percentage bonus double percentage = Double . parseDouble ( partsBySpace [1] . substring (0 , partsBySpace [1]. length () - 1) ) ; if ( percentage > 20) 148 Глава 6. Вовед во шаблони за дизаjн на софтвер throw new Bo n u sN o t Al l o we d E xc e p ti o n ( partsBySpace [1]) ; e = new Pe r c en t a ge B o nu s D ec o r at o r (e , percentage ) ; 236 237 238 239 } else { // fixed bonus double bonusAmount = Double . parseDouble ( partsBySpace [1]) ; if ( bonusAmount > 1000) throw new Bo n u sN o t Al l o we d E xc e p ti o n ( partsBySpace [1] + " $ " ) ; e = new FixedBonusDecorator (e , bonusAmount ) ; 240 241 242 243 244 245 246 247 } } return e ; 248 249 250 } 251 252 public static Employee createSimpleEmployee ( String subline , Map < String , Double > hourlyRate , Map < String , Double > ticketRate ) { String [] parts = subline . split ( " ; " ) ; String id = parts [1]; String level = parts [2]; if ( parts [0]. equalsIgnoreCase ( " H " ) ) { // hourly double hours = Double . parseDouble ( parts [3]) ; return new HourlyEmployee ( id , level , hourlyRate . get ( level ) , hours ) ; } else { // freelance List < Integer > ticketPoints = Arrays . stream ( parts ) . skip (3) . map ( Integer :: parseInt ) . collect ( Collectors . toList () ) ; return new FreelanceEmployee ( id , level , ticketRate . get ( level ) , ticketPoints ) ; } } 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 } 272 273 274 275 276 class PayrollSystem { Map < String , Double > hourlyRate ; Map < String , Double > ticketRate ; List < Employee > employees ; 277 278 279 280 281 282 283 public PayrollSystem ( Map < String , Double > hourlyRate , Map < String , Double > ticketRate ) { this . hourlyRate = hourlyRate ; this . ticketRate = ticketRate ; employees = new ArrayList < >() ; } 284 285 286 287 288 289 290 291 Employee createEmployee ( String line ) throws B o n us N o tA l l ow e d Ex c e pt i o n { Employee e = EmployeeFactory . createEmployee ( line , hourlyRate , ticketRate ) ; employees . add ( e ) ; return e ; } 292 293 294 295 296 Map < String , Double > g e t O v e r t i m e S a l a r y F o r L e v e l s () { Map < String , Double > result = employees . stream () . collect ( Collectors . groupingBy ( Employee :: getLevel , 149 Collectors . summingDouble ( Employee :: getOvertime ) 297 298 )); 299 300 List < String > keysWithZeros = result . keySet () . stream () . filter ( key -> result . get ( key ) == -1) . collect ( Collectors . toList () ) ; 301 302 303 304 keysWithZeros . forEach ( result :: remove ) ; 305 306 return result ; 307 } 308 309 void p r i n t S t a t i s t i c s F o r O v e r t i m e S a l a r y () { D ou bl e Su mm ar y St at is t ic s dss = employees . stream () . filter ( e -> e . getOvertime () != -1) . mapToDouble ( Employee :: getOvertime ) . summaryStatistics () ; 310 311 312 313 314 315 System . out . printf ( " Statistics for overtime salary : Min : %.2 f " + " Average : %.2 f Max : %.2 f Sum : %.2 f " , dss . getMin () , dss . getAverage () , dss . getMax () , dss . getSum () ) ; 316 317 318 319 320 } 321 322 Map < String , Integer > ticketsDoneByLevel () { return employees . stream () . filter ( e -> e . getTicketsCount () != -1) . collect ( Collectors . groupingBy ( Employee :: getLevel , Collectors . summingInt ( Employee :: getTicketsCount ) )); } 323 324 325 326 327 328 329 330 331 332 Collection < Employee > g e t F i r s t N Em p l o y e e s B y B on u s ( int n ) { return employees . stream () . sorted ( Comparator . comparing ( Employee :: getBonus ) . reversed () ) . limit ( n ) . collect ( Collectors . toList () ) ; } 333 334 335 336 337 338 339 340 341 } 342 343 public class PayrollSystemTest { 344 345 public static void main ( String [] args ) { 346 347 348 349 350 351 352 Map < String , Double > hourlyRateByLevel = new LinkedHashMap < >() ; Map < String , Double > ticketRateByLevel = new LinkedHashMap < >() ; for ( int i = 1; i <= 10; i ++) { hourlyRateByLevel . put ( " level " + i , 11 + i * 2.2) ; ticketRateByLevel . put ( " level " + i , 5.5 + i * 2.5) ; } 353 354 Scanner sc = new Scanner ( System . in ) ; 355 356 357 int employeesCount = Integer . parseInt ( sc . nextLine () ) ; 150 Глава 6. Вовед во шаблони за дизаjн на софтвер PayrollSystem ps = new PayrollSystem ( hourlyRateByLevel , ticketRateByLevel ) ; Employee emp = null ; for ( int i = 0; i < employeesCount ; i ++) { try { emp = ps . createEmployee ( sc . nextLine () ) ; } catch ( Bo n u sN o t Al l o we d E xc e p ti o n e ) { System . out . println ( e . getMessage () ) ; } } 358 359 360 361 362 363 364 365 366 367 368 int testCase = Integer . parseInt ( sc . nextLine () ) ; 369 370 switch ( testCase ) { case 1: // Testing createEmployee if ( emp != null ) System . out . println ( emp ) ; break ; case 2: // Testing g e t O v e r t i m e S a l a r y F o r L e v e l s () ps . g e t O v e r t i m e S a l a r y F o r L e v e l s () . forEach (( level , overtimeSalary ) -> { System . out . printf ( " Level : % s Overtime " + " salary : %.2 f \ n " , level , overtimeSalary ) ; }) ; break ; case 3: // Testing p r i n t S t a t i s t i c s F o r O v e r t i m e S a l a r y () ps . p r i n t S t a t i s t i c s F o r O v e r t i m e S a l a r y () ; break ; case 4: // Testing ticketsDoneByLevel ps . ticketsDoneByLevel () . forEach (( level , overtimeSalary ) -> { System . out . printf ( " Level : % s Tickets by level : % d \ n " , level , overtimeSalary ) ; }) ; break ; case 5: // Testing g e t Fi r s t N E m p l o y ee s B y B o n u s ( int n ) ps . g e t Fi r s t N E m p l o ye e s B y B o n u s ( Integer . parseInt ( sc . nextLine () ) ) . forEach ( System . out :: println ) ; break ; } 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 } 403 404 } Индекс на термини Архива, 61 Банкарски трансакции, 10 Брокери, 95 Вградени исклучоци, 24 Вирус, 107 Генеричка дропка, 54 Генеричка колекциjа, 114 Генеричка табела, 49 Генерички броjач, 52 Генерички споредувач, 57 Генеричко печатење, 56 Евиденциjа на работно време, 142 Именик, 77 Книга, 64 Компоненти, 67 Кориснички групи, 75 Менаџер на задачи, 137 Мерења, 58 Низи од цели броеви, 5 ООП Jава, 5 Паркинг, 81 Пица нарачки, 25 Платно, 18 Пошта, 131 Преводи, 35 Префикс дрво, 70 Приjавување, 39 Систем за нарачки, 43 Состоjба шаблон, 121 Троjка, 47 Факултет, 87 Форми, 101