50个JAVA常见代码大全(详细注解):学完这篇从Java小白到架构师_java代码
本文详细列举了50个Java编程中的关键代码示例,包括基础语法、数据类型、条件判断、循环、数组、方法、面向对象、继承、接口、抽象类、多态、封装、静态变量、内部类、匿名类、泛型、集合框架、异常处理、文件I/O、多线程、同步以及高级多线程概念,助你从入门到成长为架构师。
目录
基础语法代码示例(1 - 10)
(一)Hello World
(二)数据类型与变量声明
(三)条件判断语句
(四)循环结构
(五)数组操作
(六)方法定义与调用
(七)类与对象
(八)构造方法
(九)继承
(十)接口
面向对象高级特性代码示例(11 - 20)
(十一)抽象类
(十二)方法重载
(十三)方法重写
(十四)多态
(十五)封装
(十六)静态变量和方法
(十七)内部类
(十八)匿名类
(十九)泛型
(二十)集合框架 - ArrayList 与 HashMap
异常处理与高级编程代码示例(21 - 30)
(二十一)异常处理
(二十二)文件读取与写入
(二十三)多线程编程 - 创建线程
(二十四)多线程编程 - 线程同步
(二十五)Lambda 表达式
(二十六)方法引用
(二十七)流式操作
(二十八)反射机制
(二十九)正则表达式
(三十)注解
架构相关代码示例(31 - 40)
(三十一)设计模式 - 单例模式
(三十二)设计模式 - 工厂模式
(三十三)JDBC 操作数据库
(三十四)连接池技术
(三十五)Spring 框架 - IOC 容器
(三十六)Spring 框架 - AOP 切面编程
(三十七)MyBatis 框架
(三十八)微服务架构 - Dubbo 简介与简单示例
(四十二)代码优化 - 合理使用集合
(四十三)代码优化 - 优化循环
(四十四)JVM 调优参数示例
(四十五)安全编码 - SQL 注入防范
(四十六)安全编码 - XSS 攻击防范
(四十七)安全编码 - 密码加密存储
(四十八)性能测试工具使用示例 - JMeter
(四十九)单元测试框架使用示例 - JUnit
基础语法代码示例(1 - 10)
(一)Hello World
public class HelloWorld { public static void main(String[] args) { System.out.println(\"Hello, World!\"); }}
这是 Java 程序的 “Hello World” 示例,也是每个 Java 初学者接触的第一个程序。其中,public class HelloWorld 定义了一个公共类,类名是HelloWorld,类名需与文件名一致(如果是公共类)。public static void main(String[] args) 是 Java 程序的入口方法,程序从这里开始执行 。System.out.println(\"Hello, World!\"); 则是在控制台输出 “Hello, World!” 这句话。
(二)数据类型与变量声明
public class DataTypeExample { public static void main(String[] args) { // 整型 int age = 20; // 长整型 long population = 7_800_000_000L; // 浮点型 float price = 9.99f; // 双精度浮点型 double pi = 3.1415926; // 字符型 char gender = \'M\'; // 布尔型 boolean isStudent = true; // 字符串类型 String name = \"Alice\"; System.out.println(\"年龄:\" + age); System.out.println(\"世界人口:\" + population); System.out.println(\"价格:\" + price); System.out.println(\"圆周率:\" + pi); System.out.println(\"性别:\" + gender); System.out.println(\"是否是学生:\" + isStudent); System.out.println(\"姓名:\" + name); }}
在这个示例中,展示了 Java 中的各种基本数据类型和字符串类型的声明及使用。int 是整型,用于存储整数;long 是长整型,能存储更大范围的整数,后面需加L后缀 ;float 是单精度浮点型,用于存储小数,后面需加f后缀;double 是双精度浮点型,精度更高;char 是字符型,用于存储单个字符,用单引号括起来;boolean 是布尔型,只有true和false两个值 ;String 是字符串类型,用于存储一串字符,用双引号括起来。
(三)条件判断语句
public class ConditionalStatementExample { public static void main(String[] args) { int score = 85; if (score >= 90) { System.out.println(\"成绩等级:A\"); } else if (score >= 80) { System.out.println(\"成绩等级:B\"); } else if (score >= 70) { System.out.println(\"成绩等级:C\"); } else if (score >= 60) { System.out.println(\"成绩等级:D\"); } else { System.out.println(\"成绩等级:E\"); } // 使用switch语句判断成绩等级 int newScore = 78; switch (newScore / 10) { case 10: case 9: System.out.println(\"成绩等级:A\"); break; case 8: System.out.println(\"成绩等级:B\"); break; case 7: System.out.println(\"成绩等级:C\"); break; case 6: System.out.println(\"成绩等级:D\"); break; default: System.out.println(\"成绩等级:E\"); } }}
上述代码展示了if - else if - else和switch两种条件判断语句在 Java 中的运用。if - else if - else 语句根据score的值判断成绩等级并输出相应结果。switch语句则根据newScore除以 10 后的结果来判断成绩等级,当newScore / 10 的结果匹配到case中的值时,就会执行相应的代码块,break语句用于跳出switch语句,避免继续执行其他case。
(四)循环结构
public class LoopExample { public static void main(String[] args) { // for循环 System.out.println(\"for循环输出1到5:\"); for (int i = 1; i <= 5; i++) { System.out.print(i + \" \"); } System.out.println(); // while循环 int j = 1; System.out.println(\"while循环输出1到5:\"); while (j <= 5) { System.out.print(j + \" \"); j++; } System.out.println(); // do - while循环 int k = 1; System.out.println(\"do - while循环输出1到5:\"); do { System.out.print(k + \" \"); k++; } while (k <= 5); System.out.println(); }}
这段代码分别展示了for循环、while循环和do - while循环。for循环适用于已知循环次数的情况,在初始化部分定义循环变量,条件部分判断是否继续循环,更新部分更新循环变量。while循环先判断条件,条件为真时执行循环体,适用于不知道循环次数但知道循环结束条件的情况。do - while循环先执行一次循环体,再判断条件,所以循环体至少会执行一次。
(五)数组操作
public class ArrayExample { public static void main(String[] args) { // 数组声明和初始化 int[] numbers = {1, 2, 3, 4, 5}; // 数组遍历 System.out.println(\"数组元素:\"); for (int i = 0; i < numbers.length; i++) { System.out.print(numbers[i] + \" \"); } System.out.println(); // 增强for循环遍历数组 System.out.println(\"使用增强for循环遍历数组:\"); for (int num : numbers) { System.out.print(num + \" \"); } System.out.println(); }}
这里演示了数组的声明、初始化和遍历。int[] numbers = {1, 2, 3, 4, 5}; 声明并初始化了一个整型数组numbers 。通过普通for循环,利用数组的length属性获取数组长度,对数组进行遍历输出。增强for循环(for - each循环)则更简洁地遍历数组,for (int num : numbers) 中,num依次表示数组中的每个元素。
(六)方法定义与调用
public class MethodExample { // 定义一个方法,计算两个整数的和 public static int add(int a, int b) { return a + b; } public static void main(String[] args) { int result = add(3, 5); System.out.println(\"3和5的和是:\" + result); }}
此示例定义了一个名为add的方法,该方法接收两个int类型的参数a和b,返回它们的和。在main方法中,调用add方法并传入参数 3 和 5,将返回的结果存储在result变量中,然后输出结果。
(七)类与对象
// 定义一个类class Person { String name; int age; // 定义一个方法 public void introduce() { System.out.println(\"我叫\" + name + \",今年\" + age + \"岁。\"); }}public class ClassAndObjectExample { public static void main(String[] args) { // 创建对象 Person person = new Person(); person.name = \"Bob\"; person.age = 25; // 调用对象的方法 person.introduce(); }}
在这个代码中,首先定义了一个Person类,它包含两个成员变量name和age,以及一个成员方法introduce 。在main方法所在的类中,创建了Person类的对象person,通过对象可以访问和修改成员变量,并调用成员方法。person.name = \"Bob\"; 和person.age = 25; 分别给对象的成员变量赋值,person.introduce(); 调用对象的introduce方法输出个人信息。
(八)构造方法
class Dog { String name; int age; // 构造方法 public Dog(String dogName, int dogAge) { name = dogName; age = dogAge; } public void bark() { System.out.println(name + \" 汪汪叫,它\" + age + \"岁了。\"); }}public class ConstructorExample { public static void main(String[] args) { // 创建对象并通过构造方法初始化 Dog myDog = new Dog(\"旺财\", 3); myDog.bark(); }}
这里定义了一个Dog类,其中包含一个构造方法Dog(String dogName, int dogAge) 。构造方法的名称与类名相同,没有返回值类型(连void也没有) 。当创建Dog类的对象时,会调用构造方法进行初始化,如Dog myDog = new Dog(\"旺财\", 3); 就通过构造方法给Dog对象的name和age成员变量赋了初始值,然后调用bark方法输出狗的信息。
(九)继承
// 父类class Animal { String name; public Animal(String name) { this.name = name; } public void eat() { System.out.println(name + \" 正在吃东西。\"); }}// 子类class Dog extends Animal { public Dog(String name) { super(name); } public void bark() { System.out.println(name + \" 汪汪叫。\"); }}public class InheritanceExample { public static void main(String[] args) { Dog myDog = new Dog(\"小白\"); myDog.eat(); myDog.bark(); }}
此代码展示了 Java 中的继承关系。Dog类继承自Animal类,使用extends关键字表示继承。Dog类继承了Animal类的成员变量name和成员方法eat 。在Dog类的构造方法中,通过super(name)调用父类的构造方法,完成对从父类继承来的成员变量name的初始化 。Dog类还定义了自己特有的方法bark 。在main方法中,创建Dog类的对象myDog,可以调用从父类继承的方法eat和自身定义的方法bark。
(十)接口
// 定义一个接口interface Shape { double getArea();}// 实现接口的类class Circle implements Shape { private double radius; public Circle(double radius) { this.radius = radius; } @Override public double getArea() { return Math.PI * radius * radius; }}public class InterfaceExample { public static void main(String[] args) { Circle circle = new Circle(5); System.out.println(\"圆的面积是:\" + circle.getArea()); }}
这里首先定义了一个接口Shape,接口中只有一个抽象方法getArea ,用于获取图形的面积。然后定义了一个Circle类,通过implements关键字实现了Shape接口,必须实现接口中定义的抽象方法getArea ,在getArea方法中计算并返回圆的面积。在main方法中,创建Circle类的对象circle,并调用getArea方法获取圆的面积并输出。
面向对象高级特性代码示例(11 - 20)
(十一)抽象类
// 抽象类abstract class Shape { // 抽象方法,没有方法体,由子类实现 public abstract double getArea(); // 普通方法 public void display() { System.out.println(\"这是一个图形\"); }}// 具体子类,继承抽象类并实现抽象方法class Circle extends Shape { private double radius; public Circle(double radius) { this.radius = radius; } @Override public double getArea() { return Math.PI * radius * radius; }}
在 Java 中,使用abstract关键字定义抽象类,抽象类不能被实例化,主要为子类提供一个通用的框架。抽象类中可以包含抽象方法(如getArea),抽象方法只有方法声明而没有方法体,必须由子类实现 。也可以包含普通方法(如display) 。Circle类继承自Shape抽象类,并实现了getArea抽象方法,这样就可以创建Circle类的对象并调用其方法。抽象类常用于定义一些具有共性的行为或属性,让子类去实现具体的细节,比如在图形绘制系统中,Shape抽象类可以作为各种具体图形类(如圆形、矩形、三角形等)的父类,定义一些通用的图形操作方法,每个具体图形类根据自身特点实现这些方法。
(十二)方法重载
public class MethodOverloading { // 方法重载示例1:无参方法 public void print() { System.out.println(\"这是无参的print方法\"); } // 方法重载示例2:一个参数的方法 public void print(String message) { System.out.println(\"打印消息:\" + message); } // 方法重载示例3:两个参数的方法 public void print(int num1, int num2) { System.out.println(\"两个整数的和是:\" + (num1 + num2)); } public static void main(String[] args) { MethodOverloading overloading = new MethodOverloading(); overloading.print(); overloading.print(\"Hello, Java!\"); overloading.print(3, 5); }}
方法重载是指在同一个类中,定义多个方法名相同,但参数列表不同(参数个数、类型、顺序不同)的方法。在上述示例中,print方法被重载了三次,分别是无参的print方法、带一个String参数的print方法和带两个int参数的print方法 。编译器会根据调用方法时传入的参数来决定调用哪个方法。方法重载常用于实现一些功能相似但参数不同的操作,比如打印不同类型的数据,提高代码的可读性和复用性 。
(十三)方法重写
class Animal { public void makeSound() { System.out.println(\"动物发出声音\"); }}class Dog extends Animal { @Override public void makeSound() { System.out.println(\"狗发出汪汪的声音\"); }}public class MethodOverridingExample { public static void main(String[] args) { Animal animal1 = new Animal(); Animal animal2 = new Dog(); animal1.makeSound(); // 输出:动物发出声音 animal2.makeSound(); // 输出:狗发出汪汪的声音 }}
方法重写是指子类中定义了与父类中方法签名(方法名、参数列表、返回类型)完全相同的方法。在这个例子中,Dog类继承自Animal类,并重写了makeSound方法。当通过Animal类型的引用调用makeSound方法时,如果引用指向的是Animal对象,就调用Animal类中的makeSound方法;如果引用指向的是Dog对象,就调用Dog类中重写后的makeSound方法,这体现了多态性 。方法重写的规则包括:子类方法的访问修饰符不能比父类方法的更严格;子类方法不能抛出比父类方法声明的更多的异常等。方法重写常用于子类根据自身需求对父类方法进行个性化的实现,比如不同动物的叫声不同,就可以通过重写makeSound方法来体现。
(十四)多态
class Shape { public void draw() { System.out.println(\"绘制图形\"); }}class Circle extends Shape { @Override public void draw() { System.out.println(\"绘制圆形\"); }}class Rectangle extends Shape { @Override public void draw() { System.out.println(\"绘制矩形\"); }}public class PolymorphismExample { public static void main(String[] args) { Shape shape1 = new Circle(); Shape shape2 = new Rectangle(); shape1.draw(); // 输出:绘制圆形 shape2.draw(); // 输出:绘制矩形 }}
多态是指同一个行为具有多种不同的表现形式。在 Java 中,多态通过继承和方法重写来实现 。在上述代码中,Circle类和Rectangle类都继承自Shape类,并重写了draw方法。通过Shape类型的引用可以指向不同子类的对象,在调用draw方法时,会根据对象的实际类型来调用相应子类重写后的方法,从而实现不同的绘制效果。多态的优点包括提高代码的可扩展性和维护性,使得程序更加灵活,比如在图形绘制系统中,只需要通过Shape类型的引用就可以操作各种具体的图形对象,而不需要针对每个图形类型编写不同的代码。
(十五)封装
public class BankAccount { // 私有属性,外部无法直接访问 private double balance; // 构造方法,初始化余额 public BankAccount(double initialBalance) { balance = initialBalance; } // 存款方法 public void deposit(double amount) { if (amount > 0) { balance += amount; System.out.println(\"存款成功,当前余额为:\" + balance); } else { System.out.println(\"存款金额必须大于0\"); } } // 取款方法 public void withdraw(double amount) { if (amount > 0 && amount <= balance) { balance -= amount; System.out.println(\"取款成功,当前余额为:\" + balance); } else { System.out.println(\"取款金额无效或余额不足\"); } } // 获取余额方法 public double getBalance() { return balance; }}
封装是指将对象的属性和行为包装在一个类中,并通过访问修饰符(如private)限制对属性的直接访问 。在BankAccount类中,balance属性被声明为private,外部代码无法直接访问和修改它,只能通过类提供的deposit、withdraw和getBalance等公共方法来操作余额。这样可以保护数据的安全性和完整性,避免外部代码对数据的非法操作,同时也提供了统一的访问接口,方便代码的维护和扩展。例如,在银行系统中,通过封装可以确保账户余额的操作符合业务规则,防止余额出现负数等异常情况。
(十六)静态变量和方法
public class MathUtils { // 静态变量,用于存储圆周率 public static final double PI = 3.1415926; // 静态方法,计算圆的面积 public static double calculateCircleArea(double radius) { return PI * radius * radius; } // 普通方法 public double add(double num1, double num2) { return num1 + num2; }}
静态变量和方法属于类,而不属于类的某个具体对象。在MathUtils类中,PI是一个静态常量,它的值对于所有MathUtils类的对象都是相同的,并且在类加载时就被初始化 。calculateCircleArea是一个静态方法,可以直接通过类名调用,如MathUtils.calculateCircleArea(5) ,不需要创建MathUtils类的对象。静态成员的特点是可以在不创建对象的情况下访问,常用于工具类中,提供一些通用的方法和常量,方便在程序的各个地方使用,比如Math类中的sqrt、abs等静态方法,以及System类中的out静态变量。
(十七)内部类
public class OuterClass { private int outerData = 10; // 内部类 class InnerClass { public void display() { System.out.println(\"访问外部类的私有数据:\" + outerData); } } public void testInnerClass() { InnerClass inner = new InnerClass(); inner.display(); }}
内部类是定义在另一个类内部的类。在上述示例中,InnerClass是OuterClass的内部类,它可以访问外部类的私有成员(如outerData) 。内部类的使用场景包括:当一个类的存在仅仅是为了辅助外部类的功能,并且和外部类紧密相关时,可以将其定义为内部类;内部类可以实现更细粒度的访问控制,隐藏一些内部实现细节 。创建内部类的对象需要先创建外部类的对象,如在testInnerClass方法中,先创建OuterClass的对象,然后通过该对象创建InnerClass的对象并调用其方法。
(十八)匿名类
interface MessagePrinter { void printMessage();}public class AnonymousClassExample { public static void main(String[] args) { // 使用匿名类实现MessagePrinter接口 MessagePrinter printer = new MessagePrinter() { @Override public void printMessage() { System.out.println(\"这是一条匿名类打印的消息\"); } }; printer.printMessage(); }}
匿名类是没有名字的类,通常用于创建一个临时的类对象,该对象实现了某个接口或继承了某个类。在这个例子中,通过匿名类实现了MessagePrinter接口,并重写了printMessage方法 。匿名类的主要使用场景是当只需要创建一个实现某个接口或继承某个类的对象,并且该对象只使用一次时,可以使用匿名类来简化代码,避免定义一个单独的类。比如在事件处理中,常常使用匿名类来创建事件监听器对象。
(十九)泛型
// 泛型类class Box { private T value; public void setValue(T value) { this.value = value; } public T getValue() { return value; }}// 泛型方法class GenericMethod { public static void printArray(E[] array) { for (E element : array) { System.out.print(element + \" \"); } System.out.println(); }}
泛型是 Java 5.0 引入的新特性,它允许在定义类、接口和方法时使用类型参数 。在Box类中,是类型参数,T可以代表任何类型,这样Box类就可以存储不同类型的数据,而不需要为每种类型都定义一个单独的类。在GenericMethod类中,printArray方法是一个泛型方法,是类型参数,该方法可以打印任何类型数组的元素。泛型的优势包括提高代码的类型安全性,避免类型转换错误;增强代码的复用性,一个泛型类或方法可以适用于多种数据类型 。例如,在集合框架中广泛使用了泛型,ArrayList表示一个存储Integer类型数据的列表,HashMap表示一个键为String类型,值为Double类型的映射表。
(二十)集合框架 - ArrayList 与 HashMap
import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;public class CollectionExample { public static void main(String[] args) { // ArrayList使用示例 List names = new ArrayList(); names.add(\"Alice\"); names.add(\"Bob\"); names.add(\"Charlie\"); for (String name : names) { System.out.println(name); } // HashMap使用示例 Map ages = new HashMap(); ages.put(\"Alice\", 25); ages.put(\"Bob\", 30); ages.put(\"Charlie\", 35); for (Map.Entry entry : ages.entrySet()) { System.out.println(entry.getKey() + \" 的年龄是 \" + entry.getValue()); } }}
ArrayList是 Java 集合框架中的一个动态数组,它实现了List接口,允许存储重复的元素,并且可以根据索引快速访问元素 。在上述示例中,创建了一个ArrayList对象names,并使用add方法添加元素,通过增强for循环遍历输出元素。HashMap是一个基于哈希表的Map接口实现,它存储键值对,并且通过键来快速查找值,键不能重复 。在示例中,创建了一个HashMap对象ages,使用put方法添加键值对,通过entrySet方法获取键值对集合,并遍历输出每个键值对。集合框架是 Java 编程中非常重要的一部分,提供了丰富的数据结构和算法,方便开发者处理各种数据存储和操作需求,比如在电商系统中,ArrayList可以用于存储商品列表,HashMap可以用于存储用户信息及其对应的订单信息。
异常处理与高级编程代码示例(21 - 30)
(二十一)异常处理
public class ExceptionHandlingExample { public static void main(String[] args) { try { int result = 10 / 0; // 会抛出ArithmeticException异常 System.out.println(\"结果:\" + result); } catch (ArithmeticException e) { System.out.println(\"捕获到异常:\" + e.getMessage()); e.printStackTrace(); } finally { System.out.println(\"这是finally块,无论是否发生异常都会执行\"); } }}
在 Java 中,异常处理通过try - catch - finally 块来实现。try块中放置可能会抛出异常的代码 ,如int result = 10 / 0; 会抛出ArithmeticException异常(除以零异常) 。catch块用于捕获并处理特定类型的异常,这里catch (ArithmeticException e) 捕获ArithmeticException异常,并输出异常信息和堆栈跟踪信息 。finally块中的代码无论是否发生异常都会执行,常用于释放资源,如关闭文件、数据库连接等 。异常处理可以提高程序的健壮性,避免因异常导致程序崩溃。
(二十二)文件读取与写入
import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.FileReader;import java.io.FileWriter;import java.io.IOException;public class FileIOExample { public static void main(String[] args) { String filePath = \"example.txt\"; // 文件写入 try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) { writer.write(\"这是要写入文件的第一行内容\\n\"); writer.write(\"这是第二行内容\\n\"); } catch (IOException e) { e.printStackTrace(); } // 文件读取 try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) { String line; while ((line = reader.readLine()) != null) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } }}
这段代码展示了 Java 中文件读取和写入的基本操作。使用BufferedWriter和FileWriter进行文件写入,BufferedWriter提供了更高效的写入方式,FileWriter用于创建文件并写入数据 。writer.write(\"这是要写入文件的第一行内容\\n\"); 等语句将内容写入文件,\\n 表示换行 。使用try - with - resources语句来自动关闭资源,这样可以确保文件在使用后被正确关闭,避免资源泄漏 。文件读取使用BufferedReader和FileReader,BufferedReader提供了按行读取的方法readLine,通过循环不断读取文件的每一行并输出。文件 I/O 操作在 Java 中常用于处理配置文件、日志文件等各种数据文件 。
(二十三)多线程编程 - 创建线程
// 继承Thread类创建线程class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(\"Thread 1: \" + i); } }}// 实现Runnable接口创建线程class MyRunnable implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(\"Thread 2: \" + i); } }}public class ThreadCreationExample { public static void main(String[] args) { MyThread thread1 = new MyThread(); thread1.start(); MyRunnable myRunnable = new MyRunnable(); Thread thread2 = new Thread(myRunnable); thread2.start(); }}
在 Java 中,创建线程有两种常见方式。一种是继承Thread类,并重写run方法,run方法中定义了线程执行的任务 ,如MyThread类。通过创建MyThread类的对象并调用start方法来启动线程,start方法会启动一个新线程并调用run方法 。另一种方式是实现Runnable接口,同样重写run方法,如MyRunnable类。然后创建Thread类的对象,并将实现了Runnable接口的对象作为参数传递给Thread的构造函数,最后调用start方法启动线程。继承Thread类的方式更简单直接,但由于 Java 不支持多重继承,如果一个类已经继承了其他类,就无法再继承Thread类,此时应选择实现Runnable接口的方式,这种方式还可以更好地实现资源共享,比如多个线程可以共享同一个实现了Runnable接口的对象的资源。多线程编程可以提高程序的执行效率,充分利用 CPU 资源,在服务器端编程、图形界面编程等地方有广泛应用 。
(二十四)多线程编程 - 线程同步
public class SynchronizedExample { private int count = 0; public synchronized void increment() { count++; System.out.println(\"当前线程:\" + Thread.currentThread().getName() + \",count = \" + count); } public static void main(String[] args) { SynchronizedExample example = new SynchronizedExample(); Thread thread1 = new Thread(() -> { for (int i = 0; i { for (int i = 0; i < 5; i++) { example.increment(); } }); thread1.start(); thread2.start(); try { thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } }}
在多线程环境下,当多个线程同时访问共享资源时,可能会出现数据不一致等问题,线程同步用于解决这些问题。在这个示例中,使用synchronized关键字修饰increment方法,当一个线程进入increment方法时,它会获取对象的锁,其他线程必须等待锁的释放才能进入该方法 ,从而保证了同一时间只有一个线程可以执行increment方法,避免了count变量的竞争条件 。Thread.currentThread().getName() 用于获取当前线程的名称,方便区分不同线程的操作。join方法用于等待线程执行完毕,在main方法中,通过调用thread1.join() 和thread2.join() 确保两个线程都执行完后再继续执行main方法的后续代码 。除了synchronized关键字,还可以使用Lock接口(如ReentrantLock)等方式实现线程同步 。
(二十五)Lambda 表达式
import java.util.Arrays;import java.util.List;import java.util.stream.Collectors;public class LambdaExample { public static void main(String[] args) { List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // 使用Lambda表达式过滤出偶数 List evenNumbers = numbers.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList()); System.out.println(\"偶数列表:\" + evenNumbers); }}
Lambda 表达式是 Java 8 引入的重要特性,它可以简化代码,使代码更加简洁和易读 。在这个例子中,numbers.stream() 将List转换为流,filter(n -> n % 2 == 0) 使用 Lambda 表达式过滤出列表中的偶数,n -> n % 2 == 0 表示一个匿名函数,它接收一个整数n,如果n是偶数则返回true,否则返回false 。collect(Collectors.toList()) 将过滤后的结果收集到一个新的List中 。Lambda 表达式还常用于与函数式接口(如Runnable、Comparator等)一起使用,例如Runnable runnable = () -> System.out.println(\"Hello, Lambda!\"); 创建一个使用 Lambda 表达式实现的Runnable对象 。Lambda 表达式使代码更紧凑,减少了样板代码,提高了代码的可读性和可维护性 。
(二十六)方法引用
import java.util.Arrays;import java.util.List;public class MethodReferenceExample { public static void printNumber(int number) { System.out.println(number); } public static void main(String[] args) { List numbers = Arrays.asList(1, 2, 3, 4, 5); // 使用方法引用 numbers.forEach(MethodReferenceExample::printNumber); }}
方法引用是 Lambda 表达式的一种更简洁的表达方式,它允许直接引用已有的方法 。在这个示例中,MethodReferenceExample::printNumber 是一个方法引用,它引用了MethodReferenceExample类中的printNumber方法 。numbers.forEach(MethodReferenceExample::printNumber); 相当于numbers.forEach(n -> printNumber(n)); ,使用方法引用使代码更加简洁明了 。方法引用有四种形式:静态方法引用(如ClassName::staticMethod)、实例方法引用(如instance::instanceMethod)、对象方法引用(如variable::instanceMethod)和构造方法引用(如ClassName::new) 。方法引用进一步简化了代码,提高了代码的可读性,特别是在处理一些简单的函数式操作时,能够让代码更加优雅 。
(二十七)流式操作
import java.util.Arrays;import java.util.List;import java.util.Optional;public class StreamExample { public static void main(String[] args) { List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // 计算列表中所有数的和 int sum = numbers.stream() .mapToInt(Integer::intValue) .sum(); System.out.println(\"列表中所有数的和:\" + sum); // 查找列表中的最大值 Optional max = numbers.stream() .max(Integer::compareTo); max.ifPresent(System.out::println); }}
Java 8 引入的 Stream API 提供了一种强大的流式操作方式,用于处理集合数据 。在上述代码中,numbers.stream() 将List转换为流,mapToInt(Integer::intValue) 将流中的每个Integer对象转换为int类型 ,sum方法计算流中所有元素的和 。通过流式操作,可以以一种声明式的方式对集合进行过滤、映射、归约等操作,使代码更加简洁和易读 。Optional类用于处理可能为null的值,max方法返回一个Optional对象,ifPresent(System.out::println) 用于判断Optional中是否有值,如果有则输出该值 。Stream API 还支持并行流操作,可以充分利用多核 CPU 的优势,提高数据处理效率 ,例如numbers.parallelStream() 使用并行流进行操作 。
(二十八)反射机制
import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public void sayHello() { System.out.println(\"Hello, my name is \" + name + \", and I\'m \" + age + \" years old.\"); }}public class ReflectionExample { public static void main(String[] args) throws Exception { Class personClass = Class.forName(\"Person\"); // 获取构造函数并创建对象 Constructor constructor = personClass.getConstructor(String.class, int.class); Object person = constructor.newInstance(\"Alice\", 25); // 获取私有字段并设置值 Field nameField = personClass.getDeclaredField(\"name\"); nameField.setAccessible(true); nameField.set(person, \"Bob\"); // 获取方法并调用 Method sayHelloMethod = personClass.getMethod(\"sayHello\"); sayHelloMethod.invoke(person); }}
反射机制允许程序在运行时获取类的信息(如类的字段、方法、构造函数等),并动态地操作类的对象 。在这个示例中,首先通过Class.forName(\"Person\") 获取Person类的Class对象 。然后使用getConstructor方法获取指定参数类型的构造函数,并通过newInstance方法创建Person类的对象 。通过getDeclaredField获取私有字段name,并使用setAccessible(true) 打破封装,使其可以被访问和修改,然后使用set方法设置字段的值 。使用getMethod获取sayHello方法,并通过invoke方法调用该方法 。反射机制在框架开发(如 Spring 框架)、测试框架等地方有广泛应用,它提供了很大的灵活性,但由于反射操作会带来一定的性能开销,应谨慎使用 。
(二十九)正则表达式
import java.util.regex.Matcher;import java.util.regex.Pattern;public class RegexExample { public static void main(String[] args) { String text = \"My email is example@example.com, and yours might be test@test.com\"; String pattern = \"\\\\w+@\\\\w+\\\\.\\\\w+\"; Pattern r = Pattern.compile(pattern); Matcher m = r.matcher(text); while (m.find()) { System.out.println(\"找到的邮箱地址:\" + m.group()); } }}
正则表达式是一种用于匹配和处理字符串的强大工具 。在这个例子中,定义了一个正则表达式\\\\w+@\\\\w+\\\\.\\\\w+ ,用于匹配邮箱地址 。\\\\w 表示任意单词字符(字母、数字、下划线),+ 表示前面的字符出现一次或多次 ,@ 和. 是邮箱地址中的固定字符 。通过Pattern.compile(pattern) 将正则表达式编译成Pattern对象,然后使用matcher方法创建Matcher对象,用于在字符串中进行匹配 。m.find() 方法查找下一个匹配项,m.group() 返回找到的匹配字符串 。通过正则表达式可以进行字符串的验证(如验证邮箱格式、电话号码格式等)、替换、分割等操作,在数据处理、文本解析等场景中非常有用 。
(三十)注解
import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;// 定义一个注解@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@interface MyAnnotation { String value() default \"\";}public class AnnotationExample { @MyAnnotation(\"这是一个示例注解\") public void myMethod() { System.out.println(\"这是被注解的方法\"); } public static void main(String[] args) { AnnotationExample example = new AnnotationExample(); example.myMethod(); }}
注解是 Java 5.0 引入的一种元数据机制,它可以为程序元素(类、方法、字段等)添加额外的信息 。在这个示例中,首先使用@Retention(RetentionPolicy.RUNTIME) 和@Target(ElementType.METHOD) 定义了一个注解MyAnnotation 。@Retention(RetentionPolicy.RUNTIME) 表示该注解在运行时可用 ,@Target(ElementType.METHOD) 表示该注解只能用于方法 。MyAnnotation注解中定义了一个value属性,并有默认值\"\" 。在myMethod方法上使用@MyAnnotation(\"这是一个示例注解\") 应用注解,并设置value属性的值 。注解在框架开发中常用于配置信息(如 Spring 的@Component、@Autowired等注解)、代码生成、测试框架(如 JUnit 的@Test注解)等方面,它可以减少代码中的硬编码,提高代码的可维护性和可扩展性 。
架构相关代码示例(31 - 40)
(三十一)设计模式 - 单例模式
// 饿汉式单例模式class SingletonHungry { // 私有静态实例,在类加载时就创建 private static final SingletonHungry instance = new SingletonHungry(); // 私有构造函数,防止外部实例化 private SingletonHungry() {} // 提供公共的静态方法获取实例 public static SingletonHungry getInstance() { return instance; }}// 懒汉式单例模式class SingletonLazy { // 私有静态实例,初始化为null private static SingletonLazy instance = null; // 私有构造函数,防止外部实例化 private SingletonLazy() {} // 提供公共的静态方法获取实例,线程不安全版本 public static SingletonLazy getInstance() { if (instance == null) { instance = new SingletonLazy(); } return instance; } // 线程安全的懒汉式单例,使用synchronized关键字 public static synchronized SingletonLazy getInstanceSafe() { if (instance == null) { instance = new SingletonLazy(); } return instance; } // 双重检查锁实现线程安全的懒汉式单例,提高性能 public static SingletonLazy getInstanceDoubleCheck() { if (instance == null) { synchronized (SingletonLazy.class) { if (instance == null) { instance = new SingletonLazy(); } } } return instance; }}
单例模式确保一个类在整个应用程序中只有一个实例,并提供一个全局访问点。饿汉式单例在类加载时就创建实例,优点是线程安全,缺点是可能在不需要该实例时就创建了,造成资源浪费。懒汉式单例在第一次调用getInstance方法时才创建实例,实现了延迟加载 。但普通的懒汉式单例在多线程环境下是不安全的,可能会创建多个实例 。使用synchronized关键字修饰getInstance方法可以保证线程安全,但每次调用都要获取锁,性能较低 。双重检查锁机制在保证线程安全的同时,提高了性能,只有在实例为null时才进行同步操作 。单例模式常用于管理资源,如数据库连接池、线程池等,避免资源的重复创建和浪费 。
(三十二)设计模式 - 工厂模式
// 简单工厂模式// 抽象产品类interface Shape { void draw();}// 具体产品类:圆形class Circle implements Shape { @Override public void draw() { System.out.println(\"绘制圆形\"); }}// 具体产品类:矩形class Rectangle implements Shape { @Override public void draw() { System.out.println(\"绘制矩形\"); }}// 简单工厂类class ShapeFactory { public Shape createShape(String shapeType) { if (\"circle\".equalsIgnoreCase(shapeType)) { return new Circle(); } else if (\"rectangle\".equalsIgnoreCase(shapeType)) { return new Rectangle(); } return null; }}// 工厂方法模式// 抽象工厂类abstract class AbstractShapeFactory { public abstract Shape createShape();}// 具体工厂类:圆形工厂class CircleFactory extends AbstractShapeFactory { @Override public Shape createShape() { return new Circle(); }}// 具体工厂类:矩形工厂class RectangleFactory extends AbstractShapeFactory { @Override public Shape createShape() { return new Rectangle(); }}
工厂模式是一种创建对象的设计模式,它将对象的创建和使用分离,提高了代码的可维护性和可扩展性 。简单工厂模式通过一个工厂类根据传入的参数创建不同类型的产品对象,在ShapeFactory类中,根据shapeType参数创建Circle或Rectangle对象 。但简单工厂模式不符合开闭原则(对扩展开放,对修改关闭),当需要添加新的产品类型时,需要修改工厂类的代码 。工厂方法模式定义了一个创建对象的接口,由子类决定创建哪个具体产品类的实例 。在工厂方法模式中,每个具体产品都有对应的具体工厂类,如CircleFactory创建Circle对象,RectangleFactory创建Rectangle对象 。当需要添加新的产品类型时,只需要添加新的具体产品类和具体工厂类,而不需要修改现有的工厂类代码,符合开闭原则 。
(三十三)JDBC 操作数据库
import java.sql.Connection;import java.sql.DriverManager;import java.sql.ResultSet;import java.sql.Statement;public class JdbcExample { public static void main(String[] args) { String url = \"jdbc:mysql://localhost:3306/mydb\"; String username = \"root\"; String password = \"password\"; try (Connection connection = DriverManager.getConnection(url, username, password); Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(\"SELECT * FROM users\")) { while (resultSet.next()) { int id = resultSet.getInt(\"id\"); String name = resultSet.getString(\"name\"); int age = resultSet.getInt(\"age\"); System.out.println(\"ID: \" + id + \", Name: \" + name + \", Age: \" + age); } } catch (Exception e) { e.printStackTrace(); } }}
JDBC(Java Database Connectivity)是 Java 访问数据库的标准 API,它允许 Java 程序与各种关系型数据库进行交互 。在这个示例中,首先通过DriverManager.getConnection方法建立与数据库的连接,需要传入数据库的 URL、用户名和密码 。然后创建Statement对象,用于执行 SQL 语句 。通过statement.executeQuery方法执行查询语句,并返回一个ResultSet对象,该对象包含了查询结果 。使用resultSet.next方法遍历结果集,通过resultSet.getInt和resultSet.getString等方法获取每一行的数据并输出 。在实际应用中,还需要处理异常、关闭连接等操作,以确保程序的健壮性和资源的正确释放 。
(三十四)连接池技术
import com.mchange.v2.c3p0.ComboPooledDataSource;import javax.sql.DataSource;import java.sql.Connection;import java.sql.SQLException;public class ConnectionPoolExample { public static void main(String[] args) { DataSource dataSource = new ComboPooledDataSource(); try { ((ComboPooledDataSource) dataSource).setDriverClass(\"com.mysql.cj.jdbc.Driver\"); ((ComboPooledDataSource) dataSource).setJdbcUrl(\"jdbc:mysql://localhost:3306/mydb\"); ((ComboPooledDataSource) dataSource).setUser(\"root\"); ((ComboPooledDataSource) dataSource).setPassword(\"password\"); Connection connection = dataSource.getConnection(); System.out.println(\"获取到连接: \" + connection); connection.close(); } catch (Exception e) { e.printStackTrace(); } }}
数据库连接池技术用于管理数据库连接,提高数据库操作性能。C3P0 是一个常用的开源数据库连接池框架 。在这个示例中,创建了一个ComboPooledDataSource对象作为数据源 ,通过设置数据源的属性,如驱动类、URL、用户名和密码,来配置数据库连接信息 。使用dataSource.getConnection方法从连接池中获取一个数据库连接 ,获取连接后可以进行数据库操作,操作完成后关闭连接,连接会被归还到连接池中,而不是真正关闭,这样可以避免频繁创建和销毁连接带来的开销,提高系统性能 。除了 C3P0,还有 DBCP、HikariCP 等其他优秀的数据库连接池框架 。
(三十五)Spring 框架 - IOC 容器
假设我们有一个简单的 Spring 项目,包含一个服务类和一个数据访问类:
// 数据访问类public class UserDao { public void save() { System.out.println(\"保存用户数据\"); }}// 服务类,依赖UserDaopublic class UserService { private UserDao userDao; // 使用构造函数注入 public UserService(UserDao userDao) { this.userDao = userDao; } public void register() { System.out.println(\"用户注册服务开始\"); userDao.save(); System.out.println(\"用户注册服务结束\"); }}
配置 Spring 的 IOC 容器(使用 XML 配置):
测试代码:
import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class Main { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext(\"applicationContext.xml\"); UserService userService = context.getBean(\"userService\", UserService.class); userService.register(); }}
Spring 框架的 IOC(Inversion of Control)容器通过控制反转,将对象的创建和依赖关系的管理从应用程序代码中转移到 Spring 容器中 。在上述示例中,UserService类依赖于UserDao类,通过构造函数注入的方式,将UserDao的实例注入到UserService中 。在 Spring 的 XML 配置文件中,定义了userDao和userService两个 Bean,userService的构造函数参数通过constructor-arg标签引用userDao 。在测试代码中,通过ClassPathXmlApplicationContext加载配置文件,创建 Spring 容器,然后从容器中获取userService的实例,并调用其register方法 。IOC 容器使得代码的耦合度降低,提高了代码的可维护性和可测试性 。
(三十六)Spring 框架 - AOP 切面编程
import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.springframework.stereotype.Component;@Aspect@Componentpublic class LogAspect { @Around(\"execution(* com.example.UserService.*(..))\") public Object logMethod(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println(\"方法开始执行: \" + joinPoint.getSignature().getName()); long startTime = System.currentTimeMillis(); try { return joinPoint.proceed(); } finally { long endTime = System.currentTimeMillis(); System.out.println(\"方法执行结束: \" + joinPoint.getSignature().getName() + \", 耗时: \" + (endTime - startTime) + \"ms\"); } }}
Spring AOP(Aspect - Oriented Programming)是一种面向切面编程的实现,它允许开发者将横切关注点(如日志记录、事务管理、权限控制等)从业务逻辑中分离出来,以提高代码的可维护性和可复用性 。在这个示例中,定义了一个LogAspect切面类,使用@Aspect注解标识它是一个切面 ,使用@Component注解将其纳入 Spring 容器管理 。@Around注解定义了一个环绕通知,execution(* com.example.UserService.*(..)) 是一个切点表达式,表示匹配com.example.UserService类中的所有方法 。在logMethod方法中,在方法执行前记录开始时间并输出日志,然后通过joinPoint.proceed()调用目标方法,方法执行后记录结束时间并输出方法执行耗时的日志 。通过 AOP,可以在不修改业务方法代码的情况下,为其添加通用的功能 。
(三十七)MyBatis 框架
假设我们有一个用户表users,对应的 Java 实体类User和数据访问接口UserMapper:
// 用户实体类public class User { private int id; private String name; private int age; // 省略getter和setter方法}// 用户数据访问接口public interface UserMapper { User getUserById(int id);}
MyBatis 的配置文件mybatis-config.xml:
UserMapper.xml映射文件:
SELECT * FROM users WHERE id = #{id}
测试代码:
import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.IOException;import java.io.Reader;public class Main { public static void main(String[] args) throws IOException { String resource = \"mybatis-config.xml\"; Reader reader = Resources.getResourceAsReader(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); try (SqlSession sqlSession = sqlSessionFactory.openSession()) { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.getUserById(1); System.out.println(user); } }}
MyBatis 是一个优秀的持久层框架,它简化了数据库操作 。在上述示例中,通过 MyBatis 的配置文件mybatis-config.xml配置了数据库连接信息和事务管理器 ,并指定了映射文件UserMapper.xml 。在UserMapper.xml中,定义了getUserById查询语句,使用#{id}占位符传递参数 。在测试代码中,首先通过Resources.getResourceAsReader读取配置文件,然后使用SqlSessionFactoryBuilder构建SqlSessionFactory ,通过SqlSessionFactory打开SqlSession 。从SqlSession中获取UserMapper接口的代理对象,调用其getUserById方法查询用户数据 。MyBatis 通过映射文件将 SQL 语句与 Java 代码解耦,提高了代码的可读性和可维护性 。
(三十八)微服务架构 - Dubbo 简介与简单示例
Dubbo 是一款高性能的 Java RPC(Remote Procedure Call)框架,用于构建分布式系统和微服务架构 。它提供了服务注册与发现、负载均衡、容错机制等功能,使得微服务之间的通信更加高效和可靠 。
假设我们有一个简单的微服务示例,包含一个服务提供者和一个服务消费者:
服务提供者:
// 定义服务接口public interface HelloService { String sayHello(String name);}// 实现服务接口public class HelloServiceImpl implements HelloService { @Override public String sayHello(String name) { return \"Hello, \" + name; }}
Dubbo 服务提供者的配置文件dubbo-provider.xml:
性能优化与安全相关代码示例(41 - 50)
### (四十一)代码优化 - 避免创建不必要的对象
```java
// 不优化的代码public class UnoptimizedObjectCreation {public static void main(String[] args) {for (int i = 0; i < 1000; i++) {String message = new String(\"Hello\");System.out.println(message);}}}// 优化后的代码public class OptimizedObjectCreation {public static void main(String[] args) {String message = \"Hello\";for (int i = 0; i < 1000; i++) {System.out.println(message);}}}
在不优化的代码中,每次循环都创建一个新的String对象,这会消耗额外的内存和时间 。而优化后的代码,只创建一个String对象,然后在循环中重复使用,大大提高了性能 。对于一些不可变对象(如String),如果内容相同,尽量复用已有的对象,避免频繁创建新对象 。在实际开发中,很多对象的创建可能是在复杂的业务逻辑中,要注意识别那些不必要的对象创建操作,比如在一个频繁调用的方法中,如果每次都创建一个临时的日志记录对象,就可以考虑将其改为方法外创建,方法内复用,这样可以显著减少内存开销和垃圾回收的压力,提升系统整体性能。
(四十二)代码优化 - 合理使用集合
import java.util.ArrayList;import java.util.LinkedList;import java.util.List;public class CollectionUsageOptimization { public static void main(String[] args) { // 需求:需要频繁插入和删除元素 List list1 = new LinkedList(); long startTime1 = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { list1.add(0, i); // 在头部插入元素 } long endTime1 = System.currentTimeMillis(); System.out.println(\"LinkedList插入10000个元素耗时:\" + (endTime1 - startTime1) + \"ms\"); // 需求:需要频繁随机访问元素 List list2 = new ArrayList(); long startTime2 = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { list2.add(i); } for (int i = 0; i < 10000; i++) { list2.get(i); // 随机访问元素 } long endTime2 = System.currentTimeMillis(); System.out.println(\"ArrayList随机访问10000次耗时:\" + (endTime2 - startTime2) + \"ms\"); }}
在这个示例中,当需要频繁插入和删除元素时,使用LinkedList性能更好,因为LinkedList是基于链表结构,插入和删除操作不需要移动大量元素,时间复杂度为 O (1) 。而ArrayList是基于数组结构,插入和删除元素时可能需要移动大量元素,时间复杂度为 O (n) 。当需要频繁随机访问元素时,ArrayList性能更优,因为ArrayList可以通过索引直接访问元素,时间复杂度为 O (1) ,而LinkedList需要从头或尾开始遍历链表,时间复杂度为 O (n) 。在实际应用中,要根据具体的业务需求选择合适的集合类型,比如在实现一个聊天消息队列时,频繁的插入和删除操作适合使用LinkedList;而在实现一个学生成绩列表,需要经常根据学生编号获取成绩时,使用ArrayList更合适。
(四十三)代码优化 - 优化循环
// 未优化的循环public class UnoptimizedLoop { public static void main(String[] args) { int[] numbers = new int[10000]; for (int i = 0; i < numbers.length; i++) { numbers[i] = i; } long startTime = System.currentTimeMillis(); int sum = 0; for (int i = 0; i < numbers.length; i++) { sum += numbers[i]; // 假设这里还有一些复杂的计算 for (int j = 0; j < 100; j++) { // 内部循环进行一些简单计算 int temp = j * 2; } } long endTime = System.currentTimeMillis(); System.out.println(\"未优化的循环耗时:\" + (endTime - startTime) + \"ms\"); }}// 优化后的循环public class OptimizedLoop { public static void main(String[] args) { int[] numbers = new int[10000]; for (int i = 0; i < numbers.length; i++) { numbers[i] = i; } long startTime = System.currentTimeMillis(); int sum = 0; int length = numbers.length; for (int i = 0; i < length; i++) { sum += numbers[i]; // 将内部循环的计算逻辑提取出来 int temp = calculateInnerLoop(); } long endTime = System.currentTimeMillis(); System.out.println(\"优化后的循环耗时:\" + (endTime - startTime) + \"ms\"); } private static int calculateInnerLoop() { int result = 0; for (int j = 0; j < 100; j++) { result += j * 2; } return result; }}
在未优化的循环中,每次循环都要计算numbers.length,并且内部循环的计算逻辑在循环体内,增加了循环的复杂度 。优化后的循环,将numbers.length提前计算并赋值给length,减少了每次循环的计算量 。同时,将内部循环的计算逻辑提取到一个单独的方法calculateInnerLoop中,使循环体更加简洁,提高了代码的可读性和可维护性 。在实际开发中,对于复杂的循环,要尽量减少循环内的不必要计算和操作,比如在遍历一个数据库查询结果集进行数据处理时,如果有一些固定的计算逻辑,可以提前计算好,或者将一些复杂的计算逻辑封装成方法,在循环外调用,避免在循环内重复计算,从而提升循环的执行效率。
(四十四)JVM 调优参数示例
在启动 Java 程序时,可以通过设置 JVM 参数来优化 JVM 的性能 。以下是一些常见的 JVM 调优参数及在启动脚本中的设置示例:
# 设置堆内存初始大小和最大大小java -Xms512m -Xmx1024m -jar yourApp.jar# 设置新生代大小java -Xmn256m -jar yourApp.jar# 设置垃圾回收器java -XX:+UseG1GC -jar yourApp.jar# 开启逃逸分析java -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -jar yourApp.jar
-Xms512m表示设置 JVM 堆内存的初始大小为 512MB,-Xmx1024m表示设置堆内存的最大大小为 1024MB 。合理设置堆内存大小可以避免因内存不足导致的频繁垃圾回收或 OutOfMemoryError 异常 。-Xmn256m用于设置新生代的大小,新生代是对象创建和短期存活的区域,合适的新生代大小可以提高垃圾回收的效率 。-XX:+UseG1GC表示使用 G1 垃圾回收器,G1 垃圾回收器适用于大内存场景,能更有效地管理内存,减少垃圾回收的停顿时间 。-XX:+DoEscapeAnalysis开启逃逸分析,-XX:+EliminateAllocations开启标量替换,逃逸分析可以分析对象的作用域,对于不会逃逸出方法的对象,JVM 可以进行优化,如将对象的成员变量直接分配在栈上,而不是在堆上创建对象,从而减少堆内存的使用和垃圾回收的压力 。在实际应用中,需要根据应用程序的特点和运行环境,通过测试来选择合适的 JVM 参数,比如对于一个高并发的电商系统,可能需要增大堆内存,选择更适合高并发场景的垃圾回收器;而对于一个内存敏感的嵌入式系统应用,可能需要更精细地调整堆内存和新生代的大小,以确保系统稳定运行。
(四十五)安全编码 - SQL 注入防范
import java.sql.Connection;import java.sql.DriverManager;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;public class SqlInjectionPrevention { public static void main(String[] args) { String url = \"jdbc:mysql://localhost:3306/mydb\"; String username = \"root\"; String password = \"password\"; String userId = \"1\"; // 假设从用户输入获取,这里简化为固定值 String sql = \"SELECT * FROM users WHERE id = ?\"; try (Connection connection = DriverManager.getConnection(url, username, password); PreparedStatement preparedStatement = connection.prepareStatement(sql)) { preparedStatement.setString(1, userId); ResultSet resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { String name = resultSet.getString(\"name\"); int age = resultSet.getInt(\"age\"); System.out.println(\"Name: \" + name + \", Age: \" + age); } } catch (SQLException e) { e.printStackTrace(); } }}
SQL 注入是一种常见的安全漏洞,攻击者通过在用户输入中插入恶意 SQL 语句,从而执行非法操作 。在上述示例中,使用PreparedStatement代替Statement,通过?作为参数占位符,并使用setString方法设置参数值,这样可以确保用户输入的内容不会被当作 SQL 语句的一部分执行,从而有效防范 SQL 注入攻击 。例如,如果用户输入的userId是1 OR 1=1,在使用PreparedStatement时,它会被正确地作为字符串处理,而不会影响 SQL 语句的逻辑 。在实际应用中,对于所有涉及用户输入的 SQL 语句,都应该使用预编译语句,比如在一个用户登录功能中,对用户名和密码的验证 SQL 语句,一定要使用预编译,防止攻击者通过输入特殊字符来绕过密码验证。同时,也可以结合输入验证,对用户输入进行合法性检查,进一步增强安全性 。
(四十六)安全编码 - XSS 攻击防范
import java.io.IOException;import java.net.URLDecoder;import java.net.URLEncoder;import java.nio.charset.StandardCharsets;import java.util.Scanner;public class XssPrevention { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println(\"请输入内容:\"); String input = scanner.nextLine(); // 对用户输入进行HTML实体编码 String encodedInput = htmlEncode(input); // 假设这里将编码后的内容输出到HTML页面 System.out.println(\"编码后的内容:\" + encodedInput); } private static String htmlEncode(String input) { StringBuilder result = new StringBuilder(); for (char c : input.toCharArray()) { switch (c) { case \'\': result.append(\">\"); break; case \'&\': result.append(\"&\"); break; case \'\"\': result.append(\""\"); break; default: result.append(c); } } return result.toString(); }}
XSS(Cross - Site Scripting)攻击是指攻击者在网页中注入恶意脚本,当用户访问该网页时,恶意脚本会在用户浏览器中执行,从而窃取用户信息、篡改页面内容等 。在这个示例中,通过htmlEncode方法对用户输入进行 HTML 实体编码,将特殊字符(如、&、\")转换为对应的 HTML 实体,这样可以防止用户输入的恶意脚本在 HTML 页面中执行 。例如,如果用户输入alert(\'XSS攻击\'),经过编码后会变成<script>alert(\'XSS攻击\')</script>,在页面上会显示为普通文本,而不会执行脚本 。在实际的 Web 开发中,对于从用户获取的所有输入,在输出到 HTML 页面之前,都要进行严格的过滤和编码,不仅要进行 HTML 实体编码,还可以使用一些安全框架或工具,如 OWASP 的 ESAPI(Enterprise Security API),它提供了更全面的 XSS 防护功能,包括输入验证、输出编码等,以确保应用程序的安全性 。
(四十七)安全编码 - 密码加密存储
import java.security.SecureRandom;import java.security.spec.KeySpec;import java.util.Base64;import javax.crypto.SecretKeyFactory;import javax.crypto.spec.PBEKeySpec;public class PasswordEncryption { public static void main(String[] args) throws Exception { String password = \"userPassword\"; byte[] salt = generateSalt(); String hashedPassword = hashPassword(password, salt); System.out.println(\"盐值:\" + Base64.getEncoder().encodeToString(salt)); System.out.println(\"哈希后的密码:\" + hashedPassword); // 验证密码 boolean isValid = validatePassword(password, salt, hashedPassword); System.out.println(\"密码验证结果:\" + isValid); } private static byte[] generateSalt() { SecureRandom random = new SecureRandom(); byte[] salt = new byte[16]; random.nextBytes(salt); return salt; } private static String hashPassword(String password, byte[] salt) throws Exception { KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256); SecretKeyFactory factory = SecretKeyFactory.getInstance(\"PBKDF2WithHmacSHA256\"); byte[] hash = factory.generateSecret(spec).getEncoded(); return Base64.getEncoder().encodeToString(hash); } private static boolean validatePassword(String password, byte[] salt, String storedHashedPassword) throws Exception { String hashedInputPassword = hashPassword(password, salt); return hashedInputPassword.equals(storedHashedPassword); }}
在应用程序中,不能以明文形式存储用户密码,否则一旦数据库泄露,用户密码将完全暴露 。上述代码使用PBKDF2WithHmacSHA256算法对密码进行哈希处理,并生成随机盐值 。盐值是一个随机字符串,与密码一起进行哈希计算,可以增加密码的安全性,防止彩虹表攻击 。generateSalt方法生成一个 16 字节的随机盐值 。hashPassword方法将密码和盐值进行哈希计算,并将结果进行 Base64 编码后返回 。validatePassword方法用于验证用户输入的密码是否正确,它先对输入密码进行哈希计算,然后与存储在数据库中的哈希密码进行比较 。在实际应用中,要选择安全的加密算法和足够强度的盐值,并且定期更新密码加密策略,比如随着计算能力的提升,可能需要增加哈希迭代次数来提高密码的安全性 。同时,要注意保护好盐值和哈希密码,存储在安全的位置,防止被窃取 。
(四十八)性能测试工具使用示例 - JMeter
JMeter 是一款广泛使用的开源性能测试工具,用于测试 Web 应用程序、HTTP 服务、数据库等的性能 。以下是一个简单的使用 JMeter 测试 HTTP 接口的脚本示例:
- 打开 JMeter,创建一个新的测试计划。
- 在测试计划下添加一个线程组(Thread Group),设置线程数、循环次数等参数 。例如,设置线程数为 100,表示模拟 100 个并发用户;设置循环次数为 10,表示每个用户执行 10 次请求 。
- 在线程组下添加一个 HTTP 请求(HTTP Request),配置请求的 URL、方法(如 GET、POST)、参数等 。比如,测试一个获取用户信息的 HTTP GET 请求,设置 URL 为http://localhost:8080/user/1 。
- 在线程组下添加一个监听器,如聚合报告(Aggregate Report),用于查看测试结果 。聚合报告可以显示请求的平均响应时间、最大响应时间、吞吐量等指标 。
通过运行这个测试脚本,可以模拟大量用户并发访问 HTTP 接口,从而评估接口的性能,找出性能瓶颈,比如是否存在响应时间过长、吞吐量不足等问题 。在实际应用中,还可以使用 JMeter 进行更复杂的性能测试,如参数化测试(通过 CSV Data Set Config 元件读取外部数据文件,对请求参数进行动态变化)、压力测试(逐渐增加并发用户数,观察系统在高负载下的表现)、负载测试(模拟系统在不同负载情况下的运行情况,确定系统的最佳负载和最大负载)等 。同时,结合 JMeter 的断言功能(如 Response Code Assertion 用于断言响应状态码是否正确),可以确保接口的功能正确性 。
(四十九)单元测试框架使用示例 - JUnit
import org.junit.jupiter.api.Test;import static org.junit.jupiter.api.Assertions.*;public class Calculator { public int add(int a, int b) { return a + b; }}public class CalculatorTest { @Test public void testAdd() { Calculator calculator = new Calculator(); int result = calculator.add(3,
结语
🔥如果此文对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏、✍️评论,支持一下博主~


