java安装
以下是在 Windows 操作系统上安装 Java 的步骤:
-
下载 JDK(Java Development Kit)安装程序:访问 Oracle 官方网站(https://www.oracle.com/java/technologies/javase-jdk11-downloads.html),找到适合您操作系统的版本,点击下载。
-
运行安装程序:双击下载的 JDK 安装程序,然后按照提示进行安装。
-
下载好后会有俩个文件夹,jdk:开发环境:包括JRE+类库+开发工具包(编译器和调试工具) : jre:运行环境:包括jvm和解释器,完整的java运行环境 下载目录:D:\java\jdk.8.0
-
打开环境变量之系统变量,新建 JAVA_HOME 值 D:\java\jdk.8.0
-
打开环境变量之系统变量,编辑path 然后 新建一个 %JAVA_HOME%\bin ,此时已经配置好环境变量
-
完成配置后,重新打开一个新的命令提示符窗口,再次输入以下命令验证 Java 和 javac 是否正常工作:
java -version javac -version
如果成功安装和配置,您将分别看到 Java 和 javac 版本信息的输出。
这样,您就成功安装了 Java 开发环境(JDK)。现在可以在计算机上编写和运行 Java 程序了。
idea安装
以下是在 Windows 操作系统上安装 IntelliJ IDEA 的步骤:
-
下载 IntelliJ IDEA:访问 JetBrains 官方网站(https://www.jetbrains.com/idea/download/),找到适合您操作系统的版本,选择 Community(社区版免费)或 Ultimate(旗舰版付费),然后点击下载。
-
运行安装程序:双击下载的安装程序,然后按照提示进行安装。
-
选择安装选项:在安装过程中,您可以选择是否创建桌面快捷方式以及其他一些自定义选项。根据个人偏好进行选择。
-
安装完成后,启动 IntelliJ IDEA。
-
配置 JDK 路径:在首次运行 IntelliJ IDEA 时,会提示您配置 JDK 路径。如果已经安装了 JDK,请选择对应的路径。如果未安装 JDK,请点击“Download JDK”按钮,跳转至 JDK 下载页面,根据步骤安装 JDK,并在配置界面选择安装路径。
-
设置插件和主题:在首次启动 IntelliJ IDEA 时,您可以选择安装一些常用的插件和选择喜欢的主题。根据个人需求进行选择,并等待插件和主题安装完成。
-
完成设置后,您将看到 IntelliJ IDEA 的欢迎界面。从这里,您可以创建新项目、打开现有项目、浏览文档等。
这样,您就成功安装了 IntelliJ IDEA。现在可以使用它进行 Java 开发和其他项目的编写和管理了。
dos命令
查看本机IP
ipconfig
ping 别人机器
ping 192.168.0.1
c盘跳到D盘
D:
跳到指定目录
cd 123
返回上一级目录
cd..
创建目录123
mkdir 123 or md 123
删除目录123
rmdir 123 or rd 123
创建文件
echo 内容>文件名.txt
删除文件
del 文件名.txt
清屏
cls
java命名规则
Java 的命名规则如下:
-
包名(Package Name):使用小写字母,多个单词之间用点号分隔,例如:com.example.mypackage。
-
类名(Class Name):使用大写字母开头的驼峰命名法,例如:MyClass,StudentInfo。
-
方法名(Method Name):使用小写字母开头的驼峰命名法,例如:getAge,setScore。
-
变量名(Variable Name):使用小写字母开头的驼峰命名法,例如:name,age。
-
常量名(Constant Name):全部使用大写字母,单词之间用下划线分隔,例如:MAX_VALUE,PI。
-
接口名(Interface Name):使用大写字母开头的驼峰命名法,例如:Drawable,Runnable。
-
枚举类型名(Enum Type Name):使用大写字母开头的驼峰命名法,例如:Color,Size。
-
局部变量名和参数名(Local Variables and Parameters):使用小写字母开头的驼峰命名法,例如:count,index。
注意事项:
- 标识符应该具有描述性,能够清晰表达其用途和含义。
- 避免使用单个字符作为变量名,除非它们代表明确的含义,例如循环索引。
- 使用英文单词命名,避免使用拼音或其他非英文字符。
- 不要使用 Java 的保留关键字作为标识符。
以上是 Java 命名规则的一般约定和最佳实践,遵循这些规范可以使代码更易读、易懂,并增强代码的可维护性。
java数据类型
Java 中的数据类型可分为两类:基本数据类型(Primitive Data Types)和引用数据类型(Reference Data Types)。
-
基本数据类型:
- 整型(Integer Types):byte、short、int、long。
- 浮点型(Floating-Point Types):float、double。
- 字符型(Character Type):char。
- 布尔型(Boolean Type):boolean。
-
引用数据类型:
- 类(Class):用于创建对象的模板,例如 String、Integer 等。
- 接口(Interface):用于定义方法规范,例如 Comparable、Runnable 等。
- 数组(Array):用于存储同一类型的多个元素。
基本数据类型具有固定的大小和默认值,直接存储在内存中的栈空间中,其值是按值传递的。而引用数据类型存储在堆空间中,包含对象的地址或引用,并且可以指向 null。而方法参数传递时,引用数据类型是按引用传递的。
这些数据类型提供了不同的功能和表达能力,让程序员可以有效地处理各种数据和操作。
变量初始值
在Java中,变量的初始化值取决于其类型。基本类型的变量(如int、double、boolean等)在声明时会自动初始化为默认值,而引用类型的变量(如String、数组、对象等)会初始化为null。
以下是Java中各种类型变量的默认初始化值:
- 整数类型(byte、short、int、long):默认初始化为0。
- 小数类型(float、double):默认初始化为0.0。
- 字符类型(char):默认初始化为’\u0000’(空字符)。
- 布尔类型(boolean):默认初始化为false。
- 引用类型(String、数组、对象等):默认初始化为null。
示例代码:
public class Main {
static int myInt;
static double myDouble;
static char myChar;
static boolean myBoolean;
static String myString;
public static void main(String[] args) {
System.out.println("整数类型的默认初始化值:" + myInt);
System.out.println("小数类型的默认初始化值:" + myDouble);
System.out.println("字符类型的默认初始化值:" + myChar);
System.out.println("布尔类型的默认初始化值:" + myBoolean);
System.out.println("引用类型的默认初始化值:" + myString);
}
}
输出结果:
整数类型的默认初始化值:0
小数类型的默认初始化值:0.0
字符类型的默认初始化值:
布尔类型的默认初始化值:false
引用类型的默认初始化值:null
在上述示例中,我们声明了几个静态变量,并在main
方法中输出它们的值。由于这些变量没有被显式赋值,在输出时将显示它们的默认初始化值。
需要注意的是,局部变量(即在方法或代码块内部声明的变量)没有默认初始化值,必须在使用前进行显式赋值。否则,编译器会报错。
所占字节大小
Java 中各个数据类型所占的字节数如下:
-
基本数据类型:
- byte: 1 字节
- short: 2 字节
- int: 4 字节
- long: 8 字节
- float: 4 字节
- double: 8 字节
- char: 2 字节
- boolean: 1 字节
-
引用数据类型:不固定大小,根据对象本身的大小而变化。
这些数据类型所占的字节数是由 Java 虚拟机规范定义的,并且可能会因底层计算机架构的不同而有所变化。知道了各个数据类型所占的字节数,可以帮助程序员在设计和实现代码时更好地管理内存和提高程序效率。
数据类型转化
在Java中,可以使用类型转换来将一个数据类型转换为另一个数据类型。类型转换分为两种:隐式类型转换(自动类型转换)和显式类型转换(强制类型转换)。
-
隐式类型转换(自动类型转换):
隐式类型转换是指将一个数据类型的值赋给另一个数据类型变量时,Java会自动进行类型转换,而无需显式地进行转换操作。隐式类型转换遵循一定的规则,如下所示:- byte、short、char 可以自动类型转换为 int。
- int 可以自动类型转换为 long、float 或 double。
- long 可以自动类型转换为 float 或 double。
- float 可以自动类型转换为 double。
-
显式类型转换(强制类型转换):
显式类型转换是指通过强制转换运算符()将一个数据类型强制转换为另一个数据类型。但是需要注意的是,强制类型转换可能导致数据丢失或溢出,因此在进行强制类型转换时,需谨慎操作。
语法:目标类型变量 = (目标类型) 原始值;示例:
double d = 3.14; int i = (int) d; // 强制类型转换,将double类型转换为int类型
需要注意的是,在进行类型转换时,要确保目标类型能够容纳原始值,否则可能会导致数据溢出或损失精度。因此,在进行类型转换时,建议进行适当的边界检查和数据验证,以确保转换的正确性和安全性。
java运算符
Java中有多种类型的运算符,用于执行各种操作。下面列举了一些常见的Java运算符:
-
算术运算符:
- 加法:+
- 减法:-
- 乘法:*
- 除法:/
- 取模(取余数):%
- 自增:++
- 自减:–
-
关系运算符:
- 相等:==
- 不相等:!=
- 大于:>
- 小于:<
- 大于等于:>=
- 小于等于:<=
-
逻辑运算符:
- 逻辑与:&&
- 逻辑或:||
- 逻辑非:!
-
赋值运算符:
- 简单赋值:=
- 加法赋值:+=
- 减法赋值:-=
- 乘法赋值:*=
- 除法赋值:/=
- 模赋值:%=
-
位运算符:
- 按位与:&
- 按位或:|
- 按位异或:^
- 按位取反:~
- 左移:<<
- 右移:>>
- 无符号右移:>>>
-
条件运算符:
- 三元运算符:(?😃
-
成员访问运算符:
- 点运算符:.
-
类型运算符:
- instanceof
这只是Java运算符中的一部分,每个运算符都有其特定的用法和行为。通过使用这些运算符,可以对数据进行操作、比较、赋值等。在编写代码时,需要根据具体的需求选择合适的运算符,并了解它们的规则和优先级。
java数组
Java中的数组是一种用于存储多个相同类型数据的数据结构。数组在内存中是一个连续的数据块,可以通过索引访问和操作数组中的元素。以下是关于Java数组的基本信息:
-
声明数组:要声明一个数组,需要指定数组的类型和名称,并使用方括号
[]
表示数组。例如,int[] numbers;
声明了一个名为numbers
的整型数组。 -
创建数组:使用
new
关键字加上数组类型和大小来创建数组。例如,numbers = new int[5];
创建了一个包含5个整数的数组。 -
初始化数组:可以在创建数组时初始化数组的元素,或者在声明数组时直接赋值。例如,
int[] numbers = {1, 2, 3, 4, 5};
创建并初始化一个包含5个整数的数组。 -
访问数组元素:可以使用索引来访问数组中的元素。数组的索引从0开始,到数组长度减1。例如,
int x = numbers[2];
将数组numbers
中索引为2的元素赋给变量x
。 -
数组长度:可以使用数组的
length
属性获得数组的长度,即数组中元素的个数。例如,int length = numbers.length;
获取数组numbers
的长度。 -
多维数组:Java支持多维数组,即数组中的元素也可以是数组。多维数组的声明和使用方式类似于一维数组,只是需要用多组方括号表示维度。例如,
int[][] matrix = new int[3][3];
创建了一个3x3的二维整型数组。 -
数组操作:可以对数组进行各种操作,如遍历数组、修改元素值、查找特定元素、排序数组等。通过循环和条件语句,可以对数组进行灵活的处理。
示例代码:
int[] numbers = new int[5];
numbers[0] = 10; // 设置第一个元素的值为10
int x = numbers[2]; // 获取第三个元素的值
int length = numbers.length; // 获取数组长度
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
System.out.println(matrix[1][2]); // 输出:6
Java数组提供了一种方便和高效地存储和操作多个数据的方式。可以根据具体的需求和场景,合理使用数组来组织和处理数据。
java语句
声明语句
变量声明语句:用于声明变量,指定变量类型和名称,并可以附加初始值(可选)。
java
// 声明整型变量a
int a;
// 声明字符串变量message,并赋初始值"Hello, World!"
String message = "Hello, World!";
// 声明常量PI,并赋初始值3.14
final double PI = 3.14;
方法声明语句:用于声明方法,包括方法名、返回类型、参数列表及方法体等内容。
java
// 声明一个无返回值、无参数的方法printMessage
public void printMessage() {
System.out.println("Hello, World!");
}
// 声明一个有返回值的方法addNumbers,接收两个整型参数a和b,并返回它们的和
public int addNumbers(int a, int b) {
return a + b;
}
类声明语句:用于声明类,包括类名、访问修饰符、实现的接口和类体等内容。
java
// 声明一个公共类MyClass
public class MyClass {
// 类的成员变量和方法
}
// 声明一个抽象类AbstractClass
public abstract class AbstractClass {
// 抽象类的成员变量和方法
}
接口声明语句:用于声明接口,包括接口名、访问修饰符、继承的接口、常量和抽象方法等内容。
java
// 声明一个接口MyInterface
public interface MyInterface {
// 接口中的成员变量和方法
}
输入输出
输出语句:用于将信息打印到控制台。
java
System.out.println("Hello, World!");
输入语句:用于从用户处获得输入。
java
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
控制语句
多分支条件语句(if-else if-else语句):根据不同的条件执行对应的代码块。
java
if (condition1) {
// 执行语句块1
} else if (condition2) {
// 执行语句块2
} else {
// 执行语句块3
}
switch
是一种用于多重条件判断的控制流语句。它可以根据不同的条件值执行不同的代码块。
switch
语句的基本语法如下:
java
switch (expression) {
case value1:
// 当expression等于value1时执行的代码块
break;
case value2:
// 当expression等于value2时执行的代码块
break;
// 可以有更多的case
default:
// 当expression与所有case不匹配时执行的代码块
}
expression
是要进行匹配的表达式或变量。case
后面跟着一个常量值或字面值,表示一个待匹配的条件。如果expression
与某个case
的值相等,则执行该case
下的代码块。如果没有匹配的case
,则会执行default
下的代码块(可选)。- 每个
case
分支都需要以break
语句结束,否则程序将顺序执行后续的case
分支代码。break
语句用于跳出switch
语句块,避免执行不相关的代码。 default
是可选的,用于处理expression
与所有case
都不匹配的情况。
以下是一个使用switch
语句的示例代码:
java
public class Main {
public static void main(String[] args) {
int day = 5;
String dayName;
switch (day) {
case 1:
dayName = "Monday";
break;
case 2:
dayName = "Tuesday";
break;
case 3:
dayName = "Wednesday";
break;
case 4:
dayName = "Thursday";
break;
case 5:
dayName = "Friday";
break;
case 6:
dayName = "Saturday";
break;
case 7:
dayName = "Sunday";
break;
default:
dayName = "Invalid day";
break;
}
System.out.println("Today is " + dayName);
}
}
输出结果:
Today is Friday
在上述示例中,我们使用switch
语句根据day
的值来确定当前是星期几。根据day
的值,相应的case
将被匹配并执行相关代码块,最后输出结果。如果day
的值与所有的case
都不匹配,那么将执行default
下的代码块。
需要注意的是,expression
只能是整数类型(如int
、byte
、short
)或可以转换为整数类型的枚举、字符或字符串。而且,Java 7之前,switch
语句只支持整数和枚举类型,Java 7引入了对字符串的支持。
三目运算符
Java 中的三目运算符是一种简单的条件表达式,可以使用一个条件来决定两个不同的值中的一个。它的语法如下:
condition ? value1 : value2
其中 condition
是一个布尔表达式,如果为真,则返回 value1
,否则返回 value2
。
以下是一个示例,展示如何使用三目运算符来判断一个数字是否为偶数:
int num = 10;
String message = num % 2 == 0 ? "偶数" : "奇数";
System.out.println(message);
在上面的示例中,num % 2 == 0
是布尔表达式,用于判断 num
是否为偶数。如果是偶数,则 message
被赋值为字符串 "偶数"
,否则为 "奇数"
。最终输出的结果是 "偶数"
。
需要注意的一点是,在使用三目运算符时,应该保证 value1
和 value2
的类型相同,或者可以进行自动类型转换。如果两个值类型不兼容,则需要显式地进行类型转换。例如,可以使用 (type) value
的形式强制将 value
转换为指定的类型,其中 type
是目标类型。
循环语句
while循环:在条件为真时重复执行代码块。
java
while (condition) {
// 执行代码块
}
do-while循环:先执行一次代码块,然后在条件为真时重复执行。
java
do {
// 执行代码块
} while (condition);
for循环:按照指定的初始条件、循环条件和步进方式重复执行代码块。
java
for (initialization; condition; update) {
// 执行代码块
}
for-each循环:遍历数组或集合中的元素。
java
for (elementType element : arrayOrCollection) {
// 执行代码块
}
同步语句
synchronized语句:用于实现线程同步。
java
synchronized (object) {
// 执行同步代码块
}
异常处理
try-catch语句:捕获并处理异常。
java
try {
// 可能会抛出异常的代码
} catch (ExceptionType e) {
// 处理异常的代码
}
finally语句:无论是否发生异常,都会执行的代码块。
java
try {
// 可能会抛出异常的代码
} finally {
// 无论是否发生异常都会执行的代码
}
JVM
栈stack
只要new过得东西全部存堆(heap)里面
stack没有变量指向heap,那么它就是garbage,gc清理垃圾garbage
重点
引用数据类型在值传递的时候传递的是内存地址值
基本数据类在值传递的时候―传递的是值本身
方法
在编程中,方法(Method)是一段可重用的代码块,用于执行特定的操作或完成特定的任务。方法封装了一系列的语句,并能够接收输入参数、执行逻辑处理,最终可能返回一个值。
方法通常具有以下特点:
- 方法名称:方法有一个名称,用于唯一标识和调用该方法。
- 参数列表:方法可以接收零个或多个输入参数,这些参数是方法在执行时需要的数据。
- 返回类型:方法可能会返回一个值,其返回类型指定了返回值的数据类型。如果方法不返回任何值,可以将返回类型设置为
void
。 - 方法体:方法体包含了方法的实际逻辑代码,即一系列的语句,定义了方法的行为和功能。
通过定义方法,可以将代码分割成小块,提高代码的可读性、可维护性和可复用性。在程序中,通过调用方法来执行其中封装的代码,避免了重复编写相同的代码。
方法的定义示例:
public int addNumbers(int a, int b) {
int sum = a + b;
return sum;
}
在上面的示例中,addNumbers
是一个方法名称,它接收两个整型参数a
和b
,计算它们的和并将结果返回。方法体中包含了计算和返回结果的代码。
方法的调用示例:
int result = addNumbers(2, 3);
System.out.println(result); // 输出:5
在上面的示例中,通过方法调用addNumbers(2, 3)
可以执行方法体中的代码,并将返回值赋给变量result
。然后通过System.out.println
语句输出结果。
通过定义和使用方法,可以使程序更加模块化、可扩展和简洁,提高代码的重用性和可维护性。
方法的种类
方法可以根据其功能和用途进行不同的分类。以下是常见的方法分类:
-
无参数方法(Parameterless Methods):这些方法不接收任何参数。它们通常执行特定的操作或返回固定的值。
public void greet() { System.out.println("Hello!"); }
-
有参数方法(Methods with Parameters):这些方法接收一个或多个参数,用于提供方法执行所需的输入数据。
public int addNumbers(int a, int b) { return a + b; }
-
无返回值方法(void Methods):这些方法不返回任何值。它们通常用于执行某种操作或处理数据,但不产生结果。
public void displayMessage(String message) { System.out.println(message); }
-
有返回值方法(Methods with Return Values):这些方法在执行完任务后返回一个值。返回类型指定了返回值的数据类型。
public int calculateSum(int[] numbers) { int sum = 0; for (int num : numbers) { sum += num; } return sum; }
-
静态方法(Static Methods):这些方法属于类而不是对象,可以直接通过类名调用,无需创建对象实例。静态方法常用于实用函数和工具方法。
public static int calculateFactorial(int n) { if (n <= 1) return 1; else return n * calculateFactorial(n - 1); }
-
实例方法(Instance Methods):这些方法属于类的实例,需要通过对象引用调用。实例方法可以访问对象的状态和其他实例方法。
public void setName(String name) { this.name = name; }
-
构造方法(Constructor):这种特殊类型的方法用于创建和初始化对象。构造方法具有与类相同的名称,并且没有返回类型。
public MyClass(String name, int age) { this.name = name; this.age = age; }
-
方法重载(Method Overloading):在同一个类中,可以声明多个同名但参数列表不同的方法,称为方法重载。方法重载通过不同的参数类型和数量来区分方法。
public int add(int a, int b) { return a + b; } public double add(double a, double b) { return a + b; }
这些是常见的方法分类,根据不同的需求和场景,选择适合的方法类型来实现所需的功能。
递归
在Java中,递归是一种方法调用自身的编程技巧。通过递归,可以解决问题的重复性,并将一个复杂的问题拆分为更小的子问题来解决。
下面是一个使用递归计算阶乘的示例:
public class RecursionExample {
public static int factorial(int n) {
// 基线条件:当 n 等于 0 或 1 时,阶乘结果为 1
if (n == 0 || n == 1) {
return 1;
} else {
// 递归调用:n! = n * (n-1)!
return n * factorial(n - 1);
}
}
public static void main(String[] args) {
int number = 5;
int result = factorial(number);
System.out.println("Factorial of " + number + " is: " + result);
}
}
在上面的示例中,factorial
方法使用递归来计算给定数字 n
的阶乘。当 n
等于 0 或 1 时,递归终止,返回结果 1。否则,递归调用 factorial(n - 1)
来计算 (n-1)!
,然后将结果与 n
相乘,得到 n!
的结果。
注意在使用递归时,需要确保存在递归的终止条件(基线条件),以避免无限递归。此外,递归可能会占用较多的内存和计算资源,因此需要谨慎使用。
除了阶乘,递归还可以应用于其他许多问题,例如斐波那契数列、树的遍历、回溯等。
java可变参数
在Java中,可变参数(Variable Arguments)是一种特殊的方法参数,允许方法接受可变数量的参数。可变参数可以让我们更加灵活地传递参数,而无需事先指定参数的数量。以下是关于Java可变参数的基本信息:
-
声明可变参数:要声明可变参数,需要在方法的参数列表中使用省略号
...
来表示。例如,public void methodName(String... args)
声明了一个名为methodName
的方法,接受任意数量的字符串参数。 -
使用可变参数:在方法内部,可变参数被当作数组使用。可以通过索引访问可变参数中的元素,或者使用增强型的for循环遍历可变参数。例如,在上述的
methodName
方法内部,可以使用args[0]
来访问第一个参数。 -
调用可变参数方法:在调用带有可变参数的方法时,可以直接传递多个参数,也可以传递一个数组。编译器会根据需要自动将参数封装为数组。例如,
methodName("param1", "param2", "param3");
或methodName(array);
都是有效的调用方式。 -
可变参数的限制:可变参数必须是方法参数列表的最后一个参数。如果方法有多个参数,可变参数只能出现一次,并且必须是最后一个参数。例如,
public void methodName(String name, int age, String... hobbies)
是合法的,但public void methodName(String... args, int value)
是不合法的。
示例代码:
public void printNames(String... names) {
for (String name : names) {
System.out.println(name);
}
}
public void showNumbers(int... numbers) {
for (int num : numbers) {
System.out.println(num);
}
}
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.printNames("Alice", "Bob", "Charlie");
int[] nums = {1, 2, 3, 4, 5};
obj.showNumbers(nums);
}
上述示例代码中的printNames
方法和showNumbers
方法都使用了可变参数。printNames
方法接受任意数量的字符串参数,并在方法内部循环打印每个参数。showNumbers
方法接受任意数量的整数参数,并在方法内部循环打印每个数字。
使用可变参数可以使方法更加灵活,能够适应不同数量的参数传递,从而简化方法的调用和使用。
java对象
在Java中,对象(Object)是指类的实例化结果,它是类的具体实体。对象是具有状态和行为的实体,它可以拥有属性(成员变量)和方法(成员函数),用于表示真实世界中的事物或概念。
以下是关于Java对象的基本信息:
-
类的定义:在Java中,通过定义类来创建对象。类是一种模板或蓝图,描述了对象的属性和行为。类定义了对象的结构,包括成员变量、方法和构造函数等。
-
对象的创建:可以使用
new
关键字创建对象,使用类的构造函数来初始化对象。例如,ClassName obj = new ClassName();
创建了一个名为obj
的对象。 -
对象的访问:通过对象名称和点操作符
.
,可以访问对象的成员变量和方法。例如,obj.variableName
用于访问对象的成员变量,obj.methodName()
用于调用对象的方法。 -
对象的状态:对象的状态由其成员变量的值决定。可以在创建对象后,通过设置对象的成员变量来改变对象的状态。
-
对象的行为:对象的行为由其方法来描述。方法是定义在类中的函数,用于执行特定的操作。通过调用对象的方法,可以触发对象执行相应的行为。
-
对象的内存分配:Java中的对象都存储在堆(Heap)中。在创建对象时,Java会为对象分配堆内存,并将对象的引用地址存储在栈(Stack)中。
-
对象之间的交互:在程序中,不同的对象可以相互交互和通信。对象可以通过方法调用、参数传递和返回值等方式进行交互。
示例代码:
public class Person {
String name;
int age;
public void sayHello() {
System.out.println("Hello, my name is " + name + " and I am " + age + " years old.");
}
public static void main(String[] args) {
Person person1 = new Person();
person1.name = "Alice";
person1.age = 25;
person1.sayHello();
Person person2 = new Person();
person2.name = "Bob";
person2.age = 30;
person2.sayHello();
}
}
上述示例代码定义了一个名为Person
的类,表示人物对象。Person
类有两个成员变量name
和age
,以及一个方法sayHello
用于打印问候语。在main
方法中,创建了两个Person
对象,并设置对象的成员变量。通过调用对象的sayHello
方法,打印了两个人物对象的问候语。
通过创建对象,我们可以方便地实例化类并使用其属性和方法。Java的面向对象编程允许我们以一种结构化和模块化的方式组织代码,并模拟现实世界中的事物和概念。
实体类
实体类(Entity Class)是在面向对象编程中使用的一种特定类,用于表示真实世界中的实体或概念。它通常用于模型化业务领域的对象,包含了对象的属性和相关操作。
以下是关于实体类的一些基本信息:
-
属性:实体类包含表示实体的属性(也称为成员变量、字段)。这些属性描述了实体的状态和特征。属性可以是基本数据类型(如整数、字符串等),也可以是其他自定义类型或对象。
-
封装:实体类常常使用封装(Encapsulation)的原则来保护其属性。通过将属性设置为私有(private),并提供公有(public)的访问方法(也称为getter和setter),来控制对属性的访问和修改。
-
构造方法:实体类通常包含构造方法,用于创建实体对象并初始化其属性。构造方法可以带有参数,用于接收外部传入的值,并赋予属性初始值。
-
方法:实体类可以包含其他方法,用于执行与实体相关的操作。这些方法可以读取或修改属性的值,执行业务逻辑,以及与其他实体对象进行交互。
-
关联关系:实体类之间可以存在关联关系,表示实体之间的连接或依赖。关联关系可以是一对一、一对多或多对多等形式。关联关系的建立通常通过在实体类中使用其他实体类的对象或集合来实现。
-
序列化:在某些情况下,实体类需要支持序列化(Serialization),以在网络传输或存储中使用。为了实现序列化,实体类需要实现
Serializable
接口,并标记为可序列化。
示例代码:
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void sayHello() {
System.out.println("Hello, my name is " + name + " and I am " + age + " years old.");
}
}
上述示例代码定义了一个名为Person
的实体类,表示人物对象。Person
类具有name
和age
两个私有属性,提供了相应的getter和setter方法用于访问和修改属性值。它还包含了带参数的构造方法用于创建对象,并提供了一个sayHello
方法用于打印问候语。
实体类在面向对象编程中起到了重要的作用,它们可以优雅地封装数据和操作,使得程序结构清晰、可维护和可扩展。在实际开发中,根据业务需求,会创建各种不同的实体类来表示不同的实体或概念。
继承
在Java中,继承(Inheritance)是一种面向对象编程的重要概念,允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法。通过继承,子类可以获得父类的特征,同时还可以添加自己的特定功能。
以下是关于Java继承的一些基本信息:
-
继承关系:使用关键字
extends
来建立继承关系。子类继承父类的语法为:class 子类名 extends 父类名
。子类可以继承父类的非私有成员变量和方法。 -
子类和父类:子类是派生类,它可以从一个父类继承属性和方法。子类可以拥有自己的属性和方法,也可以重写(Override)父类的方法以实现自己的逻辑。
-
继承层次:Java允许多层次的继承,即一个类可以作为另一个类的子类,而另一个类又可以作为另一个类的子类,依此类推,形成继承的层次结构。
-
访问修饰符:继承关系中,子类可以访问父类的非私有成员变量和方法。如果父类的成员变量或方法被声明为私有(private),则子类无法直接访问,但可以通过父类提供的公共(public)方法来间接访问。
-
方法重写:子类可以重写父类的方法,即在子类中实现与父类方法名、参数列表和返回类型相同的方法。通过方法重写,子类可以改变父类方法的行为,以适应自己的需求。子类方法需要使用
@Override
注解来标识,以确保正确地重写父类方法。 -
super关键字:子类可以使用
super
关键字来引用父类的成员变量和方法。super
关键字可以用于调用父类构造方法、访问父类的成员变量、访问父类的方法等。
示例代码:
// 父类
public class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(name + " is eating.");
}
}
// 子类
public class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public void eat() {
super.eat(); // 调用父类的eat方法
System.out.println("Dog is barking while eating.");
}
public void bark() {
System.out.println("Dog is barking.");
}
}
// 测试代码
public class Main {
public static void main(String[] args) {
Dog dog = new Dog("Tom");
dog.eat(); // 调用重写后的eat方法
dog.bark(); // 调用子类的bark方法
}
}
上述示例代码中,Animal
为父类,Dog
为子类。子类Dog
继承了父类Animal
的属性和方法,并添加了自己的特定功能。Dog
类中重写了父类的eat
方法,以实现自己的逻辑。在测试代码中,创建了一个Dog
对象并调用其方法,展示了继承和重写的效果。
继承使得代码重用和扩展变得更加容易,可以提高代码的可维护性和可扩展性。它是面向对象编程的重要概念之一。
接口
在Java中,接口(Interface)是一种抽象类型,用于定义类应该实现的方法。接口只包含方法的声明,而没有对方法的具体实现。一个类可以实现一个或多个接口,通过实现接口,类可以确保自身具备接口所规定的方法。
以下是关于Java接口的一些基本信息:
-
定义接口:使用关键字
interface
来定义一个接口。接口中只能包含常量和抽象方法的声明,不包含成员变量和具体实现。 -
实现接口:使用关键字
implements
来表示一个类实现了一个或多个接口。一个类可以同时实现多个接口,多个接口之间使用逗号进行分隔。 -
接口方法:接口中的方法默认为抽象方法(abstract),不包含方法体。实现接口的类需要提供方法的具体实现。从Java 8开始,接口中也可以包含默认方法(default method)和静态方法(static method)。
-
默认方法:接口中的默认方法是在接口中提供的方法实现。实现类可以直接继承或重写默认方法。默认方法使用
default
关键字进行修饰。 -
静态方法:接口中的静态方法与默认方法类似,但静态方法在接口中有固定的实现,无法被实现类重写。可以通过接口名直接调用静态方法。
-
多继承的实现:接口在Java中解决了单继承的限制。一个类可以实现多个接口,从而获得多个接口的方法。
示例代码:
// 定义接口
public interface Animal {
void eat(); // 抽象方法
default void sleep() {
System.out.println("Animal is sleeping."); // 默认方法
}
static void run() {
System.out.println("Animal is running."); // 静态方法
}
}
// 实现接口
public class Dog implements Animal {
@Override
public void eat() {
System.out.println("Dog is eating.");
}
}
// 测试代码
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat(); // 调用实现的接口方法
dog.sleep(); // 调用默认方法
Animal.run(); // 调用静态方法
}
}
上述示例代码中,Animal
是一个接口,它声明了eat
方法,并提供了一个默认方法sleep
和一个静态方法run
。Dog
类实现了Animal
接口,并提供了eat
方法的具体实现。在测试代码中,创建了一个Dog
对象并分别调用了接口方法、默认方法和静态方法。
接口在Java中被广泛应用,它提供了一种规范和约束,使得代码更加灵活和可扩展。通过实现接口,类可以符合特定的行为规范,并且可以在不同的类之间实现代码的复用和解耦。
多态
多态(Polymorphism)是面向对象编程中的一个重要概念,表示一个对象可以拥有多种形态或类型。多态能够通过统一的接口调用不同的实现,提高代码的灵活性和可扩展性。
以下是关于Java多态的一些基本信息:
-
继承和重写:多态是建立在继承和方法重写的基础上的。子类可以继承父类的属性和方法,并对父类的方法进行重写,以实现多态的效果。
-
父类引用指向子类对象:通过将父类引用指向子类对象,可以实现多态。这意味着可以使用父类类型的引用来引用子类对象,从而调用子类对象的方法。
-
动态绑定:在多态中,方法的调用是在运行时根据实际对象的类型确定的,而不是在编译时确定的。这种动态确定方法调用的机制称为动态绑定(Dynamic Binding)或运行时绑定(Runtime Binding)。
-
向上转型:将子类对象赋值给父类引用的过程称为向上转型。向上转型是安全的,因为子类对象拥有父类的所有特征。
-
方法覆盖(Override):子类可以重写父类的方法,以改变方法的行为。当通过父类引用调用被子类重写的方法时,实际执行的是子类的方法。
-
向下转型:将父类引用强制转换为子类类型的引用,称为向下转型。向下转型需要进行类型检查,如果父类引用指向的实际对象不是转换后的类型,会抛出ClassCastException异常。
示例代码:
// 父类
public class Animal {
public void makeSound() {
System.out.println("Animal is making a sound.");
}
}
// 子类1
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog is barking.");
}
public void wagTail() {
System.out.println("Dog is wagging its tail.");
}
}
// 子类2
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Cat is meowing.");
}
public void scratch() {
System.out.println("Cat is scratching.");
}
}
// 测试代码
public class Main {
public static void main(String[] args) {
Animal animal1 = new Dog(); // 向上转型
animal1.makeSound(); // 通过父类引用调用子类重写的方法
// animal1.wagTail(); // 错误,无法调用子类特有的方法
Animal animal2 = new Cat(); // 向上转型
animal2.makeSound(); // 通过父类引用调用子类重写的方法
// animal2.scratch(); // 错误,无法调用子类特有的方法
Dog dog = (Dog) animal1; // 向下转型
dog.wagTail(); // 可以调用子类特有的方法
Cat cat = (Cat) animal2; // 向下转型
cat.scratch(); // 可以调用子类特有的方法
}
}
上述示例代码中,Animal
是父类,Dog
和Cat
是子类。子类重写了父类的makeSound
方法,并且还定义了自己的特定方法。在测试代码中,首先将子类对象向上转型为父类引用,并通过父类引用调用子类重写的方法。然后,通过向下转型,将父类引用重新转换为子类类型的引用,可以调用子类特有的方法。
多态使得代码更加灵活和可扩展,提高了代码的可维护性和复用性。它允许在不改变原有代码的情况下,通过扩展子类来实现新的功能。
this关键字
在Java中,this
是一个关键字,表示当前对象的引用。它可以在类的方法中使用,用于引用当前对象的实例。
以下是this
关键字的一些常见用法:
-
引用实例变量:在成员方法中,如果需要访问当前对象的实例变量,可以使用
this
关键字来引用。例如,this.variableName
可以引用当前对象的实例变量。 -
调用构造方法:在一个构造方法中,可以使用
this
关键字调用同一个类的其他构造方法。这种调用必须出现在构造方法的第一行,并且只能调用一个构造方法。例如,this()
表示调用无参构造方法,this(parameter)
表示调用带参数的构造方法。 -
区分局部变量和实例变量:当方法的参数名或局部变量名与实例变量名相同时,可以使用
this
关键字来区分。例如,this.variableName
表示引用当前对象的实例变量,而variableName
表示引用方法的参数或局部变量。 -
返回当前对象:在某些情况下,方法需要返回当前对象的引用。使用
return this;
语句可以返回当前对象本身。
示例代码:
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public void introduce() {
System.out.println("My name is " + this.name);
}
public Person changeName(String newName) {
this.name = newName;
return this;
}
public void printFullName(String lastName) {
String fullName = this.name + " " + lastName;
System.out.println(fullName);
}
public static void main(String[] args) {
Person person1 = new Person("Alice");
person1.introduce(); // 输出:My name is Alice
Person person2 = new Person("Bob");
person2.introduce(); // 输出:My name is Bob
person1.changeName("Carol").introduce(); // 输出:My name is Carol
person2.printFullName("Smith"); // 输出:Bob Smith
}
}
在上述示例代码中,this
关键字被用于引用当前对象的实例变量、调用其他构造方法、区分局部变量和实例变量,并在方法中返回当前对象。通过使用this
关键字,可以清晰地表示当前对象的操作和访问。
static关键字
在Java中,static
是一个关键字,用于修饰类的成员(属性和方法)。使用 static
关键字可以创建静态成员,这些成员属于类本身而不是实例。以下是 static
关键字的一些基本用法:
-
静态变量(静态字段):使用
static
修饰的成员变量被称为静态变量或静态字段。静态变量属于类,而不是类的实例。所有该类的实例共享同一个静态变量,可以通过类名直接访问。静态变量在内存中只有一份拷贝。 -
静态方法:使用
static
修饰的方法被称为静态方法。静态方法属于类,而不是类的实例。因此,静态方法不能直接访问非静态成员(例如实例变量或实例方法),只能访问静态成员。 -
静态代码块:使用
static
关键字定义的代码块称为静态代码块。静态代码块在类加载时执行,并且只执行一次。它主要用于初始化静态变量或执行其他静态操作。 -
使用静态成员:可以通过类名直接访问静态成员,无需创建类的实例。例如,
ClassName.staticVariable
可以直接访问类的静态变量,ClassName.staticMethod()
可以直接调用类的静态方法。 -
静态内部类:在一个类内部使用
static
关键字修饰的类称为静态内部类。静态内部类与外部类的实例无关,可以直接创建静态内部类的实例。使用静态内部类时,不需要依赖外部类的实例。
示例代码:
public class MyClass {
private static int staticVariable;
private int instanceVariable;
public static void staticMethod() {
System.out.println("This is a static method");
// instanceVariable = 10; // 错误,静态方法不能直接访问实例变量
staticVariable = 20; // 正确,静态方法可以访问静态变量
}
public void instanceMethod() {
System.out.println("This is an instance method");
instanceVariable = 10; // 正确,实例方法可以访问实例变量
staticVariable = 20; // 正确,实例方法可以访问静态变量
}
static {
System.out.println("This is a static block");
staticVariable = 30; // 初始化静态变量
}
public static class StaticInnerClass {
public void print() {
System.out.println("Static inner class");
}
}
public static void main(String[] args) {
staticMethod(); // 调用静态方法
MyClass instance = new MyClass();
instance.instanceMethod(); // 调用实例方法
MyClass.StaticInnerClass staticInner = new MyClass.StaticInnerClass();
staticInner.print(); // 调用静态内部类的方法
}
}
上述示例代码展示了 static
关键字的几个用法。其中,staticVariable
是静态变量,可以通过类名直接访问;staticMethod()
是静态方法,可以直接调用;静态代码块在类加载时执行;StaticInnerClass
是静态内部类,无需依赖外部类的实例就可以创建其实例。
final关键字
在Java中,final
是一个关键字,用于修饰变量、方法和类。以下是 final
关键字的一些使用场景和特性:
-
final变量:使用
final
修饰的变量被称为常量,一旦赋值后就不能再改变。常量的命名通常使用大写字母,并采用下划线分隔单词的方式。例如:final int MAX_VALUE = 100;
。常量必须在声明时或构造函数中进行初始化,可以在静态或实例上下文中声明。 -
final方法:使用
final
修饰的方法不能被子类重写。当一个方法的功能在父类中已经实现并且不允许被子类修改时,可以使用final
方法。例如:public final void printMessage() { ... }
。 -
final类:使用
final
修饰的类不能被继承。当一个类的设计已经完整,并且不希望其他类来继承扩展它时,可以使用final
类。例如:public final class MyClass { ... }
。 -
final参数:使用
final
修饰方法中的参数,表示参数值在方法内部不可修改。 -
final引用:使用
final
修饰对象的引用,表示该引用在初始化后不能再指向另一个对象。但是,被引用的对象本身的内容是可变的。
使用 final
关键字可以带来以下特性和优势:
- 安全性:通过使用
final
,可以防止常量、方法和类被修改或继承,确保其不可改变性,从而提高代码的安全性。 - 优化编译器:在Java中,编译器可能对
final
常量进行优化,例如内联常量,避免方法调用开销。 - 扩展性和设计约束:通过使用
final
,可以限制子类对父类的行为进行修改,增强代码的扩展性和稳定性。
需要注意的是,final
关键字并不意味着变量的内容无法修改,而是表示该变量的引用不可变。对于基本数据类型,其值无法改变;对于引用类型,变量所引用的对象的内容仍然可以修改。
包装类型
在判断空和调用方法的时候必须new对象
类型包装关系数据类型与对应的包装类(Wrapper Class)的对应关系如下:
byte
对应Byte
short
对应Short
int
对应Integer
long
对应Long
float
对应Float
double
对应Double
char
对应Character
boolean
对应Boolean
包装类是用于将基本数据类型包装为对象的类,通过包装类,可以在需要对象的场景中使用基本数据类型。包装类提供了一些额外的功能和方法,例如转换为字符串、比较值等。
如果你想在需要对象的上下文中使用基本数据类型,可以使用对应的包装类。在Java中,可以通过自动装箱(Autoboxing)和自动拆箱(Unboxing)的特性,在基本数据类型和包装类之间进行自动转换。例如,将 int
类型的变量赋值给 Integer
类型的引用变量,编译器会自动进行装箱操作。
以下是一个示例代码:
java
int num = 10; // 基本数据类型
Integer number = num; // 自动装箱
System.out.println(number.toString()); // 自动拆箱并调用toString()方法
double value = 3.14;
Double pi = value; // 自动装箱
System.out.println(pi.compareTo(3.0)); // 自动拆箱并调用compareTo()方法
通过包装类,我们可以在需要对象的上下文中使用基本数据类型,并调用包装类提供的方法和功能。
字符串操作
- chatat(int index)
- toCharArray(string str) 变为字符数组
- subString(int sraerIndex) 截取字符串
- subString(int start,int end)
- length() 长度
- trim() 去除空格
- split(string str) 分割字符串
- toUpperCase(string str)
- toLoweCase(string str)
- contains(string str) 包含字符串
- indexof(string str) 字符串首次出现的下标
- isEmpty() 是否为空
- equals() 相等
- replace(old,new)
- startWith() 判断开头字符
- endWith() 判断
- compartTo(string str)
//返回字符串中 第一个出现的下标 .indexof
String strr = "abcskafkahfabackajbfka";
System.out.println(strr.indexOf("a"));
//去除左右的空格
String strr1 = " abcskaf kahfa1 bac kajbfka";
System.out.println(strr1.trim());
//转大写
String strr2 = " abcskaf kahfa1 bac kajbfka";
System.out.println(strr1.toUpperCase(Locale.ROOT));
System.out.println(strr1.toLowerCase(Locale.ROOT));
//判断字符串以xx开头.结尾
String ss1 = "晋A 123542";
System.out.println("ss1字符串以晋开头吗" + ss1.startsWith("晋"));
System.out.println("ss1字符串以2结尾吗" + ss1.endsWith("2"));
//字符串替换
String ss2 = "abcabcededfa";
String replace = ss2.replace("a", "A");
System.out.println("旧字符串 =" + ss2 + "新字符串 =" + replace);
//切割字符串
String ss3 = "0123456789";
String substring = ss3.substring(1, 5);//sunstring(开始下标,结束下标减一);
System.out.println("截取字符串1到5: " + substring);
//判断字符串是否为空
System.out.println("1".isEmpty());
System.out.println("".isEmpty());
//前面字符串比后面字符串小,返回负数,前面字符串比后面大,则返回长度差值
System.out.println("abcde".compareTo("abcdef"));
System.out.println("abcdef".compareTo("abcd"));
System.out.println("abec".compareTo("abcd"));
System.out.println("abcdef".compareTo("abcd"));
System.out.println("abcde".compareTo("abcde"));
StringBuffer
可变字符串(Mutable String)是指可以在原始字符串的基础上进行修改的字符串。在Java中,String
类是不可变的,即一旦创建了一个 String
对象,其值就不能改变。当需要频繁地对字符串进行修改时,使用可变字符串会更加高效。
在Java中,可变字符串可以通过 StringBuilder
或 StringBuffer
类来实现。
StringBuilder
:StringBuilder
是线程不安全的可变字符串类,适用于单线程环境下的字符串拼接和修改操作。它提供了一系列的方法用于字符串的追加、替换、插入、删除等操作。示例代码如下:
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" ");
sb.append("World!");
String result = sb.toString(); // 将可变字符串转换为不可变的字符串
StringBuffer
:StringBuffer
是线程安全的可变字符串类,适用于多线程或并发环境下的字符串操作。除了提供了与StringBuilder
相同的方法外,StringBuffer
还包含了一些同步方法,保证了线程安全。示例代码如下:
StringBuffer sb = new StringBuffer();
sb.append("Hello");
sb.append(" ");
sb.append("World!");
String result = sb.toString(); // 将可变字符串转换为不可变的字符串
通过使用可变字符串,可以避免在每次对字符串进行修改时都创建一个新的字符串对象,从而提高效率和性能。
需要注意的是,可变字符串并不适用于所有场景。如果在单线程环境下进行字符串拼接或修改操作,并且不需要线程安全性的保证,通常建议使用 StringBuilder
。而在多线程或并发环境下,为了保证线程安全性,应该使用 StringBuffer
。
总结来说,可变字符串是指可以在原始字符串基础上进行修改的字符串。在Java中,可变字符串可以通过 StringBuilder
或 StringBuffer
类实现。它们提供了一系列的方法用于字符串的拼接、替换、插入、删除等操作,具有高效性和灵活性。选择使用 StringBuilder
还是 StringBuffer
取决于是否需要线程安全性的保证。
LocalData
java.time.LocalDate
是 Java 8 引入的日期类,用于表示日期,不包含时间和时区信息。它位于 java.time
包中,提供了许多方法来处理和操作日期。
下面是一些示例代码,演示了如何使用 LocalDate
类:
import java.time.LocalDate;
public class LocalDateExample {
public static void main(String[] args) {
// 获取当前日期
LocalDate now = LocalDate.now();
System.out.println("当前日期: " + now);
// 创建指定日期
LocalDate specificDate = LocalDate.of(2023, 9, 10);
System.out.println("指定日期: " + specificDate);
// 获取年、月、日
int year = now.getYear();
int month = now.getMonthValue();
int day = now.getDayOfMonth();
System.out.println("年: " + year);
System.out.println("月: " + month);
System.out.println("日: " + day);
// 进行日期操作
LocalDate tomorrow = now.plusDays(1);
LocalDate previousMonth = now.minusMonths(1);
System.out.println("明天的日期: " + tomorrow);
System.out.println("上个月的日期: " + previousMonth);
// 比较日期
LocalDate date1 = LocalDate.of(2023, 9, 10);
LocalDate date2 = LocalDate.of(2023, 9, 11);
System.out.println("date1 在 date2 之前: " + date1.isBefore(date2));
System.out.println("date1 在 date2 之后: " + date1.isAfter(date2));
System.out.println("date1 和 date2 相等: " + date1.isEqual(date2));
}
}
上述示例演示了如何使用 LocalDate
类来获取当前日期、创建指定日期、获取年月日、进行日期操作(如加减天数、月数等)、比较日期等常见操作。
总结来说,java.time.LocalDate
是 Java 8 引入的日期类,用于表示日期。它不包含时间和时区信息,提供了丰富的方法来处理和操作日期。可以使用它来获取当前日期、创建指定日期、获取年月日、进行日期操作、比较日期等。
Collections 工具
Collections 是 Java 中提供的一个工具类,提供了一些常用的集合操作方法,包括排序、查找、替换等。它可以对 List、Set、Map 等集合进行操作,是集合框架中的一个重要组成部分。
以下是一些常用的 Collections 工具示例:
- 排序
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("orange");
// 使用 Collections.sort() 方法对列表进行排序
Collections.sort(list);
for (String fruit : list) {
System.out.println(fruit);
}
- 查找
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("orange");
// 使用 Collections.binarySearch() 方法对列表进行二分查找
int index = Collections.binarySearch(list, "banana");
if (index >= 0) {
System.out.println("找到了,下标为:" + index);
} else {
System.out.println("没找到");
}
- 替换
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("orange");
// 使用 Collections.replaceAll() 方法对列表进行元素替换
Collections.replaceAll(list, "banana", "pear");
for (String fruit : list) {
System.out.println(fruit);
}
除此之外,Collections 还提供了诸如填充、反转、随机化等方法,可以方便地对集合进行处理。使用 Collections 工具类可以简化集合操作的代码,并提高编程效率。
需要注意的是,Collections 工具类中的很多方法都是对原始集合进行修改,因此应谨慎使用,并避免在多线程环境下使用。如果需要进行并发操作,应该使用线程安全的集合类或者手动实现同步控制。
sort(List<T> list):对指定列表进行升序排序。
reverse(List<T> list):反转指定列表的顺序。
shuffle(List<T> list):随机打乱指定列表中元素的顺序。
binarySearch(List<? extends Comparable<? super T>> list, T key):在有序列表中使用二分查找算法查找指定元素。
max(Collection<? extends T> coll):返回指定集合中的最大元素。
min(Collection<? extends T> coll):返回指定集合中的最小元素。
frequency(Collection<?> coll, Object o):返回指定集合中指定元素的出现次数。
addAll(Collection<? super T> c, T... elements):将指定元素添加到集合中。
synchronizedCollection(Collection<T> c):返回指定集合的同步(线程安全)视图。
unmodifiableCollection(Collection<? extends T> c):返回指定集合的不可修改视图,任何修改操作都会抛出 UnsupportedOperationException 异常。
List
Java中的List是一个接口,它继承自Collection接口,用于表示有序、可重复的元素序列。List接口提供了一系列方法来操作列表中的元素,例如添加、删除、获取和修改等操作。
List接口的常见实现类包括:
-
ArrayList:基于数组实现的动态数组,支持快速随机访问和增删操作。它是最常用的List实现类之一。
-
LinkedList:基于链表实现的双向链表,支持快速插入和删除操作,但访问和查找元素较慢。
-
Vector:与ArrayList类似,也是基于数组实现的动态数组,但是它是线程安全的,在多线程环境中使用较为合适。
List接口的特点包括:
-
有序性:列表中的元素按照插入顺序进行排序,并且可以根据索引进行访问。
-
可重复性:列表中可以包含重复的元素。
List接口定义了一系列常用的方法,包括:
add(E element)
:向列表末尾添加元素。add(int index, E element)
:在指定索引处插入元素。remove(int index)
:删除指定索引处的元素。get(int index)
:获取指定索引处的元素。set(int index, E element)
:将指定索引处的元素替换为新元素。size()
:返回列表的大小(元素个数)。contains(Object element)
:检查列表是否包含指定元素。indexOf(Object element)
:返回指定元素第一次出现的索引。isEmpty()
:检查列表是否为空。clear()
:清空列表中的所有元素。
这些方法展示了List接口提供的基本功能,您可以根据具体的需求选择合适的List实现类并使用相应的方法来操作列表中的元素。
方法
-
add(元素);
-
add(index ,元素);
-
addA11(co1lection) ;
-
//删除 clear();size==0
-
remove(index/object);
toArrayO; -
Arrays.asList(… );
-
//修改
set(index , object) -
//查询
size(); //int
get(index) ;//获取下标的元素 -
list.iterator() 创建一个迭代器,while循环( )
while (iterator.hasNext()){
System.out.println(iterator.next());
}
// List list1 =new ArrayList(;for(int i=0; i<=size-1; i++){
sout(list1.get(i))
}
//加强for循环
for(object o : list1) {
sout(o)
}
//
Iterator i = list1.iterator ();
while(i.hasNext()){
sout(i.next ()
}
扩容List 在创建时可以指定一个初始容量,默认情况下为 10。这表示 List 初始时可以容纳 10 个元素,如果需要添加的元素数量超过了初始容量,List 将自动进行扩容。
/**
* @author MG
* @version 1.0
* @date 2023/8/30 9:52
* @座右铭:努力到无能为力,拼搏到感动自己!!!
* @description:
* list 定义: List<String> list=new ArrayList();
* 方法有:
* list.add(插入)
* list.remove(删除)
* list.set(修改)
* list.get(下标)
* 循环查看内容
* 迭代器查询
* Iterator<String> iterator=list.iterator();
* while (iterator.hasNext()){
* System.out.println(iterator.next());
* }
*/
在 Java 中,List
是一个接口,用于表示有序的、可重复的元素集合。它是 Collection
接口的子接口之一,提供了各种操作来对列表进行增删改查等操作。
List
接口的常见实现类包括:
ArrayList
:基于数组实现的动态数组,查找和修改元素效率较高,但是插入和删除元素效率较低。LinkedList
:基于双向链表实现的列表,插入和删除元素效率较高,但是查找和修改元素效率较低。Vector
:与ArrayList
类似,线程安全的动态数组,但性能较差,通常不推荐使用。
public void creatlist(){
// 创建一个 ArrayList 列表
List<String> list = new ArrayList<>();
// 添加元素
list.add("Apple");
list.add("Banana");
list.add("Orange");
// 访问元素
String firstElement = list.get(0);
System.out.println("第一个元素:" + firstElement);
// 修改元素
list.set(1, "Grape");
System.out.println("修改后的列表:" + list);
// 删除元素
list.remove(2);
System.out.println("删除后的列表:" + list);
// 列表信息
int size = list.size();
boolean isEmpty = list.isEmpty();
boolean c = list.contains("c");
System.out.println("列表包含元素c吗?:" + c);
System.out.println("列表大小:" + size);
System.out.println("列表是否为空:" + isEmpty);
}
运行代码,你将看到如下输出:
第一个元素:Apple
修改后的列表:[Apple, Grape, Orange]
删除后的列表:[Apple, Grape]
列表包含元素c吗?:false
列表大小:2
列表是否为空:false
set
set(int index, E element)
是List接口中定义的一个方法,用于将指定索引位置的元素替换为新的元素。
语法如下:
E set(int index, E element)
参数说明:
index
:要替换元素的索引位置。element
:要替换的新元素。
返回值:
- 被替换的旧元素。
示例代码:
ArrayList<String> myList = new ArrayList<String>();
myList.add("元素1");
myList.add("元素2");
myList.add("元素3");
// 替换索引为1的元素
String oldElement = myList.set(1, "新元素");
System.out.println("被替换的旧元素:" + oldElement);
System.out.println("替换后的列表:" + myList);
输出结果:
被替换的旧元素:元素2
替换后的列表:[元素1, 新元素, 元素3]
在上述示例中,我们创建了一个ArrayList列表,并添加了三个元素。然后,我们使用set()
方法将索引为1的元素替换为新元素。替换之后,列表内容变为[元素1, 新元素, 元素3]
。set()
方法返回被替换的旧元素,这里是"元素2"。
// 创建一个存储整数的 HashSet
Set<Integer> set = new HashSet<>();
// 添加元素到集合中
set.add(1);
set.add(2);
set.add(3);
set.add(4);
// 打印集合大小
System.out.println("集合大小:" + set.size());
// 检查元素是否存在于集合中
System.out.println("集合是否包含 3:" + set.contains(3));
System.out.println("集合是否包含 5:" + set.contains(5));
// 删除元素
set.remove(2);
// 打印更新后的集合内容
System.out.println("更新后的集合:" + set);
// 遍历集合
System.out.println("遍历集合:");
for (Integer num : set) {
System.out.println(num);
}
// 清空集合
set.clear();
// 检查集合是否为空
System.out.println("集合是否为空:" + set.isEmpty());
set 与 list 区别
Set 和 List 是 Java 集合框架中两种常见的集合类型,它们有以下几个主要区别:
- 有序性:List 是有序集合,按照元素的插入顺序维护元素的顺序。即,当你迭代或遍历 List 时,元素的顺序是可以预测的。而 Set 是无序集合,不保证元素的顺序。Set 中的元素是通过哈希算法确定存储位置,所以它没有固定的顺序。
- 元素的唯一性:List 允许存储重复的元素,即使相同元素可以被多次添加到 List 中。而 Set 不允许重复元素,它会自动去除重复元素。Set 使用哈希表或树来检查元素是否已经存在,所以它具有高效的查找和插入操作。
- 数据结构:List 使用动态数组(如 ArrayList)或链表(如 LinkedList)来实现,因此可以在任意位置插入和删除元素。Set 通常使用哈希表(如 HashSet)或树(如 TreeSet)来实现,这些数据结构对于快速查找元素是非常高效的。
- 接口方法:List 接口提供了大量用于操作列表的方法,例如按索引访问元素、插入元素、移除元素等。Set 接口提供了基本的集合操作方法,例如添加元素、删除元素、判断元素是否存在等。
选择使用 List 还是 Set 取决于你的需求。如果你需要按照顺序存储元素并且允许重复元素,则选择 List。如果你不需要考虑元素的顺序,并且要确保元素的唯一性,则选择 Set。
需要注意的是,对于大多数情况下,List 和 Set 都是泛型类型(如 List),这意味着你可以指定集合中保存的元素类型。
泛型
泛型写到类上面;
public class 泛型<E> {
E e;
public E ee(E e){
return e;
}
public static void main(String[] args) {
泛型<Integer> a=new 泛型();
a.e=1;
Integer ee = a.ee(1);
System.out.println(ee);
}
}
class 泛型多个<K,V>{
K k;
V v;
public K ff1(K k){
return k;
}
public V ff2(V v){
return v;
}
public static void main(String[] args) {
泛型多个<Integer,String> a=new 泛型多个<>();
a.k=2;
a.v="str";
System.out.println(a.ff1(a.k));
System.out.println(a.ff2(a.v));
}
}
泛型写到接口上
建议在实现类上写和接口相同的字母3.在创建对象时指定具体类到
泛型写到方法上
HashMAP
HashMap是Java中常用的实现了Map接口的集合类,它提供了键值对的存储和检索功能。HashMap基于哈希表的数据结构实现,可以高效地进行元素的插入、查找和删除操作。
HashMap的特点包括:
- 键值对存储:HashMap中的元素以键值对的形式存储,每个键对应一个唯一的值。
- 无序性:HashMap中的元素没有固定的顺序,即插入和遍历的顺序不一定相同。
- 可容纳null值和null键:HashMap允许存储null值和null键。
- 非线程安全:HashMap在多线程环境下不是线程安全的,如果需要在多线程环境中使用,可以考虑使用ConcurrentHashMap。
HashMap的常用操作方法包括:
put(K key, V value)
:将指定的键值对添加到HashMap中。get(Object key)
:根据键获取对应的值。remove(Object key)
:删除指定键对应的键值对。containsKey(Object key)
:判断HashMap中是否包含指定的键。containsValue(Object value)
:判断HashMap中是否包含指定的值。size()
:返回HashMap中键值对的数量。isEmpty()
:判断HashMap是否为空。clear()
:清空HashMap中的所有键值对。- 等等。
下面是一个使用HashMap的示例代码:
import java.util.HashMap;
public class Main {
public static void main(String[] args) {
// 创建一个HashMap对象
HashMap<String, Integer> map = new HashMap<>();
// 添加键值对
map.put("apple", 5);
map.put("banana", 3);
map.put("orange", 8);
// 获取值
int appleCount = map.get("apple");
System.out.println("苹果的数量:" + appleCount);
// 遍历HashMap
for (String key : map.keySet()) {
int value = map.get(key);
System.out.println(key + ": " + value);
}
// 删除键值对
map.remove("orange");
// 判断是否包含某个键
boolean containsBanana = map.containsKey("banana");
System.out.println("是否包含香蕉:" + containsBanana);
// 清空HashMap
map.clear();
System.out.println("HashMap是否为空:" + map.isEmpty());
}
}
输出结果:
苹果的数量:5
apple: 5
banana: 3
orange: 8
是否包含香蕉:true
HashMap是否为空:true
在上述示例中,我们创建了一个HashMap对象,并向其中添加了三个键值对。然后,我们根据键获取对应的值,并通过遍历HashMap输出所有的键值对。接下来,我们删除了一个键值对,并判断了HashMap中是否包含某个键。最后,我们清空了HashMap并检查它是否为空。
//创建一个map集合
Map<Character, Integer> map = new HashMap<>();
//添加元素
map.put('a', 1);
//删除map
map.remove('c');
for (Character c : map.keySet()) {//拿到map中的买个建
Integer value = map.get(c);//拿到建对应的值
System.out.println("键:" + c + ",对应的值:" + value);
}
for (Map.Entry<String, Object> entey : map.entrySet()
) {
System.out.println("K+" + entey.getKey() + "V:" + entey.getValue());
}
检查 HashMap
是否包含指定的键或值:使用 containsKey()
方法来检查 HashMap
是否包含指定的键,使用 containsValue()
方法来检查是否包含指定的值。
boolean containsKey = hashMap.containsKey(key);
boolean containsValue = hashMap.containsValue(value);
更新
使用 put()
方法,传入键和新的值,将键对应的值更新为新值。
异常
描述 | 异常 |
---|---|
NullointerException | 空指针异常 |
ArraylndexOutOfBoundsException | 数组越界异常 |
ClassCastException | 类型转换异常 |
NumberFormatException | 数字格式化异常 |
ArithmeticException | 算术异常 |
try {
// 可能引发异常的代码块
} catch (ExceptionType1 e1) {
// 处理 ExceptionType1 异常的逻辑
} catch (ExceptionType2 e2) {
// 处理 ExceptionType2 异常的逻辑
} finally {
// 无论异常是否发生,都会执行的代码块
}
IO流
字节输入输出(传视频,图片) 1byte = 8 bit
input输入(磁盘读到内存中读到代码里面 ) output 输出
字符输入输出 (传文件) 1char=2byte
缓存流
package com.MG.学习内容;
import com.MG.pojo.Emp;
import java.io.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @author MG
* @version 1.0
* @date 2023/9/4 14:05
* @座右铭:努力到无能为力,拼搏到感动自己!!!
* @description: IO流
*/
public class IO流介绍 {
public static void main(String[] args) throws IOException {
IO流介绍 i = new IO流介绍();
i.BufferedWriterMethod();
i.BufferedReaderMethod();
i.newBufferedReaderMethod();
// i.Write字符Method();
// i.Read字符Method();
// i.字节字符转化写Method();
// i.字节字符转化读Method();
// i.IO字节ReadMethod();
//i.IO缓冲流输入OutputMethod("--" + '\n' + "你好");
// i.Buff读取数据Method();
// i.字符流写入Method();
// i.IO缓冲流ReadMethod();
// i.Object写入Method();
// i.Object读取Method();
}
//全局变量有默认值 null 如果写成局部变量必须赋值 null
InputStream inputStream = null;//从文件读取 从文件读到内存(我)
OutputStream outputStream = null;//向文件写入 从(我)内存传到文件
//----------3个读取----------
//输出每一个 .rade()不是-1 的char值
public void IO字节ReadMethod() {
System.out.println("IO字节ReadMethod");
try {
//多态创建
inputStream = new FileInputStream("LX1.txt");
/** input 字节读取
* FileInputStream(文件的地址)
* -个字节一个字节的读:内容如果读完了,返回-1,如没没有读完,把信息转int
* inputStream.read() 读取方法,返回值是-1 则读取完毕
*/
int count = 0;
while ((count = inputStream.read()) != -1) {
char c = (char) count;
System.out.println(c);
}
} catch (IOException e) {
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
//输出每一个 .rade(字节数组)不是-1 的char值 通过string(字节数组,startIndex,endIndex)
public void IO多字节ReadMethod() {
System.out.println("IO多字节Read");
try {
inputStream = new FileInputStream("LX1.txt");
byte[] buf = new byte[1024];
int count = 0;
while ((count = inputStream.read(buf)) != -1) {
String string = new String(buf, 0, count);
System.out.println(string);
}
} catch (IOException e) {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
}
//通过buff 输出每一个 .rade(字节数组)不是-1 的char值 通过string(字节数组,startIndex,endIndex)
public void IO缓冲流ReadMethod() {
System.out.println("IO缓冲流ReadMethod");
/**
*多态创建一个 inputStream FileInputStream - 找到文件位置
* 创建一个 bufferedInputStream 将inputStream 放入
* count=bufferedInputStream.read(buf) 读取数组长度 返回的count不会大于数组长度
* 调用 bufferedInputStream 的 read ( 数组 ) 方法 返回 -1 结束
* 上面方法 返回 int 赋值给string( 数组,0 ,最大值)
* String(byte[] bytes, int offset, int length)
* 数组 从该数组0开始 到了count 用来表示输出数组中 从0 到count
bytes:要转换为字符串的字节数组。
offset:指定要开始转换的起始位置(偏移量),通常是 0。
length:指定要转换的字节数。
*/
//缓存字节输入流
BufferedInputStream bufferedInputStream = null;
try {
inputStream = new FileInputStream("LX1.txt");
bufferedInputStream = new BufferedInputStream(inputStream);
StringBuilder stringBuilder = new StringBuilder();
int count = 0;
byte[] buf = new byte[90];
while ((count = bufferedInputStream.read(buf)) != -1) {
stringBuilder.append(new String(buf, 0, count));
System.out.println(stringBuilder.toString());
}
} catch (IOException e) {
} finally {
if (bufferedInputStream != null) {
try {
bufferedInputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
//----------3个输入----------
//将字符串每个字节都写进去
public void IO字符输入OutputMethod(String str) {
System.out.println("IO字符输入OutputMethod");
// ------------output 写---------
try {
outputStream = new FileOutputStream("LX1.txt", true);
char[] charArray = str.toCharArray();
for (int i = 0; i < charArray.length; i++) {
outputStream.write(charArray[i]);
}
} catch (IOException e) {
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
//将字符串的getbyte数组写进去
public void IO字节输入OutputMethod(String str) {
System.out.println("IO字节输入OutputMethod");
// ------------output 写---------
try {
outputStream = new FileOutputStream("LX1.txt", true);
byte[] bytes = str.getBytes();
outputStream.write(bytes);
} catch (IOException e) {
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
//将字符串的getbyte数组 通过buff(output) 写进去 .write .flush
public void IO缓冲流输入OutputMethod(String str) {
System.out.println("IO缓冲流输入OutputMethod");
/**
* 要使用BufferedOutputStream来实现对文件的写入操作,可以按照以下步骤进行:
*
* 创建一个FileOutputStream对象,用于将数据写入文件。可以指定文件路径和名称作为构造函数的参数。
* 创建一个BufferedOutputStream对象,将上一步创建的FileOutputStream对象作为参数传入。
* 使用BufferedOutputStream的write()方法将数据写入缓冲区。
* 使用flush()方法将缓冲区中的数据刷新到文件中,确保数据被写入文件。
* 使用close()方法关闭BufferedOutputStream和FileOutputStream,释放资源。
*/
// ------------output 写---------
try {
outputStream = new FileOutputStream("LX1.txt", true);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
byte[] bytes = str.getBytes();
bufferedOutputStream.write(bytes);
// 刷新缓冲区,确保数据被写入文件
bufferedOutputStream.flush();
} catch (IOException e) {
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
//--------------------
public void writer写入Method() {
System.out.println("writer写入Metho");
try (Writer writer = new FileWriter("LX1.txt")) {
String data = "This is some data to be written to the file.";
writer.write(data);
} catch (IOException e) {
e.printStackTrace();
}
}
public void Buff读取数据Method() {
System.out.println("Buff读取数据Method");
try (BufferedReader reader = new BufferedReader(new FileReader("LX1.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//--------------------
public void Object写入Method() {
System.out.println("Object写入Method");
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\Eclipse\\工作空间\\lianxi\\output.txt", true))) {
List<Emp> list = new ArrayList<>();
Emp emp = new Emp("John0", 20);
Emp emp1 = new Emp("John1", 21);
list.add(emp);
list.add(emp1);
oos.writeObject(list);
} catch (IOException e) {
e.printStackTrace();
}
}
public void Object读取Method() {
System.out.println("Object读取Method");
try (ObjectInputStream oos = new ObjectInputStream(new FileInputStream("D:\\Eclipse\\工作空间\\lianxi\\output.txt"))) {
List<Emp> list = (List<Emp>) oos.readObject();
Iterator<Emp> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
/* 读取对象
Object obj;
while ((obj=oos.readObject())!=null){
System.out.println(obj);
}*/
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
//--------------------
public void Read字符Method() throws IOException {
Reader reader = new FileReader("LX1.txt");
int count = 0;
char[] buf = new char[100];
while ((count = reader.read(buf)) != -1) {
String s = new String(buf, 0, count);
System.out.println(s);
}
reader.close();
}
public void Write字符Method() throws IOException {
Writer writer = new FileWriter("LX1.txt");
writer.write("1111");
writer.close();
}
public void 字节字符转化写Method() throws IOException {
Writer writer = new OutputStreamWriter(new FileOutputStream("LX1.txt"), "UTF-8");
writer.write("你好 朋友!");
writer.close();
}
public void 字节字符转化读Method() throws IOException {
Reader reader = new InputStreamReader(new FileInputStream("LX1.txt"), "UTF-8");
int count = 0;
char[] chars = new char[1024];
while ((count = reader.read(chars)) != -1) {
System.out.println(new String(chars, 0, count));
}
reader.close();
}
//-------------buff read----
public void BufferedReaderMethod() throws IOException {
Reader reader = new InputStreamReader(new FileInputStream("LX1.txt"), "UTF-8");
BufferedReader bufferedReader=new BufferedReader(reader);
int count=0;
char [] arr= new char[1024];
while ( (count = bufferedReader.read(arr))!=-1){
System.out.println(new String(arr,0,count));
}
bufferedReader.close();
}
最终版本
public void BufferedWriterMethod() throws IOException {
Writer writer=new OutputStreamWriter(new FileOutputStream("LX1.txt"),"UTF-8");
BufferedWriter bufferedWriter=new BufferedWriter(writer);
String str= "hello 大家好!";
bufferedWriter.write(str);
bufferedWriter.newLien(); /空
bufferedWriter.flush();
bufferedWriter.close();
}
public void newBufferedReaderMethod() throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("LX1.txt"),"UTF-8"));
String string=null;
while ( (string=reader.readLine())!=null ){
System.out.println(string);
}
reader.close();
}
}
File
public static void main(String[] args) throws IOException {
FileMethod();
DirectoryMethod();
}
//文件的各种方法
public static void FileMethod() throws IOException {
File file=new File("D:\\Eclipse\\工作空间\\lianxi\\LX");
System.out.println("file 存在吗 " + file.exists());
System.out.println("file 是目录吗 "+ file.isDirectory());
System.out.println("file 是文件吗 "+ file.isFile());
System.out.println("file地址"+ file.getPath());
System.out.println("file绝对地址"+ file.getAbsolutePath());
System.out.println("file名称 "+file.getName());
System.out.println("file 父目录名称"+file.getParent());
System.out.println("file 长度"+file.length());
System.out.println("file 创建时间"+new Date(file.lastModified()).toGMTString());
System.out.println("file 是否可写"+ file.canWrite());
System.out.println("file 是否隐藏"+file.isHidden());
if(!file.exists()){
file.createNewFile();
}
}
//创建目录
public static void DirectoryMethod() throws IOException {
File file = new File("D:\\Eclipse\\工作空间\\lianxi\\LX2");
if (!file.exists()) {
file.mkdir();
// file.mkdirs();创建多个
// file.delete();删除
}
System.out.println("file是否目录 " + file.isDirectory());
}
递归
public static void listFilesRecursive(File directory) {
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isFile()) {
System.out.println("文件: " + file.getAbsolutePath());
} else if (file.isDirectory()) {
System.out.println("文件夹: " + file.getAbsolutePath());
listFilesRecursive(file); // 递归调用,处理子目录
}
}
}
}
常用类
Java中有很多常用的类,这些类提供了各种功能和工具,可以帮助我们更方便地进行编程和开发。以下是一些常用的Java类:
String
:用于处理字符串,提供了字符串的各种操作和方法。StringBuilder
和StringBuffer
:用于高效地拼接和修改字符串。ArrayList
、LinkedList
和Vector
:用于实现动态数组,提供了对数组元素的增删改查等操作。HashMap
、TreeMap
和LinkedHashMap
:用于实现映射(键-值对)数据结构,提供了快速的查找和插入操作。HashSet
、TreeSet
和LinkedHashSet
:用于实现集合数据结构,提供了去重和集合运算等功能。Date
和Calendar
:用于处理日期和时间,提供了日期和时间的计算和格式化等功能。File
:用于操作文件和目录,提供了创建、删除、读写等文件系统相关的操作。Math
:提供了各种数学计算相关的方法,如三角函数、对数函数、随机数生成等。Scanner
和BufferedReader
:用于从输入流中读取用户输入或者文件数据。Exception
和其他异常类:用于处理异常情况,提供了异常的捕获和处理机制。
除了上述类,还有许多其他常用的类库,如多线程相关的类(如Thread
、Runnable
等)、网络编程相关的类(如Socket
、URL
等)、数据库操作相关的类(如Connection
、Statement
等)等。
需要根据具体的开发需求选择合适的类库来使用。Java提供了丰富的类库,可以满足各种不同的编程和开发需求。
反射
反射(Reflection)是Java语言的一个特性,它允许程序在运行时动态地获取、检查和操作类的信息、对象的属性和方法。
获取反射的三种方式
public void 获取反射Method() throws ClassNotFoundException {
//方法一
Class<?> aClass = Class.forName("com.MG.pojo.Emp");
//方法二
Class<Emp> empClass = Emp.class;
//方法三
Emp emp = new Emp();
Class<? extends Emp> aClass1 = emp.getClass();
}
获得包名全限定路径名继承
Class<?> aClass = Class.forName("com.MG.pojo.Emp");
System.out.println("Emp 反射的包名" + aClass.getPackage().getName());
System.out.println("Emp 反射的全限定路径名" + aClass.getName());
System.out.println("Emp 继承 " + aClass.getSuperclass());
获得属性
- getDeclaredFields()这个方法用于获取指定类中具有指定方法名和参数类型的任何方法(包括私有方法和受保护方法)。
- .getMethod:这个方法用于获取指定类(包括其父类和超类)中具有指定方法名和参数类型的公共方法(public method)。它只能获取公共方法,并且继承自父类或超类的公共方法也可以被获取到。
Field[] declaredFields = aClass.getDeclaredFields();
for (Field f : declaredFields) {
System.out.println("属性名" + f.getName());
System.out.println("属性类型" + f.getType().getName());
}
Field[] declaredFields1 = aClass.getFields();
for (Field f1 : declaredFields1) {
System.out.println("属性名:" + f1.getName());
System.out.println("属性类型:" + f1.getType().getName());
}
获得方法
Method[] methods = aClass.getMethods();
for (Method m : methods) {
System.out.println("name :"+m.getName());
Parameter[] parameters = m.getParameters();
Class<?> returnType = m.getReturnType();
System.out.println("参数 :"+Arrays.toString(parameters));
System.out.println("返回值 :"+returnType);
}
创建构造器new对象
public static void 获取构造器Method() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Class<?> aClass = Class.forName("com.MG.pojo.Emp");
//空参构造器
Constructor<?> constructor = aClass.getConstructor();
System.out.println(constructor);
//直接建一个对象 , 默认无参
Object o = aClass.newInstance();
//有参构造器
Constructor<?> constructor1 = aClass.getConstructor(String.class);
//创建一个带参数的对象必须先创建一个带参数的构造器,然后去newinstance 对象
Object o1 = constructor1.newInstance("name1");
System.out.println(o1);
}
创建对象与调用方法
public static void 调用Method() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
//获得反射
Class<?> aClass = Class.forName("com.MG.pojo.Emp");
//拿到一个无参无反方法
Method empMethod = aClass.getDeclaredMethod("empMethod");
//invoke方法,里面得创一个对象
System.out.println(empMethod.invoke(aClass.newInstance()));
//创建一个对象
Object o = aClass.newInstance();
//拿到一个有参无反方法
Method setName = aClass.getDeclaredMethod("setName", String.class);
//invoke方法,里面得创一个对象
setName.invoke(o,"dada");
//拿到一个无参有返回值方法
Method getName = aClass.getDeclaredMethod("getName");
//invoke方法,里面得创一个对象
System.out.println(getName.invoke(o));
//拿到一个有参有返回值方法
Method emp2Method = aClass.getMethod("emp2Method", String.class);
//invoke 方法,里面得创一个对象
System.out.println("返回值是:"+emp2Method.invoke(aClass.newInstance(), "传入的参数内容"));
}
通过反射,可以在运行时获取类的名称、父类、接口、字段、方法等信息,并且可以在运行时创建对象、调用方法、修改属性等。反射机制使得程序可以在编译时期未知具体类型的情况下,对类进行操作和调用。
Java的反射机制主要涉及以下几个核心类:
Class
类:代表一个类或接口,在运行时可以获取该类的各种信息,如名称、父类、接口、字段、方法等。可以通过Class类的静态方法forName()
获取一个类的Class对象。例如,Class.forName("com.example.MyClass")
返回MyClass类的Class对象。Field
类:代表类的成员变量(字段),可以获取和修改字段的值。可以使用getDeclaredFields()
方法获取所有声明的字段,使用get()
和set()
方法获取和设置字段的值。Method
类:代表类的方法,可以调用方法。可以使用getDeclaredMethods()
方法获取所有声明的方法,使用invoke()
方法调用方法。Constructor
类:代表类的构造函数,可以创建类的实例。可以使用getDeclaredConstructors()
方法获取所有声明的构造函数,使用newInstance()
方法创建对象。
通过反射,可以做到以下一些常见的操作:
- 动态创建对象:根据类的名称动态创建对象,而不需要提前知道具体类的类型。
- 动态调用方法:根据方法的名称和参数类型动态调用对象的方法,而不需要在编码时确定具体的方法。
- 动态修改字段:获取并修改类的字段的值,可以实现对私有字段的访问和修改。
需要注意的是,反射机制在一般情况下会比直接调用方法或访问字段的方式更加复杂和低效,因为它需要在运行时进行额外的检查和处理。因此,在使用反射时需要权衡使用场景和性能考虑。
总之,反射是Java语言的一个特性,通过它可以在运行时动态地获取、检查和操作类的信息、对象的属性和方法。反射机制为程序提供了更大的灵活性和可扩展性,但同时也需要谨慎使用以避免性能问题。
Enum
枚举(Enum)是一种特殊的数据类型,它是一个有限的、预定义的值集合。在 Java 中,枚举类型是通过 enum
关键字来定义的。
以下是一个简单的枚举示例:
enum Day {
SUNDAY,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY
}
public class EnumExample {
public static void main(String[] args) {
// 使用枚举值
Day today = Day.WEDNESDAY;
System.out.println("今天是:" + today);
// 遍历枚举值
for (Day day : Day.values()) {
System.out.println(day);
}
// 比较枚举值
if (today == Day.WEDNESDAY) {
System.out.println("今天是周三");
}
// 使用 switch 语句处理枚举值
switch (today) {
case MONDAY:
case TUESDAY:
case WEDNESDAY:
case THURSDAY:
case FRIDAY:
System.out.println("平日");
break;
case SATURDAY:
case SUNDAY:
System.out.println("周末");
break;
}
}
}
上述示例定义了一个名为 Day
的枚举类型,包含了一周中的每一天。在 main
方法中,我们演示了如何使用枚举值。首先,我们将 today
设置为 Day.WEDNESDAY
,然后使用 System.out.println()
打印当前日期。接下来,我们遍历了所有的枚举值,并打印每个值。然后,我们使用 ==
运算符比较枚举值,以及使用 switch
语句处理不同的枚举值情况。
枚举是一种有用的数据类型,特别适用于表示一组固定的常量值。它提供了类型安全性和可读性,并且可以很方便地进行比较和处理。你可以根据自己的需求定义枚举,并使用其中的值在程序中进行操作。
package com.MG.pojo;
//常量对象
public enum 枚举练习 {
//常量对象 简化写法 全部大写
ADD_SUCCESS(true, 1001, "ADD成功"),
ADD_ERROR(false, 1002, "ADD失败");
//指定常量对象的属性
private final boolean succes;
private final int code;
private final String msg;
枚举练习(boolean succes, int code, String msg) {
this.succes = succes;
this.code = code;
this.msg = msg;
}
public boolean isSucces() {
return succes;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
好处
Java中的枚举(Enumeration)是一种特殊的数据类型,用于定义一组有限的常量。枚举具有以下好处:
- 清晰的常量定义:使用枚举可以在代码中清晰地定义常量,并为每个常量指定一个有意义的名称。这样可以提高代码的可读性和可维护性,减少出错的可能性。
- 类型安全:枚举是强类型的,编译器会在编译时检查枚举值的类型安全性。这意味着无效的枚举值无法通过编译,确保了程序的健壮性。
- 限定取值范围:枚举可以限定变量的取值范围,只能从预定义的常量中选择。这样可以避免传递无效的值,增加代码的可靠性。
- 可读性和可维护性:通过使用枚举,可以为常量赋予有意义的名称,使得代码更易读、更易理解。在需要添加新的常量时,只需简单地扩展枚举类型即可,不需要修改大量的代码。
- 强大的功能:Java枚举类型还可以包含方法和属性,可以向枚举常量中添加行为,使得代码更加灵活和功能丰富。
总之,Java枚举提供了一种优雅且类型安全的方式来定义一组常量,提高了代码的可读性、可维护性和可靠性。它在许多情况下是一种很好的替代传统常量的方式。
多线程
多线程指的是在一个程序中同时执行多个线程,每个线程可以独立地执行任务。Java 提供了多线程编程的支持,可以通过创建 Thread 类的实例来创建和管理线程。
以下是一个简单的多线程示例:
public class MultiThreadExample {
public static void main(String[] args) {
// 创建线程对象
Thread thread1 = new MyThread("Thread 1");
Thread thread2 = new MyThread("Thread 2");
// 启动线程
thread1.start();
thread2.start();
}
}
class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + ": " + i);
try {
// 线程休眠一段时间
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
上述示例中,我们创建了一个继承自 Thread 类的自定义线程类 MyThread
。在 run()
方法中,每个线程会打印自己的名称和计数器的值,并休眠一段时间。在主程序中,我们创建了两个线程对象,并通过调用 start()
方法来启动这两个线程。
多线程程序可以实现并发执行,每个线程都可以独立地执行任务。在上述示例中,我们可以看到两个线程交替执行,每个线程打印出自己的计数器值。由于线程之间的执行是异步的,因此输出的结果可能会交错出现。
使用多线程可以提高程序的性能和响应能力,特别是对于需要并行处理任务或进行异步操作的情况。然而,多线程编程也需要注意处理线程同步、共享数据等问题,以避免出现竞态条件和数据一致性问题。
请注意,Java 还提供了其他实现多线程的方式,例如实现 Runnable 接口、使用线程池等。以上示例只是演示了最基本的多线程使用方法。
- 一个程序一个进程,一个进程有多个线程
- 线程是CPU调度的最小单位,必须依赖于进程而存在。
- 并发:单位时间内执行的线程数.(一秒内一亿人抢票)
- javaMain天生是多线程的
-
方式一:
继承 thread 重写 run方法多态创建 对象
然后调用 .start 方法启动线程
.run 执行逻辑的方法
-
方式二:
类去实现runnuable接口,重新run方法
多态创建runnable对象,放在thread对象里面
start() 线程
package com.MG.学习内容;
/**
* @author MG
* @date 2023/9/7 16:39
* @今天一定要开心哦!
* @description: 一个进程包含多个线程, 线程是促排调度的最小单位
*/
public class 多线程介绍 {
}
class 多线程方式一 extends Thread {
/**
* 方式一: 继承 thread 重写 run方法
* 多态创建 对象 然后调用 .start 方法启动线程
* .run 执行逻辑的方法
*
* @paramtarget
*/
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
//创建多线程对象 .start 启动线程
Thread thread = new 多线程方式一();
thread.start();
//创建多线程对象 .start 启动线程
Thread thread1 = new 多线程方式一();
thread1.start();
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
class 多线程方式二 implements Runnable {
public static void main(String[] args) {
//创建runnable对象
Runnable runnable=new 多线程方式二();
//创建Thread对象
Thread thread=new Thread(runnable);
Thread thread1=new Thread(runnable);
thread.start();
thread1.start();
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
class 自定义多线程名字 extends Thread {
public 自定义多线程名字(String name) {
super(name);
}
public static void main(String[] args) {
//创建Thread对象
Thread thread=new 自定义多线程名字("线程一");
Thread thread1=new 自定义多线程名字("线程二");
thread.start();
thread1.start();
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
class 自定义多线程名字二 implements Runnable {
public static void main(String[] args) {
//创建runnable对象
Runnable runnable=new 多线程方式二();
//创建Thread对象
Thread thread=new Thread(runnable,"线程一");
Thread thread1=new Thread(runnable,"线程二");
thread.start();
thread1.start();
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
class 匿名内部类多线程 {
public static void main(String[] args) {
//实现接口
Runnable runnable=new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() );
}
};
//启用线程 添加接口 然后启动
Thread thread=new Thread(runnable,"多线程name");
thread.start();
System.out.println(Thread.currentThread().getName());
}
}
多线程的五个方法
-
public static void sleep(long millis)
当前线程主动休眠 millis毫秒。进入到TIMED_WAITING状态
-
public static void yield()
yield() 方法通常在以下情况下使用:
- 当线程需要等待某个条件,但又不想占用 CPU 时间时,可以调用 yield() 方法,以便其他线程有机会获得 CPU 时间执行。
- 在多个线程需要执行互斥操作时,可以使用 yield() 方法来提高并发性。例如,当一个线程持有锁并且等待其他资源时,可以调用 yield() 方法放弃 CPU 时间,使其他线程有机会获取锁并执行相应操作。
-
public final void join()
join()
方法常用于以下场景:- 在主线程中等待子线程执行完毕,以确保后续操作能够正确地依赖于子线程的结果。
- 在多线程并发处理中,需要等待其他线程的计算或操作完成后再进行下一步操作。
-
public void setPriority(int)
public void setPriority(int)
是一个实例方法,用于设置线程的优先级。线程的优先级决定了在多个线程竞争 CPU 时间时被调度的顺序。 -
public void setDaemon(boolean)
public void setDaemon(boolean)
是一个实例方法,用于设置线程的守护状态。守护线程(Daemon Thread)是一种特殊类型的线程,它会在主线程执行完毕后自动结束,而无需显式地调用stop()
或者其他方法来终止线程。
线程安全问题
//多个线程操作共享资源,导致出现线程安全问题
//两个线程都去执行count++,
// 两个线程互相抢时间片,
// 导致执行同一个资源时发生错误,
// count结果不是2000
三种解决方案
//synchronized 锁 : 表示当前线程执行此方法时候,不会被其他线程抢走时间片,抢走资源
public synchronized static void Method() {
count++;
}
public void Method() {
//使用this 只能锁住一个对象
synchronized (this){
count++;
}
}
public void Method() {
//Object可以锁住不同的对象
synchronized (object){
count++;
}
}
锁
lock
读锁、写锁和互斥锁是在并发编程中使用的不同类型的锁机制,它们有以下区别:
- 读锁(Read Lock):也称为共享锁(Shared Lock),它被设计用于允许多个线程同时读取共享资源,但不允许写操作。当一个线程持有读锁时,其他线程可以继续获取读锁,但无法获取写锁。读锁之间是共享的,因此多个线程可以同时持有读锁,这样可以提高并发性能。
- 写锁(Write Lock):也称为排它锁(Exclusive Lock),它被设计用于只允许一个线程进行写操作。当一个线程持有写锁时,其他线程无法获得读锁或写锁。写锁是独占的,因此只能有一个线程持有写锁,这样可以确保数据的一致性和完整性。
- 互斥锁(Mutex Lock):互斥锁是一种基本的锁机制,它通过在临界区代码周围放置锁来确保同一时间只有一个线程可以进入临界区。互斥锁是独占的,当一个线程持有互斥锁时,其他线程必须等待锁的释放才能进入临界区。互斥锁旨在保护共享资源,防止并发访问导致的数据竞争和不一致性。
ReentrantLock
重入锁
同一个线程对于已经获得到的锁,可以多次继续申请到该
锁的使用权”。而synchronized关键字隐式的支持重进入,比如一个synchronized修饰的递归方法,在方法执行时,执行线程在获取了锁之后仍能连续多次地获得该锁。ReentrantLock在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。
class 读写锁 {
//读写锁
private static ReentrantReadWriteLock rrw = new ReentrantReadWriteLock();
//读锁
private ReentrantReadWriteLock.ReadLock readLock = rrw.readLock();
//写锁
private ReentrantReadWriteLock.WriteLock writeLock = rrw.writeLock();
//互斥锁
private ReentrantLock reentrantLock=new ReentrantLock();
private String name="小乔";
//锁:
public void readMethod() throws InterruptedException {
reentrantLock.lock();
Thread.sleep(1000);
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+name);
}
reentrantLock.unlock();
}
}
class stu implements Runnable{
读写锁 d;
public stu(读写锁 d) {
this.d = d;
}
@Override
public void run() {
try {
d.readMethod();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(5);
Runnable runnable=new stu(new 读写锁());
for (int i = 0; i < 5; i++) {
executorService.execute(runnable);
}
System.out.println("Main end.");
}
}
公平锁
线程创建的顺序与获取锁的顺序—致,先来先得.
非公平锁
抢,竞争
线程池
线程池是一种用于管理和复用线程的机制。它通过预先创建一组线程,并将任务分配给这些线程来提高多线程应用程序的性能和效率。
使用线程池的好处包括:
-
减少线程创建和销毁的开销:线程的创建和销毁是一项昂贵的操作,会消耗大量的系统资源。使用线程池可以避免频繁地创建和销毁线程,而是复用已经存在的线程,减少了资源开销。
-
控制并发线程数:线程池可以限制同时执行的线程数量,防止系统过载。通过设置线程池的大小,可以根据系统资源和需求进行合理的调节,避免同时运行过多的线程导致系统性能下降。
-
提高响应速度:线程池可以在任务到达时立即执行,而不需要每次都创建新的线程来处理任务。这样可以减少任务等待的时间,提高系统的响应速度。
Java提供了一个内置的线程池实现,即java.util.concurrent.Executors
类。通过该类,可以方便地创建和管理线程池。常用的线程池类型有以下几种:
-
FixedThreadPool:固定大小的线程池,在池中保持固定数量的线程。如果所有线程都处于活动状态,新任务将在队列中等待,直到有可用的线程。适用于控制并发线程数的场景。
-
CachedThreadPool:无限大小的线程池,线程数量根据任务的需求进行自动调整。如果有空闲线程,就复用已有线程;如果没有空闲线程,就创建新线程。适用于执行大量短期任务的场景。
-
SingleThreadExecutor:只包含一个线程的线程池,保证所有任务按顺序执行。适用于需要保持任务顺序执行的场景。
-
ScheduledThreadPool:支持延迟执行或定时执行任务的线程池。可以按固定的频率执行任务,或者在某个固定的延迟时间后执行任务。
下面是一个使用FixedThreadPool
的示例代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建固定大小的线程池,最多同时运行3个线程
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 提交任务给线程池执行
for (int i = 1; i <= 10; i++) {
int taskNumber = i;
executorService.execute(() -> {
System.out.println("Task " + taskNumber + " is running in thread: " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executorService.shutdown();
}
}
上述代码创建了一个固定大小为3的线程池,并提交了10个任务给线程池执行。每个任务打印自己的编号和执行线程的名称,并在执行过程中休眠1秒钟。通过执行shutdown()
方法关闭线程池。
线程池是多线程编程中非常重要和常用的概念,可以帮助提高并发性能和资源利用率。当你在开发多线程应用时,考虑使用线程池来管理线程,以获得更好的性能和可维护性。
线程池理论
线程池理论:
假设: 核心线程数 5 最大线程数 10 等待队列5
提供的线程小于核心线程数,比如:2,因为2<5,会直接在线程工厂里面new一个新线程
提供的线程数大于核心线程数,比如10,因为10>5 ,所以多出来的线程去等得队列里面等待多出5个 刚好等待区也有5 个线程
提供的线程数大于核心线程数,比如11,因为11>5 ,等待线程队列满了后 ,去跟最大线程数比较,如果没有达到最大线程数,
则新线程进入等待对列,最先进去等待队列的线程转移到线程池中,新建一个线程 线程工厂里面是:1,2,3,4,5,6 等待队列是:7,8,9,10,11
当6 执行结束后,会在设定的时间结束后销毁,最终留下5个活跃线程.
线程池参数
线程池的性能和行为可以通过一些重要参数进行配置。下面是线程池中一些常用的重要参数:
- 核心线程数(corePoolSize):线程池中一直保持活动的线程数量。在没有任务执行时,核心线程也不会被回收。
- 最大线程数(maximumPoolSize):线程池中允许同时存在的最大线程数量。当任务数超过核心线程数且任务队列已满时,线程池会创建新的线程,直到线程数量达到最大线程数。
- 任务队列(workQueue):用于存放还未被执行的任务的队列。线程池会从队列中取出任务分配给工作线程执行。
- 空闲线程存活时间(keepAliveTime):当线程池中的线程数量超过核心线程数且空闲一段时间后,多余的线程会被回收销毁,以减少资源消耗。
- 拒绝策略(rejectedExecutionHandler):当线程池无法接受新的任务时,如何处理被拒绝的任务。常见的拒绝策略包括抛出异常、丢弃任务或者将任务回退给提交任务的线程来执行等。
- 线程工厂(ThreadFactory):用于创建新线程的工厂。线程工厂定义了如何创建线程对象,并可以为线程设置名称、优先级、异常处理器等属性。
- 线程执行前的预启动处理器(ThreadPreProcessor):在线程执行任务之前,对线程进行一些额外的配置或操作。例如,可以设置线程的上下文类加载器、线程的命名等。
- 饱和策略(saturationPolicy):当线程池已满且无法执行新的任务时,控制提交任务的行为。饱和策略有多种选择,如抛出异常、调用者运行任务、丢弃最老的任务或丢弃当前的任务等。
- 线程池的名称(threadPoolName):给线程池起一个可识别的名称,便于日志记录和监控。
这些参数对于线程池的性能和行为具有重要的影响,我们需要根据具体的业务需求进行合理的配置。
一般来说,合理地配置线程池参数可以使得线程池达到更好的性能和资源利用率。例如,通过调整核心线程数和最大线程数,可以确保线程池既能满足并发需求,又不会因线程过多而导致资源浪费;通过合理选择任务队列的容量和类型,可以平衡任务提交与执行之间的速度差异;合理设置空闲线程存活时间可以避免线程的频繁创建和销毁等。
线程池的拒绝策略
线程池的拒绝策略是指当线程池无法接受新的任务时,应该如何处理这些被拒绝的任务。Java 中的 ThreadPoolExecutor
类提供了几种默认的拒绝策略,同时也支持自定义拒绝策略。
以下是几种常见的线程池拒绝策略:
-
AbortPolicy
(默认):该策略会抛出一个RejectedExecutionException
异常,阻止任务提交。这是默认的拒绝策略,如果不显式地指定其他拒绝策略,线程池会使用这个策略。 -
CallerRunsPolicy
:当线程池无法接受新任务时,会将任务回退给提交任务的线程(调用者)来执行。也就是说,如果线程池已满,任务会在调用execute()
方法的线程中直接执行。 -
DiscardPolicy
:该策略将简单地丢弃被拒绝的任务,不做任何处理。如果线程池已满,新任务会被直接丢弃。 -
DiscardOldestPolicy
:当线程池无法接受新任务时,会丢弃执行队列中最旧(最先提交)的任务,然后尝试再次提交新任务。
除了这些默认的拒绝策略,你也可以通过实现 RejectedExecutionHandler
接口来自定义拒绝策略。下面是一个示例:
import java.util.concurrent.*;
public class 线程池测试 {
public static void main(String[] args) {
RejectedExecutionHandler customRejectedExecutionHandler = new CustomRejectedExecutionHandler();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1, // 核心线程数
1, // 最大线程数
0L, // 空闲线程存活时间
TimeUnit.MILLISECONDS, // 时间单位
new ArrayBlockingQueue<>(1), // 任务队列
customRejectedExecutionHandler // 拒绝策略
);
// 提交任务
for (int i = 0; i < 5; i++) {
executor.submit(new MyTask(i));
}
executor.shutdown();
}
}
class MyTask implements Runnable {
private final int taskId;
public MyTask(int taskId) {
this.taskId = taskId;
}
@Override
public void run() {
System.out.println("Task " + taskId + " is running...");
try {
// 模拟任务执行时间
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task " + taskId + " is completed.");
}
}
class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("Task rejected. Executor info: " + executor.toString());
// 自定义处理被拒绝的任务
// 这里可以根据需求进行处理,比如将被拒绝的任务重新提交到队列中等待执行
}
}
在这个示例代码中,我们使用了自定义的拒绝策略 CustomRejectedExecutionHandler
。当线程池无法接受新任务时,会调用 rejectedExecution()
方法处理被拒绝的任务。你可以在这个方法中实现自己的逻辑,比如将被拒绝的任务重新提交到队列中等待执行。
关闭线程池
关闭线程池是确保线程池正常终止并释放资源的重要操作。以下是关闭线程池的一般步骤:
- 调用线程池的
shutdown()
方法:这个方法会启动线程池的关闭过程。调用后,线程池将不再接受新的任务,但会完成所有已提交的任务和正在执行的任务。 - 调用线程池的
awaitTermination()
方法(可选):可以选择等待一段时间,让线程池中的任务在指定时间内完成执行。这个方法可以避免在关闭过程中丢失还未完成的任务。 - 调用线程池的
shutdownNow()
方法(可选):如果在shutdown()
方法后需要立即中止线程池,可以调用这个方法。它会尝试取消所有正在执行的任务,并返回尚未开始执行的任务列表。 - 最后,进行必要的资源清理工作,例如关闭与线程池相关的资源、释放线程池占用的资源等。
使用Java中的ThreadPoolExecutor
类来创建和配置线程池,并设置一些额外的参数:
import java.util.concurrent.*;
public class ThreadPoolExample {
public static void main(String[] args) {
int corePoolSize = 5;
int maxPoolSize = 10;
long keepAliveTime = 5000; // 5 seconds
int queueCapacity = 100;
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(queueCapacity), threadFactory, rejectedExecutionHandler);
// 设置线程池的名称
threadPoolExecutor.setThreadNamePrefix("MyThreadPool-");
// 设置线程执行前的预启动处理器
threadPoolExecutor.setThreadPreProcessor(new ThreadPreProcessor() {
@Override
public void process(Thread thread) {
System.out.println("Thread [" + thread.getName() + "] is about to start.");
}
});
// 提交任务给线程池
for (int i = 0; i < 20; i++) {
threadPoolExecutor.execute(new MyTask(i));
}
// 关闭线程池
threadPoolExecutor.shutdown();
}
}
class MyTask implements Runnable {
private final int taskId;
public MyTask(int taskId) {
this.taskId = taskId;
}
@Override
public void run() {
System.out.println("Task [" + taskId + "] is running on thread " + Thread.currentThread().getName());
}
}
在此示例中,使用ThreadPoolExecutor
创建了一个线程池。通过设置核心线程数、最大线程数、任务队列容量、空闲线程存活时间、线程工厂和拒绝策略等参数,配置了线程池的行为。
同时,通过重写ThreadPreProcessor
接口,在线程执行之前输出一条消息,实现了线程执行前的预启动处理。
然后,通过提交任务给线程池,可以看到任务被分配给不同的线程执行。
最后,调用shutdown()
方法关闭线程池。
有返回值的线程
class Callable类 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Method();
}
public static void Method() throws ExecutionException, InterruptedException {
/**
* callable中写业务螺距,返回值给到FutureTask
* FutureTask 将返回值存到一个变量 outcome 中
* 最后通过 FutureTask .get() 方法取出返回值.
*/
Callable callable = new Callable() {
@Override
public Object call() throws Exception {
return "nihao";
}
};
//FutureTask 充当 runnable
FutureTask<String> futureTask = new FutureTask<>(callable);
Thread t1 = new Thread(futureTask);
t1.start();
System.out.println(futureTask.get());
}
}
安全集合
CopyOnWriteArrayList
CopyOnWriteArrayList
是Java中的一个并发容器类,它提供了与普通 ArrayList
类似的接口,并且具有线程安全的特性。其内部实现使用了“写时复制”的技术来实现线程安全。
以下是 CopyOnWriteArrayList
中常用的方法:
add(E e)
:向列表末尾添加元素,并返回添加是否成功。addAll(Collection<? extends E> c)
:将指定集合中的元素全部添加到列表末尾,并返回是否添加成功。clear()
:清空列表中的所有元素。contains(Object o)
:判断列表中是否包含指定元素。get(int index)
:获取列表中指定索引位置的元素。indexOf(Object o)
:获取列表中指定元素的索引位置,如果不存在则返回 -1。isEmpty()
:判断列表是否为空。iterator()
:返回一个迭代器对象,可以用于遍历列表中的元素。remove(int index)
:删除列表中指定索引位置的元素,并返回被删除的元素。remove(Object o)
:删除列表中指定元素,如果删除成功返回 true,否则返回 false。set(int index, E element)
:替换列表中指定索引位置的元素,并返回被替换掉的旧元素。size()
:返回列表中元素的数量。toArray()
:将列表中的元素转换成一个数组返回。
除了以上方法,CopyOnWriteArrayList
还提供了其他一些和迭代器相关的方法,比如 listIterator()
和 spliterator()
等。
public static void CopyOnWriteArrayListMethod() {
CopyOnWriteArrayList copyOnWriteArrayList=new CopyOnWriteArrayList();
copyOnWriteArrayList.add("管大");
copyOnWriteArrayList.add("管二");
copyOnWriteArrayList.add("管三");
System.out.println(copyOnWriteArrayList);
System.out.println("index 是 1 的元素是: "+ copyOnWriteArrayList.get(1));
Iterator iterator = copyOnWriteArrayList.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
CopyOnWriteArraySet
CopyOnWriteArraySet
是 Java 中的一个并发容器类,它实现了 Set
接口,并提供了线程安全的集合操作。与普通的 Set
不同,CopyOnWriteArraySet
内部使用 “写时复制” 的技术来实现线程安全。
下面是 CopyOnWriteArraySet
中常用的方法:
add(E e)
:将指定元素添加到集合中,如果集合已经包含该元素,则返回 false。addAll(Collection<? extends E> c)
:将指定集合中的所有元素添加到集合中,如果有新元素被添加,则返回 true;如果所有元素都已经存在于集合中,则返回 false。clear()
:清空集合中的所有元素。contains(Object o)
:判断集合是否包含指定元素。isEmpty()
:判断集合是否为空。iterator()
:返回一个迭代器对象,可以用于遍历集合中的元素。remove(Object o)
:从集合中移除指定元素,如果删除成功返回 true,否则返回 false。size()
:返回集合中元素的数量。toArray()
:将集合中的元素转换为数组返回。
需要注意的是,CopyOnWriteArraySet
采用了 “写时复制” 的策略,在进行集合修改操作时会复制一份原有的数据。这意味着在修改操作期间,对原有集合的迭代操作不会受到影响,因为迭代器仍然引用原来的集合。只有当修改操作完成后,迭代器才会使用新的集合数据。
由于每次修改操作都需要复制整个集合的数据,所以对于修改操作频繁的场景,CopyOnWriteArraySet
的性能可能较低。但对于读取操作频繁、并且对实时性要求不高的场景,CopyOnWriteArraySet
是一个线程安全且方便使用的选择。
public static void CopyOnWriteArraySetMethod(){
CopyOnWriteArraySet copyOnWriteArraySet=new CopyOnWriteArraySet();
copyOnWriteArraySet.add("小乔");
copyOnWriteArraySet.add("大乔");
copyOnWriteArraySet.add("乔妹");
System.out.println(copyOnWriteArraySet);
System.out.println("元素总数量是: "+ copyOnWriteArraySet.size());
}
ConcurrentHashMap
ConcurrentHashMap
是 Java 中的一个并发容器类,它实现了 Map
接口,并提供了线程安全的键值对存储和操作。相较于传统的 HashMap
,ConcurrentHashMap
可以在多线程环境下进行高效的并发访问。
下面是 ConcurrentHashMap
中常用的方法:
put(K key, V value)
:将指定的键值对存储到ConcurrentHashMap
中,如果键已经存在,则替换对应的值,并返回旧值。putIfAbsent(K key, V value)
:当指定的键不存在时,将键值对存储到ConcurrentHashMap
中,返回之前与键关联的值或者null
。get(Object key)
:返回与指定键关联的值,如果键不存在,则返回null
。remove(Object key)
:从ConcurrentHashMap
中移除指定键关联的键值对,并返回之前与键关联的值。containsKey(Object key)
:判断ConcurrentHashMap
是否包含指定的键。containsValue(Object value)
:判断ConcurrentHashMap
是否包含指定的值。size()
:返回ConcurrentHashMap
中键值对的数量。isEmpty()
:判断ConcurrentHashMap
是否为空。keySet()
:返回一个包含所有键的集合视图。values()
:返回一个包含所有值的集合视图。entrySet()
:返回一个包含所有键值对的集合视图。
需要注意的是,ConcurrentHashMap
中的方法都是线程安全的,可以在多个线程同时访问和修改。它使用了分段锁的机制,将整个映射表分成了多个小的部分,在进行修改操作时只会锁定对应的小部分,从而减少了锁的竞争,提高了并发性能。
ConcurrentHashMap
还提供了一些其他的方法,比如 replace()
、compute()
、merge()
等,用于更复杂的键值对操作和计算。这些方法可以在并发环境下安全地进行操作,并且具有原子性和一致性的保证。
public static void ConcurrentHashMapMethod(){
ConcurrentHashMap concurrentHashMap=new ConcurrentHashMap();
concurrentHashMap.put("姓名","火男");
concurrentHashMap.put("职业","法师");
concurrentHashMap.put("性别","男");
System.out.println(concurrentHashMap);
System.out.println("姓名 对应的值是" + concurrentHashMap.get("姓名"));
//迭代器遍历HASHMAP
ConcurrentHashMap.KeySetView keySetView = concurrentHashMap.keySet();
Iterator iterator = keySetView.iterator();
while (iterator.hasNext()){
Object KeyName=null;
System.out.println((KeyName=iterator.next()) + " : " + concurrentHashMap.get(KeyName));
}
}
Queue队列
LinkedList
LinkedList
中常用的方法:
add(E e)
:在链表尾部添加一个元素。add(int index, E element)
:在指定位置插入一个元素。addAll(Collection<? extends E> c)
:将一个集合中的所有元素添加到链表尾部。addAll(int index, Collection<? extends E> c)
:将一个集合中的所有元素插入到指定位置。remove()
:删除链表头部的元素,并返回被删除的元素。remove(int index)
:删除指定位置的元素,并返回被删除的元素。remove(Object o)
:删除链表中第一次出现的指定元素,并返回是否删除成功。clear()
:清空链表中的所有元素。get(int index)
:获取指定位置的元素。set(int index, E element)
:将指定位置的元素替换为新的值。size()
:返回链表中元素的个数。isEmpty()
:判断链表是否为空。contains(Object o)
:判断链表中是否存在指定元素。indexOf(Object o)
:返回链表中第一次出现指定元素的位置。lastIndexOf(Object o)
:返回链表中最后一次出现指定元素的位置。toArray()
:将链表转换为数组。toArray(T[] a)
:将链表转换为指定类型的数组。iterator()
:返回一个迭代器,可以用于依次访问链表中的元素。descendingIterator()
:返回一个逆序迭代器,用于倒序访问链表中的元素。offer(E e)
:将指定元素插入到链表尾部,并返回是否插入成功。poll()
:删除并返回链表头部的元素。peek()
:返回链表头部的元素,但不删除它。
public static void LinkedListMethod(){
Queue<String> queue=new LinkedList<>();
//添加
queue.offer("小乔");
queue.offer("大乔");
System.out.println(queue);
System.out.println("队头元素是:"+queue.peek());
System.out.println("删除的元素是:"+queue.poll());
System.out.println(queue);
}
ConcurrentLinkedQueue
特点:
- 线程安全:
ConcurrentLinkedQueue
是线程安全的数据结构,可在多线程环境下并发访问和修改,无需额外的同步措施。 - 无界队列:
ConcurrentLinkedQueue
是一个无界队列,不会限制元素的数量,可以不断地添加元素。 - 高效性能:在高并发场景下,
ConcurrentLinkedQueue
提供了良好的并发性能。由于它是基于非阻塞算法实现的,可以减小锁的竞争,提高吞吐量。 - 先进先出:
ConcurrentLinkedQueue
是一个先进先出(FIFO)的队列,即首先添加的元素将首先被移除。 - 无阻塞操作:
ConcurrentLinkedQueue
的常见操作,如添加、删除、获取头部元素等,都采用了非阻塞的方式,避免了线程的等待和阻塞,提高了并发性能。 - 无需加锁:
ConcurrentLinkedQueue
内部使用 CAS(Compare and Swap)等非阻塞算法来保证线程安全,避免了使用显式锁,减少了锁的开销。 - 支持迭代:
ConcurrentLinkedQueue
提供了迭代器,可以对队列进行遍历操作。
需要注意的是,由于 ConcurrentLinkedQueue
是无界队列,如果在并发场景下不断添加元素而没有相应的消费,可能会导致内存溢出。因此,在使用 ConcurrentLinkedQueue
时应谨慎处理队列元素的添加和删除,以避免潜在的问题。
方法:
add(E e)
:将指定元素添加到队列尾部。offer(E e)
:将指定元素添加到队列尾部,并返回是否成功。poll()
:获取并移除队列头部的元素,如果队列为空则返回 null。peek()
:获取但不移除队列头部的元素,如果队列为空则返回 null。size()
:返回队列中的元素个数。isEmpty()
:判断队列是否为空。contains(Object o)
:判断队列中是否包含指定元素。remove(Object o)
:从队列中移除指定元素。clear()
:清空队列中的所有元素。iterator()
:返回一个迭代器,可以用于遍历队列中的元素。toArray()
:将队列转换为数组。
以下是一个使用 ConcurrentLinkedQueue
的简单例子:
import java.util.concurrent.ConcurrentLinkedQueue;
public class ConcurrentLinkedQueueExample {
public static void main(String[] args) {
// 创建 ConcurrentLinkedQueue 对象
ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
// 添加元素到队列
queue.add("Apple");
queue.add("Banana");
queue.add("Orange");
// 获取并移除队列头部的元素,并打印
String head = queue.poll();
System.out.println("Head: " + head);
// 获取但不移除队列头部的元素,并打印
String peek = queue.peek();
System.out.println("Peek: " + peek);
// 遍历队列并打印每个元素
System.out.println("Elements in the queue:");
for (String element : queue) {
System.out.println(element);
}
// 判断队列是否包含指定元素
boolean contains = queue.contains("Banana");
System.out.println("Contains Banana: " + contains);
// 清空队列
queue.clear();
// 判断队列是否为空
boolean isEmpty = queue.isEmpty();
System.out.println("Is Empty: " + isEmpty);
}
}
这个例子展示了 ConcurrentLinkedQueue
的常见用法,包括添加元素、获取和移除元素、遍历和判断队列状态等操作。
ArrayBlockingQueue
ArrayBlockingQueue
是 BlockingQueue
接口的一个实现类,它提供了以下常用的方法:
-
add(E e)
:将指定元素添加到队列尾部,如果队列已满,则抛出IllegalStateException
异常。 -
offer(E e)
:将指定元素添加到队列尾部,如果队列已满,则返回false
,否则返回true
。 -
put(E e)
:将指定元素添加到队列尾部,如果队列已满,则阻塞当前线程直到有空间可用。 -
poll()
:移除并返回队列头部的元素,如果队列为空,则返回null
。 -
take()
:移除并返回队列头部的元素,如果队列为空,则阻塞当前线程直到有元素可用。 -
peek()
:返回队列头部的元素,如果队列为空,则返回null
。 -
isEmpty()
:判断队列是否为空,如果队列为空,则返回true
,否则返回false
。 -
isFull()
:判断队列是否已满,如果队列已满,则返回true
,否则返回false
。 -
size()
:返回队列中当前元素的数量。 -
clear()
:清空队列,移除队列中所有的元素。
除了上述方法,ArrayBlockingQueue
还可以使用 iterator()
方法获取队列的迭代器,以便进行遍历操作。此外,还有一些其他方法用于检查队列中是否包含指定元素、删除指定元素等操作。
需要注意的是,在使用 ArrayBlockingQueue
时,要根据具体需求选择适当的方法,并根据返回值判断操作是否成功。此外,由于 ArrayBlockingQueue
是有界队列,必须设置合适的容量,否则在队列已满或队列空时会导致线程阻塞。
以下是一个使用 ArrayBlockingQueue
的简单例子:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class ArrayBlockingQueueExample {
public static void main(String[] args) {
// 创建容量为 3 的 ArrayBlockingQueue 对象
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
// 添加元素到队列
blockingQueue.add("元素1");
blockingQueue.offer("元素2");
try {
blockingQueue.put("元素3");
} catch (InterruptedException e) {
e.printStackTrace();
}
// 尝试添加超过容量的元素
boolean added = blockingQueue.offer("元素4");
System.out.println("添加结果:" + added); // 输出:添加结果:false
// 移除并返回队列头部的元素
String removedElement = blockingQueue.poll();
System.out.println("移除的元素:" + removedElement); // 输出:移除的元素:元素1
// 返回队列头部的元素
String peekedElement = blockingQueue.peek();
System.out.println("队列头部的元素:" + peekedElement); // 输出:队列头部的元素:元素2
// 判断队列是否为空
boolean empty = blockingQueue.isEmpty();
System.out.println("队列是否为空:" + empty); // 输出:队列是否为空:false
// 清空队列
blockingQueue.clear();
// 判断队列是否为空
empty = blockingQueue.isEmpty();
System.out.println("队列是否为空:" + empty); // 输出:队列是否为空:true
}
}
在这个例子中,我们创建了一个容量为 3 的 ArrayBlockingQueue
对象,并分别使用 add()
、offer()
和 put()
方法将元素添加到队列中。然后,我们尝试使用 offer()
方法添加一个超过容量的元素,结果返回 false
,表示添加失败。接着,我们使用 poll()
方法移除并返回队列头部的元素,使用 peek()
方法返回队列头部的元素。最后,我们使用 isEmpty()
方法判断队列是否为空,并使用 clear()
方法清空队列。
懒汉饿汉
- 声明为null是懒汉
- 声明不为null是饿汉
package com.MG.学习内容.工厂.modul;
/**
* @author MG
* @date 2023/9/8 9:32
* @今天一定要开心哦!
* @description:
*/
public class GC_people_懒汉_声明为NUll {
private String name;
//提前声明
//声明为null 是懒汉 声明不是null是饿汉
public static GC_people_懒汉_声明为NUll gcPeople;
private GC_people_懒汉_声明为NUll() {
}
public static GC_people_懒汉_声明为NUll newInstance(){
return gcPeople= new GC_people_懒汉_声明为NUll();
}
public void Method() {
System.out.println("My is people");
}
}
package com.MG.学习内容.工厂.modul;
/**
* @author MG
* @date 2023/9/8 9:32
* @今天一定要开心哦!
* @description:
*/
public class GC_people_懒汉_声明为NUll {
private String name;
//提前声明
//声明为null 是懒汉 声明不是null是饿汉
public static GC_people_懒汉_声明为NUll gcPeople;
private GC_people_懒汉_声明为NUll() {
}
public static GC_people_懒汉_声明为NUll newInstance(){
return gcPeople= new GC_people_懒汉_声明为NUll();
}
public void Method() {
System.out.println("My is people");
}
}
网络编程
网络编程是指在计算机网络中进行程序设计和开发的过程。它涉及到通过网络连接进行数据传输、通信和协作等操作。网络编程可以用于构建各种分布式应用,如客户端-服务器应用、Web应用、网络游戏等。
在Java中,进行网络编程通常使用的是Java的标准库中的java.net
包。这个包提供了一组类和接口,用于处理与网络相关的操作,如创建网络连接、发送和接收数据、处理网络协议等。下面是一些常用的类和概念:
-
Socket:
Socket
类用于表示网络上的一个端点。它提供了建立网络连接、发送和接收数据的方法。Socket
类有两个主要的子类:ServerSocket
用于在服务器端监听并接受连接请求,Socket
用于在客户端与服务器建立连接。 -
URL:
URL
类用于表示统一资源定位符,用于访问网络上的资源。它可以解析URL字符串,并提供获取相关信息的方法,如协议、主机、端口、路径等。 -
URLConnection:
URLConnection
类是URL
类的抽象基类,它代表与特定URL的连接。URLConnection
类提供了读写数据、设置请求头、获取响应信息等方法。 -
InetAddress:
InetAddress
类用于表示IP地址和主机名。它提供了获取主机名、IP地址和域名解析等方法。 -
ServerSocket:
ServerSocket
类用于在服务器端监听并接受连接请求。通过调用accept()
方法,可以创建一个新的Socket
实例,用于与客户端进行通信。 -
DatagramPacket和DatagramSocket:这两个类用于在网络上发送和接收UDP数据报。
DatagramPacket
表示一个UDP数据包,包含了要发送或接收的数据和目标地址。DatagramSocket
用于创建和发送DatagramPacket
。
下面是一个简单的示例代码,演示了使用Socket
和ServerSocket
进行简单的客户端-服务器通信:
服务器端:
import java.net.ServerSocket;
import java.net.Socket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class Server {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(1234);
System.out.println("服务器已启动,等待客户端连接...");
Socket socket = serverSocket.accept();
System.out.println("客户端已连接:" + socket.getInetAddress().getHostAddress());
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
byte[] buffer = new byte[1024];
int length = inputStream.read(buffer);
String message = new String(buffer, 0, length);
System.out.println("接收到客户端的消息:" + message);
String response = "Hello, Client!";
outputStream.write(response.getBytes());
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端:
import java.net.Socket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class Client {
public static void main(String[] args) {
try {
Socket socket = new Socket("127.0.0.1", 1234);
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
String message = "Hello, Server!";
outputStream.write(message.getBytes());
byte[] buffer = new byte[1024];
int length = inputStream.read(buffer);
String response = new String(buffer, 0, length);
System.out.println("接收到服务器的消息:" + response);
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上述示例中,服务器端使用ServerSocket
监听端口1234,客户端使用Socket
连接到服务器。服务器接收到客户端发送的消息后,向客户端发送回复。注意,在实际应用中,需要进行异常处理和错误处理,以确保网络连接能够正确建立和关闭。
这只是网络编程的基础,Java提供了更多用于处理网络操作的类和方法,如URLConnection
用于进行HTTP通信,DatagramSocket
用于进行UDP通信等。你可以根据具体需求进行进一步学习和扩展。
网络聊天室
package com.example.网络聊天;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class 多人聊天 {
public static void main(String[] args) {
//存储客户端线程
Vector<UserTherad1> vector=new Vector<>();
//创造线程池 ,执行线程
ExecutorService es= Executors.newFixedThreadPool(5);
//创建服务器的socket
try {
ServerSocket server =new ServerSocket(8888);
System.out.println("服务器已启动,正在等待连接……");
while (true){
Socket socket= server.accept();
UserTherad1 user=new UserTherad1(socket,vector);
es.execute(user);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//客户端处理的线程
class UserTherad1 implements Runnable{
private String name;//唯一的名字
private Socket socket;
Vector<UserTherad1> vector;//客户端处理线程的集合
private ObjectInputStream ois;
private ObjectOutputStream oos;
Boolean flag=true;
public UserTherad1(Socket socket, Vector<UserTherad1> vector) {
this.socket = socket;
this.vector = vector;
vector.add(this);//将当前客户端的线程添加到集合中
}
@Override
public void run() {
System.out.println("客户端:"+socket.getInetAddress().getHostAddress()+"已连接");
try {
ois=new ObjectInputStream(socket.getInputStream());
oos=new ObjectOutputStream(socket.getOutputStream());
while (flag){
Message msg= (Message) ois.readObject();
String From=msg.getFrom();
int type=msg.getType();
switch (type){
case MessageType.Type_login:
name=msg.getFrom();
msg.setInfo("欢迎你!!!");
oos.writeObject(msg);
System.out.println(msg);
break;
case MessageType.Type_seed:
System.out.println(msg);
String to=msg.getTo();
UserTherad1 ut;
int size=vector.size();
for (int i=0;i<size;i++){
ut=vector.get(i);
if(to.equals(ut.name) && ut!=this){
ut.oos.writeObject(msg);
break;
}
}
break;
case MessageType.Type_seedq:
System.out.println(msg);
UserTherad1 ut1;
int size1=vector.size();
for (int i=0;i<size1;i++){
ut1=vector.get(i);
if(ut1!=this){
ut1.oos.writeObject(msg);
}
}
break;
}
}
ois.close();
oos.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class MessageType{
public static final int Type_login =01;
public static final int Type_seed =02;
public static final int Type_seedq =03;
}
class Message implements Serializable {
private String from;
private String to;
private int type;
private String info;
public Message(String from, String to, int type, String info) {
this.from = from;
this.to = to;
this.type = type;
this.info = info;
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
public Message(){
}
@Override
public String toString() {
return "Message{" +
"from='" + from + '\'' +
", to='" + to + '\'' +
", type=" + type +
", info='" + info + '\'' +
'}';
}
}
package com.example.网络聊天;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class 多人聊天客户端 {
static String name;
public static void main(String[] args) {
Scanner s=new Scanner(System.in);
ExecutorService es =Executors.newSingleThreadExecutor();
try {
Socket socket=new Socket("localhost",8888);
System.out.println("服务器连接成功");
ObjectOutputStream oos=new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream ois=new ObjectInputStream(socket.getInputStream());
//向服务器发送登录信息
System.out.println("请输入名称:");
name=s.nextLine();
Message msg=new Message(name,null,MessageType.Type_login,null);
oos.writeObject(msg);
msg= (Message) ois.readObject();
System.out.println("服务器发送消息:"+msg.getFrom()+msg.getInfo());
// System.out.println(msg.getFrom()+msg.getTo()+msg.getType()+msg.getInfo());
System.out.println(msg);
//启动一个读取消息的线程
es.execute(new ReadThread(ois));
//使用主线程发送消息
boolean flag=true;
while (flag){
msg=new Message();
System.out.println("您的身份是"+name+"请输入 对方名字 或 群发 ");
String st =s.nextLine();
if(st.equals("群发")){
msg.setFrom("___________"+name+"进行群发 ");
msg.setType(MessageType.Type_seedq);
System.out.println("内容是:");
msg.setInfo(s.nextLine());
System.out.println(msg);
oos.writeObject(msg);
}else{
msg.setTo(st);
msg.setFrom("___________"+name);
msg.setType(MessageType.Type_seed);
System.out.println("发送的内容是:");
msg.setInfo(s.nextLine());
System.out.println(msg);
oos.writeObject(msg);
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class ReadThread extends 多人聊天客户端 implements Runnable{
private ObjectInputStream in;
private boolean flag=true;
public ReadThread(ObjectInputStream in) {
this.in = in;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
try {
while (flag){
Message msg = (Message) in.readObject();
System.out.println(msg.getFrom()+"对我 ("+name+") 说:"+msg.getInfo());
System.out.println("您的身份是"+name+"请输入 对方名字 或 群发 ");
}
if(in!=null){
in.close();
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}