11.
Inner Classes
Defined as a non-static class inside another class.
class Outer {
private String message = "Hello";
class Inner {
void showMessage() {
System.out.println(message);
}
}
}
public class Main {
public static void main(String[] args) {
// Create an instance of the Outer class
Outer outer = new Outer();
// Create an instance of the Inner class using the Outer instance
Outer.Inner inner = outer.new Inner();
// Call the showMessage method of the Inner class
inner.showMessage();
}
}
javac Outer.java Main.java
java Main
Static Nested Class:
class Outer1 {
static String message = "Hello";
static class Nested {
void showMessage() {
System.out.println(message);
}
}
public static void main(String[] args) {
// Create an instance of the static nested class
Outer.Nested nested = new Outer.Nested();
// Call the showMessage method of the Nested class
nested.showMessage();
}
}
Local Inner Class:
class Outer {
void display() {
// Local Inner Class defined within a method
class Inner {
void show() {
System.out.println("Inside local inner class");
}
}
// Create an instance of the local inner class and call its method
Inner inner = new Inner();
inner.show();
}
public static void main(String[] args) {
// Create an instance of the Outer class
Outer outer = new Outer();
// Call the display method to invoke the local inner class
outer.display();
}
}
Anonymous Inner Class:
public class Main {
public static void main(String[] args) {
// Create an anonymous inner class implementing Runnable
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Anonymous inner class");
}
};
// Call the run method
runnable.run();
}
}
---------------------
12. Annotations
Annotations in Java provide metadata about the code. They do not directly affect
the code's behavior but can influence how the code is processed (e.g., by the
compiler or frameworks).
Commonly Used Annotations:
@Override:
Indicates a method is overriding a superclass method.
public class ExampleClass {
@Override
public String toString() {
return "Example";
}
public static void main(String[] args) {
// Create an instance of the ExampleClass
ExampleClass example = new ExampleClass();
// Call the toString method (implicitly or explicitly)
System.out.println(example); // Implicit call to toString()
System.out.println(example.toString()); // Explicit call to toString()
}
}
@Deprecated:
Marks a method or class as outdated, advising against its use.
public class ExampleClass1 {
@Deprecated
void oldMethod() {
System.out.println("This is a deprecated method. Use something newer.");
}
void newMethod() {
System.out.println("This is the new and recommended method.");
}
public static void main(String[] args) {
ExampleClass1 example = new ExampleClass1();
// Calling the deprecated method
example.oldMethod();
// Calling the new method
example.newMethod();
}
}
@SuppressWarnings:
Suppresses compiler warnings.
import java.util.ArrayList;
import java.util.List;
public class ExampleClass2 {
@SuppressWarnings("unchecked")
void method() {
// Example of unchecked operation: assigning a raw type to a generic type
List rawList = new ArrayList();
rawList.add("Hello");
rawList.add(123); // Mixed types in raw list
// Casting rawList to a generic List<String>, which can cause warnings
List<String> stringList = rawList;
// Printing elements (this may cause runtime issues if types mismatch)
for (String item : stringList) {
System.out.println(item);
}
}
public static void main(String[] args) {
ExampleClass2 example = new ExampleClass2();
// Call the method containing suppressed warnings
example.method();
}
}
Custom Annotations:
Developers can define their own annotations.
MyAnnotation.java
----------------------------
// Define the custom annotation
@interface MyAnnotation {
String value();
}
ExampleClass3.java
------------------------------
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
public class ExampleClass3 {
// Apply the custom annotation to a method
@MyAnnotation(value = "This is a custom annotation")
void annotatedMethod() {
System.out.println("Method is annotated with MyAnnotation.");
}
public static void main(String[] args) throws Exception {
ExampleClass3 example = new ExampleClass3();
// Access the custom annotation using reflection
Method method = example.getClass().getMethod("annotatedMethod");
// Check if the method is annotated with MyAnnotation
if (method.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
System.out.println("Annotation value: " + annotation.value());
}
// Call the annotated method
example.annotatedMethod();
}
}
Why it’s Important in Android:
Android uses annotations like @Nullable, @NonNull, @RequiresPermission, and
@UiThread to enhance code readability, prevent bugs, and enforce proper threading.
14. Generics
Generics enable classes, interfaces, and methods to operate on types specified at
runtime, ensuring type safety and reducing runtime errors.
How Generics Work:
Generic Class:
Box.java
------------
// Generic class Box that can hold any type of object
class Box<T> {
private T value;
// Method to set the value of the box
void setValue(T value) {
this.value = value;
}
// Method to get the value from the box
T getValue() {
return value;
}
}
Utils.java
-------------
// Utility class with a generic method to print elements of an array
class Utils {
// Generic method to print an array of any type T
public static <T> void printArray(T[] array) {
for (T item : array) {
System.out.println(item);
}
}
}
Main.java
----------------
public class Main {
public static void main(String[] args) {
// Using the Box class with an Integer type
Box<Integer> intBox = new Box<>();
intBox.setValue(10);
System.out.println("Value in intBox: " + intBox.getValue());
// Using the Box class with a String type
Box<String> strBox = new Box<>();
strBox.setValue("Hello, Generics!");
System.out.println("Value in strBox: " + strBox.getValue());
// Using the Utils class to print an array of Integers
Integer[] intArray = {1, 2, 3, 4, 5};
Utils.printArray(intArray);
// Using the Utils class to print an array of Strings
String[] strArray = {"A", "B", "C"};
Utils.printArray(strArray);
}
}
--------------------------------------------------------------------
Generic Method:
java
Copy code
class Utils {
public static <T> void printArray(T[] array) {
for (T item : array) {
System.out.println(item);
}
}
}
Bounded Type Parameters:
Restrict the types used in generics.
java
Copy code
class NumberBox<T extends Number> {
private T value;
void setValue(T value) {
this.value = value;
}
T getValue() {
return value;
}
}
---------------------------------------------------------------------------------
Why it’s Important in Android:
Generics are widely used in Android APIs, such as ArrayAdapter,
RecyclerView.Adapter, and AsyncTask, to ensure type safety and reduce boilerplate
code.
15. Basic Design Patterns
Design patterns are reusable solutions to common problems in software design. They
provide a standard way to structure and organize code.
Key Design Patterns for Android:
Singleton Pattern:
Ensures a class has only one instance and provides global access to it.
Singleton.java
---------------------
public class Singleton {
// Static variable to hold the single instance
private static Singleton instance;
// Private constructor to prevent instantiation
private Singleton() {}
// Public method to provide access to the instance
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Main.java
---------------
public class Main3 {
public static void main(String[] args) {
// Get the Singleton instance
Singleton singleton = Singleton.getInstance();
// Print the instance to check if it's created
System.out.println(singleton);
}
}
Usage in Android: Managing a shared resource like a database or network connection.
Factory Pattern:
Creates objects without exposing the instantiation logic to the client.
Shape.java
---------------
public interface Shape {
void draw();
}
Circle.java
------------------
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a Circle");
}
}
Rectangle.java
---------------------
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a Rectangle");
}
}
ShapeFactory.java
-----------------------------
public class ShapeFactory {
// Factory method to return different shapes based on the input type
public static Shape getShape(String type) {
if ("circle".equalsIgnoreCase(type)) {
return new Circle();
} else if ("rectangle".equalsIgnoreCase(type)) {
return new Rectangle();
}
return null;
}
}
Main.java
---------------
public class Main4 {
public static void main(String[] args) {
// Get a Circle object from the factory
Shape circle = ShapeFactory.getShape("circle");
circle.draw(); // Output: Drawing a Circle
// Get a Rectangle object from the factory
Shape rectangle = ShapeFactory.getShape("rectangle");
rectangle.draw(); // Output: Drawing a Rectangle
// Attempt to get an invalid shape
Shape unknown = ShapeFactory.getShape("triangle");
if (unknown == null) {
System.out.println("Shape not recognized.");
}
}
}
Observer Pattern:
Establishes a one-to-many dependency so when one object changes state, its
dependents are notified.
Observer.java
--------------------
public interface Observer {
void update(String message);
}
Subject.java
-------------------
import java.util.ArrayList;
import java.util.List;
public class Subject {
private List<Observer> observers = new ArrayList<>();
// Add an observer to the list
public void addObserver(Observer observer) {
observers.add(observer);
}
// Remove an observer from the list
public void removeObserver(Observer observer) {
observers.remove(observer);
}
// Notify all observers about a change
public void notifyObservers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
ConcreteObserver.java
-----------------------------------
public class ConcreteObserver implements Observer {
private String name;
public ConcreteObserver(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + " received message: " + message);
}
}
Main.java
------------
public class Main5 {
public static void main(String[] args) {
// Create a subject
Subject subject = new Subject();
// Create observers
Observer observer1 = new ConcreteObserver("Observer 1");
Observer observer2 = new ConcreteObserver("Observer 2");
Observer observer3 = new ConcreteObserver("Observer 3");
// Add observers to the subject
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.addObserver(observer3);
// Notify all observers with a message
subject.notifyObservers("The subject has changed!");
// Remove observer2 and notify again
subject.removeObserver(observer2);
subject.notifyObservers("The subject has changed again!");
}
}
Usage in Android: The LiveData and ViewModel architecture components are based on
this pattern.
Model-View-Controller (MVC) and Model-View-ViewModel (MVVM):
Architectures used to separate concerns in an application. Usage in Android: MVVM
is widely adopted with Jetpack libraries.