Files
codejava.tech/public/en.search-data.json
me 40407fcabc
All checks were successful
Deploy / deploy (push) Successful in 11s
update
2026-04-22 09:02:23 +03:00

1 line
66 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"/courses/prog-intro/homeworks/sum/":{"data":{"solution#Solution":"After reading about String, Integer, System.err we now know about some usefull methods:\nInteger.parseInt(String s, int radix) which parses the string argument as a signed integer in the radix specified by the second argument. So it basically converts a number from the String data type to int. Character.isWhitespace(char ch) which checks if a character ch is a space symbol or not. Now lets start coding. Firstly lets define the structure of our program. I will be putting the name of the file and its path (if its nessesary) at the first line comment.\n// Sum.java public class Sum { public static void main(String[] args) { // ... } } Okay thats done. What do we do next? Lets look at String[] args argument to our main method. It represents an array of command line arguments which we need to sum. So now we made our task a little bit easier. Now we can just say that we need to find sum of elements of array args.\nHow are we going to do it though? First lets understand what we can do with this array. Lets modify our class a little bit.\n// Sum.java public class Sum { public static void main(String[] args) { System.out.println(args.length); } } Weve added System.out.println(args.length) which takes the field length from our args object and prints it to the console.\nLets try it. First compile our class using\n$ javac Sum.java And then we can do some manual testing.\n$ java Sum 1 2 3 3 We got 3 as an output as expected. We gave our program 3 command line arguments: 1, 2 and 3.\nHere are all of the examples\n$ java Sum 1 2 -3 3 $ java Sum \"1 2 3\" 1 Note\nNotice, that we got 1 instead of 3. Thats because we put our arguments in \"\" so this becomes a single string argument.\n$ java Sum \"1 2\" \" 3\" 2 $ java Sum \" \" 1 Note\nHere program gives us 1 instead of 0, because despite not having any numbers in the arguments a single whitespace is still an argument.\nNow lets try not only to count our arguments but to list them as well. Lets modify our program a little bit more.\n// Sum.java public class Sum { public static void main(String[] args) { System.out.println(\"number of arguments: \" + args.length); for (String argument : args) { System.out.println(argument); } } } Here I used for loop to do printing for every String element in args.\nLets try this with our examples. And dont forget to recompile using javac Sum.java.\n$ java Sum 1 2 3 number of arguments: 3 1 2 3 $ java Sum 1 2 -3 number of arguments: 3 1 2 -3 $ java Sum \"1 2 3\" number of arguments: 1 1 2 3 Note\nAgain, notice only one string argument.\n$ java Sum \"1 2\" \" 3\" number of arguments: 2 1 2 3 $ java Sum \" \" number of arguments: 1 Okay, now that we know how to iterate (or do something for every element), we can calculate the sum of all the numbers in the args array. To do that we need to create a new variable sum of integer type and assign it the value of 0. Then for every number in args we will add it to sum, and so by the end of array we will have the sum of all numbers stored in variable sum. This is what the code will look like\n// Sum.java public class Sum { public static void main(String[] args) { System.out.println(\"number of arguments: \" + args.length); int sum = 0; for (String argument : args) { sum = sum + argument; System.out.println(argument); System.out.println(sum); } System.out.println(sum); } } Seems ok. But lets test it.\n$ javac Sum.java Sum.java:9: error: incompatible types: String cannot be converted to int sum = sum + argument; ^ 1 error We got a compilation error that says String cannot be converted to int. It means that when we try to add 2 variables sum and argument their types dont match. The type of sum is integer and string for argument. It seems pretty logical because what do we expect when adding for example 1 and apple?..\nImportant\nSo we need to cast argument to integer data type so that we can add it to sum.\nWe can do so using Integer.parseInt() method.\nIt takes a String s and returns an int, which a string s represents. For example this code will work as expected\n// ParseIntExample.java public class ParseIntExample { public static void main(String[] args) { String s = \"123\"; int sum = 321; //! int result = s + sum; } } The commented out line (//!) would have given us an exception.\n$ javac ParseIntExample.java ParseIntExample.java:8: error: incompatible types: String cannot be converted to int int result = s + sum; ^ 1 error Important\nPlease note that if we change result data type to String, the code will work just fine.\n// ParseIntExample.java public class ParseIntExample { public static void main(String[] args) { String s = \"123\"; int sum = 321; String result = s + sum; System.out.println(result); } } $ javac ParseIntExample.java \u0026\u0026 java ParseIntExample 123321 In this case sum will be automatically casted to String and two strings will just concatenate.\nSo we figured out that we need to cast argument to integer type. Lets change our code and test it.\n// Sum.java public class Sum { public static void main(String[] args) { System.out.println(\"number of arguments: \" + args.length); int sum = 0; for (String argument : args) { sum = sum + Integer.parseInt(argument); System.out.println( \"current argument: \" + argument + \", current sum: \" + sum ); } System.out.println(\"final sum: \" + sum); } } $ java Sum 1 2 3 number of arguments: 3 current argument: 1, current sum: 1 current argument: 2, current sum: 3 current argument: 3, current sum: 6 final sum: 6 $ java Sum 1 2 -3 number of arguments: 3 current argument: 1, current sum: 1 current argument: 2, current sum: 3 current argument: -3, current sum: 0 final sum: 0 $ java Sum \"1 2 3\" number of arguments: 1 Exception in thread \"main\" java.lang.NumberFormatException: For input string: \"1 2 3\" at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67) at java.base/java.lang.Integer.parseInt(Integer.java:662) at java.base/java.lang.Integer.parseInt(Integer.java:778) at Sum.main(Sum.java:9) As we can see because we have only 1 argument (\"1 2 3\") we try to parse it as an integer but \"1 2 3\" contains empty spaces, so it cant be parsed as an integer.\nWe need to manually extract numeric values from the argument string.\nIn order to do that we will iterate over each symbol of the argument string and extract numeric values.\nWe will have another variable number of String type in which we will be storing extracted numeric values.\nFor every character or symbol in the string, we will check it to be an empty space or a digit. If it is a digit, we will add/concatenate with number, and if it is an empty space, we will add the numeric value of number to sum and then assign empty string to number because we reached the end of the current number and are ready to move to the next one.\nImportant\nIt is important to add numeric value of number (if its not empty) to sum after we iterated over the entire string because if it ends like this \"1 2 3\", we dont nessesarily have an empty space at the end so \"3\" (in this case) will be stored in number but NOT added to sum.\nLets implement those ideas into solution. We will get rid of all the println messages so its not drawing our attention.\nNote\nAlso I want to point out that we cant just iterate over String. First we need to turn it into an array of characters using String.toCharArray() method.\nNote\nWe will be checking for an empty space using Character.isWhitespace(char ch) which outputs true or false accordingly.\n// Sum.java public class Sum { public static void main(String[] args) { int sum = 0; for (String argument : args) { String number = \"\"; // we will form numbers here for (char c : argument.toCharArray()) { if (!Character.isWhitespace(c)) { // if character is not whitespace number = number + c; // concatenate new digit } else { // if character is whitespace if (!number.isEmpty()) { // if number is not empty sum = sum + Integer.parseInt(number); // add number to sum } number = \"\"; // empty the number } } // check for any remaining digits in number if (!number.isEmpty()) { // if number is not empty sum = sum + Integer.parseInt(number); // add number to sum } } System.out.println(sum); } } $ java Sum 1 2 3 6 $ java Sum 1 2 -3 0 $ java Sum \"1 2 3\" 6 $ java Sum \"1 2\" \" 3\" 6 $ java Sum \" \" 0 This code works and passes all the tests!\nBut can we improve it? One efficiency improvement we can make is to get rid of concatenating strings. You can read why its bad here or here.\nWe can use StringBuilder because it can build strings more efficiently.\nSo heres the updated code\n// Sum.java public class Sum { public static void main(String[] args) { int sum = 0; for (String argument : args) { StringBuilder number = new StringBuilder(); // we will form numbers here using StringBuilder for (char c : argument.toCharArray()) { if (!Character.isWhitespace(c)) { // if character is not whitespace number.append(c); // concatenate new digit } else { // if character is whitespace if (!number.isEmpty()) { // if number is not empty sum = sum + Integer.parseInt(number.toString()); // add number to sum } number = new StringBuilder(); // empty the number by creating new empty StringBuilder } } // check for any remaining digits in number if (!number.isEmpty()) { // if number is not empty sum = sum + Integer.parseInt(number.toString()); // add number to sum } } System.out.println(sum); } } Important\nNote, that we cant just do Integer.parseInt(number), because number is an instance of StringBuilder, not just a regular String. In order to get the current state of the string we can use StringBuilder.toString() method.\nNote\nAlso its worth mentioning, that we can shorten operations like thse\narg1 = arg1 X arg2 to\narg1 X= arg2 where X is some operation like +, -, *, / and so on.\nLets make this change to our code\n// Sum.java public class Sum { public static void main(String[] args) { int sum = 0; for (String argument : args) { StringBuilder number = new StringBuilder(); // we will form numbers here using StringBuilder for (char c : argument.toCharArray()) { if (!Character.isWhitespace(c)) { // if character is not whitespace number.append(c); // concatenate new digit } else { // if character is whitespace if (!number.isEmpty()) { // if number is not empty sum += Integer.parseInt(number.toString()); // add number to sum } number = new StringBuilder(); // empty the number by creating new empty StringBuilder } } // check for any remaining digits in number if (!number.isEmpty()) { // if number is not empty sum += Integer.parseInt(number.toString()); // add number to sum } } System.out.println(sum); } } This is it! The code is ready.","task#Task":"Task You need to create a Sum class which will sum integers from command line arguments and output the sum to console.\nExamples:\njava Sum 1 2 3 Expected output: 6.\njava Sum 1 2 -3 Expected output: 0.\njava Sum \"1 2 3\" Expected output: 6.\njava Sum \"1 2\" \" 3\" Expected output: 6.\njava Sum \" \" Expected output: 0.\nArguments can be:\ndigits, signes + and -, space symbols You can assume that int type is sufficient for in-between calculations and result.\nBefore doing the task make sure to read docs for classes String and Integer.\nFor debugging use System.err, because it will be ingnored by the testing program."},"title":"Sum"},"/courses/prog-intro/homeworks/sum/conclusion/":{"data":{"":"Here I just wanted to apply some of the best practises to improve our code a little better."},"title":"Summing Up"},"/courses/prog-intro/homeworks/sum/sumbigdecimalhex/":{"data":{"solution#Solution":"This is the hardest (and last) modification for this homework.\nLets modify our SumDoubleHex parseNumber method. This is what it looks now\nstatic double parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString(); if ( numberString.startsWith(\"0x\") || numberString.startsWith(\"0X\") ) { if (numberString.contains(\".\")) { return Double.parseDouble(numberString); } return Long.parseLong(numberString.substring(2), 16); } else { return Double.parseDouble(numberString); } } else { return 0; } } Lets make some small changes:\nFirstly, lets change the output data type to BigDecimal. So we will change this line\nstatic double parseNumber(StringBuilder number) { to this\nstatic BigDecimal parseNumber(StringBuilder number) { Also lets change this line from the parsing of a decimal number option\nreturn Double.parseDouble(numberString); to\nreturn new BigDecimal(numberString); We will change this line\nreturn 0; to\nreturn BigDecimal.ZERO; as we did almost the same thing for SumBigIntegerOctal modification.\nAlso lets change our numberString to lowercase letters just for convenience\nString numberString = number.toString().toLowerCase(); Note\nHere we are using String.toLowerCase() method that converts all the characters to lower case.\nSo our code will currently look like this\nstatic BigDecimal parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString().toLowerCase(); // toLowerCase() added if (numberString.startsWith(\"0x\")) { numberString = numberString.substring(2); if ( /* numberString has \"s\" in it */ ) { /* somehow parse number */ } /* somehow parse number */ } else { return new BigDecimal(numberString); } } else { return BigDecimal.ZERO; } } Note\nNotice, that we no longer need to check for numberString to begin with \"0X\". Since there cant be an upper case letter in the numberString after converting all of its letters to lower case.\nNote\nAlso in this line\nnumberString = numberString.substring(2); we strip off first two symbols (0x) in order for correct parsing later.\nLets solve our “comments” one-by-one.\nThe easiest is to understand if there is an \"s\" in numberString. We can do so using String.contains(CharSequence s) that returns true if and only if this string contains the specified sequence of char values. In our case the sequence of values will be \"s\".\nLets apply this logic to our method\nstatic BigDecimal parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString().toLowerCase(); if (numberString.startsWith(\"0x\")) { numberString = numberString.substring(2); if (numberString.contains(\"s\")) { // \u003c-- /* somehow parse number */ } /* somehow parse number */ } else { return new BigDecimal(numberString); } } else { return BigDecimal.ZERO; } } Note\nAgain, we are not checking for upper case \"S\", since there are no upper case letters in numberString because we used .toLowerCase().\nOkay, now lets parse our number if it doesnt contain \"s\". Its actually very simple. First we need to convert it to BigInteger and then pass the result to BigDecimal. It will look like this\n// using 16 to convert from hexadecimal to decimal // and only then passing to BigDecimal return new BigDecimal(new BigInteger(numberString, 16)); Important\nHere, we dont assign BigInteger object to any variable so it is used only for conversion from hexadecimal to decimal. After its value is passed to BigDecimal a garbage collector can come and destroy it, since there is no way to access it anymore.\nWe have only one “unsolved comment” left, but its going to require a bit of theory. So basically s splits our number in two parts: everything on the left is called a mantissa and on the right is exponent. So the form is basically the following\n0x{mantissa}s{exponent} 0x\\{mantissa\\}s\\{exponent\\} 0x{mantissa}s{exponent}In order to get the number we will first need to convert each part from hexadecimal to decimal. Exponent represents decimal places. For example 0xABs02: AB in hex equals 171 in decimal, 02 means 2 decimal places, so we shift point (.) 2 places left and get 1.71. Mathematically we can shift a point (.) by multiplying 10 with some power (in our case exponent). But to shift left we need to first negate the exponent so the point (.) shifts left (not right).\nSo the formula is\nmantissa⋅10exponent mantissa \\cdot 10^{-exponent} mantissa⋅10exponentOkay, we are done with theory, but how can we implement it in our code.\nFirst of all we need somehow to split our numberString by \"s\". To do that we need to get the index at which \"s\" in placed. To do that we can use String.indexOf(char ch) method that returns the index of the first occurrence of the specified character (in our case \"s\"). Okay, then using String.substring() we can split into two parts. Heres what its gonna look like\nint indexOfS = numberString.indexOf('s'); // all characters before 's', without including 's' itself String mantissaHexString = numberString.substring(0, indexOfS); // all characters after 's', without including 's' itself String exponentHexString = numberString.substring(indexOfS + 1); Note\nNote, that we use 's' (not \"s\") in indexOf method. Thats because it uses char data type as its argument, but \"s\" is a string. So we need single quotes in order to represent char.\nNote\nWhen extracting exponent we can use only one argument (indexOfS + 1), because it will automatically go to the end of the string itself.\nOkay, now that we have mantissaHexString and exponentHexString we just need to convert them into decimal. That we know how to do using BigInteger with base specifier.\nBigInteger mantissa = new BigInteger(mantissaHexString, 16); BigInteger exponent = new BigInteger(exponentHexString, 16); Okay, one thing left is to use the formula that we got\nmantissa⋅10exponent mantissa \\cdot 10^{-exponent} mantissa⋅10exponentI will write the solution first and explain it right after\nreturn new BigDecimal(mantissa).scaleByPowerOfTen(exponent.negate().intValueExact()); Here we are using BigDecimal.scaleByPowerOfTen(int n) method to basically multiply by 10exponent10^{-exponent}10exponent. Then we use BigInteger.negate() to basically multiply by -1. And finally we use BigInteger.intValueExact() to get the integer value. We do that because scaleByPowerOfTen method recieves integer argument (not BigInteger).\nSo our method will look like this\nstatic BigDecimal parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString().toLowerCase(); if (numberString.startsWith(\"0x\")) { numberString = numberString.substring(2); if (numberString.contains(\"s\")) { int indexOfS = numberString.indexOf('s'); String mantissaHexString = numberString.substring(0, indexOfS); String exponentHexString = numberString.substring(indexOfS + 1); BigInteger mantissa = new BigInteger(mantissaHexString, 16); BigInteger exponent = new BigInteger(exponentHexString, 16); return new BigDecimal(mantissa).scaleByPowerOfTen(exponent.negate().intValueExact()); } return new BigDecimal(new BigInteger(numberString, 16)); } else { return new BigDecimal(numberString); } } else { return BigDecimal.ZERO; } } Okay, now lets change main method accordingly. Lets take main method from SumBigIntegerOctal modification. Heres what it looks like now\npublic static void main(String[] args) { BigInteger sum = new BigInteger(\"0\"); for (String argument : args) { StringBuilder number = new StringBuilder(); for (char c : argument.toCharArray()) { if (!Character.isWhitespace(c)) { number.append(c); } else { sum = sum.add(parseNumber(number)); number = new StringBuilder(); } } sum = sum.add(parseNumber(number)); } System.out.println(sum); } All we need to do is to replace the sum type from BigInteger to BigDecimal. So full code will look like this\n// SumBigDecimalHex.java import java.math.BigDecimal; import java.math.BigInteger; public class SumBigDecimalHex { public static void main(String[] args) { BigDecimal sum = new BigDecimal(\"0\"); for (String argument : args) { StringBuilder number = new StringBuilder(); for (char c : argument.toCharArray()) { if (!Character.isWhitespace(c)) { number.append(c); } else { sum = sum.add(parseNumber(number)); number = new StringBuilder(); } } sum = sum.add(parseNumber(number)); } System.out.println(sum); } static BigDecimal parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString().toLowerCase(); if (numberString.startsWith(\"0x\")) { numberString = numberString.substring(2); if (numberString.contains(\"s\")) { int indexOfS = numberString.indexOf('s'); String mantissaHexString = numberString.substring(0, indexOfS); String exponentHexString = numberString.substring(indexOfS + 1); BigInteger mantissa = new BigInteger(mantissaHexString, 16); BigInteger exponent = new BigInteger(exponentHexString, 16); return new BigDecimal(mantissa).scaleByPowerOfTen(exponent.negate().intValueExact()); } return new BigDecimal(new BigInteger(numberString, 16)); } else { return new BigDecimal(numberString); } } else { return BigDecimal.ZERO; } } } This code works correctly and passes all the tests!","task#Task":"Task Input data fits into BigDecimal data type There are decimal and hexadecimal numbers in input Hexadecimal numbers has 0x prefix, so for example 0xbsc equals 11·10⁻¹² (mantissa and exponent are integers) Input is case insensitive Class should be named SumBigDecimalHex"},"title":"SumBigDecimalHex"},"/courses/prog-intro/homeworks/sum/sumbigintegeroctal/":{"data":{"solution#Solution":"Lets change our parseNumber method code from SumLongOctal modification. Heres what it looked like\nstatic long parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString(); int numberStringLength = numberString.length(); if (numberString.endsWith(\"o\") || numberString.endsWith(\"O\")) { return new BigInteger( number.substring(0, numberStringLength - 1), 8 ).longValue(); } else { return Long.parseLong(numberString); } } else { return 0; } } First of all lets change the output type of our method to BigInteger.\nstatic BigInteger parseNumber(StringBuilder number) {...} Also in case of an empty number we cant just return 0 anymore because it doesnt have BigInteger data type. Instead we are going to return BigInteger.ZERO.\nHere we can get rid of .longValue() because we just need straight BigInteger type.\nreturn new BigInteger(number.substring(0, numberStringLength - 1), 8); // .longValue(); Also we change this\nreturn Long.parseLong(numberString); to this\nreturn new BigInteger(numberString); for the same reason. So the parseNumber will look like this\nstatic BigInteger parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString(); int numberStringLength = numberString.length(); if (numberString.endsWith(\"o\") || numberString.endsWith(\"O\")) { return new BigInteger( number.substring(0, numberStringLength - 1), 8 ); } else { return new BigInteger(numberString); } } else { return BigInteger.ZERO; } } Lets rework our main method as well. Heres what it looks like now\npublic static void main(String[] args) { int sum = 0; for (String argument : args) { StringBuilder number = new StringBuilder(); for (char c : argument.toCharArray()) { if (!Character.isWhitespace(c)) { number.append(c); } else { sum += parseNumber(number); number = new StringBuilder(); } } sum += parseNumber(number); } System.out.println(sum); } First of all, lets change the data type of sum to BigInteger like this\nBigInteger sum = new BigInteger(\"0\"); Important\nNotice, we cant just do new BigInteger() (without any arguments) like we did with StringBuilder. We need to put the initial value as an argument like new BigInteger(String val).\nSo in this stage our code will look like this\n// SumBigIntegerOctal.java import java.math.BigInteger; public class SumBigIntegerOctal { public static void main(String[] args) { BigInteger sum = new BigInteger(\"0\"); for (String argument : args) { StringBuilder number = new StringBuilder(); for (char c : argument.toCharArray()) { if (!Character.isWhitespace(c)) { number.append(c); } else { sum += parseNumber(number); number = new StringBuilder(); } } sum += parseNumber(number); } System.out.println(sum); } static BigInteger parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString(); int numberStringLength = numberString.length(); if (numberString.endsWith(\"o\") || numberString.endsWith(\"O\")) { return new BigInteger( number.substring(0, numberStringLength - 1), 8 ); } else { return new BigInteger(numberString); } } else { return BigInteger.ZERO; } } } And lets try to compile this\n$ javac SumBigIntegerOctal.java SumBigIntegerOctal.java:15: error: bad operand types for binary operator '+' sum += parseNumber(number); ^ first type: BigInteger second type: BigInteger SumBigIntegerOctal.java:20: error: bad operand types for binary operator '+' sum += parseNumber(number); ^ first type: BigInteger second type: BigInteger 2 errors Note\nWe cant use + operand with BigInteger data type. Instead we need to use BigInteger.add(BigInteger val).\nSo we will modify like so\n// SumBigIntegerOctal.java import java.math.BigInteger; public class SumBigIntegerOctal { public static void main(String[] args) { BigInteger sum = new BigInteger(\"0\"); for (String argument : args) { StringBuilder number = new StringBuilder(); for (char c : argument.toCharArray()) { if (!Character.isWhitespace(c)) { number.append(c); } else { sum = sum.add(parseNumber(number)); number = new StringBuilder(); } } sum = sum.add(parseNumber(number)); } System.out.println(sum); } static BigInteger parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString(); int numberStringLength = numberString.length(); if (numberString.endsWith(\"o\") || numberString.endsWith(\"O\")) { return new BigInteger( number.substring(0, numberStringLength - 1), 8 ); } else { return new BigInteger(numberString); } } else { return BigInteger.ZERO; } } } And this code will pass all the tests!","task#Task":"Task Input data fits into BigInteger data type. Octal numbers ends with o. Class should be named SumBigIntegerOctal."},"title":"SumBigIntegerOctal"},"/courses/prog-intro/homeworks/sum/sumdouble/":{"data":{"solution#Solution":"This modification is fairly easy. All we need to do is to replace all integers with floating point numbers.\nNote\nHere we can see all of the benefits of using Character.isWhitespace(), because if we had had for example a set of digits and check if the character is in that set we would need to add . (point) to that set to count floating point numbers as well.\nSo heres the updated code\n// SumDouble.java public class SumDouble { public static void main(String[] args) { double sum = 0; // int -\u003e double for (String argument : args) { StringBuilder number = new StringBuilder(); for (char c : argument.toCharArray()) { if (!Character.isWhitespace(c)) { number.append(c); } else { if (!number.isEmpty()) { sum += Double.parseDouble(number.toString()); // Integer.parseInt -\u003e Double.parseDouble()) } number = new StringBuilder(); } } if (!number.isEmpty()) { sum += Double.parseDouble(number.toString()); // Integer.parseInt() -\u003e Double.parseDouble() } } System.out.println(sum); } } This code will work fine and pass all the tests, but can we improve it?\nWe can see repeating part of code here\nif (!number.isEmpty()) { sum += Double.parseDouble(number.toString()); // Integer.parseInt() -\u003e Double.parseDouble() } And if we want to change it, we would need to do so in 2 places. Thats considered bad practice. You can read about DRY principle here.\nSo lets extract this code to its own method called parseNumber.\nstatic double parseNumber(StringBuilder number) { return number.isEmpty() ? 0.0 : Double.parseDouble(number.toString()); } Its a static method, because we cant call a non-static method from a static context which is main method. If we didnt have static keyword in parseNumber method we would have gotten an error.\n$ javac SumDouble.java SumDouble.java:13: error: non-static method parseNumber(StringBuilder) cannot be referenced from a static context sum += parseNumber(number); // method call ^ SumDouble.java:18: error: non-static method parseNumber(StringBuilder) cannot be referenced from a static context sum += parseNumber(number); // method call ^ 2 errors Also we need to discuss the ternary operator. It is a shortened form of if-statement. Its structure looks like this\n(/* condition */) ? (/* do if true */) : (/* do if false */) First argument states a condition. If the condition is true, then the second block gets executed. If the condition if false, then the third block gets executed. It will work the same as\nif (/* condition */) { /* do if true */ } else { /* do if false */ } So heres the updated code\n// SumDouble.java public class SumDouble { public static void main(String[] args) { double sum = 0; for (String argument : args) { StringBuilder number = new StringBuilder(); for (char c : argument.toCharArray()) { if (!Character.isWhitespace(c)) { number.append(c); } else { sum += parseNumber(number); // method call number = new StringBuilder(); } } sum += parseNumber(number); // method call } System.out.println(sum); } static double parseNumber(StringBuilder number) { return number.isEmpty() ? 0.0 : Double.parseDouble(number.toString()); } }","task#Task":"Below are some modifications to the code for deeper understanding of basic language constructions primarily data types. It is always advised to try completing them by yourself before reading the solution. Modifications progress in difficulty from easy to hard.\nTask Input data is 64-bytes floating point numbers. Class should be named SumDouble."},"title":"SumDouble"},"/courses/prog-intro/homeworks/sum/sumdoublehex/":{"data":{"solution#Solution":"Lets change our SumHex parseNumber method modification code. Currently it looks like this\nstatic long parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString(); if ( numberString.startsWith(\"0x\") || numberString.startsWith(\"0X\") ) { return Long.parseLong(numberString.substring(2), 16); } else { return Integer.parseInt(numberString); } } else { return 0; } } Lets change it a little bit\nstatic double parseNumber(StringBuilder number) { // long -\u003e double if (!number.isEmpty()) { String numberString = number.toString(); if ( numberString.startsWith(\"0x\") || numberString.startsWith(\"0X\") ) { if (/* there is a dot (.) inside a number */) { return Double.parseDouble(numberString); } return Long.parseLong(numberString.substring(2), 16); } else { return Double.parseDouble(numberString); // Integer.parseInt(...) -\u003e Double.parseDouble(...) } } else { return 0; } } Note\nNotice, that we pass only one argument into Double.parseDouble() method, because parseDoubles parser recognizes 0x pattern itself.\nAside of obvious analogical changes, we need to somehow understand if the number contains a dot (.). We can do so by using String.contains(CharSequence s). Which checks if the s is present in the string.\nNote\nIn the defenition of the contains method there is CharSequence data type. String data type fits into CharSequence data type as it is declared in the documentation.\nSo our parseNumber method code will look like this\nstatic double parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString(); if ( numberString.startsWith(\"0x\") || numberString.startsWith(\"0X\") ) { if (numberString.contains(\".\")) { return Double.parseDouble(numberString); } return Long.parseLong(numberString.substring(2), 16); } else { return Double.parseDouble(numberString); } } else { return 0; } } And after changing sums data type to double in main method we get\n// SumDoubleHex.java public class SumDoubleHex { public static void main(String[] args) { double sum = 0; for (String argument : args) { StringBuilder number = new StringBuilder(); for (char c : argument.toCharArray()) { if (!Character.isWhitespace(c)) { number.append(c); } else { sum += parseNumber(number); number = new StringBuilder(); } } sum += parseNumber(number); } System.out.println(sum); } static double parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString(); if ( numberString.startsWith(\"0x\") || numberString.startsWith(\"0X\") ) { if (numberString.contains(\".\")) { return Double.parseDouble(numberString); } return Long.parseLong(numberString.substring(2), 16); } else { return Double.parseDouble(numberString); } } else { return 0; } } } This code will work and pass all the tests!","task#Task":"Task Input data are 64-bit floating point numbers There are decimal and hexadecimal numbers in the input Hexadecimal numbers has 0x prefix, for example 0xa.bp2 equals (10 + 11/16)·4 = 42.75 Input is case-insensitive Class should be named SumDoubleHex"},"title":"SumDoubleHex"},"/courses/prog-intro/homeworks/sum/sumhex/":{"data":{"solution#Solution":"This modification doesnt seem too complicated either.\nLets modify our initial Sum.java and apply all our improvements from a previous modification. (A good exercise would be to try doing that yourself and then comeback to check)\n// SumHex.java public class SumHex { public static void main(String[] args) { int sum = 0; for (String argument : args) { StringBuilder number = new StringBuilder(); for (char c : argument.toCharArray()) { if (!Character.isWhitespace(c)) { number.append(c); } else { sum += parseNumber(number); number = new StringBuilder(); } } sum += parseNumber(number); } System.out.println(sum); } static int parseNumber(StringBuilder number) { return number.isEmpty() ? 0 : Integer.parseInt(number.toString()); } } Note\nThe importance of these changes, is that in all further modifications we will only be changing our parseNumber method and leaving main method almost unchanged. The only modifications to main would be changing the data type of sum.\nNow parseNumber thinks that every number is decimal, but we dont know what to do with hexadecimal numbers. Lets try that\n$ java SumHex 0x1 0x2 0x3 Exception in thread \"main\" java.lang.NumberFormatException: For input string: \"0x1\" at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67) at java.base/java.lang.Integer.parseInt(Integer.java:662) at java.base/java.lang.Integer.parseInt(Integer.java:778) at SumHex.parseNumber(SumHex.java:25) at SumHex.main(SumHex.java:18) So we need to split decimal and hexadecimal processing inside parseNumber. Our code would look something like this.\nstatic int parseNumber(StringBuilder number) { if (!number.isEmpty()) { if (/* number is hexadecimal */) { return /* parse hexadecimal */; } else { // if the number if decimal return Integer.parseInt(number.toString()); } } return 0; } Here we have 2 major questions:\nHow to check if the number is hexadecimal? How to parse the hexadecimal number? We can answer the 1st question, because we know that hexadecimal number starts with \"0x\". And we know that the input is not case sensitive, so \"0X\" is also a valid option.\nSo we can change our pseude-code like this\nstatic int parseNumber(StringBuilder number) { if (!number.isEmpty()) { if (/* number starts with \"0x\" or \"0X\" */) { return /* parse hexadecimal */; } else { // if the number is decimal return Integer.parseInt(number.toString()); } } return 0; } But how to check if the string starts with some prefix? There is a String.startsWith(String prefix) that returns true if the string starts with prefix and false otherwise.\nSo we can change our code using this information.\nstatic int parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString(); if ( numberString.startsWith(\"0x\") || numberString.startsWith(\"0X\") ) { return /* parse hexadecimal */; } else { // if the number is decimal return Integer.parseInt(number.toString()); } } return 0; } Note\n|| here is a logical or.\nWe still have one problem. How to parse the hexadecimal number?\nUsing Integer.parseInt() method. It turnes out, that it has the second argument radix where we can specify the base of the numerical system (in our case its 16).\nSo our code will look like this.\nstatic int parseNumber(StringBuilder number) { if (number.isEmpty()) { String numberString = number.toString(); if ( numberString.startsWith(\"0x\") || numberString.startsWith(\"0X\") ) { return Integer.parseInt(numberString.substring(2), 16); } else { return Integer.parseInt(numberString); } } return 0; } Note\nHere we used String.substring(int beginIndex) method to cut off first two symbols (\"0x\"/\"0X\") in order for parseInt to parse number correctly and not give us exceptions. In our case beginIndex = 2 because its the first index of the substring that we want to get. Remember that indexing starts with 0 (not 1).\nImportant\nIts important to know that we cant store all hexadecimal numbers in int data type. Lets look at this example\n$ java SumHex 0x80000000 Exception in thread \"main\" java.lang.NumberFormatException: For input string: \"80000000\" under radix 16 at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67) at java.base/java.lang.Integer.parseInt(Integer.java:662) at SumHex.parseNumber(SumHex.java:30) at SumHex.main(SumHex.java:18) Thats why we will need to use long data type for storing hexademical values.\nHeres the updated code\nstatic long parseNumber(StringBuilder number) { // int -\u003e long if (!number.isEmpty()) { String numberString = number.toString(); if (numberString.startsWith(\"0x\") || numberString.startsWith(\"0X\")) { // Integer.parseInt -\u003e Long.parseLong return Long.parseLong(numberString.substring(2), 16); } else { return Integer.parseInt(numberString); } } else { return 0; } } Lets present the full solution\n// SumHex.java public class SumHex { public static void main(String[] args) { int sum = 0; for (String argument : args) { StringBuilder number = new StringBuilder(); for (char c : argument.toCharArray()) { if (!Character.isWhitespace(c)) { number.append(c); } else { sum += parseNumber(number); number = new StringBuilder(); } } sum += parseNumber(number); } System.out.println(sum); } static long parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString(); if (numberString.startsWith(\"0x\") || numberString.startsWith(\"0X\")) { return Long.parseLong(numberString.substring(2), 16); } else { return Integer.parseInt(numberString); } } else { return 0; } } } Important\nNotice, that we didnt change main method at all, because we have a separate function handling parsing. Thats a good sign because our main method doesnt know what number parsing algorithm is used and therefore not dependent on it.","task#Task":"Task The input is in decimal and hexadecimal numbers Hexademical numbers has the prefix 0x The input is case insensitive Class should be named SumHex"},"title":"SumHex"},"/courses/prog-intro/homeworks/sum/sumlongoctal/":{"data":{"solution#Solution":"We need to change our parseNumber method yet again. Lets take a look at it from SumHex modification\nstatic long parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString(); if ( numberString.startsWith(\"0x\") || numberString.startsWith(\"0X\") ) { return Long.parseLong(numberString.substring(2), 16); } else { return Integer.parseInt(numberString); } } else { return 0; } } Lets adapt our new logic on a concept level\nstatic long parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString(); int numberStringLength = numberString.length(); if ( /* number is octal */ ) { return Long.parseLong(numberString.substring(0, numberStringLength - 1), 8); // 16 -\u003e 8 } else { return Integer.parseInt(numberString); } } else { return 0; } } As you can see is that we need to only change method of checking if the number is octal. Its actually fairly easy. With hexadecimal numbers we checked if the number started with \"0x\" or \"0X\". Now we need to check if the number ends with \"o\" or \"O\". To do that we will use String.endsWith(String suffix) method that checks if the string ends with prefix.\nAnd also we change 16 to 8 here\nreturn Long.parseLong(numberString.substring(0, numberStringLength - 1), 8); Because we are converting from octal, not hexadecimal anymore.\nNote\nNote, that we alse changed how we crop out the last character of our numberString. First, we introduced new variable numberStringLength, that represents a length of numberString (or number of characters in the string). Then, we take a substring using substring(0, numberStringLength - 1). We start at first index (0) and go all the way to the second from the end of the string. We use - 1 and not - 2 because last index is exclusive (meaning it doesnt count).\nSo the code for parseNumber method will be\nstatic long parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString(); int numberStringLength = numberString.length(); if ( numberString.endsWith(\"o\") || numberString.endsWith(\"O\") ) { return Long.parseLong(numberString.substring(0, numberStringLength - 1), 8); // 16 -\u003e 8 } else { return Integer.parseInt(numberString); } } else { return 0; } } So the solution would be\n// SumLongOctal.java public class SumLongOctal { public static void main(String[] args) { int sum = 0; for (String argument : args) { StringBuilder number = new StringBuilder(); for (char c : argument.toCharArray()) { if (!Character.isWhitespace(c)) { number.append(c); } else { sum += parseNumber(number); number = new StringBuilder(); } } sum += parseNumber(number); } System.out.println(sum); } static long parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString(); int numberStringLength = numberString.length(); if (numberString.endsWith(\"o\") || numberString.endsWith(\"O\")) { return Long.parseLong(numberString.substring(0, numberStringLength - ), 8); } else { return Integer.parseInt(numberString); } } else { return 0; } } } Lets try to test it with test like this\n$ java SumLongOctal \" +12345678901234567 \" Exception in thread \"main\" java.lang.NumberFormatException: For input string: \"+12345678901234567\" at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67) at java.base/java.lang.Integer.parseInt(Integer.java:662) at java.base/java.lang.Integer.parseInt(Integer.java:778) at sum.SumLongOctal.parseNumber(SumLongOctal.java:32) at sum.SumLongOctal.main(SumLongOctal.java:15) As you can see we got a NumberFormatException. Here its not an octal number and it doesnt fit into int data type, so lets change int to long which is bigger.\n// SumLongOctal.java public class SumLongOctal { public static void main(String[] args) { long sum = 0; // int -\u003e long for (String argument : args) { StringBuilder number = new StringBuilder(); for (char c : argument.toCharArray()) { if (!Character.isWhitespace(c)) { number.append(c); } else { sum += parseNumber(number); number = new StringBuilder(); } } sum += parseNumber(number); } System.out.println(sum); } static long parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString(); int numberStringLength = numberString.length(); if (numberString.endsWith(\"o\") || numberString.endsWith(\"O\")) { return Long.parseLong(numberString.substring(0, numberStringLength - 1), 8); } else { return Long.parseLong(numberString); // Integer.parseInt(...) -\u003e Long.parseLong(...) } } else { return 0; } } } Note\nNotice we changed data type of sum to long in order to store 64 bit numbers.\nLets test some more\n$ java SumLongOctal.java \"1777777777777777777776o\" Exception in thread \"main\" java.lang.NumberFormatException: For input string: \"1777777777777777777776\" under radix 8 at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67) at java.base/java.lang.Long.parseLong(Long.java:709) at SumLongOctal.parseNumber(SumLongOctal.java:29) at SumLongOctal.main(SumLongOctal.java:18) We can see that this number doesnt fit into long data type. So we need to use something called BigInteger and more specifically we will be using its BigInteger.longValue() method.\nFirst we need to import it using\nimport java.math.BigInteger; and then we will replace\nreturn Long.parseLong(numberString.substring(0, numberStringLength - 1), 8); with\nreturn new BigInteger(number.substring(0, numberStringLength - 1), 8).longValue(); So the final code would be\n// SumLongOctal.java import java.math.BigInteger; public class SumLongOctal { public static void main(String[] args) { long sum = 0; for (String argument : args) { StringBuilder number = new StringBuilder(); for (char c : argument.toCharArray()) { if (!Character.isWhitespace(c)) { number.append(c); } else { sum += parseNumber(number); number = new StringBuilder(); } } sum += parseNumber(number); } System.out.println(sum); } static long parseNumber(StringBuilder number) { if (!number.isEmpty()) { String numberString = number.toString(); int numberStringLength = numberString.length(); if (numberString.endsWith(\"o\") || numberString.endsWith(\"O\")) { return new BigInteger( number.substring(0, numberStringLength - 1), 8 ).longValue(); } else { return Long.parseLong(numberString); } } else { return 0; } } }","task#Task":"Task The input data is in 64 bit decimal numbers Octal numbers has suffix \"o\" Class should be named SumLongOctal"},"title":"SumLongOctal"},"/courses/prog-intro/lectures/intro/intro/":{"data":{"how-should-java-code-look-like#How should Java code look like?":"You can go and learn about it here.","what-does-java-consist-of#What does Java consist of?":"Java consists of multiple main components. The first one being the Java compiler. The process of converting human-readable text to machine code is called compilation.\nImg.2 - Simplified Compilation Process\nThe behaviour of the Java compiler is described by the Java Language Specification or JLS.\nLets talk about compilers a little bit. For example if we will take C++. If we take C++ compiler for x86-64 platform and Windows operating system, and launch the compiler on source code it will turn it directly into assembly code.\nImg. X C++ code compilation process under x86-64 architecture.\nBut what if I want to run my program on Linux instead of Windows. I will need to take a different compiler under different operating system and recompile my code using a new compiler. Its not very convenient.\nJava tries to protect us from that. By converting the Java source code into Java byte code. And then the byte code will be ran on the Java virtual machine which will run our program on the native processor.\nImg. X Java code compilation\nThis approach allows to change only JVM according to our platform (x86_64, ARM, …) and operating system (Windows, MacOS, GNU-Linux, …) while byte code stays the same. We can only write our code once, than compile it and run under everywhere.\nAs the motto says:\nWrite once debug run everywhere.\nThe third component of Java is the standart library which is included in the JVM.\nThere are multiple redactions of Java-platforms:\nStandart edition For regular aplications Enterprise edition For server aplications Micro-edition For mobile aplications Isnt in use nowadays 🪦 Java Card Sim- and smart-cards There also were multiple versions of Java throughout its history.\nJDK 1.0 (Jan 1996) J2SE 1.2 (Dec 1998) Collections Framework J2SE 5.0 (Sep 2004) Generics Java SE 8 (Mar 2014) Streams and Lambdas Java SE 9 (Sep 2017) Modules Java SE 10 (Mar 2018) var Java 11 (Sep 2018) jshell Java 17 [LTS-old] (Sep 2021) Previous stable version Many little changes Java 21 [LTS] (Sep 2023) Current stable version Java 25 (Sep 2025) Next version Java comes in two parts: JDK - Java Development Kit and JVM - Java Virtual Machine.\nThere is also a JRE - Java Runtime Environment. For example if we want to run our code somewhere on the server we dont need to compile it there because we have our byte code and we just need JRE to run it.\nSome of the most popular JVMs right now are:\nOpenJDK Eclipse Azul Systems Excelsior JET The disadvantage of such system is in the connection between JVM and a native processing unit. In case of C++ compiler that we reviewed earlier the source code is compiled directly into machine-code but in case with Java it is compiled into byte-code. And so the problem is to develop such a JVM that would quickly turn our byte-code into machine-code. Anyway it takes extra time. Thats why it mostly will be slower than direct compilation into machine-code. So ultimately while we have the advantage of compiling out code only once, we have the disadvantage of turning byte-code into machine-code slower.\nNone the less, there is a way to speed up this process which is called JIT - Just In Time compilation. How does it work? While our program is running some of the instructions or functions turns directly into processor commands.","what-is-garbage-collection#What is garbage collection?":"For example we have int which is represented with 4 bytes of data which is directly stored in memory.\nImg. X int in memory\nBut what if we have a String. How many memory cells does this string take? We dont know. We will say that our String that has length of 5 symbols is stored at 0x12347865. We defined an address where this string is located in memory. And somewhere in the memory of our programm will be a large buffer where the 5 cells will be stored.\nImg. X String in memory\nBut what do we do with that buffer? We wont need it forever and so sometimes we need to clear that buffer. In case with C/C++ whoever created the memory for that string is in charge of clearing it. There are also languages with garbage collection such as Java and Python. The purpose of the Garbage Collector is to automatically find variables that we no longer need and clear their memory.\nSuppose we have some code like this.\nif (someCondition) { x = [1, 3, 7] // first link // some code here y = x // second link // some code here } // no links After the if-statement we no longer need x or y. Every variable in this case x and y is the link to our array ([1, 3, 7]). After we left the if-statement the amount of links to the array is zero, so we can safely delete the array. This approach is implemented in Python. The problem is that if we have to objects linked to one another and there are no external links, they will not be deleted by garbage collector since there link counter is 1.\nImg. X Edge case for link counting\nThe advanced way of implementing the garbage collection is traversing the graph of links which is implemented in Java, C# and Go.","what-other-advantages-does-java-have#What other advantages does Java have?":"Its easy (in terms of syntax) Its secure and stable It supports Unicode It supports multithreading It has backwards compatibility","why-do-we-choose-java#Why do we choose Java?":"Why do we choose Java?According to TIOBE Programming Community Index Java is one of the most demanded languages in the programming field.\nImg. 1 - TIOBE Programming Community Index\nAlso, Java has a lot of advantages for beginners such as:\nIts fairly easy It has a broad spectre of usage Server Desktop Mobile devices Smart-cards"},"title":"Lecture 1. Introduction"},"/courses/spring-boot/":{"data":{"configuring-application-properties#Configuring Application Properties":"Lets take a look at src/main/resources/application.properties:\nspring.application.name=store To use this property in our code, we can use the @Value annotation. Lets update HomeController to print the application name:\npackage tech.codejava.store; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class HomeController { @Value(\"${spring.application.name}\") private String appName; @RequestMapping(\"/\") // this represents the root of our website public String index() { System.out.println(\"application name = \" + appName); return \"index.html\"; // this returns the view } } After running the application, we can see store printed in the terminal:\n... 2026-02-19T15:32:37.507+03:00 INFO 41536 --- [store] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2026-02-19T15:32:37.509+03:00 INFO 41536 --- [store] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms application name = store ...","controllers#Controllers":"Spring MVC stands for Model View Controller.\nModel is where our applications data lives. It represents the business logic and is usually connected to a database or other data sources. In Spring Boot, the model can be a simple Java class. View is what the user sees. Its the HTML, CSS or JavaScript rendered in the browser. In Spring MVC, views can be static files or dynamically generated. Controller is like a traffic controller. It handles incoming requests from the user, interacts with the model to get data and then tells the view what to display. Lets add a new Java class called HomeController at src/main/java/tech/codejava/store/HomeController.java:\npackage tech.codejava.store; public class HomeController {} To make this a controller, decorate it with the @Controller annotation:\npackage tech.codejava.store; import org.springframework.stereotype.Controller; @Controller public class HomeController {} Now lets add an index method. When we send a request to the root of our website, we want this method to be called:\npackage tech.codejava.store; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class HomeController { @RequestMapping(\"/\") // this represents the root of our website public String index() { return \"index.html\"; // this returns the view } } Now we need to create the view. Add index.html at src/main/resources/static/index.html:\n\u003c!doctype html\u003e \u003chtml lang=\"en\"\u003e \u003chead\u003e \u003cmeta charset=\"UTF-8\" /\u003e \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /\u003e \u003ctitle\u003eView\u003c/title\u003e \u003c/head\u003e \u003cbody\u003e \u003ch1\u003eHello world!\u003c/h1\u003e \u003c/body\u003e \u003c/html\u003e Lets build and run our application using mvn spring-boot:run. From the logs:\n2026-02-19T14:55:23.948+03:00 INFO 36752 --- [store] [ main] o.s.boot.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http) Our app is up and running at localhost:8080.\nImg. 7 — Our app is up and running!","dependency-injection#Dependency Injection":"Imagine were building an E-Commerce application that handles placing orders. When an order is placed, the customers payment needs to be processed — so OrderService depends on a payment service like StripePaymentService. We can say that OrderService is dependent on (or coupled to) StripePaymentService.\nImg. 8 — Depends On/Coupled To relation\nLets talk about the issues that arise when one class is tightly coupled to another.\nInflexibility — OrderService can only use StripePaymentService. If tomorrow we decide to switch to a different payment provider like PayPal, we would have to modify OrderService. Once we change it, it has to be recompiled and retested, which could impact other classes that depend on it. Untestability — We cannot test OrderService in isolation, because OrderService is tightly coupled with StripePaymentService and we cant test its logic separately from it. Note\nThe problem here isnt that OrderService depends on StripePaymentService — dependencies are normal in any application. The issue is about how the dependency is created and managed.\nAnalogy: Think of a restaurant. A restaurant needs a chef — thats a perfectly normal dependency. If the current chef becomes unavailable, the restaurant can hire another one.\nImg. X — Restaurant — Chef dependency (Normal)\nNow what if we replace “chef” with a specific person: John? Our restaurant is now dependent on John specifically. If John becomes unavailable, we cant replace him — the restaurant is in trouble. This is an example of tight or bad coupling.\nImg. X — Restaurant — John dependency (Bad coupling)\nWe dont want OrderService to be tightly coupled to a specific payment service like Stripe. Instead, we want it to depend on a PaymentService interface, which could be Stripe, PayPal, or any other provider. To achieve this we can use the interface to decouple OrderService from StripePaymentService.\nImg. X — PaymentService as interface\nIf OrderService depends on a PaymentService interface, it doesnt know anything about Stripe, PayPal, or any other payment provider. As long as these providers implement PaymentService, they can be used to handle payments — and OrderService wont care which one is being used.\nBenefits:\nIf we replace StripePaymentService with PayPalPaymentService, the OrderService class is not affected. We dont need to modify or recompile OrderService. We can test OrderService in isolation, without relying on the specific payment provider like Stripe. With this setup, we simply give OrderService a particular implementation of PaymentService. This is called dependency injection — we inject the dependency into a class.\nImg. X — Dependency Injection example\nLets see how it works in our project. Create OrderService at src/main/java/tech/codejava/store/OrderService.java:\npackage tech.codejava.store; public class OrderService { public void placeOrder() {} } Note\nIn a real project we would need to provide something like Order order to this method, but for teaching purposes we wont do that.\nNow create StripePaymentService in the same directory:\npackage tech.codejava.store; public class StripePaymentService { public void processPayment(double amount) { System.out.println(\"=== STRIPE ===\"); System.out.println(\"amount: \" + amount); } } Lets implement placeOrder in OrderService using StripePaymentService:\npackage tech.codejava.store; public class OrderService { public void placeOrder() { var paymentService = new StripePaymentService(); paymentService.processPayment(10); } } Important\nThis is our before setup — before we introduced the interface. In this implementation, OrderService is tightly coupled to StripePaymentService. We cannot test OrderService in isolation, and switching to another payment provider would require modifying OrderService.\nLets fix this. Create a PaymentService interface in the same directory:\npackage tech.codejava.store; public interface PaymentService { void processPayment(double amount); } Modify StripePaymentService to implement PaymentService:\npackage tech.codejava.store; public class StripePaymentService implements PaymentService { @Override public void processPayment(double amount) { System.out.println(\"=== STRIPE ===\"); System.out.println(\"amount: \" + amount); } } The recommended way to inject a dependency into a class is via its constructor. Lets define one in OrderService:\npackage tech.codejava.store; public class OrderService { private PaymentService paymentService; public OrderService(PaymentService paymentService) { this.paymentService = paymentService; } public void placeOrder() { paymentService.processPayment(10); } } Now lets see this in action. Modify StoreApplication:\npackage tech.codejava.store; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class StoreApplication { public static void main(String[] args) { // SpringApplication.run(StoreApplication.class, args); var orderService = new OrderService(new StripePaymentService()); orderService.placeOrder(); } } Running the application (output intentionally reduced):\n... === STRIPE === amount: 10.0 ... Now lets create a PayPalPaymentService in the same directory:\npackage tech.codejava.store; public class PayPalPaymentService implements PaymentService { @Override public void processPayment(double amount) { System.out.println(\"=== PayPal ===\"); System.out.println(\"amount: \" + amount); } } Now we can switch from StripePaymentService to PayPalPaymentService in StoreApplication — without touching OrderService at all:\npackage tech.codejava.store; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class StoreApplication { public static void main(String[] args) { // SpringApplication.run(StoreApplication.class, args); // var orderService = new OrderService(new StripePaymentService()); var orderService = new OrderService(new PayPalPaymentService()); orderService.placeOrder(); } } ... === PayPal === amount: 10.0 ... Notice that we didnt change OrderService. In object-oriented programming this is known as the Open/Closed Principle:\nA class should be open for extension and closed for modification.\nIn other words: we should be able to add new functionality to a class without changing its existing code. This reduces the risk of introducing bugs and breaking other parts of the application.","dependency-management#Dependency Management":"Dependencies are third-party libraries or frameworks we use in our application. For example, to build a web application we need an embedded web server like Tomcat, libraries for handling web requests, building APIs, processing JSON data, logging and so on.\nIn Spring Boot applications, instead of adding multiple individual libraries, we can use a starter dependency.\nImg. 5 — Spring Boot Starter Web\nTo use this dependency, copy the following into your pom.xml:\norg.springframework.boot spring-boot-starter-web 4.1.0-M1 So the dependencies section would look like this:\norg.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-web Important\nNotice that the version is commented out. Its a better practice to let Spring Boot decide what version of the dependency to use, as it ensures compatibility across your project.","initialize-spring-boot-project#Initialize Spring Boot Project":"To initialize a new Spring Boot project, go to start.spring.io and select the options that suit you.\nImg. 3 — Spring Boot options\nAfter unpacking the zip archive, youll have this template project:\n. ├── HELP.md ├── mvnw ├── mvnw.cmd ├── pom.xml ├── src │ ├── main │ │ ├── java │ │ │ └── tech │ │ │ └── codejava │ │ │ └── store │ │ │ └── StoreApplication.java │ │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── tech │ └── codejava │ └── store │ └── StoreApplicationTests.java └── target ├── classes │ ├── application.properties │ └── tech │ └── codejava │ └── store │ └── StoreApplication.class ├── generated-sources │ └── annotations ├── generated-test-sources │ └── test-annotations ├── maven-status │ └── maven-compiler-plugin │ ├── compile │ │ └── default-compile │ │ ├── createdFiles.lst │ │ └── inputFiles.lst │ └── testCompile │ └── default-testCompile │ ├── createdFiles.lst │ └── inputFiles.lst ├── surefire-reports │ ├── TEST-tech.codejava.store.StoreApplicationTests.xml │ └── tech.codejava.store.StoreApplicationTests.txt └── test-classes └── tech └── codejava └── store └── StoreApplicationTests.class The “heart” of our project is pom.xml:\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\" ?\u003e","prerequisites#Prerequisites":"PrerequisitesBefore diving in, make sure youre comfortable with the following:\nJava — solid understanding of the language Object-oriented programming — classes, methods and interfaces Databases — tables, primary keys, foreign keys, relationships, etc. SQL — ability to write basic SQL statements","setter-injection#Setter Injection":"Another way to inject a dependency is via a setter. In OrderService, lets define one:\npackage tech.codejava.store; public class OrderService { private PaymentService paymentService; public void setPaymentService(PaymentService paymentService) { this.paymentService = paymentService; } public OrderService(PaymentService paymentService) { this.paymentService = paymentService; } public void placeOrder() { paymentService.processPayment(10); } } We can use it like this in StoreApplication:\npackage tech.codejava.store; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class StoreApplication { public static void main(String[] args) { // SpringApplication.run(StoreApplication.class, args); // var orderService = new OrderService(new StripePaymentService()); var orderService = new OrderService(new PayPalPaymentService()); orderService.setPaymentService(new PayPalPaymentService()); orderService.placeOrder(); } } Important\nIf you remove the constructor from OrderService and forget to call the setter, the application will crash with a NullPointerException. Use setter injection only for optional dependencies — ones that OrderService can function without.","what-is-a-spring-framework#What is a Spring Framework?":"Spring is a popular framework for building Java applications. It has a lot of modules, each designed to handle a specific task. They are combined into a few different layers.\nImg. 1 — Spring layers\nLayer Purpose Core Handling dependency injection, managing objects Web Building web applications Data Working with databases AOP Aspect oriented programming Test Testing spring components While the Spring Framework is powerful, using it often involves a lot of configuration. For example, if you want to build a web app you might need to setup a web server, configure routing and manage dependencies manually. Thats when Spring Boot comes in.\nNote\nYou can think of Spring Boot as a layer on top of the Spring Framework that takes care of all of the setup. Spring Boot simplifies Spring development by providing sensible defaults and ready-to-use features.\nBy the way, the Spring Framework is just one part of a larger family of projects in the Spring ecosystem.\nImg. 2 — Spring ecosystem\nModule Name Purpose Spring Data Simplifying database access Spring Security Adding authentication and authorization Spring Batch Batch processing Spring Cloud Building microservices and distributed systems Spring Integration Simplifying messaging and integration between systems"},"title":"_index"}}