Annotations in Java are like labels or notes you add to your code. They give extra info to the compiler, tools, or frameworks. They don't change how the code works but help in understanding or processing the code better. Introduced in Java 5, they are widely used in modern applications like web development, frameworks (Spring, Hibernate), and Android.
@Override
.class Parent { void show() { System.out.println("Parent show"); } } class Child extends Parent { @Override // This annotation checks if we're correctly overriding void show() { System.out.println("Child show"); } }
Here, @Override
tells the compiler: "I'm changing the parent's method." If you spell
show
wrong, it warns you.
class Animal { void sound() { System.out.println("Animal sound"); } } class Dog extends Animal { @Override void sound() { // compiler ensures method matches parent System.out.println("Dog barks"); } }
Why use? Prevents mistakes. Without @Override
, if you accidentally write
sounds()
instead of sound()
, it won’t override – just create a new method.
class OldCode { @Deprecated void oldMethod() { System.out.println("Old logic"); } } class Test { public static void main(String[] args) { OldCode oc = new OldCode(); oc.oldMethod(); // Compiler shows warning } }
Why use? Tells other developers: "Don’t use this method anymore, there’s a better way." Useful when updating libraries or frameworks.
import java.util.*; class WarningExample { @SuppressWarnings("unchecked") void demo() { List list = new ArrayList(); // Raw type (usually gives warning) list.add("Hello"); System.out.println(list.get(0)); } }
Why use? Sometimes warnings are unnecessary. This annotation hides them, but use carefully — it may hide real issues.
RegEx is used to find, match, or replace parts of strings using patterns.
In Java, it is available in the java.util.regex
package (classes: Pattern
and
Matcher
).
"\\d+"
→ numbers)..
→ any one character*
→ zero or more+
→ one or more\\d
→ digit (0-9)\\w
→ word (letters, digits, _)^
→ start of text$
→ end of textimport java.util.regex.*; public class Main { public static void main(String[] args) { String text = "I have 5 apples and 10 oranges."; Pattern p = Pattern.compile("\\d+"); // pattern for numbers Matcher m = p.matcher(text); while (m.find()) { System.out.println(m.group()); // prints 5 then 10 } } }
find()
→ finds next matchgroup()
→ gets matched valuematches()
→ checks if whole string matches patternreplaceAll()
→ replace text
text.replaceAll("\\d+", "X")
→ "I have X apples and X oranges."
split()
→ split string
"a,b,c".split(",")
→ ["a","b","c"]
String email = "test@example.com"; if (email.matches("^[\\w.-]+@[\\w.-]+\\.[a-z]{2,}$")) { System.out.println("Valid email"); }
Common Uses: Form validation (emails, phone numbers), extracting data, search & replace.
\\d
for numbers
or \\w
for words.
Threads let your Java program do many tasks at the same time, like multitasking. This is concurrency. Uses
java.lang.Thread
and java.util.concurrent
.
main()
method.class MyTask extends Thread { public void run() { // Code here runs in thread System.out.println("Task running"); } } MyTask t = new MyTask(); t.start(); // Start it
2. Use Runnable (better):
class MyRun implements Runnable { public void run() { System.out.println("Run task"); } } Thread t = new Thread(new MyRun()); t.start();
start()
.run()
.start():
Begins thread.run():
The work (don't call directly).sleep(1000):
Pause 1 second.join():
Wait for thread to end.interrupt():
Ask to stop (thread checks itself).int count = 0; synchronized void add() { count++; }
import java.util.concurrent.locks.*; Lock lock = new ReentrantLock(); lock.lock(); count++; lock.unlock();
Lambdas were introduced in Java 8. They are a short way to write code (functions) without creating a full class. They work only with Functional Interfaces (an interface with exactly one abstract method).
(parameters) -> { body }
(int x) -> x * 2
// doubles a number() -> "Hi"
@FunctionalInterface interface Add { int sum(int a, int b); } public class Main { public static void main(String[] args) { Add adder = (a, b) -> a + b; // Lambda instead of writing a class System.out.println(adder.sum(3, 4)); // Output: 7 } }
list.forEach(s -> System.out.println(s));
Collections.sort(list, (a, b) -> a.compareTo(b));
list.stream().filter(n -> n > 0).count();
Sorting means arranging data in order (like A→Z or small→big). In Java, sorting can be done in two ways:
Arrays.sort(array);
Collections.sort(list);
If you want objects to be sorted in their natural order (like names alphabetically), the class
should implement Comparable
.
class Fruit implements Comparable{ String name; Fruit(String n) { name = n; } @Override public int compareTo(Fruit other) { return this.name.compareTo(other.name); // A-Z order } } List fruits = new ArrayList<>(); fruits.add(new Fruit("Banana")); fruits.add(new Fruit("Apple")); fruits.add(new Fruit("Mango")); Collections.sort(fruits); // Sorts by name
Why? Comparable is used when the class itself has a default way to compare objects.
If you want different ways to sort, use Comparator
.
ComparatorbyLength = (f1, f2) -> Integer.compare(f1.name.length(), f2.name.length()); fruits.sort(byLength); // Sorts by name length
Why? Comparator is flexible – you can define multiple custom sorting rules without changing the class.
Sorting numbers in descending order:
Listnums = Arrays.asList(3, 1, 4); nums.sort(Comparator.reverseOrder()); // [4, 3, 1]
Comparable
for natural/default order.
- Use Comparator
for custom rules (like by length, reverse, etc).
@Override
? Give example. (Explain it checks
overriding.)