【JavaSE】基础学习以及简单的计算器应用程序GUI实现
计算机中的二进制
在计算机中,二进制表示是指使用两种状态来表示数字、字符或其他数据。计算机只能处理0和1,因为它们可以通过物理设备的两种状态(如高电压或低电压)来表示。二进制数的每一位称为一个bit,而八位称为一个字节(Byte)。下面用Java代码解释二进制表示的基本概念。
1. 十进制到二进制的转换
计算机内部的整数是以二进制存储的。例如,十进制数10的二进制表示为1010
。我们可以通过Java的Integer.toBinaryString()
方法来查看二进制表示:
public class BinaryExample {
public static void main(String[] args) {
int decimal = 10;
// 将十进制数转为二进制字符串
String binary = Integer.toBinaryString(decimal);
System.out.println("十进制 " + decimal + " 的二进制表示是: " + binary);
}
}
输出:
十进制 10 的二进制表示是: 1010
2. 二进制的位操作
二进制数可以进行各种位操作,如与(AND)、或(OR)、异或(XOR)、左移(<<)、右移(>>)等。这些操作经常用于底层的高效运算。
示例:按位与(AND)操作
&
操作符用于对每一位进行与操作。当且仅当两位都为1时,结果为1,否则为0。
public class BitwiseANDExample {
public static void main(String[] args) {
int num1 = 6; // 二进制: 110
int num2 = 3; // 二进制: 011
int result = num1 & num2; // 二进制结果: 010
System.out.println("6 & 3 = " + result); // 输出: 2
}
}
解释:
110 (6)
& 011 (3)
= 010 (2)
3. 二进制表示的符号位
在Java中,整型的二进制表示采用补码表示法。正数和负数的区别在于最高位的符号位:
- 0代表正数
- 1代表负数
例如,-10
的二进制表示为补码形式:
public class NegativeBinaryExample {
public static void main(String[] args) {
int negativeDecimal = -10;
String binary = Integer.toBinaryString(negativeDecimal);
System.out.println("-10 的二进制表示是: " + binary);
}
}
输出:
-10 的二进制表示是: 11111111111111111111111111110110
解释:-10
的二进制补码是 11111111111111111111111111110110
,表示负数时使用补码。
4. 二进制浮点数表示
Java中浮点数使用IEEE 754标准,即使用符号位、指数位和尾数位表示浮点数。示例中不详细展示其二进制结构,但可以通过Java类Double.doubleToLongBits()
来获取浮点数的二进制位表示:
public class FloatBinaryExample {
public static void main(String[] args) {
double num = 10.5;
long bits = Double.doubleToLongBits(num);
String binary = Long.toBinaryString(bits);
System.out.println("10.5 的二进制表示是: " + binary);
}
}
输出:
10.5 的二进制表示是: 100000000010010100000000000000000000000000000000000000000000000
总结
- 整数在计算机中是以补码的形式存储的,符号位区分正负数。
- 浮点数使用IEEE 754标准表示,包含符号位、指数位和尾数位。
- 位操作可以有效操作二进制数位,广泛用于底层开发和性能优化。
整数类型
在Java中,整数类型用于表示没有小数部分的数字,分为四种基本类型:byte
、short
、int
和 long
。它们之间的主要区别是所占字节数和表示的数值范围。
1. byte 类型
- 大小: 1 字节 (8 位)
- 取值范围: -128 到 127
- 适用场景: 用于需要节省空间且数值较小的情况。
public class ByteExample {
public static void main(String[] args) {
byte b = 100;
System.out.println("Byte 值: " + b);
}
}
2. short 类型
- 大小: 2 字节 (16 位)
- 取值范围: -32,768 到 32,767
- 适用场景: 用于需要比
byte
更大,但不需要int
那么大的整数时。
public class ShortExample {
public static void main(String[] args) {
short s = 20000;
System.out.println("Short 值: " + s);
}
}
3. int 类型
- 大小: 4 字节 (32 位)
- 取值范围: -2^31 到 2^31-1 (即 -2,147,483,648 到 2,147,483,647)
- 适用场景: 这是Java中最常用的整数类型,用于表示绝大多数场景下的整数。
public class IntExample {
public static void main(String[] args) {
int i = 100000;
System.out.println("Int 值: " + i);
}
}
4. long 类型
- 大小: 8 字节 (64 位)
- 取值范围: -2^63 到 2^63-1 (即 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807)
- 适用场景: 当需要表示非常大的整数时,比如处理大数据或科学计算时,
long
类型更适合。
public class LongExample {
public static void main(String[] args) {
long l = 10000000000L; // 注意:长整型值要以L或l结尾
System.out.println("Long 值: " + l);
}
}
整数类型对比表
类型 | 字节数 | 位数 | 最小值 | 最大值 |
---|---|---|---|---|
byte | 1 | 8 | -128 | 127 |
short | 2 | 16 | -32,768 | 32,767 |
int | 4 | 32 | -2^31 (-2,147,483,648) | 2^31-1 (2,147,483,647) |
long | 8 | 64 | -2^63 (-9,223,372,036,854,775,808) | 2^63-1 (9,223,372,036,854,775,807) |
自动类型转换与强制类型转换
Java中,较小的整数类型可以自动转换为较大的类型。例如,byte
可以自动转换为int
。但从较大类型转换为较小类型时,需要强制类型转换,因为可能存在溢出。
自动类型转换示例:
public class AutoCastingExample {
public static void main(String[] args) {
byte b = 42;
int i = b; // 自动转换
System.out.println("Int 值: " + i);
}
}
强制类型转换示例:
public class ManualCastingExample {
public static void main(String[] args) {
int i = 1000;
byte b = (byte) i; // 强制类型转换,可能会发生数据丢失
System.out.println("Byte 值: " + b); // 输出将不是1000,而是截断后的结果
}
}
总结
- byte 和 short 用于节省空间的小整数。
- int 是Java中最常用的整数类型。
- long 用于表示非常大的整数。
- 自动类型转换 在小类型转大类型时发生,而强制类型转换则需要显式地进行。
浮点类型
Java中的浮点类型用于表示带小数部分的数值。浮点类型有两种:float 和 double,它们的主要区别在于精度和存储范围。
1. float 类型
- 大小: 4 字节 (32 位)
- 取值范围: 大约为 ±1.4E-45 到 ±3.4028235E38
- 精度: 6-7 位有效数字
- 适用场景: 用于内存限制较大的环境或对精度要求不高的应用,例如图形处理。
public class FloatExample {
public static void main(String[] args) {
float f = 5.75f; // 注意:float 类型的值需要以 'f' 或 'F' 结尾
System.out.println("Float 值: " + f);
}
}
注意:浮点数的字面量默认为double
类型,所以在声明float
类型时,值后面要加上f
或F
来指定是float
。
2. double 类型
- 大小: 8 字节 (64 位)
- 取值范围: 大约为 ±4.9E-324 到 ±1.7976931348623157E308
- 精度: 15-16 位有效数字
- 适用场景: 用于需要高精度和大范围的浮点数操作,通常在科学计算和金融应用中使用。
public class DoubleExample {
public static void main(String[] args) {
double d = 19.99;
System.out.println("Double 值: " + d);
}
}
浮点类型对比表
类型 | 字节数 | 有效位数 | 最小值 | 最大值 |
---|---|---|---|---|
float | 4 | 6-7 位 | ±1.4E-45 | ±3.4028235E38 |
double | 8 | 15-16 位 | ±4.9E-324 | ±1.7976931348623157E308 |
3. 浮点数的自动转换与强制转换
和整数类型类似,float 可以自动转换为 double,因为 double 精度更高。当需要将 double 转换为 float 时,需要进行强制类型转换。
自动类型转换示例:
public class AutoFloatDoubleConversion {
public static void main(String[] args) {
float f = 5.75f;
double d = f; // 自动转换为 double
System.out.println("Double 值: " + d);
}
}
强制类型转换示例:
public class ManualDoubleToFloatConversion {
public static void main(String[] args) {
double d = 19.99;
float f = (float) d; // 强制转换为 float
System.out.println("Float 值: " + f);
}
}
4. 浮点数的精度问题
浮点类型在表示某些十进制小数时会产生精度问题,因为它们在底层是以二进制表示的,而某些十进制小数(如 0.1)不能被精确地用二进制表示。这可能会导致计算结果有误差。为了高精度运算,可以使用 BigDecimal 类。
示例:浮点数精度问题
public class FloatPrecisionProblem {
public static void main(String[] args) {
float f = 0.1f + 0.2f;
System.out.println("0.1f + 0.2f = " + f); // 输出可能不为 0.3
}
}
输出:
0.1f + 0.2f = 0.30000004
5. 使用 BigDecimal 进行精确计算
如果你需要进行高精度的浮点数运算,Java 提供了 BigDecimal 类。Java在java.math
包中提供的API类BigDecimal
,用来对超过16位有效位的数进行精确的运算。它避免了二进制浮点数的精度问题,特别适用于金融和需要高精度的小数运算场景。
import java.math.BigDecimal;
public class BigDecimalPrecisionExample {
public static void main(String[] args) {
// 使用非常大的浮点数
BigDecimal bd1 = new BigDecimal("12345678901234567890.12345678901234567890");
BigDecimal bd2 = new BigDecimal("98765432109876543210.98765432109876543210");
// 执行加法操作
BigDecimal sum = bd1.add(bd2);
// 执行减法操作
BigDecimal difference = bd2.subtract(bd1);
// 执行乘法操作
BigDecimal product = bd1.multiply(bd2);
// 执行除法操作,并设置精度
BigDecimal quotient = bd2.divide(bd1, 30, BigDecimal.ROUND_HALF_UP);
// 输出结果
System.out.println("BigDecimal加法结果: " + sum);
System.out.println("BigDecimal减法结果: " + difference);
System.out.println("BigDecimal乘法结果: " + product);
System.out.println("BigDecimal除法结果: " + quotient);
}
}
解释:
- 更大数值的处理:示例中的两个数字使用了20位以上的小数和整数部分,这在普通的
float
或double
类型中会导致精度丢失,而BigDecimal
能够精确地处理。 - 高精度加减乘除:这个示例展示了高精度的四则运算,特别是在除法操作中,指定了30位小数的精度来避免舍入误差。
- 控制精度和舍入:在除法中,
BigDecimal.ROUND_HALF_UP
指定了使用"四舍五入"的舍入模式,同时设置了30位的小数精度。
输出:
BigDecimal加法结果: 111111111011111111101.11111111011111111100
BigDecimal减法结果: 86419753208641975320.86419753208641975320
BigDecimal乘法结果: 1219326311370217952237463801111263526900.748299319460170868200073223971
BigDecimal除法结果: 8.000000072900000058320000001953472
总结
- float 用于内存敏感且对精度要求不高的场景,常用于图形处理或简单计算。
- double 是Java默认的浮点类型,适用于大多数情况下的浮点数运算,精度较高。
- 浮点数由于底层二进制表示的原因,可能会出现精度问题。对于需要高精度的场景,应该使用 BigDecimal。
面向对象基础
理解 类 与 对象 是面向对象编程的核心概念。在 Java 中,类是创建对象的模板或蓝图,而对象是类的具体实例。下面我将通过一些示例来解释这几个概念。
1. 类与对象的概念
- 类:是对一类事物的抽象定义,它包含了属性(成员变量)和行为(方法)。
- 对象:是类的实例,每个对象都有独立的属性值。
2. 类的定义与对象的创建
在 Java 中,类的定义包含属性(成员变量)和行为(方法)。对象是通过类创建的,表示类的一个具体实例。
示例:类的定义与对象创建
// 定义一个 Car 类
class Car {
// 属性(成员变量)
String brand;
String color;
int speed;
// 方法:打印汽车信息
public void displayInfo() {
System.out.println("品牌: " + brand);
System.out.println("颜色: " + color);
System.out.println("速度: " + speed + " km/h");
}
// 方法:改变速度
public void accelerate(int increment) {
speed += increment; // 增加速度
}
}
public class Main {
public static void main(String[] args) {
// 创建 Car 类的对象
Car myCar = new Car();
// 设置对象的属性值
myCar.brand = "Toyota";
myCar.color = "Red";
myCar.speed = 120;
// 调用对象的方法
myCar.displayInfo(); // 打印汽车信息
// 改变速度
myCar.accelerate(30); // 增加速度
// 打印改变后的汽车信息
myCar.displayInfo();
}
}
解释:
- 类的定义:我们定义了一个
Car
类,类中包含三个属性(brand
,color
,speed
)和两个方法(displayInfo
和accelerate
)。 - 对象创建:在
main
方法中,使用new
关键字创建了Car
类的对象myCar
。 - 对象的使用:通过
myCar
对象,可以访问和修改类中的属性,并调用方法(如displayInfo
和accelerate
)。
3. 对象的使用
通过对象,我们可以访问类中的属性或调用方法来操作对象。
示例:对象的属性和方法使用
public class ObjectExample {
public static void main(String[] args) {
// 创建 Car 类的对象
Car myCar = new Car();
// 设置对象的属性
myCar.brand = "Honda";
myCar.color = "Blue";
myCar.speed = 90;
// 使用对象的方法
myCar.displayInfo(); // 打印初始信息
// 调整速度
myCar.accelerate(20); // 增加速度 20 km/h
// 再次调用方法,显示新的速度
myCar.displayInfo();
}
}
输出:
品牌: Honda
颜色: Blue
速度: 90 km/h
品牌: Honda
颜色: Blue
速度: 110 km/h
4. 方法的创建与使用
- 方法 是类中定义的行为,它们可以用来操作类的属性或者执行某些操作。
- 方法通常包括方法名、参数列表、返回值类型以及方法体。
示例:创建和使用方法
class Calculator {
// 方法:加法
public int add(int a, int b) {
return a + b;
}
// 方法:减法
public int subtract(int a, int b) {
return a - b;
}
}
public class Main {
public static void main(String[] args) {
// 创建 Calculator 类的对象
Calculator calc = new Calculator();
// 调用 add 和 subtract 方法
int sum = calc.add(10, 5);
int difference = calc.subtract(10, 5);
// 打印结果
System.out.println("加法结果: " + sum);
System.out.println("减法结果: " + difference);
}
}
输出:
加法结果: 15
减法结果: 5
总结:
- 类 是一组属性和行为的定义,定义了对象的类型。
- 对象 是类的实例,具有独立的属性值。
- 方法 定义了对象的行为,可以用来操作属性或执行任务。
进阶使用方法
1. 方法的重载 (Method Overloading)
方法重载是指在同一个类中,可以有多个同名方法,但这些方法的参数列表必须不同。参数的个数、类型或顺序不同即可。
示例:方法重载
class Calculator {
// 两个参数的加法
public int add(int a, int b) {
return a + b;
}
// 三个参数的加法(方法重载)
public int add(int a, int b, int c) {
return a + b + c;
}
// 不同参数类型的加法(方法重载)
public double add(double a, double b) {
return a + b;
}
}
public class Main {
public static void main(String[] args) {
Calculator calc = new Calculator();
// 调用不同的 add 方法
System.out.println("两个整数相加: " + calc.add(10, 5));
System.out.println("三个整数相加: " + calc.add(10, 5, 3));
System.out.println("两个浮点数相加: " + calc.add(4.5, 2.3));
}
}
输出:
两个整数相加: 15
三个整数相加: 18
两个浮点数相加: 6.8
2. 递归方法 (Recursive Methods)
递归是指一个方法调用它自身。递归方法通常用于解决某些问题时,问题的解可以归结为一个或多个规模较小的相同问题。
示例:递归计算阶乘
class FactorialCalculator {
// 递归方法计算阶乘
public int factorial(int n) {
if (n == 1) { // 基本条件
return 1;
} else {
return n * factorial(n - 1); // 递归调用自身
}
}
}
public class Main {
public static void main(String[] args) {
FactorialCalculator calc = new FactorialCalculator();
// 计算5的阶乘
System.out.println("5的阶乘: " + calc.factorial(5));
}
}
输出:
5的阶乘: 120
3. 方法的返回类型与多态 (Return Type & Polymorphism)
在继承关系中,子类可以重写父类的方法(称为方法覆盖),并且可以根据对象的实际类型调用相应的方法。这是多态的重要表现。
示例:方法覆盖与多态
class Animal {
// 父类的 speak 方法
public void speak() {
System.out.println("动物在发出声音");
}
}
class Dog extends Animal {
// 子类重写父类的 speak 方法
@Override
public void speak() {
System.out.println("狗在汪汪叫");
}
}
class Cat extends Animal {
// 子类重写父类的 speak 方法
@Override
public void speak() {
System.out.println("猫在喵喵叫");
}
}
public class Main {
public static void main(String[] args) {
// 创建子类对象
Animal myDog = new Dog();
Animal myCat = new Cat();
// 调用 speak 方法,实际调用子类的版本(多态)
myDog.speak(); // 输出: 狗在汪汪叫
myCat.speak(); // 输出: 猫在喵喵叫
}
}
输出:
狗在汪汪叫
猫在喵喵叫
4. 静态方法 (Static Methods)
静态方法属于类,而不是某个对象实例。静态方法可以通过类名直接调用,通常用于不依赖于对象状态的方法。
示例:静态方法
class MathUtils {
// 静态方法计算平方
public static int square(int x) {
return x * x;
}
}
public class Main {
public static void main(String[] args) {
// 调用静态方法,无需创建对象
System.out.println("4 的平方: " + MathUtils.square(4));
}
}
输出:
4 的平方: 16
5. 可变参数方法 (Varargs)
Java 提供了可变参数的方法,允许传递任意数量的参数给方法。可变参数通过 ...
来表示,实质上是将这些参数作为数组传递。
示例:可变参数方法
class VarargsExample {
// 可变参数方法:计算多个数的总和
public int sum(int... numbers) {
int total = 0;
for (int num : numbers) {
total += num;
}
return total;
}
}
public class Main {
public static void main(String[] args) {
VarargsExample example = new VarargsExample();
// 调用可变参数方法
System.out.println("Sum of 1, 2, 3: " + example.sum(1, 2, 3));
System.out.println("Sum of 4, 5, 6, 7, 8: " + example.sum(4, 5, 6, 7, 8));
}
}
输出:
Sum of 1, 2, 3: 6
Sum of 4, 5, 6, 7, 8: 30
6. 方法的链式调用 (Method Chaining)
链式调用是一种编程风格,允许通过连续调用方法来简化代码结构。通常在对象的setter方法中返回当前对象来实现。
示例:链式调用
class Person {
String name;
int age;
// 设置名字并返回当前对象
public Person setName(String name) {
this.name = name;
return this;
}
// 设置年龄并返回当前对象
public Person setAge(int age) {
this.age = age;
return this;
}
// 打印个人信息
public void displayInfo() {
System.out.println("名字: " + name + ", 年龄: " + age);
}
}
public class Main {
public static void main(String[] args) {
Person person = new Person();
// 链式调用设置属性
person.setName("John").setAge(30).displayInfo();
}
}
输出:
名字: John, 年龄: 30
总结
- 方法重载:同名方法可以有不同的参数,提供灵活性。
- 递归方法:让方法自己调用自己,解决分而治之的问题。
- 多态与方法覆盖:在继承关系中,不同对象类型的实际方法调用体现了多态性。
- 静态方法:与类相关的方法,无需实例化对象。
- 可变参数:允许传递可变数量的参数。
- 链式调用:通过连续调用方法,简化代码风格。
构造方法
构造方法(也称为构造器,constructor)是用于在创建对象时初始化对象的特殊方法。它在对象创建时自动调用,通常用于为对象的属性赋初始值。
特点:
- 名称与类名相同:构造方法的名称必须与类名相同。
- 没有返回类型:构造方法没有返回类型,即使是
void
也不能声明。 - 自动调用:当使用
new
关键字创建对象时,构造方法会自动调用。
示例:构造方法的使用
class Car {
// 成员变量
String brand;
String color;
int speed;
// 构造方法:用于初始化属性
public Car(String brand, String color, int speed) {
this.brand = brand;
this.color = color;
this.speed = speed;
}
// 显示汽车信息的方法
public void displayInfo() {
System.out.println("品牌: " + brand);
System.out.println("颜色: " + color);
System.out.println("速度: " + speed + " km/h");
}
}
public class Main {
public static void main(String[] args) {
// 使用构造方法创建对象,并初始化属性
Car myCar = new Car("Toyota", "Red", 120);
// 调用方法显示汽车信息
myCar.displayInfo();
}
}
输出:
品牌: Toyota
颜色: Red
速度: 120 km/h
解释:
-
定义构造方法:
Car(String brand, String color, int speed)
是一个构造方法,用于初始化brand
、color
和speed
三个属性。在这个方法中,this
关键字用于区分成员变量和参数名称相同的情况。 -
对象创建时调用构造方法:在
main
方法中,new Car("Toyota", "Red", 120)
创建了一个Car
对象,并调用了构造方法对brand
、color
和speed
属性进行初始化。 -
无需手动调用构造方法:当
new
关键字用于创建对象时,构造方法自动被调用。
其他类型的构造方法:
1. 无参构造方法(默认构造方法)
如果类中没有定义任何构造方法,Java会为你提供一个默认的无参构造方法。但如果类中定义了一个带参数的构造方法,就必须显式定义无参构造方法。
class Car {
String brand;
String color;
int speed;
// 无参构造方法
public Car() {
this.brand = "Default Brand";
this.color = "Default Color";
this.speed = 0;
}
// 显示汽车信息的方法
public void displayInfo() {
System.out.println("品牌: " + brand);
System.out.println("颜色: " + color);
System.out.println("速度: " + speed + " km/h");
}
}
public class Main {
public static void main(String[] args) {
// 使用无参构造方法创建对象
Car defaultCar = new Car();
defaultCar.displayInfo();
}
}
输出:
品牌: Default Brand
颜色: Default Color
速度: 0 km/h
2. 多个构造方法(构造方法重载)
Java 支持多个构造方法,也就是构造方法的重载。你可以定义多个构造方法,每个构造方法有不同的参数,以便根据不同的初始化需求创建对象。
class Car {
String brand;
String color;
int speed;
// 无参构造方法
public Car() {
this.brand = "Unknown";
this.color = "Unknown";
this.speed = 0;
}
// 带参数的构造方法
public Car(String brand, String color) {
this.brand = brand;
this.color = color;
this.speed = 100; // 默认速度
}
// 带更多参数的构造方法
public Car(String brand, String color, int speed) {
this.brand = brand;
this.color = color;
this.speed = speed;
}
// 显示汽车信息的方法
public void displayInfo() {
System.out.println("品牌: " + brand);
System.out.println("颜色: " + color);
System.out.println("速度: " + speed + " km/h");
}
}
public class Main {
public static void main(String[] args) {
// 使用不同的构造方法创建对象
Car defaultCar = new Car();
Car toyotaCar = new Car("Toyota", "Red");
Car bmwCar = new Car("BMW", "Blue", 150);
// 调用方法显示汽车信息
defaultCar.displayInfo();
toyotaCar.displayInfo();
bmwCar.displayInfo();
}
}
输出:
品牌: Unknown
颜色: Unknown
速度: 0 km/h
品牌: Toyota
颜色: Red
速度: 100 km/h
品牌: BMW
颜色: Blue
速度: 150 km/h
总结:
- 构造方法 用于在对象创建时初始化对象的状态。
- 无参构造方法 可以用于提供默认值。
- 构造方法重载 允许根据不同的参数来创建和初始化对象。
- 构造方法自动调用,无需手动调用。
静态变量和静态方法
静态变量 和 静态方法 是属于类本身的,而不是类的某个实例。换句话说,静态成员可以通过类名直接访问,不需要创建对象。让我们通过详细解释和示例来理解它们。
1. 静态变量(Static Variable)
静态变量 是属于类的,而不是属于类的某个对象。静态变量在所有对象之间共享,即所有对象都访问同一个静态变量的值。它在类加载时就已经被分配内存,而不是在对象创建时。
特点:
- 属于类,不属于某个对象。
- 可以通过类名访问(无需创建对象)。
- 在所有实例中共享一份静态变量的值。
示例:静态变量
class Counter {
// 静态变量:用于所有对象共享
public static int count = 0;
// 构造方法:每创建一个对象,计数器加1
public Counter() {
count++;
}
// 显示当前计数值
public void displayCount() {
System.out.println("当前计数: " + count);
}
}
public class Main {
public static void main(String[] args) {
// 创建多个对象
Counter c1 = new Counter();
Counter c2 = new Counter();
Counter c3 = new Counter();
// 访问静态变量
c1.displayCount(); // 输出: 当前计数: 3
c2.displayCount(); // 输出: 当前计数: 3
c3.displayCount(); // 输出: 当前计数: 3
// 也可以通过类名访问静态变量
System.out.println("通过类名访问静态变量: " + Counter.count); // 输出: 3
}
}
输出:
当前计数: 3
当前计数: 3
当前计数: 3
通过类名访问静态变量: 3
解释:
- 静态变量
count
:静态变量count
在所有对象中共享,类中任何对象都对它进行修改时,其他对象也能看到修改后的值。每次创建Counter
对象时,count
都会递增。 - 通过类名访问:静态变量可以通过
Counter.count
直接访问,无需创建对象。
2. 静态方法(Static Method)
静态方法 同样是属于类的,而不是某个实例。静态方法不能访问实例变量或调用实例方法,但可以访问静态变量并调用其他静态方法。静态方法通常用于一些不依赖对象状态的操作,或者用于工厂方法等创建对象的用途。
特点:
- 属于类,不属于类的实例。
- 可以通过类名直接调用(无需创建对象)。
- 静态方法中不能直接访问非静态的成员变量和方法。
示例:静态方法
class MathUtil {
// 静态方法:计算两个数的平方和
public static int squareSum(int a, int b) {
return (a * a) + (b * b);
}
// 静态方法:判断一个数是否为偶数
public static boolean isEven(int number) {
return number % 2 == 0;
}
}
public class Main {
public static void main(String[] args) {
// 调用静态方法,无需创建对象
int sumOfSquares = MathUtil.squareSum(3, 4);
boolean isNumberEven = MathUtil.isEven(10);
System.out.println("3 和 4 的平方和: " + sumOfSquares); // 输出: 25
System.out.println("10 是偶数吗? " + isNumberEven); // 输出: true
}
}
输出:
3 和 4 的平方和: 25
10 是偶数吗? true
解释:
- 静态方法
squareSum
和isEven
:这些方法都属于类MathUtil
,无需创建对象,直接通过类名MathUtil
调用。 - 静态方法限制:静态方法不能访问实例变量,因为静态方法是在类级别的,它们不依赖于对象实例。
3. 静态变量与静态方法结合使用
静态方法通常会用于操作静态变量。由于静态变量属于类而非某个对象,因此静态方法也应该主要用于类级别的操作。
示例:静态变量与静态方法的结合使用
class Student {
// 静态变量:记录学生总数
public static int totalStudents = 0;
// 构造方法:每创建一个学生对象,计数器加1
public Student() {
totalStudents++;
}
// 静态方法:返回当前的学生总数
public static int getTotalStudents() {
return totalStudents;
}
}
public class Main {
public static void main(String[] args) {
// 创建多个学生对象
Student s1 = new Student();
Student s2 = new Student();
Student s3 = new Student();
// 调用静态方法获取学生总数
System.out.println("当前学生总数: " + Student.getTotalStudents()); // 输出: 3
}
}
输出:
当前学生总数: 3
解释:
- 静态变量
totalStudents
:这个静态变量用于跟踪创建的学生对象总数,每创建一个Student
对象,totalStudents
会递增。 - 静态方法
getTotalStudents
:静态方法返回静态变量的值,表示当前学生总数。由于这是一个类级别的操作,因此它是静态方法。
总结:
- 静态变量 属于类,所有实例共享相同的值,可以通过类名访问。它适合用于保存全局状态或计数器等用途。
- 静态方法 也属于类,可以通过类名直接调用,适合不依赖于对象状态的操作。静态方法不能直接访问非静态变量,但可以访问静态变量。
静态成员的使用使得类的设计更加灵活,可以用于封装类级别的功能,比如工厂方法、实用工具方法等。
包和访问控制
在Java中,包(Package)和访问控制用于组织代码和控制类、方法、变量等的可见性和访问权限。我们可以通过一些示例来帮助理解这些概念。
1. 包声明和导入
包是一个命名空间,用于组织类和接口。Java提供了大量的内置包,并且开发者也可以自定义包。通过将类放入包中,可以避免类名冲突,并方便类的管理。
示例:包声明与导入
// 声明包名
package com.example.shapes;
// 定义类
public class Circle {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
public double getArea() {
return Math.PI * radius * radius;
}
}
上面代码中的 Circle
类属于包 com.example.shapes
。
导入包
为了使用其他包中的类,必须先导入对应的类。导入包的方式包括以下两种:
- 单个类导入:
import 包名.类名;
- 整个包导入:
import 包名.*;
示例:导入包中的类
// 导入我们自定义的包中的 Circle 类
import com.example.shapes.Circle;
public class Main {
public static void main(String[] args) {
// 使用 Circle 类
Circle c = new Circle(5);
System.out.println("圆的面积: " + c.getArea());
}
}
通过 import
语句,我们可以使用自定义包中的 Circle
类。
2. 访问控制
Java提供了四种访问控制修饰符,用来控制类、方法、变量的访问权限:
public
: 对所有类可见。protected
: 对同一包内的类和子类可见。default
(无修饰符): 仅对同一包内的类可见。private
: 仅对类内部可见。
示例:访问控制
package com.example.people;
// 定义一个 Person 类
public class Person {
public String name; // 公共的,任何地方都可访问
protected int age; // 受保护的,同包和子类可访问
String city; // 默认访问控制,同包内可访问
private String ssn; // 私有的,仅类内部可访问
public Person(String name, int age, String city, String ssn) {
this.name = name;
this.age = age;
this.city = city;
this.ssn = ssn;
}
// 公共方法可以访问私有属性
public void displayInfo() {
System.out.println("Name: " + name + ", Age: " + age + ", City: " + city + ", SSN: " + ssn);
}
}
示例:访问权限的使用
import com.example.people.Person;
public class Main {
public static void main(String[] args) {
Person p = new Person("John", 30, "New York", "123-45-6789");
System.out.println(p.name); // 公共成员,可以访问
// System.out.println(p.age); // 错误:受保护成员,不能直接访问
// System.out.println(p.city);// 错误:包级访问控制,不能访问
// System.out.println(p.ssn); // 错误:私有成员,不能访问
p.displayInfo(); // 公共方法可以访问私有成员
}
}
3. 基本类型包装类
Java为每种基本类型提供了一个对应的包装类,用于将基本类型转换为对象。包装类是java.lang
包中的类。常见的基本类型及其对应的包装类如下:
int
→Integer
char
→Character
boolean
→Boolean
double
→Double
这些包装类允许我们将基本数据类型作为对象使用,比如在集合框架中,不能直接使用基本类型。
示例:基本类型包装类
public class WrapperExample {
public static void main(String[] args) {
// 使用基本类型的包装类
Integer intObj = Integer.valueOf(10); // 将 int 值包装为 Integer 对象
Double doubleObj = Double.valueOf(3.14); // 将 double 值包装为 Double 对象
// 自动装箱
Integer autoBoxedInt = 100;
// 自动拆箱
int intValue = autoBoxedInt; // 自动将 Integer 对象转换为基本类型
System.out.println("Integer对象: " + intObj);
System.out.println("Double对象: " + doubleObj);
System.out.println("自动拆箱后的int值: " + intValue);
}
}
4. 特殊包装类
除了基本类型的包装类外,Java还提供了一些特殊的包装类,用于处理更复杂的数据类型,例如BigInteger
和BigDecimal
,它们可以处理精度更高的大数运算。
示例:BigInteger 和 BigDecimal
import java.math.BigInteger;
import java.math.BigDecimal;
public class SpecialWrapperExample {
public static void main(String[] args) {
// BigInteger 用于处理非常大的整数
BigInteger bigInt1 = new BigInteger("123456789123456789123456789");
BigInteger bigInt2 = new BigInteger("987654321987654321987654321");
BigInteger sum = bigInt1.add(bigInt2); // 大数相加
System.out.println("BigInteger 相加结果: " + sum);
// BigDecimal 用于处理高精度的小数
BigDecimal bd1 = new BigDecimal("0.123456789123456789");
BigDecimal bd2 = new BigDecimal("0.987654321987654321");
BigDecimal result = bd1.add(bd2); // 高精度小数相加
System.out.println("BigDecimal 相加结果: " + result);
}
}
5. 总结
- 包声明和导入:包用于组织类和接口,可以通过
package
声明包,使用import
导入包中的类。 - 访问控制:Java提供了四种访问控制修饰符(
public
、protected
、default
、private
),控制类、方法、变量的可见性。 - 基本类型包装类:Java为每种基本类型提供了包装类,用于在需要对象的场合包装基本数据类型。
- 特殊包装类:
BigInteger
和BigDecimal
是特殊包装类,提供高精度运算,适用于大数和高精度小数的处理。
基础数据类型及正则表达式
主页另一篇Java基础也有这方面介绍。
1. 数组
数组是用来存储相同类型数据的容器。Java 支持 一维数组、多维数组 和 可变长参数数组。
1.1 一维数组
一维数组是最基本的数组形式,它是一组连续的相同类型的元素。
示例:一维数组
public class OneDArrayExample {
public static void main(String[] args) {
// 创建一个包含 5 个整数的数组
int[] numbers = new int[5];
// 为数组元素赋值
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;
numbers[3] = 40;
numbers[4] = 50;
// 遍历数组并输出元素
for (int i = 0; i < numbers.length; i++) {
System.out.println("元素 " + i + ": " + numbers[i]);
}
}
}
1.2 多维数组
多维数组可以看作是数组的数组,最常见的是二维数组,它可以用来表示表格或矩阵。
示例:二维数组
public class TwoDArrayExample {
public static void main(String[] args) {
// 创建一个 2 行 3 列的二维数组
int[][] matrix = {
{1, 2, 3},
{4, 5, 6}
};
// 遍历并输出二维数组
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
System.out.print(matrix[i][j] + " ");
}
System.out.println();
}
}
}
1.3 可变长参数数组
可变长参数允许一个方法接收不定数量的参数。它在方法签名中使用省略号(...
)来定义。
示例:可变长参数
public class VarargsExample {
// 定义一个接收可变长参数的方法
public static void printNumbers(int... numbers) {
for (int number : numbers) {
System.out.println("数字: " + number);
}
}
public static void main(String[] args) {
// 传递多个参数
printNumbers(1, 2, 3, 4, 5);
}
}
2. 字符串(StringBuilder 类)
Java 中的字符串是不可变的对象,每次对字符串的修改都会创建新的字符串对象。为了高效地操作字符串,Java 提供了 StringBuilder 类,它允许在原字符串的基础上进行修改,而无需创建新对象。
示例:StringBuilder
public class StringBuilderExample {
public static void main(String[] args) {
// 创建 StringBuilder 对象
StringBuilder sb = new StringBuilder("Hello");
// 追加字符串
sb.append(" World");
// 插入字符串
sb.insert(6, "Java ");
// 删除部分字符串
sb.delete(0, 6);
// 输出结果
System.out.println("结果: " + sb.toString());
}
}
解释:
append
:追加字符串。insert
:在指定位置插入字符串。delete
:删除指定范围内的字符。
3. 正则表达式(Regular Expressions)
正则表达式 是一种用于定义字符串匹配规则的工具,它可以用来查找、替换或验证字符串中的特定模式。
在 Java 中,正则表达式主要通过 java.util.regex.Pattern
类和 Matcher
类来使用。常用的正则表达式包括查找特定字符、数字、单词、或验证电子邮件地址等。
常见的正则表达式符号:
.
:匹配任意字符。\d
:匹配数字。\w
:匹配字母或数字。*
:匹配前一个字符零次或多次。+
:匹配前一个字符一次或多次。?
:匹配前一个字符零次或一次。^
:表示行的开头。$
:表示行的结尾。
示例:正则表达式匹配
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexExample {
public static void main(String[] args) {
// 定义一个正则表达式,匹配邮箱地址
String regex = "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$";
Pattern pattern = Pattern.compile(regex);
// 待匹配的字符串
String email = "example@domain.com";
// 创建 matcher 对象
Matcher matcher = pattern.matcher(email);
// 验证是否匹配
if (matcher.matches()) {
System.out.println(email + " 是一个有效的邮箱地址");
} else {
System.out.println(email + " 不是一个有效的邮箱地址");
}
}
}
解释:
- 正则表达式
^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$
用于验证邮箱格式。^
表示字符串的开头。[\\w-\\.]
匹配字母、数字、点或短横线。@
匹配@
符号。([\\w-]+\\.)+
匹配邮箱的域名部分。[\\w-]{2,4}$
匹配顶级域名。
总结
-
数组
- 一维数组:用于存储相同类型的连续元素。
- 多维数组:可以表示矩阵或表格等数据结构。
- 可变长参数:允许方法接收可变数量的参数。
-
字符串(StringBuilder 类)
StringBuilder
是一种用于高效处理可变字符串的类。- 它可以通过
append
、insert
、delete
等方法修改字符串内容。
-
正则表达式
- 正则表达式是用于匹配字符串模式的工具,常用于验证、查找或替换字符串中的特定模式。
内部类
在 Java 中,内部类是定义在另一个类内部的类。内部类能够访问其外围类的成员(包括私有成员),并且可以与外围类进行密切配合。Java 支持多种类型的内部类,包括:
- 成员内部类(Member Inner Class)
- 静态内部类(Static Nested Class)
- 局部内部类(Local Inner Class)
- 匿名内部类(Anonymous Inner Class)
让我们逐个介绍这些内部类类型,并提供示例来帮助理解。
1. 成员内部类(Member Inner Class)
成员内部类是定义在外围类的内部,并且在外围类的每个实例中都有一个隐式的外部类实例引用。
示例:成员内部类
public class OuterClass {
private String outerField = "Outer Field";
// 成员内部类
class InnerClass {
public void display() {
// 可以访问外部类的成员
System.out.println("Accessing outer class field: " + outerField);
}
}
public static void main(String[] args) {
// 创建外围类对象
OuterClass outer = new OuterClass();
// 创建成员内部类对象
OuterClass.InnerClass inner = outer.new InnerClass();
inner.display();
}
}
2. 静态内部类(Static Nested Class)
静态内部类是定义在外围类中的静态类。它不能访问外围类的实例成员,但可以访问外围类的静态成员。
示例:静态内部类
public class OuterClass {
private static String staticOuterField = "Static Outer Field";
// 静态内部类
static class StaticNestedClass {
public void display() {
// 只能访问外围类的静态成员
System.out.println("Accessing static outer class field: " + staticOuterField);
}
}
public static void main(String[] args) {
// 创建静态内部类对象
OuterClass.StaticNestedClass staticNested = new OuterClass.StaticNestedClass();
staticNested.display();
}
}
3. 局部内部类(Local Inner Class)
局部内部类是在方法或代码块中定义的类。它只能在定义它的方法或代码块中访问,不能在其他地方使用。
示例:局部内部类
public class OuterClass {
public void outerMethod() {
// 局部内部类
class LocalInnerClass {
public void display() {
System.out.println("Inside local inner class method");
}
}
// 创建局部内部类对象
LocalInnerClass localInner = new LocalInnerClass();
localInner.display();
}
public static void main(String[] args) {
// 创建外围类对象
OuterClass outer = new OuterClass();
outer.outerMethod();
}
}
4. 匿名内部类(Anonymous Inner Class)
匿名内部类是没有名字的内部类,通常用于简化代码,特别是在需要快速实现某个接口或抽象类时。它通常用于创建一次性使用的类实例。
示例:匿名内部类
abstract class AbstractClass {
public abstract void display();
}
public class OuterClass {
public static void main(String[] args) {
// 创建匿名内部类对象
AbstractClass anonymous = new AbstractClass() {
@Override
public void display() {
System.out.println("Inside anonymous inner class");
}
};
anonymous.display();
}
}
总结
- 成员内部类:普通的内部类,可以访问外围类的所有成员。
- 静态内部类:不能访问外围类的实例成员,只能访问静态成员。
- 局部内部类:定义在方法或代码块中,只能在定义它的作用域中使用。
- 匿名内部类:没有名字的内部类,通常用于实现接口或抽象类的简化代码。
泛型
1. 泛型(Generics)
泛型允许在类、接口和方法中使用类型参数,使得代码能够适用于多种类型,从而增强了代码的重用性和类型安全性。
示例:泛型类
// 定义一个泛型类
public class Box<T> {
private T content;
// 设置内容
public void setContent(T content) {
this.content = content;
}
// 获取内容
public T getContent() {
return content;
}
public static void main(String[] args) {
// 创建一个 Box 对象,存储整数
Box<Integer> intBox = new Box<>();
intBox.setContent(123);
System.out.println("Integer Box Content: " + intBox.getContent());
// 创建一个 Box 对象,存储字符串
Box<String> strBox = new Box<>();
strBox.setContent("Hello Generics");
System.out.println("String Box Content: " + strBox.getContent());
}
}
2. 多态(Polymorphism)
多态允许不同类型的对象以相同的方式进行处理。Java 的多态主要通过继承和接口来实现,它允许对象在运行时动态地决定调用哪个方法。
示例:多态
// 基类
class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
}
// 派生类
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
}
// 另一个派生类
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Cat meows");
}
}
public class PolymorphismExample {
public static void main(String[] args) {
Animal myAnimal;
// 动态绑定
myAnimal = new Dog();
myAnimal.makeSound(); // 输出: Dog barks
myAnimal = new Cat();
myAnimal.makeSound(); // 输出: Cat meows
}
}
3. 泛型方法(Generic Methods)
泛型方法允许在方法中定义类型参数,使得方法能够操作不同类型的数据。
示例:泛型方法
public class GenericMethodExample {
// 泛型方法
public <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
public static void main(String[] args) {
GenericMethodExample example = new GenericMethodExample();
// 打印整数数组
Integer[] intArray = {1, 2, 3, 4, 5};
example.printArray(intArray);
// 打印字符串数组
String[] strArray = {"Hello", "World"};
example.printArray(strArray);
}
}
4. 泛型的界限(Bounded Type Parameters)
泛型的界限限制了类型参数可以是哪些类型。通过指定界限,可以强制泛型类型参数继承自某个类或实现某个接口。
示例:泛型的界限
// 定义一个接口
interface Comparable<T> {
int compareTo(T o);
}
// 定义一个类实现 Comparable 接口
class Person implements Comparable<Person> {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public int compareTo(Person other) {
return this.name.compareTo(other.name);
}
@Override
public String toString() {
return name;
}
}
// 泛型类,限制类型参数必须是 Comparable 接口的实现类
public class CompareUtil {
public static <T extends Comparable<T>> T findMax(T[] array) {
T max = array[0];
for (T item : array) {
if (item.compareTo(max) > 0) {
max = item;
}
}
return max;
}
public static void main(String[] args) {
Person[] people = {
new Person("Alice"),
new Person("Bob"),
new Person("Charlie")
};
Person maxPerson = findMax(people);
System.out.println("Max Person: " + maxPerson);
}
}
总结
- 泛型(Generics):允许在类、接口和方法中使用类型参数,提高代码的灵活性和安全性。
- 多态(Polymorphism):允许不同的对象以相同的方式处理,通过继承和接口实现。
- 泛型方法(Generic Methods):允许在方法中定义类型参数,使得方法能够操作不同类型的数据。
- 泛型的界限(Bounded Type Parameters):限制泛型类型参数必须是某个类的子类或实现某个接口,提供了更多控制和灵活性。
函数式接口
函数式接口 是 Java 8 引入的一个概念,它代表了一个仅包含一个抽象方法的接口。函数式接口能够用于 lambda 表达式和方法引用,使得 Java 编程更加简洁和灵活。
1. 函数式接口的定义
函数式接口是一个包含单个抽象方法的接口。为了表明一个接口是函数式接口,可以使用 @FunctionalInterface
注解,这个注解是可选的,但它能帮助编译器检查接口是否符合函数式接口的定义。
示例:函数式接口
@FunctionalInterface
interface MyFunctionalInterface {
// 单个抽象方法
void doSomething();
}
2. 使用函数式接口
函数式接口可以用来作为 lambda 表达式的目标类型。Lambda 表达式可以用来实现函数式接口的单个抽象方法,从而简化代码。
示例:函数式接口与 Lambda 表达式
@FunctionalInterface
interface Greeting {
void greet(String name);
}
public class FunctionalInterfaceExample {
public static void main(String[] args) {
// 使用 lambda 表达式实现函数式接口
Greeting greeting = name -> System.out.println("Hello, " + name + "!");
greeting.greet("World");
}
}
3. 常见的函数式接口
Java 8 的标准库中定义了几个常用的函数式接口,主要包括:
Predicate<T>
:用于测试某个条件,返回boolean
值。Function<T, R>
:接受一个参数,返回一个结果。Consumer<T>
:接受一个参数,执行某种操作,没有返回值。Supplier<T>
:不接受参数,提供一个结果。UnaryOperator<T>
和BinaryOperator<T>
:用于对单个或两个操作数进行运算,返回一个结果。
示例:使用常见的函数式接口
import java.util.function.Predicate;
import java.util.function.Function;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class StandardFunctionalInterfaces {
public static void main(String[] args) {
// Predicate: 测试一个条件
Predicate<Integer> isEven = num -> num % 2 == 0;
System.out.println("Is 4 even? " + isEven.test(4));
// Function: 转换数据类型
Function<String, Integer> stringLength = str -> str.length();
System.out.println("Length of 'Hello': " + stringLength.apply("Hello"));
// Consumer: 执行某种操作
Consumer<String> printMessage = message -> System.out.println(message);
printMessage.accept("Hello, World!");
// Supplier: 提供一个结果
Supplier<Double> randomValue = () -> Math.random();
System.out.println("Random value: " + randomValue.get());
}
}
4. 方法引用
方法引用是一种简化 lambda 表达式的语法。它直接引用一个方法,可以使代码更加简洁和易读。
示例:方法引用
import java.util.function.Function;
public class MethodReferenceExample {
// 静态方法
public static int stringToLength(String str) {
return str.length();
}
public static void main(String[] args) {
// 使用方法引用来实现 Function 接口
Function<String, Integer> lengthFunction = MethodReferenceExample::stringToLength;
System.out.println("Length of 'Hello': " + lengthFunction.apply("Hello"));
}
}
总结
- 函数式接口 是包含单个抽象方法的接口,可以使用 lambda 表达式或方法引用来实现。
- 常见的函数式接口包括
Predicate
、Function
、Consumer
和Supplier
。 - 方法引用 是简化 lambda 表达式的一种语法,可以使代码更加简洁和易读。
集合类与IO
在 Java 中,集合类和输入输出(I/O)是非常重要的概念。集合类用于存储和处理一组对象,而 I/O 操作则涉及到数据的读写。下面是对集合类和 I/O 的详细解释,包括根接口、列表、迭代器、队列和双端队列等内容。
1. 集合根接口
集合框架的根接口是 Collection
和 Map
。所有的集合类都直接或间接地实现了这些接口。
Collection
:表示一组对象的集合,它是 Java 集合框架中最基本的接口。Map
:表示一个键值对的集合,其中每个键映射到一个值。
示例:集合根接口
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CollectionInterfaces {
public static void main(String[] args) {
// 使用 List 接口
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
System.out.println("List: " + list);
// 使用 Map 接口
Map<String, Integer> map = new HashMap<>();
map.put("Apple", 1);
map.put("Banana", 2);
System.out.println("Map: " + map);
}
}
2. List 列表
List
是一种有序的集合,它允许重复的元素,并提供基于索引的访问。常见的 List
实现包括 ArrayList
和 LinkedList
。
示例:List 和 ListIterator
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class ListExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
// 使用 ListIterator 进行遍历
ListIterator<String> iterator = list.listIterator();
while (iterator.hasNext()) {
System.out.println("Forward: " + iterator.next());
}
// 从后向前遍历
while (iterator.hasPrevious()) {
System.out.println("Backward: " + iterator.previous());
}
}
}
3. 迭代器
Iterator
是 Java 集合框架中用于遍历集合的接口。它提供了 hasNext()
、next()
和 remove()
方法来遍历集合中的元素。
示例:使用迭代器
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class IteratorExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
// 使用 Iterator 进行遍历
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println("Element: " + element);
}
}
}
4. Queue 和 Deque
Queue
:表示一个先进先出(FIFO)的集合,用于存储待处理的元素。常见实现包括LinkedList
和PriorityQueue
。Deque
:双端队列,允许在队列的两端插入和删除元素。常见实现包括ArrayDeque
和LinkedList
。
示例:Queue 和 Deque
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Deque;
import java.util.ArrayDeque;
public class QueueDequeExample {
public static void main(String[] args) {
// Queue 示例
Queue<String> queue = new LinkedList<>();
queue.offer("First");
queue.offer("Second");
queue.offer("Third");
System.out.println("Queue:");
while (!queue.isEmpty()) {
System.out.println(queue.poll());
}
// Deque 示例
Deque<String> deque = new ArrayDeque<>();
deque.offerFirst("First");
deque.offerLast("Second");
deque.offerLast("Third");
System.out.println("Deque:");
while (!deque.isEmpty()) {
System.out.println(deque.pollFirst());
}
}
}
总结
-
集合根接口:
Collection
:所有集合的根接口,包含集合操作的基本方法。Map
:表示键值对的集合。
-
List
列表:- 有序集合,允许重复元素,常见实现包括
ArrayList
和LinkedList
。 ListIterator
:可以前后遍历List
,并支持修改操作。
- 有序集合,允许重复元素,常见实现包括
-
Iterator
:- 用于遍历集合,提供
hasNext()
、next()
和remove()
方法。
- 用于遍历集合,提供
-
Queue
和Deque
:Queue
:先进先出的集合,常见实现包括LinkedList
和PriorityQueue
。Deque
:双端队列,支持在两端插入和删除元素,常见实现包括ArrayDeque
和LinkedList
。
多线程与反射
多线程和反射是 Java 编程中两个重要的概念。多线程用于并发执行多个任务,而反射允许在运行时动态地访问和操作类和对象。以下是对多线程和反射的详细解释和示例:
多线程
1. 线程的创建和启动
在 Java 中,有两种主要方法来创建线程:
- 继承
Thread
类 - 实现
Runnable
接口
示例:继承 Thread
类
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running.");
}
}
public class ThreadCreationExample {
public static void main(String[] args) {
// 创建线程对象
MyThread thread = new MyThread();
// 启动线程
thread.start();
}
}
示例:实现 Runnable
接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable is running.");
}
}
public class RunnableExample {
public static void main(String[] args) {
// 创建 Runnable 对象
MyRunnable runnable = new MyRunnable();
// 创建线程对象
Thread thread = new Thread(runnable);
// 启动线程
thread.start();
}
}
2. 线程的休眠和中断
- 休眠(sleep):让线程暂停执行指定的时间。
- 中断(interrupt):请求线程停止其当前操作。
示例:休眠和中断
class SleepAndInterruptThread extends Thread {
@Override
public void run() {
try {
System.out.println("Thread is sleeping.");
Thread.sleep(5000); // 休眠5秒
System.out.println("Thread woke up.");
} catch (InterruptedException e) {
System.out.println("Thread was interrupted.");
}
}
}
public class SleepAndInterruptExample {
public static void main(String[] args) {
SleepAndInterruptThread thread = new SleepAndInterruptThread();
thread.start();
// 主线程稍等
try {
Thread.sleep(2000); // 主线程休眠2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
// 中断线程
thread.interrupt();
}
}
3. 线程的优先级
线程优先级用于指定线程的执行优先级,但实际优先级的实现由 JVM 决定。
示例:设置线程优先级
class PriorityThread extends Thread {
@Override
public void run() {
System.out.println("Thread priority: " + getPriority());
}
}
public class ThreadPriorityExample {
public static void main(String[] args) {
PriorityThread lowPriorityThread = new PriorityThread();
PriorityThread highPriorityThread = new PriorityThread();
lowPriorityThread.setPriority(Thread.MIN_PRIORITY);
highPriorityThread.setPriority(Thread.MAX_PRIORITY);
lowPriorityThread.start();
highPriorityThread.start();
}
}
4. 线程的礼让和加入
- 礼让(yield):让当前线程让出 CPU 给其他线程,但不是强制的。
- 加入(join):让当前线程等待另一个线程完成执行。
示例:线程礼让和加入
class YieldAndJoinThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running.");
Thread.yield(); // 礼让
System.out.println(Thread.currentThread().getName() + " resumes.");
}
}
public class YieldAndJoinExample {
public static void main(String[] args) throws InterruptedException {
YieldAndJoinThread thread1 = new YieldAndJoinThread();
YieldAndJoinThread thread2 = new YieldAndJoinThread();
thread1.start();
thread2.start();
thread1.join(); // 等待 thread1 完成
thread2.join(); // 等待 thread2 完成
System.out.println("Both threads have finished.");
}
}
5. 线程锁和线程同步
线程锁和同步用于避免多个线程同时访问共享资源造成的数据不一致问题。
示例:线程锁和同步
class Counter {
private int count = 0;
// 使用 synchronized 关键字来同步方法
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class SynchronizationExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Final count: " + counter.getCount());
}
}
反射
反射允许在运行时动态地访问和操作类和对象。通过反射可以获取类的信息、创建对象、调用方法和访问字段。
示例:反射的基本用法
import java.lang.reflect.Method;
public class ReflectionExample {
public static void main(String[] args) {
try {
// 获取类的 Class 对象
Class<?> clazz = Class.forName("java.lang.String");
// 打印类的方法
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.println("Method: " + method.getName());
}
// 创建对象
String str = (String) clazz.getConstructor(String.class).newInstance("Hello Reflection");
System.out.println("Created object: " + str);
// 调用方法
Method lengthMethod = clazz.getMethod("length");
int length = (int) lengthMethod.invoke(str);
System.out.println("Length of string: " + length);
} catch (Exception e) {
e.printStackTrace();
}
}
}
总结
-
多线程:
- 线程的创建和启动:通过继承
Thread
类或实现Runnable
接口创建线程。 - 线程的休眠和中断:使用
sleep()
方法暂停线程和使用interrupt()
方法中断线程。 - 线程的优先级:使用
setPriority()
方法设置线程优先级。 - 线程的礼让和加入:使用
yield()
方法让出 CPU 和使用join()
方法等待线程完成。 - 线程锁和线程同步:使用
synchronized
关键字进行同步,避免数据不一致问题。
- 线程的创建和启动:通过继承
-
反射:
- 允许在运行时动态地获取类的信息、创建对象、调用方法和访问字段。
GUI程序开发
使用 Java Swing 创建一个简单的计算器应用程序。这个计算器将具备基本的加、减、乘、除功能。
1. 创建计算器窗口
首先,我们需要创建一个窗口,并在窗口中放置按钮和文本框。
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class CalculatorExample {
private JFrame frame;
private JTextField textField;
private double num1, num2, result;
private String operator;
public CalculatorExample() {
frame = new JFrame("Calculator");
textField = new JTextField();
textField.setFont(new Font("Arial", Font.PLAIN, 24));
textField.setEditable(false);
// 设置按钮
JButton[] numberButtons = new JButton[10];
for (int i = 0; i < 10; i++) {
numberButtons[i] = new JButton(String.valueOf(i));
numberButtons[i].setFont(new Font("Arial", Font.PLAIN, 18));
numberButtons[i].addActionListener(new NumberButtonListener());
}
JButton addButton = new JButton("+");
JButton subButton = new JButton("-");
JButton mulButton = new JButton("*");
JButton divButton = new JButton("/");
JButton eqButton = new JButton("=");
JButton clrButton = new JButton("C");
addButton.setFont(new Font("Arial", Font.PLAIN, 18));
subButton.setFont(new Font("Arial", Font.PLAIN, 18));
mulButton.setFont(new Font("Arial", Font.PLAIN, 18));
divButton.setFont(new Font("Arial", Font.PLAIN, 18));
eqButton.setFont(new Font("Arial", Font.PLAIN, 18));
clrButton.setFont(new Font("Arial", Font.PLAIN, 18));
addButton.addActionListener(new OperatorButtonListener());
subButton.addActionListener(new OperatorButtonListener());
mulButton.addActionListener(new OperatorButtonListener());
divButton.addActionListener(new OperatorButtonListener());
eqButton.addActionListener(new EqualButtonListener());
clrButton.addActionListener(new ClearButtonListener());
// 创建面板和布局
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(4, 4, 10, 10));
panel.add(numberButtons[1]);
panel.add(numberButtons[2]);
panel.add(numberButtons[3]);
panel.add(addButton);
panel.add(numberButtons[4]);
panel.add(numberButtons[5]);
panel.add(numberButtons[6]);
panel.add(subButton);
panel.add(numberButtons[7]);
panel.add(numberButtons[8]);
panel.add(numberButtons[9]);
panel.add(mulButton);
panel.add(clrButton);
panel.add(numberButtons[0]);
panel.add(eqButton);
panel.add(divButton);
// 布局
frame.setLayout(new BorderLayout());
frame.add(textField, BorderLayout.NORTH);
frame.add(panel, BorderLayout.CENTER);
frame.setSize(400, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private class NumberButtonListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
JButton source = (JButton) e.getSource();
textField.setText(textField.getText() + source.getText());
}
}
private class OperatorButtonListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
JButton source = (JButton) e.getSource();
operator = source.getText();
num1 = Double.parseDouble(textField.getText());
textField.setText("");
}
}
private class EqualButtonListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
num2 = Double.parseDouble(textField.getText());
switch (operator) {
case "+":
result = num1 + num2;
break;
case "-":
result = num1 - num2;
break;
case "*":
result = num1 * num2;
break;
case "/":
result = num1 / num2;
break;
}
textField.setText(String.valueOf(result));
}
}
private class ClearButtonListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
textField.setText("");
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(CalculatorExample::new);
}
}
代码说明
-
创建窗口:
- 使用
JFrame
创建窗口。 - 使用
JTextField
作为显示区域。
- 使用
-
添加按钮:
- 使用
JButton
创建数字按钮和操作符按钮。 - 为按钮添加监听器来处理点击事件。
- 使用
-
设置布局:
- 使用
GridLayout
布局管理器来排列按钮。
- 使用
-
事件处理:
- 数字按钮事件:在文本框中添加相应的数字。
- 操作符按钮事件:记录操作符和第一个操作数。
- 等号按钮事件:计算结果并显示在文本框中。
- 清除按钮事件:清空文本框。
总结
这个示例展示了如何使用 Java Swing 创建一个简单的计算器应用程序。通过这个示例,你可以了解如何创建窗口、添加组件、使用布局管理器以及处理用户事件。你可以根据需要扩展计算器的功能,例如添加更多的操作符和功能。