Introduction: Why Java Code Performance Matters from the Start
\nEvery developer, even a beginner, encounters situations where a program runs slowly. In the world of Java, where code runs on a virtual machine (JVM), performance optimization is not just about "speeding things up," but the art of using resources wisely. Understanding the basic principles of optimization will help you write not only functional but also fast code that efficiently uses memory and the processor.
\nIn this article, we will break down the key aspects of Java application optimization for beginners: from choosing the right data structures to configuring the garbage collector. You will learn how to avoid typical "bottlenecks" and make your code more performant without unnecessary complexity.
\n\n1. Efficient Memory Usage: String Pool and Primitives
\n1.1. Avoid Creating Unnecessary String Objects
\nOne of the most common mistakes beginners make is creating a large number of strings in loops. Java uses the String Pool for optimization, but this only works with the right approach.
\nBad example:
\nString result = "";\nfor (int i = 0; i < 1000; i++) {\n result += "number " + i + " ";\n}\nSystem.out.println(result);\nIn this case, a new String object is created on each iteration, putting a huge load on the garbage collector.
Good example:
\nStringBuilder sb = new StringBuilder();\nfor (int i = 0; i < 1000; i++) {\n sb.append("number ").append(i).append(" ");\n}\nString result = sb.toString();\nSystem.out.println(result);\nUse StringBuilder (or StringBuffer for multithreading) for string concatenation in loops. This saves memory and speeds up execution by tens of times.
1.2. Use Primitives Instead of Wrappers
\nJava has primitive types (int, double, boolean) and their object wrappers (Integer, Double, Boolean). Wrappers are convenient, but they create extra objects on the heap.
// Slow: many Integer objects are created\nInteger sum = 0;\nfor (int i = 0; i < 100000; i++) {\n sum += i; // autoboxing\n}\n\n// Fast: uses a primitive\nint sumPrimitive = 0;\nfor (int i = 0; i < 100000; i++) {\n sumPrimitive += i;\n}\nUse primitives wherever possible, especially in loops and mathematical operations.
\n\n2. Optimizing Work with Collections and Loops
\n2.1. Choosing the Right Collection
\nNot all collections are equally useful. For example, ArrayList is faster for index-based access, while LinkedList is faster for insertion/deletion in the middle of the list. But for most tasks, ArrayList is the optimal choice.
- \n
- ArrayList — for frequent reading and index-based access. \n
- LinkedList — for frequent insertion/deletion at the beginning or middle. \n
- HashSet — for fast element existence checks (O(1)). \n
- HashMap — for storing key-value pairs with fast access. \n
Tip: If the collection size is known in advance, set the initial capacity via the constructor to avoid unnecessary array resizing.
\n// Bad: ArrayList will be resized multiple times\nList<String> list = new ArrayList<>();\n\n// Good: set the initial capacity\nList<String> list = new ArrayList<>(1000);\n\n2.2. Loop Optimization: Move Invariants Outside
\nIf a value computed inside a loop does not change from iteration to iteration, move it outside the loop.
\n// Slow: list.size() is computed each time\nfor (int i = 0; i < list.size(); i++) {\n