Professional Documents
Culture Documents
Adnan Miljković
Lectures Schedule #1
Week 1 - User input, Printing on screen & Conditional statements
Week 3 - Methods, ArrayList data structure, Streams and connecting to the database - Quiz 1
Week 7 - Lambda functions, Records, Optionals , final keyword, & Preparation for the Midterm Exam -
Quiz 3
Lectures Schedule #2
Week 8 & 9 - MIDTERM
Week 10 - Regular expressions, File Manipulation, Iterators, Reflection & Custom Annotations
Exercise
- student numbers start with the string "01" which is followed by seven numerical
digits from 0 to 9
Regular Expressions
Example
if (num.matches("01[0-9]{7}")) {
System.out.println("The form is valid.");
} else {
System.out.println("The form is not valid.");
}
Vertical Bar: Logical or
The vertical bar means that the parts of the regular expression are optional.
For instance, the expression 00|111|0000 defines the strings 00, 111 and 0000.
The method matches returns true if the string matches one of the alternatives defined.
Vertical Bar: Logical or
Example
if(string.matches("00|111|0000")) {
System.out.println("The string was matched by some of the alternatives");
} else {
System.out.println("The string not was matched by any of the alternatives");
}
Vertical Bar: Logical or
The regular expression 00|111|0000 requires the exactly same form of the string:
its functionality is not like "contains".
if(string.matches("00|111|0000")) {
System.out.println("The string was matched by some of the alternatives");
} else {
System.out.println("The string not was matched by any of the alternatives");
}
Round Brackets: a Delimited Part of the String
With the help of round brackets it is possible to define what part of the regular
expression is affected by the symbols.
If we want to allow for the alternatives 00000 and 00001, we can define it with the
help of a vertical bar: 00000|00001.
Thanks to round brackets we can delimit the choice to only a part of the string.
Example
System.out.print("Write a form of the verb to look: ");
String word = reader.nextLine();
if (word.matches("look(|s|ed|ing|er)")) {
System.out.println("Well done!");
} else {
System.out.println("Check again the form.");
}
Repetitions
if(string.matches("trolo(lo)*")) {
System.out.println("The form is right.");
} else {
System.out.println("The form is wrong.");
}
Repetitions
if(string.matches("tro(lo)+")) {
System.out.println("The form is right.");
} else {
System.out.println("The form is wrong.");
}
Repetitions
if(string.matches("(10){2}")) {
System.out.println("The form is right.");
} else {
System.out.println("The form is wrong.");
}
Repetitions
The symbol {a,b} stands for a repetition from a to b times, for instance
String string = "1";
if(string.matches("1{2,4}")) {
System.out.println("The form is right.");
} else {
System.out.println("The form is wrong.");
}
Repetitions
The symbol {a,} stands for a repetition from a to n times, for instance
String string = "11111";
if(string.matches("1{2,}")) {
System.out.println("The form is right.");
} else {
System.out.println("The form is wrong.");
}
Repetitions
You can also use various different repetition symbols within one regular
expression.
For instance, the regular expression 5{3}(1|0)*5{3} defines strings which start
and end with three fives.
The characters are written inside the brackets, and we can define an interval with the
help of a hyphen (-).
For instance, [145] means the same as (1|4|5), whereas [2-36-9] means the
same as (2|3|6|7|8|9). Accordingly, [a-c]* defines a regular expression with a
string made only of characters a, b and c.
File manipulation
- One more important thing is that we HAVE TO CLOSE a file every time we
finish with processing it. If you leave it opened, nothing will be written to the file
(it will be created as empty file) and you have a memory leak.
- The `\n` is character used to specify that it should be a new row in the file
Simple file writing
public static void simpleWrite(String text) throws IOException {
BufferedWriter bufferedWriter = new BufferedWriter(
new FileWriter("output.txt")
);
bufferedWriter.write("Our first line \n");
bufferedWriter.close();
}
- One more important thing is that we HAVE TO CLOSE a file every time we
finish with processing it. If you leave it opened, nothing will be written to the file
(it will be created as empty file) and you have a memory leak.
Array file writing
bufferedWriter.close();
}
Reading values from the file
bufferedReader.close();
}
Reading all values from the file cont.
- We can also utilize streams to transform a file lines into ArrayList
bufferedReader.close();
}
Files
- We have saw different methods for file manipulation and yet, there are so many
different ways and different utility libraries for file manipulation in Java
- Any of the approaches you might find is good as long as you are are able to
manipulate with files in the fast way
- Always pay attention to the process of closing files not to encounter the memory
leak
Java iterators
- It's important to note that starting from Java 5, the enhanced for-loop (for-each
loop) provides a more convenient syntax for iterating over collections, and it
internally uses iterators.
- The enhanced for-loop can be used with any class that implements the Iterable
interface, including collections that provide iterators.
- Creating custom iterators in Java is useful in situations where you have a custom
data structure or collection and you want to define your own way of iterating
over its elements. There are many reasons for implementing the custom iterators
like: Filtering or Transformation, Custom Data Structures, Specific Traversal
Logic, Encapsulation and Abstraction of data structures
Java iterators
- In order to implement the Iterator on your custom object one has to implement
the Iterator interface and provide implementation for the next() and hasNext()
methods
- Here's a simple example: Suppose you have a custom collection that holds only
positive integers, and you want to create an iterator that skips over even numbers
during iteration. You can achieve this with a custom iterator.
Java iterators
class OddNumbersIterator implements @Override
Iterator<Integer> { public Integer next() {
private int[] elements; if (!hasNext()) {
throw new NoSuchElementException();
private int currentIndex = 0;
}
return elements[currentIndex++];
public OddNumbersIterator(int[] elements) { }
this.elements = elements; }
}
@Override
public boolean hasNext() {
while (currentIndex < elements.length &&
elements[currentIndex] % 2 == 0) {
currentIndex++; // Skip even numbers
}
return currentIndex < elements.length;
}
Java iterators
- Reflection API gives you ability to develop a program that can “look” at
itself and change itself while it is running
- Use it at your own risk!
- Highly powerful and allows you to break all of the rules of your program
- Change element fo your Java class while it is running
- myClassObject.getClass() -> is a window to the wide variety of reflection
functions
Reflection example
- Let us create a small class called Cat with two attributes (name and age)
public class Cat {
private final String name;
private int age;
- Let us create a small class called Cat with two attributes (name and age)
public class Cat {
private final String name;
private int age;
- We will create getters and setters for the attributes (note that final attribute
cannot have setter)
public String getName() {
return name;
}
- Let us create couple of more methods that will be needed for the
demonstration purposes
public static void thisIsPublicStaticMethod() {
System.out.println("I'm public and static");
}
public void meow() {
System.out.println("meow");
}
public void saySomething(String something) {
System.out.println("I said something ".concat(something));
}
private void heyThisIsPrivate() {
System.out.println("How did you call this?");
}
Reflection example
- Let us use some reflection now to get all fields inside of the Cat class
- The method getDeclaredMethods includes all methods declared by the class itself, whereas
getMethods returns only public methods, but also those inherited from a base class (here
from java.lang.Object).
Why do we need it at all?
- In order to specify how you plan to use the annotation we have to use the annotation as well
Creating Annotations
- First annotation is the @Target() allows us to specify which kind of Java element this
annotation is valid to be used on. If you omit this annotation, you annotation is valid on any
Java element. It can take following values:
- ANNOTATION_TYPE − An annotation type declaration
- CONSTRUCTOR − A constructor declaration
- FIELD − A field declaration
- LOCAL_VARIABLE − A local variable declaration
- METHOD − A method declaration
- PACKAGE − A package declaration
- PARAMETER − A parameter declaration
- TYPE − A class, interface, enum, or record declaration
- TYPE_PARAMETER − A type parameter declaration
- TYPE_USE − A use of a type
Creating Annotations
- Since our annotation will be used on the class level we will say that it is allowed to be used
on the class level by using the following syntax
@Target(ElementType.TYPE)
- If we try to use our annotation on some other Java element, it will give us the compile time
error.
- If we want the annotation to be used on multiple Java elements, let us say the class and
method level we would use the following syntax
@Target({ElementType.TYPE, ElementType.METHOD})
Creating Annotations
- Second annotation that we will specify for our annotation is called @Retention()
- It indicates how long the annotated type should be kept in the program's lifecycle and it can
take following values
- RUNTIME - it will be available to other code while program is running (Reflection)
- SOURCE - Java will get rid of this annotation before it compiles the code (Used for the annotations that
are needed before the code has been compiled ex. @SuppressWarnings)
- CLASS - Java will keep it during the compilation of the program, but once it stars running it will get rid of
it
- We want our annotation to be available during the runtime so we will use the following
syntax
@Retention(RetentionPolicy.RUNTIME)
Testing annotation
- Now we can test if our annotation is configured correctly by using a reflection
- We have created a new record called Lion that has only one property: name: String that is
annotated by using the @VeryImportant annotation
- In order to get the annotation and see is it present on our object we have used the following
code
Lion lion = new Lion("Lavcina");
System.out.println(lion.getClass().isAnnotationPresent(VeryImportant.class));
- The code returned us a true meaning that the annotation is present above the class Lion
Method level annotations
- Next let us create an annotation called @RunImmediately that will run the method
immediately if the annotation is present above that method
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RunImmediately {}
Method level annotations cont.
- Now we will create two methods in the Lion class, one that will have the annotation
@RunImmediately and the other that will not have that annotation
record Lion(String name) {
@RunImmediately
public void saySomething() {
System.out.println("Lion method saySomething");
}
public void saySomethingButDontRun() {
System.out.println("Lion method saySomethingButDontRun");
}
}
Method level annotations cont.
- Now, let us create a logic that will automatically run the method with the previously created
annotation
for (Method method : lion.getClass().getDeclaredMethods()) {
if(method.isAnnotationPresent(RunImmediately.class)){
System.out.println("Method name is " + method.getName());
method.invoke(lion);
}
}
- If we run this code, we will see that the method called saySomething will be runned by
the program written
Annotations with parameters
- What if we want to create and annotation that will run the method automatically n number
of times where the n is a parameter that we pass to our annotation
- Let us create the annotation called @RunImmediatelyNTimes that will as a parameter
accept the integer that will specified how many times this method will be invoked
automatically
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RunImmediatelyNTimes {
int times();
}
Annotations with parameters cont.
- In our annotation we have declared the method (only methods are allowed) called times
- We can also give a default value to our methods (if we don’t specify the default value you
will be enforced to pass the parameter to the annotation).
- Important thing to note is that the parameters to our interface can only be one of the
following:
- Primitive data type
- String
- Class type
- Array of any of the above mentioned types
Annotations with parameters cont.
- We can also give a default value to our methods (if we don’t specify the default value you
will be enforced to pass the parameter to the annotation).
- Let us say that the default value for our @RunImmediatelyNTimes is 1 we will add
following code:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RunImmediatelyNTimes {
int times() default 1;
}
Annotations with parameters cont.
- We want to develop a program that will check if the annotation is present above the method
and if it is we want to run it specified number of times
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ImportantString {}
- So let us say that in our program we want to check whether the field in class Fox is
annotated with @ImportantString annotation and if it is we want to print to the console the
value of that attribute
- Note that we have to change the accessibility of the attribute before accessing it as it has
been declared as a private attribute
Testing field level annotations
Fox fox = new Fox("Lija", 4);
for (Field field : fox.getClass().getDeclaredFields()){
if(field.isAnnotationPresent(ImportantString.class)){
field.setAccessible(true);
Object value = field.get(fox);
if(value instanceof String myFoxName) // String myFoxName = (String) value
System.out.println(
"The field name is " +
field.getName() +
" and its value in uppercase is " + myFoxName.toUpperCase());
}
}
System.out.println("Thanks, bye!");