📱

Get Our Mobile App

Take your business learning on the go!

Download on the App StoreGet it on Google Play

Small Version 2. Classes and Objects (Classes in Depth, String and String Pool)

პროგრამირება1:03:34

Transcription

Hello friends, now I will record, as I promised you, a short version of the second lecture. The second lecture will cover these topics, mainly classes and objects, I will touch upon them in detail and some nuances related to them. We will go through primitive types, the corresponding object types for primitive types that we have, and automatic conversion between them. We will see primitive type promotion, how it happens in arithmetic operations, for example, the result of double and int is double. We will also see the String class and what a String pool is. We will also review object-oriented programming principles again, and we will see that they added a fourth one in Java to the well-known three. We will go through constructors again and see how we can call our own constructor with the `this` keyword and with the parentheses of this constructor method. Then we will go through method overloading. This is not overriding, it works similarly to overloading in C++. All of this in Java, we will discuss inheritance and override methods inherited from the parent. We will also see how we can use the parent's part with the `super` keyword. We will also see the `toString` method which is in the `Object` class and is inherited by all classes. How can we override it and why does the `Object` class method exist at all, so that it is inherited by everyone. Then we will see the `final` keyword. This `final` keyword is usually written in lowercase, so let's write it like this. We will see where we can use the `final` keyword and what it means. Well, in our opinion, the `final` keyword is the same as the `const` keyword in C++. Then we will see how a variable number of arguments can be declared and used, what the rules are, and finally, we will discuss static and instance initialization blocks. Okay, let's write it like this. The first is primitive types. In Java, we have numeric integer, numeric floating-point types, and also char, character, and boolean. These are what are listed here, and you can memorize the sizes. Byte is a type for storing integers, one byte in size, primitive. Short, I'll write short by chance, it seems short is two. So it doubles. See, this is also for storing integers. Then 4-byte int, long 8. We don't have more than long, we don't have long long and long int and things like that. Here are these primitive types that are written. Then we have floating-point numbers, where you want to store fractions. And this is not an integer. Float is 4 bytes, and for double precision, double is often used. Int is used for everything, and for floats, double is used directly because memory is cheap today, and there is no need to rack your brain. And you will then see how changes happen when using small-sized numbers, primitive types, after arithmetic operations, what type it becomes. Then we have char, 4 bytes. See, char was 1 byte in C++, and in Java, char is 2 bytes. That's why this byte type was introduced so that a declaration and usage of one byte could be made in the language. If you don't have one byte in the language, it's a bad language, right? What is the introduction of one byte? How can I not be able to do it? And at the end, we have boolean, which occupies one byte. Well, not exactly, but of course, now you can store boolean in one bit, but in memory, we can use at least a byte. We can't use bits. You can do something with bit manipulation, but this is the rule. These are the default values, which are assigned to class fields if no value is assigned, and to empty array cells. Otherwise, there are no default values. And these are the default values. These are numeric, simply zeros. You can call these values, meaning literal values. In literal values, you can write L at the end to indicate that it is a long primitive type, or for float, you will need f or F, and for double, you can not write it, meaning a non-integer notation automatically implies a double type. If you want, you can write D, or not. And this is the range. See, the range also includes negative numbers. We have signed variants, or we don't have unsigned variants. All primitive numeric types are of this type. They decided not to rack their brains. Then they introduced, well, there are some variants, some ways to use it as an unsigned type. These are bit manipulations and things like that. And there is some way, as far as I remember, to use long as some kind of unsigned, but these things are not related to primitive types. These things seem like a workaround if you need them, but most likely you won't need them. Unsigned variants. Well, look at the character. Here you can describe the character's value in Unicode form, in hexadecimal form. This is a literal value. So, rules to remember regarding primitives. I've said some of this partially. You can look at the rest yourself if I miss anything. I don't want the lecture to take too much time. This is an example of primitive types and their usage, including arithmetic operations. And now, look at this example of casting. x and y are ints, and during division, of course, here too, an operation on int returns int, no matter what you do. That's why, to avoid losing the type, casting to double is done manually here. And this double will then be divided by int and give you a double. Here too, there are examples of casting. If you cast a double to int manually like this, you will lose this part, truncation, meaning cutting off. Now, wrapper types and autoboxing. Every primitive type in Java has its own, what do you call it, class type, object type. For example, int has it, but mentioned by its full name, meaning Integer. And we have a class named Integer which stores, of course, the int value that this class wraps. Now, why are these classes useful to us? Remember, char is char, and we will have a class named Character. If you want to use it, and so on, and so on. Float, double, boolean. Why are these classes good? In classes, we can describe fields, constant fields, static fields, static methods. And these wrapper classes are not only used to store some value inside, but they also give us some capabilities. For example, we can make the numeric value nullable. For example, someone might not enter input, and now, in the case of a primitive, we know that you cannot enter null for a primitive value, you have to enter something. And in this case, we can make all object types nullable. Everything can be made null if it's an object type. So be careful, always write null checks in if statements where you don't want someone to pass null. Also, we can, for example, read the max value from Integer, max value, min value, byte size, int size, long size, and so on. And we also have this static method that converts to binary. Here you indicate this 10, and it will give you a string for this 10, for example. This will also return one zero one zero in binary. Like this. And so on. So, the main uses are these. Of course, object types are heavier than primitive types. Because, first of all, primitives are stored on the stack, these are stored on the heap. And besides the 4 bytes that an Integer would occupy, this will occupy more. Object type. Autoboxing and unboxing. What is it? When you use objects or their primitives, you will need to use methods for conversion between them, meaning wrapping. So, for example, using the Integer valueOf method to get from a primitive, or creating with the new constructor, or for example, calling intValue of an object to get the primitive type it stores. And so that we don't have to do this, they came up with something like this: let wrapper types and primitive types automatically convert to each other. For example, you can directly write that Integer 10 is equal to this Integer object, and you don't have to write any conversion. This is autoboxing, meaning automatically wrapping into an object type. And they also introduced the unboxing option. You can automatically assign an Integer to a primitive int, and its unboxing will happen. That's all. They introduced this so that we developers don't have to write a lot of things manually. And remember, Java does not perform excessive automatic conversions between types. This is introduced. It does this, but some if conditions, like -1, are impossible in an if block, in a while loop, where a boolean is needed, we must pass a boolean. No conversions from boolean to int, and so on, happen. Here are notes regarding autoboxing and wrapper classes. Well, you can look at this yourself. In short, the key points are here. An example is given, and we partially saw an example ourselves. Now let's look at primitive type operations, arithmetic, and promotion. Promotion means converting types to other types as a result of an arithmetic operation, or rather, bringing the result to some type. And this right image clearly shows what happens to types participating in arithmetic operations. If the participating types are these, it doesn't matter if char is with char, or short with short, or byte with byte. Char is a unique type that you can use as an integer, and you can assign a literal value to char, and conversion will happen automatically. Char is such an exception. So, an operation of short and short will give me int. Byte, short, and char, meaning between them, three, meaning an operation of two numbers, right? Arithmetic, let's say, gives you a byte result. Result equals byte plus byte. It will be a compilation error. Remember this. Any arithmetic operations between bytes, shorts, and chars, even with themselves, will result in int. And the rest are understandable, right? For example, long plus float will give float. Int plus long is the same as long plus long. In fact, the final conversion and then the arithmetic operation happens logically. So, it will convert to a larger bit size, and then the arithmetic operation happens. Here too, there is an example. Here, bitwise operations are also shown. Look at working with char. So, you can add an integer to char, and since the operation of char and int will give us this, remember this for a moment. I will pause. You can look at this. Okay, friends, let's continue. These are the key points regarding promotion of primitive types. Now let's move on to another topic. This is String, meaning the string type. And String is used very often, more than other types, even object types. Compared to other types, String, an object type, is used more often. And let's see what String pool is. In Java, they said something like this: String is an object, but you can describe its value like this, a literal value, it's usually in red in IntelliJ. But they also said something like this: String is an immutable object. Immutable means that if you change a String, for example, you don't change this object, but you get a different String. That's the concept of an immutable object, so to speak. And these Strings created uniquely with quotation marks, let's store them uniquely in a specific memory on the heap, which we will call the String pool. So, let's store these Strings here, because these Strings are often used in many places. For example, in a text input field, this String can be used in many places in the same application, and why should I create it? Why should I waste memory for the same String unnecessarily? So they came up with something like this: let's make String immutable and create a String pool, and store all Strings described like this with quotation marks in this String pool. If you want to store Strings separately in the heap, not uniquely, in separate memories, you can create them with `new` like this, as an object. This String, and this "Hello" will then be outside the heap memory. Well, the example here is a bit mixed up. "Hello world" should be written here. Well, here, just on the picture, it should be written a bit differently. In short, if you create "Hello world" with `new`, it will be created outside the String pool. There won't be a unique instance anymore. And memory comparison is done with double equals. So be careful. In the case of object types, double equals is memory comparison. And here, s and this, since they point to the same entry, double equals will return true, of course. But this, this will be outside, its memory will be different, and theirs will be different. And therefore, S3 will not be equal to S2 or S1. The memories will be different. If we want to compare them character by character, meaning compare the values of Strings with each other, we can use the `equals` method inherited from the `Object` class and overridden and implemented in String. Or `equalsIgnoreCase` if you want to ignore case during comparison. In this case, this one and this three are equal in value. So, remember, always compare Strings with the `equals` method, never with double equals, because if this String is outside the String pool, it will give the wrong result, of course. If they are equal in the pool, these Strings will be equal, but still, to be safe, you should always compare Strings with `equals`. And one of the nuances, which is not shown here, is that any manipulation of a String like this fills the String pool with newly created unique Strings. So, for example, if we have a logic for building a String with a for loop using the plus equals operator with concatenation, then those intermediate Strings built by concatenation will be placed uniquely in this String pool, and the String pool will be filled with unnecessary intermediate Strings. What should we do in that case? For that case, there is a helper class, `StringBuilder`, which, with its methods, mainly the `append` method and various other methods, allows us to build a String in such a way that the intermediate Strings that the builder would give us, if it weren't a normal class, will not be placed in the String pool. Let's write a practical example of `StringBuilder` now to get used to using it. And there is a similar class to `StringBuilder`, `StringBuffer`, which contains the same methods and is used for the same purpose, but it is simply thread-safe. And we will see what thread-safe means later when we go through threads. Here I will create a project in IntelliJ, I will name it lesson 2, and its project will be Maven. Be sure to get used to working with Maven projects. And the group ID, meaning the project family, will be `com.example`, and the artifact ID, meaning the project name, will be `lesson 2`. And IntelliJ will automatically assign version 1.0-SNAPSHOT. You can change this version later. I don't always like this SNAPSHOT. I'll change it a bit like this. Wait a bit for it to load here and for indexing and other things to happen by IntelliJ. Indexing means figuring out, say, what JDK you are using, what version of compilation you want, how to look at the code, what kind of project. So, this information, loading all of this, is called indexing. Now, in the `src/main/java` folder, I will create a launcher class, or let's call it `StringDemo`, and always work within the `example` package and create a sub-package within this package. Here I will now do `public static void main`. It's suggesting something, but I don't want it. [Music] And you, we can, for example, consider an example of `StringBuilder`. I will introduce a class with your permission. Let's write classes separately in separate Java files. And Person will have some trivial methods. Now, this AI, since I've created this class a million times, understands what I want to do. Now it didn't understand here. If I click here, it understands that I want a constructor, and it might understand getters, but I'll do getters for all of them with Alt+Insert. And at the end, let's override the `toString` method as well. It automatically suggests that I override this. And then we will also go through the idea of using the `toString` method. And let's also override `equals` and `hashCode`. Let's ask the generator to generate these methods for us. Overriding means changing the body of methods inherited from the parent. In Java, overriding happens directly. The method has the same name as the parent's method. There is no `@Override` annotation. And we don't know about `hashCode` yet. We haven't used `hashCode` for anything so far. We will see what Java uses it for. Other libraries. And the comparison logic is exactly this, and it compares automatically using both fields. And for `hashCode` too, it will use both fields. `HashCode` simply returns some unique value, some integer, using some logic. This is a helper static method of the `Object` class, which we use to get a unique integer using both these fields. And the methods are very simple. `Equals` is also simple. This overridden method is a trivial `Person` class. Let's make these trivial methods smaller with Shift+Minus so they don't strain our eyes. And let's introduce an array of Persons like this. And let's tell it to fill with random information, say 10 people, use celebrity names and ages. The AI assistant will use it and insert some result here. And I can click, or rather, I think I need to click Enter. It's been working for a second. Yes, I think. Oh, yes. In short, this insertion, I just don't remember how to tell it with Tab that everything is fine and leave this code. Then at the end, let's put a comma here. I have the right to do that in an array. And let's just print these people here. And since I've overridden `toString`, the result will be printed. But what I want, for example, imagine that we want a method that will print this array nicely, and let's say we don't know `toString`. For example, we don't write this for loop, but directly try to pass it to `println`. And most likely, we know from the first lesson that in the case of an array, if `toString` is not overridden, it will print something like this. And we should use the static method `Arrays.toString` from the `util` package. Let's pass this to it, and it will give us a human-readable string. But let's do it manually. Let's write this `toString` method here, for example, `public static String` and let's call it `peopleToString`. And let's write `peopleToString` manually, meaning, let's write it crudely. So, `String result` is a bad option. And now the AI has given me something. And here it suggests using `StringBuilder`. IntelliJ tries to suggest something good in the form of warnings. Yellow color is always a warning. And now, if we call this, let's see what we get. So, this is like a list. We can also write some other logic here to wrap it in cubes and so on, but let's leave it for now. Now, what is the problem with this method? See, for example, for five, many new unique strings will be obtained through these operations, right? For example, if we get it with String, of course, a new string will be created. This will fall into the String pool for each person. This `toString` here. Also, the new string obtained by concatenation will fall here. Well, this will also fall here. But this is just one character, this is one byte, or two bytes, depending on what characters are used in the String. Nothing, that's not a problem. But these are the problems, that is, 1, 2, 3, 4, 5, then a newline, then another five, meaning 10 extra strings have fallen. And then plus their variants. This with this, this with this, and this with this, and then a newline. And in short, a lot of intermediate strings will fall into the String pool. Instead of this, it's better to use `StringBuilder`. And `StringBuilder` is an object. So, remember, everything in Java is an object, except for primitive types, arrays, Strings, and so on. This `StringBuilder` is also a class, whose object we create with `new`. And how do we use it? It has an `append` method, meaning concatenation. If you want to add something to the end, you can use `append`. And these methods are chaining methods, meaning you can call them on themselves. So you can do `append` again after calling it, and so on. This is how they write it, so that it's visible that I did this first, then this. `append`. And it stores these characters in its internal array, as far as I remember. The builder stores characters of the appended string. And then, of course, at the end, this is of a different type. I want to return a String, and I can't return a `StringBuilder`. I have to return this `toString` call, which will give me the assembled single String. And only that will fall into the String pool. These intermediate options will no longer be there, because I store these intermediate options in a byte array. Now, if we run it, we will get the same result. So, you should also use this AI. But use it only for trivial operations, say, for creating demo persons, and so on. Now, let me pause for a moment. Okay, let's continue. This is, in short, the key points related to this. Now let's move on to the theoretical part. Well, here is an example that we discussed, String and String pool. Now let's move on to OOP. This is already a comprehensive topic. Now we know the principles of OOP: encapsulation, polymorphism, inheritance. But what is abstraction? What do we need? We know three principles. They added abstraction. And it simply means that when you have some fields declared in a class, which you can abstract separately and describe in a class, and use that class, it's better to do so. For example, in Person, we can add an address here. Let's comment out this option, bring this back, and in Person, let's add an address here, for example. I'll show you abstraction. What should I do? Here, someone can add, say, `addressName` or `streetName` and `number`. And as you can see, this `streetName` and `number` are both parts of the address. And it's not better to extract this into an `Address` class and then use that `Address` class here? One option is because, well, something like `streetNumber` could be added, like zip code or something, postal code, and it will grow. And it has nothing to do with Person. It's better to group it separately. And that's exactly abstraction, meaning to extract it separately and group common logic and fields into one class, let's call it `Address`. And let's describe it here simply as `private`. Now, look at this AI assistant. It understands that since I used these words `streetName` and `number` elsewhere, and I'm writing `address` here, it thinks that it should also give me these fields. But it made a mistake. I want to use short names here, because this class `AddressName` and `Number` already indicates that. Now let's write these getters. It's annoying when it happens. If it annoys you, you can disable this AI assistant plugin. From these plugins, I recommend disabling it from settings if you're not lazy to write. And the second type is this Juny, which is an AI agent. You can also disable this. This is for physical manipulations. And here, similarly, I will override `equals` and `hashCode`, and if `toString` is also overridden, like this, automatically using the generator. If it's not complex logic. And here I will introduce the `address` field. And since I introduced `address`, all of this needs to be regenerated. The constructor also needs to be like this. Getters also need to be for `address`. Well, you can introduce it only for `address`, but you would have to change `equals` and `hashCode`, and also `toString`. And simply, I move like this. So, we will also have a problem in `main`. Meaning, in `main`, we will have to add addresses here. Now I will tell the AI to, say, use the `address` object as the last constructor argument and use random address names and state and numbers. It gives me some options. Except that it says to press Enter, meaning I made an exception, and it gave me some address. And if we run this now, we will get this result. So, don't be discouraged. At first, I thought this AI was useless, that no one would need it, what's happening? And this AI tool simply allows you to quickly do these small things. Otherwise, for monumental tasks, for complex logic, you can never, or rather, you can bring something to some level with these AI tools, but still, unfortunately, there is a probability that it will generate something with an error, and it will always be necessary for a person to at least describe the task. Human involvement will always be necessary. Now, regarding constructors and `this`. We have seen constructors, but we haven't seen the use of `this`. What is `this`? `this` is a keyword that refers to my own object, which will be created from within the class later. That is, I refer to myself. And I can use `this` like this, with parentheses, meaning I can call my own constructor from other constructors. But there is a rule. This call must be the very first call. If I write it as the first command in the constructor, and then the rest, I can write them below. That's the rule. Meaning, for example, if I call the parameterless one, I can call my own parameterized one, and simply write some default values here, say, unassigned. Or, for example, in the second constructor, which is two-parameterized and not three, I can call this three-parameterized one again, and simply pass an unassigned default value in the third one, and leave this as it is. And as I told you, remember, the parent's parameterless constructor is called by default. Even if you don't write it, meaning at the beginning of the constructor, you can write either calling another constructor via `this`, or calling the parent's constructor. If you don't write it, it will be written. If you write it, the corresponding one will be called. So, in the end, `super` is always called. You see, if I call others, and others call others, `super` is still called. A chain reaction will happen. When I create my object with `new`, the constructor of the first ancestor, so to speak, will be called, then its child, then the descendant's descendant, and then I come to my constructor. This is the idea of this chain reaction of constructors, so to speak, of initialization and calling. This works similarly in C++. These are the rules, remember them. Now, what does method overloading mean? Similarly, you just need to remember the rules. The easiest way to remember is like this: method overloading means describing a method with the same name and different parameters. Of course, within the class block, you can't describe it separately outside. For example, I can describe it for two parameters and describe a method with the same name for three, or for doubles. The number of parameters or the types of parameters must be different. And this already implies overloading. This is it, in short. Well, remember these rules that are written here, definitely. And these key points. Now, how to use `super` with overriding. Inheritance, meaning how can I describe myself as a child class? For example, we have `Animal`, and if I want to describe a child `Animal`, I can do it with the `extends` keyword. And in Java and other human languages, like C#, we have single inheritance. You are a child of someone, you cannot be a child of two or more classes, because this creates problems. A method inherited from one class, or a field, if it also comes from a second class, which one should I use? Which one can I override? And such problems arise. In Java, C#, and other human languages like PHP, we have single inheritance, and that problem is gone. Why is it called `extends`? Because in reality, when you describe a child class, you extend the parent, meaning you expand the parent. The parent has three members described within the class: fields, constructors, and methods. And all of this is inherited by the child as well. And the child expands the parent's part, adds its own methods, fields, constructors, or, for example, changes the body of what was inherited from the parent, or overrides it. That's why it's called `extends`. In C#, a dot is used, and the `extends` keyword is also used in other languages. Java, for example, imitated JavaScript, where in new JavaScript classes, we use `extends`. And in TypeScript, the word `extends` is also used as an indicator of the child class's so-called expansion. Now, how can a method be overridden, meaning inherited from the parent? For example, if we inherit `makeSound` from the parent, and in the child, I don't like this `makeSound`, or rather, it makes a sound, I want it to say something else. How can I override it? I can describe a method with the same prototype, meaning with the same name, with the same access modifier, and the same return type, within myself. And this already means overriding. So, the word itself implies that something is there, and rather, override your version of something. And remember, you can also increase the access modifier during overriding. For example, if it's `public`, let's write it like this. You can increase the access. Polymorphism. In this case, it will not be violated. What is polymorphism? Looking at child objects through the parent's reference and acting as if I am treating it as the parent. For example, treating any of its descendants as if I am treating `Animal`. And since the parent's version sees such a thing and works in a protected environment with this method, of course, it will also work in the public environment from where the code is visible. But the other way around is not possible. You cannot decrease the access modifier during overriding. For example, if it's `public`, you cannot make it `default` or `private`, because then you won't be able to see it through the parent's eyes and use this method as if it were `public`. And polymorphism will be violated in this case. Now, we have seen overriding. We have mentioned the rules. And `super` keyword. One moment, folks, I'll pause. Let's continue. We were talking about the `super` keyword. Meaning, with `super`, I can call parent's things. For example, of course, if the parent's part is visible, it is not necessary to use `super` to refer to that method. Meaning, if I have overridden it, and I want to call the parent's version, how do I indicate that I want to call the parent's, not my overridden method? You have to write this keyword. `super`. In C#, it's called `base`. In C++, it's also like that. But here it's called `super`. And in JavaScript, it's `super`, and in TypeScript, it's also `super`. Now, annotations. What was this dog-like thing? What is it? This dog-like thing, and this `@Override` is an annotation. And remember, in Java, we have primitive types and class-like things, so to speak. And this is also a class-like type, an annotation, which is described in its own way. We will go through this. But what is the purpose of an annotation? An annotation is like a sticker, meaning it carries additional information about code elements. So, for example, you can attach a sticker somewhere, and you can also attach it to a method, to class descriptions, parameters, fields, constructors, at the beginning, it doesn't matter. You can write it like this. It's usually written like this, it's more visible, as if it's attached to the top of this method. And this `@Override` annotation is used so that the compiler checks if you have overridden correctly, because we no longer have `@Override` for the compiler to correct it like that. And to check if I am overriding. I might accidentally write a small 's' here and think that I have overridden, but with a small 's', I have introduced a new method. Therefore, for the compiler to check that I have indeed overridden, and for it to be visible to me that this is method overriding and not my own introduced method, they write `@Override` on top. That's all. This `@Override` keyword, in short. The `toString` method, we have also discussed. Meaning, the `toString` method is described in `Object` and gives us the right to override it in any class. And stringifying an object is a frequent operation. Because it was frequent, it is described in the `Object` class. So that every class can then inherit it and override it as it wishes. For example, `Person` is good for this. I have written here that it is good for debugging. And it is automatically called if a String is concatenated with an object in operations, say `hello` + `person`, meaning the String of `person` is appended to this `hello` String, and a new String is obtained. And also, if you pass it to `print` methods, `println`, `printf`, and so on, it will use the `toString` of that object directly. It will print that to the console. Now, the `FINAL` keyword. One moment, I'll pause again. Okay, now we have reached the `Final` keyword. And the `final` keyword is simply like the `const` keyword. In Java, they said that the `const` keyword should be reserved for future use, but it is not used for anything to this day. And the `final` keyword is used similarly to `const` with local variables, but you can also use it with parameters of a method, because a parameter is ultimately a local variable of a method. With class fields, with class declarations, and with class methods. These are the places: one, meaning in three or four places: with fields, with variables, with local variables, with classes, and with methods. Now, when you use it with local variables or parameters, the rule is this: a `final` variable must be assigned a value only once before it is used somewhere, and you cannot change it. Meaning, before use. Here, where we use it, I can assign a value somewhere above, before using it. If I assign it and change it, it's a compilation error. And if I don't assign it and use it, well, of course, if a local variable is undeclared, it also causes a compilation error. But let's look at other places. With fields, if I write `final` here, what does it force me to do? It forces me to assign a value to it only once when the object is created. For example, I can write the value nine here once. But if I don't write the `final` assignment here once, then during object creation, what parts do I have where I can assign a value to it at least once? Constructors. Meaning, I can assign a value in constructors. If, of course, the constructor doesn't call other variants with `this`, it must assign a value to this `final` once. Meaning, since these two constructors are independent of each other, they don't call each other, therefore, both will have to assign some value to `final` once. Let's add `this` here for clarity. `this` is not necessary because it already understands what it refers to. This `workingHours` refers to the compiler. Meaning, the compiler, which will assemble the bytecode for us from this, which will then be executed, is our compiler. And when we call it, it will say an error, it will understand that we mean the compiler. Now let's see what happens with methods. If I click on a method and not click, but write this `final` keyword. What does this mean? Well, logically, you will only have this variant of the method, meaning a child cannot override it. That's all. And `private` methods are approximately similar to `final` methods. Meaning, `private` is also something that a child cannot override, because first of all, it cannot see it, and it resembles some variants. In short, they call these `final` variants. These `private` methods. And if you write it with a class, the logic is the same. If you write it with a class, that class cannot be extended by someone else. For example, String is such a class. Look, if we go into String by Ctrl+Click, we see that it cannot be extended. Similarly, System is also `final`. Also, `StringBuilder` was `final`, meaning no one can write their own `String` variant and then change some methods of String. This is good, but it's also bad sometimes. For example, in C#, we have extension methods. If you want to add your method variant to String, so to speak, in quotation marks, they are called extension methods. Kotlin also adopted them later, but unfortunately, not in Java. Meaning, I cannot add some methods to closed `final` classes from the outside to simplify life. Now we have reached varargs. And varargs is similar to other, well, in C++, for example, and it allows us to describe an array with three dots in a method parameter. When using it, we can pass multiple arguments separated by commas when calling the method. And remember the rules. The only rule is that varargs must be the only and last parameter declaration in a method. Meaning, you can use only one varargs in the method declaration, in the parameters, and it must be at the end of the parameters. And it's the same as an array. And the third rule is that you can pass an empty list, meaning nothing in that place when using varargs. Or you can pass two, meaning one or more arguments separated by commas. Look, for example, I have a super flexible `sum` method that works with an array of integers. This ultimately, this `numbers` is still an array. This `varargs` is still an array. And look how it works during the call. You can simply pass it like this, without passing anything, and you will have an empty array. In the empty case, the total will be zero. Or, for example, pass one, pass three arguments like this, four arguments, like this. Meaning, it beautifies the usage. Otherwise, you can't use varargs everywhere, right? Because it has its own rules. It can only be one, and it must be written at the end of the parameter list in the method. Therefore, it has a peculiar, somewhat limited usage. Now, static and instance blocks. This is the last topic of our today's lesson. Well, instance initialization block. Let's write it like this, instance initialization block. Now, in the case of this initialization block, let's consider static first, it's the easiest. These initialization blocks were introduced in classes and class-like things, and they said, let's allow running some code with this block. Mainly for initialization logic. And a static init block is written like this directly in the class, wherever you want, but they are executed sequentially. One or more. And inside, code is mainly written that initializes some static field. For example, this one assigned 100, and then the next block increased its value by 50. And the static init block will be executed when the class is represented in memory, when the information is loaded. At that moment, once, and that's it. Like these static fields, static fields and methods are loaded when the class is loaded. When the JVM runs our application, it needs to understand who this class is, who the second class is, what it has, what fields it has. At that time, when the information is loaded into memory, static things and everything, the declaration will happen, and then these static init blocks will be executed once. That's all. Well, in the static case, it's very simple, right? Nothing here, just executed sequentially. If it's written above, this will be executed first, and then this. In the case of static blocks and init blocks, this sequence is essential. And we have a second option, non-static, meaning instance initialization block, meaning instance, meaning class initialization block, non-static. And it carries a similar idea, but it's executed differently. You can introduce one or many blocks of init, instance initialization block. They will be executed sequentially, first the upper one, and then the lower one as described in the code. And when will it be executed? It will be executed after the call to `super`, meaning before other logic of the constructor is executed, this init block logic will be executed. And the idea is the same, to assign some value to an instance field of my class, meaning a field of my class. For example, assign 10, and then increase it by five. Now you might think, well, now the use of this static init block, it can be used to run some code once when the application starts. You can write it in the init block and it will be executed. But for instance init blocks, we can't easily think of a use. Yes, in 90% of the code, you will not encounter these init blocks at all, neither static nor instance. You will see static more often than this instance init block. And in reality, after compilation, the instance init block does not exist. Let's do a demonstration of this. Meaning, it's in the syntax before compilation. And after compilation, it's as if it didn't exist. Let's do it for Person. Let's go into Person. And here, or for example, let's do it for Address. Let's do it for Address. Meaning, let's write it directly like this. And here, the default value, well, let's write `System.out.println("Initializing Address")`. And let the `name` and `number` be zero. And this will be executed before running this code. Imagine that this is pasted here. And in reality, that's what happens after compilation. So, `super`. Now, `super` is written by default here. This will be pasted after `super`, and before my code. Like this. And let's see. Let's introduce a second block, for example, `initializing default name` or whatever. And the second one will be `default number`. Let's bring this down here. And you can also make these blocks smaller if they strain your eyes, but I usually leave them. And when we run this now, it will be printed, of course, how many times it will be printed for each object, meaning how many times you make the constructor call when creating an object. So, it will be printed 10 times, right? 10 times, one, two, one, two, consecutively. First for the cruise, then for the cruise's address, and so on. And let's look at the code. Let's look at the bytecode. The bytecode is in the `target/classes` folder, in the compiled classes, in the corresponding packages, in the class files. And let's click on Address twice. And let's use the decompiler. The decompilation of Java bytecode is a simple process. Therefore, don't write sensitive information in Java bytecode, because decompilation, or rather, the operation of decompiling bytecode is simple. If I click on Address, where did the init blocks go? They didn't go anywhere. What was written in the init block, the compiler wrote it in the constructors, after the call to `super`, and before my code. Like this. If I had a second constructor, it would also paste it there. Let's try to introduce a parameterless constructor for Address and see what happens. Parameterless, like this. And let's run it again. Well, it will be compiled. You can compile separately here with this hammer. With `build`, simply click this hammer, and it will only compile, not run. And let's click on this again, twice. See what was in the block? It's also pasted here. And the same here. Meaning, the init block is the same as if we had written `copy` at the time of compilation. In the init block, at the beginning of these constructors, meaning, we might encounter these static elements two or three times. Now I'll pause for a bit. Let's continue. Let's continue, and this is finished, or rather, we have reached the end. And here are practical examples written. First, of course, I will color this, and I will put these slides like this tomorrow, around tomorrow morning, for example, because it's already late. And I want you to just look at these examples, remember these rules. It seems like we covered a lot, but if you think about it, if you know another language, you'll realize that it's nothing special, because other languages have more features than Java. You have less to learn about primitives in Java, but as you can see, a lot to write. Thank you very much, folks. Goodbye for now.