Uploaded by davidhristov10

scribd.vdownloaders.com zbirka-reseni-zadaci-stefan-i-madzarov

advertisement
Збирка решени задачи по предметот
напредно програмирање
Ѓорѓи Маџаров
Стефан Андонов
Факултет за информатички науки и комп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
Download