Java基础
常用命令行窗口命令:D: dir cd cls
开发Java程序: .java源代码->通过javac编译->.class字节码文件->使用java运行
文件名称必须与类名称一致
JDK组成:JVM虚拟机(Java虚拟机、真正运行Java程序的地方)和核心类库组成JRE(JAVA的运行环境)、Java、Javac...
IDEA管理Java程序的结构:project,module,package,class,便于管理项目代码
Java注释
//单行注释
/*
多行注释
*/
/**
文档注释
*/
字面量
Java支持整数、小数、字符(\n换行 \t代表一个tab、空字符)、字符串、布尔值、
Java支持二进制、八进制、十六进制的数据,具体用0b(0B)、0、0x(0X)表示
基本数据类型
引用数据类型:String
字符串类型(注意开头大写),变量存储的是对象的地址
整数字面量默认为int
类型,如果希望整形字面量默认为long
类型,需要在后面加l/L
小数字面量默认是double
,如果需要float后面需要加f/F
在表达式中,最终结果是由表达式中的最高类型决定的,byte
short char
是直接转化成int类型参与运算的
如何区分字面量(Literal)和数据类型(Data Type)?
数据类型是变量或表达式存储数据的“分类没包括基本数据类型(int
char
float
double
boolen
byte
short
long
)和引用数据类型(String
数组 接口等)
字面量是直接在代码中编写的“固定值”,表示具体的数值、字符或字符串。(如42
3.14
'A'
"Hello"
true
100L
5.0f
)
原码 补码 反码
补码取反再加1后可以得到原码
正数的原码、反码、补码均相同 ,补码的此特性使得计算机只需一套加法电路即可处理正负数运算。
以+5为例:
原码:0000 0101
反码:0000 0101
补码:0000 0101
以-5为例:
原码:1000 0101
(最高位1表示负号)
反码:1111 1010
(符号位不变,数值位取反)
补码:1111 1011
(反码+1)
对补码按位取反(包括符号位):000 0100
再加1:000 0101
恢复符号位:1000 0101
(最高位改为1,即原码)
类型转换
类型范围小的变量,可以直接赋值给范围类型大的变量
byte a = 12;
int b = a;
在表达式中,小范围类型的变量,会自动转换成表达式中较大范围的类型,在参与运算。
表达式的最终结果类型由表达式中的最高类型决定,在表达式中,byte
short
char
是直接转换为int
类型参与运算的。(防止数值溢出)
byte i = 10;
short j = 30;
int rs = i + j; //最高类型为int,而不是short
范围类型大的数据或者变量,直接赋值给类型范围小的变量会报错。
int a = 20;
byte b = a; //报错
byte b = (byte)a;//强制类型转换
double d = 99.5;
int m = (int)d; //m = 99,丢掉小数部分,保留整数部分
运算符
基本运算符: +
-
*
/
%
在Java中,两个整数相除的结果还是整数(截断)在工程中如果需要小数可以用1.0 * i / j
int a = 5;
int b = 2;
System.out.println(5 / 2); //结果为2
System.out.println(5.0 / 2); //结果为2.5
System.out.println(1.0*a / b); //结果为2.5
+
符号可以做连接符,其结果依然是一个字符串
"abc" + 5 --->"abc5"
int a = 5;
System.out.println("abc" + a); //abc5
System.out.println(a + 5); //10
System.out.println("ilovejava" + a + 'a'); //ilovejava5a
System.out.println(a + 'a' + "ilovejava"); //102ilovejava
自增自减运算符: ++
--
int a = 10;
a++; // a= a + 1
//++a;
System.out.println(a); //a = 11
a--; // a = a - 1
//--a;
System.out.println(a); //a = 10
与C语言同理,++a
先运算后输出,a++
先输出后运算。只能操作变量,不能操作字面量
//int a = 10++; //报错,不能操作字面量
int i = 10;
int rs = ++i; // 先加后用
System.out.println(i); // i = 11
System.out.println(rs);// rs = 11
int j = 10;
int rs2 = j++; // 先用后加
System.out.println(j); // j = 11
System.out.println(rs2);// rs2 = 10
int c = 10;
int d = 5;
int rs3 = c++ + ++c - --d - ++d + 1 + c--;
System.out.println(rs3); // rs3 = 10 + 12 - 4 - 5 + 1 + 12 = 26
System.out.println(c); // c = 11
System.out.println(d); // d = 5
扩展赋值运算符: +=
-=
*=
/=
%=
byte x = 10;
byte y = 30;
x = x + y;//错误的
x = (byte)(x + y);//正确的
x += y;//等价于(byte)(x + y),正确的
关系运算符
逻辑运算符
&
逻辑与、|
逻辑或、!
逻辑非、^
逻辑异或,右边都要执行
&&
短路与、||
短路或,如果左边可以判定结果,则右边不执行(判断结果与&
、|
相同,只是过程不同。效率更高,在实际开发中用的更多)
int i = 10;
int j = 20;
system.out.println(i > 100 && ++j > 99); //i > 100不成立,右边不参与计算
system.out.println(j); // j = 20
三元运算符:条件表达式 ? 值1 : 值2;
首先计算关系表达式的值,如果值为true,返回值1,如果为false,返回值2.
// 找出两个整数中的最大值,并输出
int a = 99;
int b = 67;
int max = a > b ? a : b;
System.out.println(max);
// 找出三个整数中的最大值,并输出
int i = 10;
int j = 45;
int k = 34;
int max = i > j && i > k ? i : j > k ? j : k;
System.out.println(max);
运算符优先级
System.out.println(10 > 3 || 10 > 3 && 10 < 3); // &&优先级大于|| 10 > 3 && 10 < 3 为false 10 > 3 || false为True
System.out.println((10 > 3 || 10 > 3) && 10 < 3); // (10 > 3 || 10 > 3)为True,True && 10 < 3为false
流程控制
分支结构
if分支
switch分支(性能较好)
表达式类型不支持double
float
long
boolean
case给出的值不允许重复,且只能是字面量,不能是变量。
加入break
语句防止出现穿透现象。
穿透性有时也可以简化代码,存在多个case分支的代码一样时,可以把代码写到一个case块,其他case块通过穿透性能,穿透到该case块即可。
循环结构
for循环
for (int i = 0; i < 3; i++){
System.out.println("Hello World");
}
while循环
int i = 0;
while(i < 5){
System.out.println("Hello World");
i++;
}
do-while循环
int i = 0;
do{
System.out.println("Hello World");
i++;
}while(i < 3)
跳转关键字
break
:跳出并结束当前所在循环
continue
:跳出当前所在循环,直接进入下一次循环
数组
int[] arr = {1,2,3};
String[] names = {"张三",“李四”};
数组属于引用数据类型,存储的是数组在内存中的地址信息。
静态初始化数组
定义数组的时候直接给数组赋值
//完整格式
int[] ages = new int[]{1, 2, 3};
double[] scores = new double[]{99.5, 98.5, 97.5};
//简化格式
int[] ages = {1,2,3};
double[] scores = {99.5, 98.5, 97.5};
//数据类型[] 数组名也可以写成数据类型 数组名[]
int ages[] = {1,2,3}; //写法同C语言
double scores[] = {99.5, 98.5, 97.5};
动态初始化数组
定义数组时先不存入具体的元素值,只确定数组存储的数据类型和数组的长度。
int[] arr = new int[3]
元素值默认为都为0
int[] arr = new int[3]{30, 40, 50}
这种写法是错误的,两种方法不能混用
数组的访问
数组名[索引]
System.out.println(arr[0]);// 取值
arr[2] = 100; //赋值
System.out.println(arr[2]);// 100
System.out.println(arr.length); //获取数组长度
//数组的遍历
int[] ages = {12, 24, 36};
for(int i= 0; i < ages.length; i++){ //ages.length = 3
System.out.println(ages[i]);
}
数组的内存执行原理
在Java中,程序实在JVM虚拟机中执行的,JVM为了便于执行程序,将内存区域成五个区域:方法区、栈、堆、本地方法栈、程序计数器
方法区:存放编译后的class字节码文件。
栈内存:方法运行时所进入的内存,变量(基本数据类型)存放在这里。
堆内存:new出来的东西会在这块内存中开辟空间并产生地址,数组(引用数据类型)就存放在这里。
在上面代码中,a
是变量,直接放在栈内存中,a
变量里存储的数据就是10
这个值。
int[] arr = {11, 22, 33};
是创建一个数组对象arr,会在堆内存中开辟区域存储三个整数。
arr
是变量,在栈内存中,arr
中存储的是数组对象在堆内存中的地址值
如果某个数组变量存储的地址是null
,那么该数组变量将不再指向任何对象。
int[] arr1 = null;
System.out.println(arr1); // null
System.out.println(arr1[1]); // 空指针异常
System.out.println(arr1.length);// 空指针异常
浅拷贝
当多个变量指向同一个数组对象时
int[] arr1 = [11, 22, 33];
int[] arr2 = arr1; // 将int类型的数组变量arr1赋值给int类型的数组变量arr2
System.out.println(arr1); // [I@4c873330
System.out.println(arr2); // [I@4c873330 说明指向了同一地址
arr2[1] = 99; // 改变arr2数组变量中的第二个元素
System.out.println(arr1[1]); // arr1[1] = 99; 由于arr2和arr1指向同一地址,因此改变arr2数组变量中的元素,就相当于改变了arr1数组变量里的元素。
方法
方法是一种语法结构,可以把一段代码封装成一个功能,以便重复调用。
public static void main(String[] args) {
int rs = sum(10,20); //调用求和方法
System.out.println(rs);
}
public static int sum(int a,int b){
return a + b;
} //求和方法
方法在类中的位置没有要求,但一个方法不能定义在另一个方法里面。
方法的返回值类型为void
(无返回申明)时,方法内就不能用使用return
返回数据,如果方法的返回类型写了具体类型,则方法内部必须使用return
返回对应的数据类型。
调用有参数类型的方法时,必须严格匹配方法的参数情况。
方法类型
无参数、无返回值
//打印三行Hello World
public static void printHelloWorld(){
for(int i = 1; i <= 3; i++){
Systemp.out.println("Hello World");
}
}
有参数、无返回值
//打印多行Hello World
public static void printHelloWorld2(int num){ //形参不能定义初始值
for(int i = 1; i <= num; i++){
Systemp.out.println("Hello World");
}
}
无参数、有返回值
有参数、无返回值
方法的内存执行原理
方法被调用的时候,是进入到栈内存中运行。先进后出,可以保证一个方法调用完另一个方法后可以回来。
参数传递机制
值传递:在传输实参给方法的形参时,传输的是实参变量中存储的值的副本。
基本类型的参数传递
public static void main(String[] args){
int a = 10;
change(a);
System.out.println(a); // 此时a仍然为10
}
public static void change(int a){
System.out.println(a); // a = 10
a = 20;
System.out.println(a); // a = 20
}
引用类型的参数传递
public static void main(String[] args){
int[] arrs = new int[]{10, 20, 30};
change(arrs);
System.out.println(arrs[1]); // 此时传递的是数组的地址值的副本,通过地址访问,改变数组元素,arr[1] = 222
}
public static void change(int[] arrs){
System.out.println(arrs[1]); // arr[1] = 20
arrs[1] = 222;
System.out.println(arrs[1]); // arr[1] = 222
}
方法重载
一个类中,出现多个方法的名称相同,但是形参列表(个数、类型、顺序)不同,那么这些方法成为方法重载。
public static void main(String[] args) {
test(); // ===test1===
test(1);// ===test2===1
}
public static void test(){
System.out.println("===test1===");
}
public static void test(int a){
System.out.println("===test2===" + a);
}
int test(int a){ // 形参列表冲突,与前面是否有修饰符无关
return a;
}
void test(int a, double b){
}
void test(int b, double a){ //形参列表冲突,与形参名称无关
}
return关键字使用技巧
return;
可以用在无返回值的方法中,作用是立即跳出并结束当前方法的执行
public static void main(String[] args){
chu(10, 0);
}
public static void chu(int a , int b){
if(b == 0){
System.err.println("除数不能为0!!!");
return; // 直接跳出并结束当前chu方法的执行
}
int c = a / b;
System.out.println("结果为" + c);
}
面向对象基础
public class Student {
String name;
double chinese;
double math;
public void printTotalScore(){
System.out.println(name + "的总成绩是:" + (chinese + math));
}
public void printAverageScore(){
System.out.println(name + "的总成绩是:" + (chinese + math) / 2.0);
}
}
public class Test {
public static void main(String[] args) {
// 创建一个学生对象,封装数据
Student s1 = new Student();
s1.name = "学生1";
s1.chinese = 100;
s1.math = 90;
s1.printAverageScore(); // 学生1的平均成绩是:95.0
s1.printTotalScore(); // 学生1的总成绩是:190.0
Student s2 = new Student();
s2.name = "学生2";
s2.chinese = 100;
s2.math = 100;
s2.printAverageScore(); // 学生2的平均成绩是:100.0
s2.printTotalScore(); // 学生2的总成绩是:200.0
}
}
面向对象变成有什么好处?
万物皆对象,更符合人类思维习惯,变成更简单、更直观。
对象本质上是一种特殊的数据结构,里面包含各种变量(对象的属性)和方法(对象的行为)。
public class 类名{
//1、变量,用来说明对象可以处理什么数据
//2、方法,描述对象有什么功能,也就是可以对数据进行什么样的处理
//……
}
//类名 对象名 = new 类名();
对象的内存执行原理
Student s1 = new Student(); //创建学生对象
学生类放在方法区,s1变量放在栈内存中,new出来的对象会放在堆内存中。堆内存中的对象有自己的地址,并且还有一个类的地址信息,指向方法区中的学生类。
System.out.println(s1); // ren.imuzi0221.object.Student@4f3f6b24
System.out.println(s2); // ren.imuzi0221.object.Student@15aeb7ab
注意事项
类名建议用英文单词,首字母大写,满足驼峰模式,且要有意义。如:Student
、Car
…
成员变量本身存在默认值,所以在定义成员变量时不需要赋初始值。
Student s = new Student();
System.out.println(s.name); // null
System.out.println(s.chinese); // 0.0
System.out.println(s.math); // 0.0
一个代码文件中,可以写多个class
类,但只能有一个用public
修饰,且public
修饰的类名必须成为代码文件名。
对象与对象之间的数据不会相互影响,但多个变量指向同一个对象会相互影响。
Student s1 = new Student();
s1.name = "张三";
Student s2 = new Student();
s2.name = "李四";
System.out.println(s1.name);// s1的name还是张三
Student s1 = new Student();
s1.name = "张三";
Student s2 = s1;
s2.name = "李四";
System.out.println(s1.name);// 此时s1的name也变成了李四
this关键字
this就是一个变量,可以用在方法中,来拿到当前对象。主要用来解决变量名称冲突问题。
public class Student{
double score;
public void printThis(){
System.out.println(this);
}
public void printPass(double score){
if(this.score > score){ // 使用this.score来指代Student类中的score属性,防止变量名称冲突
}
}
}
构造器
用于创建对象时对对象属性的初始化赋值
public class Student{
// 无参数构造器
public Student(){
...
}
// 有参数构造器
public Student(String name, double score){
...
}
}
Student s = new Student(); // 执行无参数构造器
Student s = new Student("张三",100); // 执行有参数构造器
类在设计时,如果不写构造器,Java会为类自动生成一个无参构造器。一旦定义了有参数构造器,Java就不会自动生成无参构造器了。
封装
用类设计对象处理某一个事物的数据时,应该把要处理的数据,和处理这些数据的方法,设计到一个对象中去。
设计规范:合理隐藏、合理暴露
通过关键字:public
、private
实现
public static void main(String[] args){
Student s1 = new Student();
s1.score = -99; // 无法访问,不能赋值
s1.setScore(-99); // 通过共有方法,可以赋值,但是方法内部有参数校验
System.out.println(s1.getScore()); // 通过共有方法获取值
}
public class Student{
//public double score; // 共有成员变量,此时可以通过外界访问
private double score; // 私有成员变量,外界无法访问
// 此时如果业务对score变量有需求,此时可以通过共有方法的方式,对变量进行赋值
public void setScore(double socre){
if(score >= 0 && score <=100){
this.score = score;
}else{
System.out.println("数据非法!");
}
}
public void getScore(double socre){
return score;
}
public void printPass(){
System.out.println(score >= 60 ? "成绩及格" : "成绩不及格");
}
}
实体JavaBean(实体类)
类中的成员变量都是私有,并且要有对外提供相应的getXxx
、setXxx
方法
类中必须要有一个公共的无参构造器
用来保存某个事物的数据,除此之外没有其他处理数据的方法
出现数据和数据业务处理分离(实际开发)
// 实体类
public class Student {
private String name; // 成员变量私有
private double score;
public Student() { // 公共的无参构造器
}
public Student(String name, double score) {
this.name = name;
this.score = score;
}
// 相应的getXxx、setXxx方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
}
// 操作类
public class StudentOperator {
private Student student;
public StudentOperator(Student student){
this.student = student;
}
public void printPass(){
if(student.getScore() >= 60){
System.out.println(student.getName() + "学生成绩及格");
}else {
System.out.println(student.getName() + "学生成绩不及格");
}
}
}
public class Test {
public static void main(String[] args) {
Student s1 = new Student();
s1.setName("张三");
s1.setScore(99);
System.out.println(s1.getName());
System.out.println(s1.getScore());
StudentOperator operator = new StudentOperator(s1); //创建操作类对象operator
operator.printPass(); // 调用操作方法
}
}
成员变量和局部变量的区别
常用API(一)
API(Application Programming Interface:应用程序编程接口)
包
包是分门别类管理各种不同的程序,类似于文件夹,建包有利于程序的管理和维护。
在自己程序中调用其他包下的程序的注意事项:
同一个包下的类,互相可以直接调用。
如果当前程序中,要调用其他包下的程序,则必须在当前程序中导包,才可以访问。导包格式:import 包名.类名;
如果当前程序中,要调用Java提供的程序,也需要先导包才可以使用(java.lang
包下的程序不需要导包,可以直接使用)。
如果当前程序中,访问多个其他包下的程序,且这些程序名一样的情况下,默认只能导入一个程序,另一个程序必须带报名和类名来访问。
例如此时ren.imuzi0221.pkg
包下有pkg1
和pkg2
两个包,pkg1
下新建一个Demo
类,pkg2
下新建个Demo
类。
// pkg1包
package ren.imuzi0221.pkg.pkg1;
public class Demo {
public void print(){
System.out.println("pkg1");
}
}
// pkg2包
package ren.imuzi0221.pkg.pkg2;
public class Demo {
public void print(){
System.out.println("pkg2");
}
}
// 主程序
import ren.imuzi021.pkg.pkg2.Demo;
public calss Test{
public static void main(String[] args) {
Demo d1 = new Demo(); // 在创建对象时,可以选择两个包中的其中一个进行导入,可以选择此时导入的是pkg2中的方法
d1.print(); // 此时会打印pkg2,此时说明调用的确实是pkg2下的程序
ren.imuzi0221.pkg.pkg1.Demo d2 = new ren.imuzi0221.pkg.pkg1.Demo(); // 如果要调用pkg1下的方法,此时必须要写上全包名ren.imuzi0221.pkg.pkg1.Demo
d2.print(); // 此时会打印pkg1,说明调用的是pkg1下的程序
}
}
String类
String概述
java.lang.String
代表字符串,用来封装字符串数据和处理字符串的方法。
创建字符串对象,封装字符串数据,调string
的方法
1.直接通过双引号得到字符串对象,封装字符串数据
String name = "iloveJava";
System.out.println(name); // 此时name指向的是对象的地址,但是Java内部进行了处理,可以打印字符串内容
2.通过new String
类创建字符串对象,并调用构造器初始化字符串
String rs1 = new String();
System.out.println(rs1); // 输出空字符 ""
String rs2 = new String("iloveJava");
System.out.println(rs2); // 输出iloveJava
char[] chars = {'study', "Java"};
String rs3 = new String(chars);
System.out.println(rs3); // 输出studyJava
byte[] bytes = {97, 98 ,99};
String rs4 = new String(bytes);
System.out.println(rs4); // 输出abc
String的常用方法
String使用时的注意事项
String对象的内容不可改变,被称为不可变字符串对象。
public static void main(String[] args {
String name = "我";
name += "爱";
name += "Java";
System.out.println(name); // 我爱Java
}
这里就有问题了,name指向的字符串对象确实变化了,
事实上,每次试图改变字符串对象,实际上是产生了新的字符串对象,变量每次都是指向了新的字符串对象,所以之前字符串对象的内容确实没有改变。
只要是以双引号"…"
方式写出的字符串对象,会存储到字符串常量池,且相同内容的字符串只存储一份(节约内存)。而通过new
方式创建字符串对象,每new
一次都会产生一个新的对象放在堆内存中。
String s1 = "abc";
String s2 = "abc";
System.out.println(s1 == s2); // true,相同内容的字符串只存储一份,s1和s2本质是一个对象,因此地址相同
String s3 = new String("abc");
String s4 = new String("abc");
System.out.println(s3 == s4); // false,每new一次都会产生一个新的对象放在堆内存中,因此s3和s4的地址不同
ArrayList类
ArraryList概述
集合是一种容器,用来存储数据,类似于数组。
数组和集合的区别?
当数组定义完成并启动后,长度就固定了。而集合大小可变,开发中用的更多。
ArraryList
就是一种常用的集合
ArraryList的常用方法
// 创建ArrayList集合对象
ArrayList list = new ArrayList();
//ArrayList<String> list = new ArrayList<>(); // 约束集合只能存放字符串类型
// 增删改查的方法
list.add("Java");
list.add(666);
list.add(66.6);
System.out.println(list); // [Java, 666, 66.6]
list.add(1,"MySQL"); // [Java, MySQL, 666, 66.6] 在指定索引位置处添加一个数据
System.out.println(list.get(1)); // MySQL 获取指定索引位置处的值
System.out.println(list.size()); // 5 返回集合中元素的个数
System.out.println(list.remove(1)); // 返回被删除的元素MySQL 删除指定索引位置的元素
System.out.println(list); // [Java, 666, 66.6]
list.remove("Java"); // 直接删除指定的元素 [666, 66.6],如果集合中存在多个相同元素,默认只删除第一次出现的元素
System.out.println(list.set(0,"ABC")); // 修改指定索引位置的元素,返回被删除的元素Java, 此时集合变成[ABC, 66.6]
面向对象高级
static
static修饰成员变量
static
:静态,可以修饰成员变量、成员方法。
成员变量按照有无static
修饰,分为两种:类变量、示例变量(对象的变量)
public class Student{
static String name; // 类变量,有static修饰,属于类,在计算机里只有一份,会被类的全部对象共享
int age; // 实例变量,无static修饰,属于每个对象的。
}
public static void main(String[] args){
Student.name = "Java"; // 可以通过 类名.类变量访问(推荐)
Student s1 = new Student();
s1.name = "ABC"; // 也可以通过 对象名.类变量访问(不推荐)
Student s2 = new Student();
s2.name = "AAA";
System.out.println(s1.name); // 全部对象共享
System.out.println(Student.age) // 报错,实例变量属于每个对象,不能共享,无法通过类名访问
}
案例:在开发中,如果某个数据只需要一份,且希望能够被共享(访问、修改),则该数据可以定义成类变量来记住
public class User{
// 类变量
public static int number; // 用户类可以记住自己创建了多少个用户对象(这个变量只需要一份,可以定义为类变量)
// 构造器
public User(){
// User.number++;
number++; // 在同一个类中,访问自己类的类变量,才可以省略类名不写。
}
}
public static void main(String[] args){
User u1 = new User();
User u2 = new User();
User u3 = new User();
User u4 = new User();
System.out.println(User.number); //4
}
static修饰成员方法
成员方法按照有无static
修饰,分为两种:类方法、示例方法(对象的方法)
public class Student{
double score;
// 类方法
public static void printHelloWorld(){
System.out.println("Hello World");
System.out.println("Hello World");
}
// 实例方法
public void printPass(){
System.out.println("成绩:" + (score >=60 ? "及格" : "不及格"));
}
}
public static void main(String[] args){
Student.printHelloWorld(); //类名.类方法(推荐)
Student s = new Student();
s.printHelloWorld(); // 对象.类方法(不推荐)
s.printPass(); // 对象.实例方法
Student.printPass(); //报错,实例方法只能用对象来访问
}
main方法也是一种类方法
类方法最常用的应用场景是做工具类。
工具类中的方法都是一些类方法,每个方法都是用来完成一个功能的,工具类是给开发人员功能共同使用的。
使用类方法设计工具类可以提高代码的服用性,调用方便,提高开发效率。
public class XxxxUtil {
private XxxxUtil(){ // 将构造器私有化,防止从外部创建对象
}
public static void xxx(){
// ......
}
public static boolean xxxx(String email){
// ......
}
public static String xxxxx(int n){
// ......
}
}
static注意事项
类方法中可以直接访问类的成员,不可以直接访问实例成员
实例方法中既可以直接访问类的成员,也可以直接访问实例成员
实例方法中可以出现this
关键字,类方法中不可以出现this
关键字
pubilc class Student(){
static String schoolName; // 类变量
double score; // 实例变量
// 类方法中可以直接访问类的成员,不可以直接访问实例成员
public static void printHelloWorld(){
schoolName = "Alaska"; // 同一个类中,访问类成员可以省略类名不写
printHelloWorld2();
System.out.println(score); // 报错,不能直接访问实例成员
printPass();; // 报错
System.out.println(this); // 报错,类方法没有对象
}
// 类方法
pubilc static void printHelloWorld2(){
}
// 实例方法中既可以直接访问类的成员,也可以直接访问实例成员
pubilc void printPass(){
schoolName = "Alaska2"; // 实例方法可以访问类成员
printHelloWorld2();
System.out.println(socre); // 实例方法可以访问实例成员
printPass2();
System.out.println(this);
}
// 实例方法
pubilc void printPass2(){
}
}
单例设计模式
设计模式:一个问题的最优解法,称之为设计模式
单例设计模式确保一个类只有一个对象,可以避免浪费内存。
饿汉式单例写法: 将类的构造器私有。
定义一个类变量记住类的一个对象。
定义一个类方法,返回对象。
// 饿汉式单例 在获取类的对象时,对象已经创建好了
pubilc class A{
// 定义类变量记住类的一个对象
private static A a = new A(); // 该语句只会被执行一次
// 构造器私有
private A(){
}
// 定义类方法返回对象
public static A getObject(){
return a;
}
}
public static void main(String[] args) {
new A(); //报错,不能从外部通过new创建对象
A a1 = A.getObject();
A a2 = A.getObject(); // 只会得到一个对象
}
饿汉式单例写法: 将类的构造器私有。
定义一个类变量存储对象。
提供一个类方法,保证返回的是同一个对象。
// 懒汉式单例 拿对象时,才开始创建对象
pubilc class B{
// 定义类变量用于存储对象。
private static B b; // 该语句只会被执行一次
// 构造器私有
private B(){
}
// 提供一个类方法,这个方法要保证第一次调用是才创建一个对象,后面调用时都会用这同一个对象返回
public static B getInstance(){
if(B ==null){
b = new B();
}
return b;
}
}
public static void main(String[] args) {
B b1 = B.getInstance(); // 第一次拿对象
B b2 = B.getInstance(); // 不再创建对象
}
继承
extends
关键字可以让一个类和另一个类建立起父子关系,子类能继承父类的非私有成员(成员变量、成员方法),子类的对象是由子类和父类共同完成的。
pubilc class B extends A{ // A类称为父类,B类称为子类
}
// 父类
public class A{
// 公开成员
public int i;
public void print1(){
System.out.println("===print===1");
}
// 私有成员
private int j;
private void print2(){
System.out.println("===print2===");
}
}
// 子类
public class B entends A{
public void print3{
System.out.println(i); // i是公开的成员变量,可以被继承
print1(); // print1是公开的成员方法,可以被继承
// System.out.println(j); 私有成员不可以被继承
// pring2();
}
}
public static void main(String[] args) {
B b = new B();
System.out.println(b.i); //公开
b.print1(); //公开
// b.pring2(); //私有
}
使用继承的好处:减少重复代码的编写
在子类方法中访问其他成员,依照就近原则
public class A{
String name = "我是父类";
public void print1(){
System.out.println("==父类的print1方法执行==");
}
}
public class B extends A{
String name = "我是子类";
public void showName(){
String name = "局部名称";
System.out.println(name); //输出:局部名称
System.out.println(this.name); //子类成员变量:我是子类
System.out.println(super.name); //父类成员变量:我是父类
}
}
public static void main(String[] args) {
B b = new B();
b.showName(); //输出:局部名称,就近原则 局部名称->我是子类->我是父类
}
权限修饰符
单继承
Java是单继承的,Java中的类不支持多继承,但是支持多层继承。Object
类是Java中所有类的祖宗。
class A{} // class A extends Object{} 默认继承Object类
class B extends A{}
class C extends B{}; // C类继承B类,单继承
// class C extends B,A(); 报错,不能多继承
class D extends B{}; // 支持多层继承
方法重写
当子类觉得父类中的某个方法不好用,或者无法满足自己的需求时,子类可以重写一个和方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写。
重写后,方法的访问,Java会遵循就近原则。
子类重写父类方法时,访问权限必须大于或等于父类该方法的权限。
重写的方法返回值类型,必须与被重写方法的返回值类型一样,或者范围更小。
私有方法、静态方法不能被重写。
public class A{
public void print1(){
System.out.println("111");
}
public void print2(int a, int b){
System.out.println("111111");
}
}
public class B extends A{
// 方法重写
@Override // 使用Override注解,可以让编译器帮助检查重写的格式是否正确,同时增强代码的可读性
public void print1(){
System.out.println("666");
}
// 方法重写
@Override
public void print2(int a, int b){
System.out.println("666666");
}
}
public static void main(String[] args) {
B b = new B();
b.print1(); // 输出666
b.pring2(2,3); // 输出666666
}
子类重写Object
类的toString()
方法,以便方法对象的内容
public class C{
// 默认返回的是对象的地址
// 重写Object类的toString()方法
public void toString(){
return ....;
}
}
子类构造器
子类的全部构造器,都会先调用父类的构造器,再执行自己
如果父类没有无参构造器,子类的全部构造器都会报错,此时需要使用super(...)
来调用父类的有参构造器。
public class F{
public F(){
System.out.println("父类F的无参数构造器执行了");
}
}
public class Z extends F{
public Z(){
super(); //默认存在此行代码,调用父类的构造器
System.out.println("子类Z的无参数构造器执行了");
}
}
public static void main(String[] args) {
Z z = new Z();
// 此时会输出:
// 父类F的无参数构造器执行了
// 子类Z的无参数构造器执行了
}
this(...)
调用兄弟构造器(该类的其他构造器)
多态
多态实在继承/实现情况下的一种现象,表现为:对象多态、行为多态。
多态的前提:有继承/实现关系;存在父类引用子类对象;存在方法重写。
People p1 = new Student();
p1.run();
People p2 = new Teacher();
p2.run();
在多态形式下,右边对象是解耦合的,更便于扩展和维护。
定义方法时,使用父类类型的变量作为形参,可以接收一切子类对象,扩展性更强、更便利。
Student s = new Student();
go(s);
Teacher t = new Teacher();
go(t);
public static void go(People p){
}
多态下存在的问题:无法直接调用子类的独有功能。
public class Teacher extends People{
public void teach(){
// 教书
}
}
public class Student extends People{
public void test(){
// 考试
}
}
public static void main(String[] args){
People p1 = new Student();
// p1.test() 无法调用子类(Student是People的子类)的独有功能(考试)
}
因此需要多态下的类型转换:子类 变量名 = (子类)父类变量
Teacher t = (Teacher)p; // 强制类型转换
public class Teacher extends People{
public void teach(){
// 教书
}
}
public class Student extends People{
public void test(){
// 考试
}
}
public static void main(String[] args){
People p1 = new Student();
Student s1 = (Student)p1; //强制类型转换
s1.test(); // 此时可以调用子类的独有功能
// 强制类型在有继承或者实现关系时就可以进行强制转换,但是运行时可能出现类型转换异常
// Teacher t1 = (Teacher)p1; // 报错:java.lang.ClassCastException
// instanceof
if(p1 instanceof Student) {
Student s2 = (Student)p1;
s2.test();
}else{
Teacher t2 = (Teacher)p1;
t2.teach();
}
}
final关键字
final
关键字可以用来修饰类、方法、变量
修饰类:该类被称为最终类,不能被继承。
final class A{} // A类被final修饰
//class B extends A{} 不能被继承
修饰方法:该方法被称为最终方法,不能被重写。
class C{
public final void test(){ // test方法被final修饰
}
}
class D extends C{
// @Override
// public void test(){ 不能被重写
//
// }
}
修饰变量:该变量只能被赋值一次。用于常量(public static final
修饰的成员变量):建议名称全部大写,多个单词下划线连接。
public class test{
public static final String MY_NAME = "Alaska";
public static void main(String[] args){
System.out.println(MY_NAME); // 可读性更好,更易于维护
System.out.println(MY_NAME);
System.out.println(MY_NAME);
System.out.println(MY_NAME);
}
}
抽象类
abstract
关键字,用于修饰类、成员方法。
// 抽象类
public abstract class A{
// 抽象方法:必须用abstract修饰,只有方法签名,且一定不能有方法体
public abstract void run();
}
抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类。
类该有的成员(成员变量、方法、构造器),抽象类都可以有。
抽象类最主要的特点:抽象类不能创建对象,仅作为一种特殊的父类,让子类继承实现。
一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
模版方法设计模式
写法:
定义一个抽象类
在里面定义两个方法:一个是模板方法,把相同代码放里面去;一个是抽象方法,具体实现交给子类完成。
public abstract class People{
public final void write(){ // 最好使用final修饰,防止被子类重写
System.out.println("--------相同代码1----------");
System.out.println("--------相同代码2----------");
System.out.println("--------相同代码3----------");
System.out.println(writeMain()); // 不同代码,交给抽象方法完成
System.out.println("--------相同代码4----------");
}
// 定义一个抽象方法写不同代码,具体的实现交给子类来完成
public abstract String writeMain();
}
public class Student extends People{
// 子类重写方法
@Override
public String writeMain(){
System.out.println("--------不同代码1----------");
}
}
public class Teacher extends People{
// 子类重写方法
@Override
public String writeMain(){
System.out.println("--------不同代码2----------");
}
}
public static void main(String[] args){
Teacher t = new Teacher();
t.write();
Student s = new Student();
s.write();
}
接口
interface
关键字,可以定义一种特殊结构:接口。接口也是抽象的,不能创建实例对象。
public interface 接口名 {
// 成员变量 (常量)
// 成员方法 (抽象方法)
}
public interface A {
String MY_NAME = "Alaska"; // 常量需要赋初始值
// public static final String MY_NAME = "Alaska"; 默认添加有public static final,多此一举
void test(); // 抽象方法
// public abstract void test(); 默认添加有public abstract,多此一举
// public A(){} 接口中不能有构造器
// static {} 接口中不能有代码块
}
接口是用来被类实现(关键字:implemtents
)的,实现接口的类成为实现类。
一个类可以实现多个接口,实现类实现多个接口,必须重写完全部接口的全部抽象方法,否则实现类需要定义为抽象类。
修饰符 class 实现类 implements 接口1,接口2,接口3,…{
}
public interface B {
void testb1();
void testb2();
}
public interface C {
void testc1();
void testc2();
}
// 实现类
public class D implements B,C {
// 必须重写完全部接口的全部抽象方法
@Override
public void testb1(){
}
@Override
public void testb2(){
}
@Override
public void testc1(){
}
@Override
public void testc2(){
}
}
接口可以弥补类单继承的不足,一个类同时可以实现多个接口。
让程序可以面向接口编程,这样程序员就可以灵活方便的切换各种业务实现。
class Student{
}
//class A extends Student{ // A类不能再继承别的类了,如果想要继承多个类,可以用接口实现
//
//}
class A extends Student implements Driver, Singer{
@Override
public void drive(){
}
@Override
public void sing(){
}
}
class B extends Student implements Driver{
@Override
public void drive(){
}
}
interface Driver{
void drive();
}
interface Singer{
void sing();
}
public static void main(String[] args){
Student s1 = new A();
Driver s2 = new A();
Singer s3 = new A();
//Driver d = new A();
Driver d = new B(); // 面向接口编程,可以随时切换实现类对象,便于后期维护的修改
}
接口的其他细节
默认方法
默认方法:必须使用default
修饰,默认会被public
修饰
默认方法是一种实例方法,必须使用实现类的对象来访问。
public interface A {
default void test1(){
System.out.println("===默认方法===");
}
}
public class B implements A {
}
public static void main(String[] args){
B b = new B();
b.test1();
}
私有方法
私有方法:必须使用private
修饰,私有方法也是一种实例方法,默认会被public
修饰
public interface A {
// 默认方法
default void test1(){
System.out.println("===默认方法===");
test2(); // 调用私有方法
}
// 私有方法
private void test2(){
System.out.println("===私有方法===");
}
}
public class B implements A {
}
public static void main(String[] args){
B b = new B();
b.test1();
}
静态方法
静态方法:必须使用static
修饰,默认会被public
修饰
public interface A {
// 默认方法
default void test1(){
System.out.println("===默认方法===");
test2(); // 调用私有方法
}
// 私有方法
private void test2(){
System.out.println("===私有方法===");
}
// 静态方法
static void test3(){
System.out.println("===静态方法===");
}
}
public class B implements A {
}
public static void main(String[] args){
B b = new B();
b.test1(); // 调用默认方法
A.test3(); // 调用静态方法
}
接口的多继承
interface A{}
interface B{}
interface C{}
// 接口是多继承的
interface D extends C,B,A{
}
// 类E实现了A、B、C、D接口
class E implements D{
}
一个接口继承多个接口,如果多个接口中存在方法签名冲突,则此时不支持多继承。
interface I {
void test1();
}
interface J {
String test1();
}
// 此时不能继承,存在签名冲突,不知道该重写哪个抽象方法
//interface K extends I,J{
//
//}
一个类实现多个接口,如果多个接口中存在方法签名冲突,则此时不支持多实现。
interface I {
void test1();
}
interface J {
String test1();
}
// 此时不能实现多个接口,存在签名冲突,不知道该重写哪个抽象方法
//class E implements I,J{
//
//}
一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会优先用父类的。
class Fu{
pubilc void run(){
System.out.println("===父类的run方法执行了===");
}
}
interface IT{
default void run(){
System.out.println("===接口IT中的方法执行了===");
}
}
class Zi extends Fu implements IT{
}
public static void main(String[] args){
Zi zi = new Zi();
zi.run(); // ===父类的run方法执行了===
}
一个类实现了多个接口,多个接口中存在同名的默认方法,可以不冲突,这个类重写该方法即可。
interface It1{
default void test(){
System.out.println("IT1");
}
}
interface It2{
default void test(){
System.out.println("IT2");
}
}
class N implements It1, IT2{
@Override
public void test(){
System.out.println("自己的");
}
}
内部类
是类中的五大成分之一(成员变量、方法、构造器、内部类、代码块),如果一个类定义在另一个类的内部,这个类就是内部类。
当一个类的内部,包含了一个完整的事物,且这个事物没有必要单独设计时,就可以把这个事物设计成内部类。
public class Car{
// 内部类
public class Engine{
}
}
成员内部类
静态内部类
局部内部类
匿名内部类(重点)
匿名:指的是成员不需要为这个类声明名字。用于更方便的创建一个子类对象。
new 类或接口(参数值...){
类体(一般是方法重写);
};
class abstract Animal{
public abstract void cry();
}
/*
class Cat extends Animal{
@Override
pubilc void cry(){
System.out.println("喵喵喵");
}
}
*/
public static void main(String[] args){
// Animal a = new Cat();
// a.cry();
// 匿名内部类
// 把这个匿名内部类编译成一个子类,然后会立即创建一个子类对象出来
Animal a = new Animal(){
@Override
public void cry(){
System.out.println("喵喵喵");
}
};
a.cry();
}
匿名内部类在开发中的使用场景:通常作为一个参数传输给方法。
// 猫和狗都要参加游泳比赛
interface Swimming{
void swim();
}
// 设计一个方法,可以接收swimming接口的一切实现类对象进来参加游泳比赛
public static void go(Swimming s){
s.swim();
}
pubilc static void main(String[] args){
/*
Swimming s1 =
go(s1); // 狗在游泳...
*/
go(new Swimming(){
@Override
public void swim(){
System.out.println("狗在游泳...");
}
});
}
枚举
枚举是一种特殊类,这些名称本质是常量,每个常量都会记住枚举类的一个对象。
修饰符 enum 枚举类名{
// 第一行必须罗列的是枚举对象的名字
名称1, 名称2, ...;
其他成员...
}
public enum A{
X, Y, Z;
...
}
枚举类的构造器都是私有的,因此枚举类对外不能创建对象。
枚举类都是最终类,不可以被继承。
抽象枚举:
public enum B {
X(){
@Override
public void go(){
}
},Y(){
@Override
public void go(){
}
};
public abstract void go();
}
枚举的应用场景:做信息标志和分类
// 硬编码,可读性不好
public static void check(int flag){
switch (flag){
case 0:
....
break;
case 1:
....
break;
}
}
public static void main(String[] args){
check(1);
}
// 软编码,但是入参不会受约束
public static void check(int flag){
switch (flag){
case Constant.BOY:
....
break;
case Constant.GIRL:
....
break;
}
}
public class Constant{
public static final int BOY = 0;
public static final int GIRL = 1;
}
// 枚举
public enum Constant2 {
BOY , GIRL;
}
public static void check(Constant2 sex){
switch (flag){
case BOY:
....
break;
case GIRL:
....
break;
}
}
public static void main(String[] args){
check(Constant2.BOY); // 只能填BOY、GIRL,被限制更安全
}
泛型
定义类、接口、方法时,同时声明了一个或者多个类型变量(如:<E>),称为泛型类、泛型接口、泛型方法,他们统称为泛型。
ArrayList<String> list1 = new ArrayList<>();
list1.add("java1"); //String
list1.add("java2"); //String
list1.add("java3"); //String
list1.add(new Cat()); // 报错,Class类型,只能添加String类型数据
public class Cat{
}
泛型提供了在编译阶段约束所能操作的数据类型,并自动进行检查的能力,这样可以避免强制类型转换,及其可能出现的异常。
泛型是工作在编译阶段的,一旦程序编译成class文件,class文件中就不存在泛型了,这就是泛型擦除。
泛型不支持基本数据类型,只能支持对象类型(引用数据类型)。
// ArrayList<int> list1 = new ArrayList<>(); 报错
ArrayList<Integer> list1 = new ArrayList<>();
// ArrayList<double> list1 = new ArrayList<>();
ArrayList<Double> list1 = new ArrayList<>();
泛型类
修饰符 class 类名<类型变量,类型变量,...>{
}
public class MyArrayList<E> {
public boolean add(E e){
...
}
}
泛型接口
修饰符 interface 接口名<类型变量,类型变量,...>{
}
public interface A<E>{
...
}
泛型方法
修饰符 <类型变量,类型变量,...> 返回值类型 方法名(形参列表){
}
public static <T> T test(T t){
return t;
}
class Dog{
}
test("java"); // String类型
test(new Dog()); // Dog类型
// ? 通配符,在使用泛型的时候可以代表一切类型 ? extends Car(上限) ? super Car(下限)
public static void go(ArrayList<? extends Car> cars){
}
常用API(二)
Object类
Object类是Java中所有类的祖宗类,Java中所有类的对象都可以直接使用Object类中提供的一些方法。
Objects类
Objects类是一个工具类,提供了很多操作对象的静态方法。
包装类
包装类就是把基本类型的数据包装成对象。
Integer a2 = Integer.valueOf(12);
自动装箱:可以自动把基本类型的数据转换成对象。
Integer a3 = 12;
自动拆箱:可以自动把包装类型的对象转换成对应的基本类型数据。
int a4 = a3;
泛型和集合不支持基本数据类型,只能支持引用数据类型。
// ArrayList<int> list = new ArrayList<>(); int为基本数据类型
ArrayList<Integer> list = new ArrarList<>();
list.add(12); // 自动装箱
int rs = list.get(1); // 自动拆箱
// 把基本类型的数据转换成字符串
Integer a = 23;
String rs1 = Integer.toString(a); // "23"
System.out.println(rs + 1); // "231"
String rs2 = a.toString(); // "23"
System.out.println(rs2 + 1); // "231"
String rs3 = a + "";
System.out.println(rs3 + 1); // "231"
// 把字符串类型的数值转换成对应的基本类型
String ageStr = "29";
//String ageStr = "29.85"; 不能转换
// int ageI = Integer.parseInt(ageStr); // 29
int ageI = Integer.valueOf(ageStr); // 29
System.out.println(ageI + 1); // 30
String scoreStr = "99.5";
// double score = Double.parseDouble(scoreStr); // 99.5
double score = Double.valueOf(scoreStr); // 99.5
System.out.println(score + 0.5); // 100.0
StringBuilder、StringBuffer、StringJoiner
StringBuilder
代表可变字符串对象,相当于一个容器,它里面装的字符串是可以改变的,用来操作字符串的。
StringBuilder
比String
更适合做字符串的修改操作,效率会更高,代码更简洁。
StringBuilder s = new StringBuilder("Java"); // s "Java"
// 拼接内容
s.append(12);
s.append("Gulf");
s.append(true);
// 支持链式编程
s.append(666).append("imuzi");
System.out.println(s); // s "Java12Gulftrue666imuzi"
// 反转操作
s.reverse();
System.out.println(s); // s "izumi666eurtfluGavaJ"
// 返回字符串的长度
System.out.println(s.length());
// 把StringBuilder对象又转换成String类型
String rs = s.toString();
System.out.println(rs);
StringBuffer
的用法与StringBuilder
是一模一样的,但StringBuilder
是线程不安全的,StringBuffer
是线程安全的。
JDK8之后,跟StringBuilder
一样,也是用来操作字符串的,也可以看成是一个容器,创建之后里面的内容是可变的。
StringJoiner
比StringBuilder
在有些场景下代码会更简洁
StringJoiner s = new StringJoiner(",", "[", "]");
s.add("java1");
s.add("java2");
s.add("java3");
System.out.println(s); // [java1,java2,java3] 自动拼接
Math、System、Runtime
System.out.println(Math.abs(-12)); // 12
System.out.println(Math.abs(123)); // 123
System.out.println(Math.abs(-3.14)); // 3.14
System.out.println(Math.ceil(4.00000001)); // 5.0
System.out.println(Math.ceil(4.0)); // 4.0
System.out.println(Math.floor(4.9999999)); // 4.0
System.out.println(Math.floor(4.0)); // 4.0
System.out.println(Math.round(3.4999)); // 3.0
System.out.println(Math.round(3.5001)); // 4.0
System.out.println(Math.max(10, 20)); // 20
System.out.println(Math.min(10, 20)); // 10
System.out.println(Math.pow(2, 3)); // 2的3次方 8.0
System.out.println(Math.rondom()); // 随机值,范围[0.0, 1.0)
BigDecimal
用于解决浮点型运算时,出现结果失真的问题。
double a = 0.1;
double b = 0.2;
double c = a + b;
System.out.println(c); // 0.30000000000000000004
// 将double类型的数字编程字符串封装成BigDecimal对象来运算
// BigDecimal a1 = new BigDecimal(Double.toString(a));
// BigDecimal b1 = new BigDecimal(Double.toString(b));
BigDecimal a1 = BigDecimal.valueOf(a);
BigDecimal b1 = BigDecimal.valueOf(b);
BigDeciaml c1 = a1.add(b1); // 加
BigDeciaml c2 = a1.subtract(b1); // 减
BigDeciaml c3 = a1.multiply(b1); // 乘
BigDeciaml c4 = a1.divide(b1); // 除 除不尽的结果会报错
BigDeciaml c5 = a1.divide(b1,2,RoundingMode.HALF_UP); // 精确到2位小数,四舍五入
double rs = c5.doubleValue();
日期、时间
JDK8之前
Date日期类
Date d = new Date();
System.out.println(d); // Fri Feb 28 21:41:40 CST 2025
long time = d.getTime(); // 获得时间戳
time += 2 * 1000;
Date d2 = new Date(time); // 获得2s之后的时间
System.out.println(d2); // Fri Feb 28 21:41:42 CST 2025
Date d3 = new Date();
d3.setTime(time); // 设置日期对象的时间为当前时间毫秒值对应的时间
SimpleDateFormat
Date d = new Date();
System.out.println(d); // Sat Mar 01 10:58:32 CST 2025
long time = d.getTime();
System.out.println(time); // 1740797912137
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss EEE a");
String rs = sdf.format(d);
String rs2 = sdf.format(time);
System.out.println(rs); // 2025-03-01 10:58:32 周六 上午
System.out.println(rs2); // 2025-03-01 10:58:32 周六 上午
String dateStr = "2025-03-01 11:06:34";
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 指定的时间格式必须与被解析的时间格式一模一样,否则会出bug
Date d2 = sdf2.parse(dateStr);
System.out.println(d2); // Sat Mar 01 11:06:34 CST 2025
Calendar
// 得到系统此时时间对应的日历对象
Calendar now = Calendar.getInstance();
System.out.println(now); // java.util.GregorianCalendar[time=1740799715638,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=31,lastRule=null],firstDayOfWeek=2,minimalDaysInFirstWeek=1,ERA=1,YEAR=2025,MONTH=2,WEEK_OF_YEAR=9,WEEK_OF_MONTH=1,DAY_OF_MONTH=1,DAY_OF_YEAR=60,DAY_OF_WEEK=7,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=11,HOUR_OF_DAY=11,MINUTE=28,SECOND=35,MILLISECOND=638,ZONE_OFFSET=28800000,DST_OFFSET=0]
// 获取日历中的某个信息
int year = now.get(Calendar.YEAR);
System.out.println(year); // 2025
int days = now.get(Calendar.DAY_OF_YEAR);
System.out.println(days); // 60
// 拿到日历中记录的日期对象
Date d = now.getTime();
System.out.println(d); // Sat Mar 01 11:32:59 CST 2025
// 拿到时间毫秒值
long time = now.getTimeInMillis();
System.out.println(time); // 1740800085913
// 修改日历中的某个信息
now.set(Calendar.MONTH, 9); // 修改月份成为10月份
Date d2 = now.getTime();
System.out.println(d2); // Wed Oct 01 11:32:59 CST 2025
// 为某个信息增加/减少指定的值
now.add(Calendar.MONTH, -7); // 在当前月份基础上减去7个月
System.out.println(now.getTime()); // Sat Mar 01 11:32:59 CST 2025
JDK8之后
LocalDate、LocalTime、LocalDateTime
LocalDate
:代表本地日期(年、月、日、星期)
LocalTime
:代表本地时间(时、分、秒、纳秒)
LocalDateTime
:代表本地时期、时间(年、月、日、 星期、时、分、秒、纳秒)
// 获取本地日期对象(不可变对象)
LocalDate ld = LocalDate.now();
System.out.println(ld); // 2025-03-01
// 获取日期对象中的信息
int year = ld.getYear(); // 年
int month = ld.getMonthValue(); // 月(1-12)
int day = ld.getDayOfMonth(); // 日
int dayOfYear = ld.getDayOfYear(); // 一年中的第几天
int dayOfWeek = ld.getDayOfWeek().getValue(); // 星期几
System.out.println(year); // 2025
System.out.println(month); // 3
System.out.println(day); // 1
System.out.println(dayOfWeek); // 6
System.out.println(dayOfYear); // 60
// 直接修改某个信息:withYear、withMouth、withDayOfMonth、withDayOfYear
LocalDate ld2 = ld.withYear(2077);
System.out.println(ld2); // 2077-03-01
LocalDate ld3 = ld.withMonth(12);
System.out.println(ld3); // 2025-12-01
System.out.println(ld); // 2025-03-01
// 把某个信息加多少: plusYears、plusMonths、plusDays、plusWeeks
LocalDate ld4 = ld.plusYears(20);
System.out.println(ld4); // 2045-03-01
LocalDate ld5 = ld.plusMonths(2);
System.out.println(ld5); // 2025-05-01
System.out.println(ld); // 2025-03-01
// 把某个信息减多少: minusYears、minusMonths、minusDays、minusWeeks
LocalDate ld6 = ld.minusYears(20);
System.out.println(ld6); // 2005-03-01
LocalDate ld7 = ld.minusMonths(2);
System.out.println(ld7); // 2025-01-01
System.out.println(ld); // 2025-03-01
// 获取指定日期的LocalDate对象: public static LocalDate of(int year, int month, int dayOfMonth)
LocalDate ld8 = LocalDate.of(2077, 7 ,7);
System.out.println(ld8); // 2077-07-07
// 判断2个日期对象:是否相等,在前还是在后: equals、isBefore、isAfter
System.out.println(ld8.equals(ld7)); // false
System.out.println(ld8.isAfter(ld7)); // true
// 获取本地时间对象(不可变对象)
LocalTime lt = LocalTime.now(); // 时 分 秒 纳秒
System.out.println(lt); // 2025-03-01
// 获取时间对象中的信息
int hour = lt.getHour(); // 时
int minute = lt.getMinute(); // 分
int second = lt.getSecond(); // 秒
int nano = lt.getNano(); // 纳秒
System.out.println(hour); // 12
System.out.println(minute); // 41
System.out.println(second); // 38
System.out.println(nano); // 616146200
// 直接修改某个信息:withHour、withMinute、withSecond、withNano
LocalTime lt2 = lt.withHour(22);
System.out.println(lt2); // 22:41:38.616146200
LocalTime lt3 = lt.withMinute(12);
System.out.println(lt3); // 12:12:38.616146200
System.out.println(lt); // 12:41:38.616146200
// 把某个信息加多少: plusHours、plusMinutes、plusSeconds、plusNanos
LocalTime lt4 = lt.plusMinutes(20);
System.out.println(lt4); // 13:01:38.616146200
LocalTime lt5 = lt.plusSeconds(2);
System.out.println(lt5); // 12:41:40.616146200
System.out.println(lt); // 12:41:38.616146200
// 把某个信息减多少: minusHours、minusMinutes、minusSeconds、minusNanos
LocalTime lt6 = lt.minusHours(20);
System.out.println(lt6); // 16:41:38.616146200
LocalTime lt7 = lt.minusMinutes(2);
System.out.println(lt7); // 12:39:38.616146200
System.out.println(lt); // 12:41:38.616146200
// 获取指定时间的LocalTime对象: public static LocalTime of(int hour, int minute)
LocalTime lt8 = LocalTime.of(13, 14 ,1);
System.out.println(lt8); // 13:14:01
// 判断2个时间对象:是否相等,在前还是在后: equals、isBefore、isAfter
System.out.println(lt8.equals(lt7)); // false
System.out.println(lt8.isAfter(lt7)); // true
// 获取本地日期时间对象(不可变对象)
LocalDateTime ldt = LocalDateTime.now(); // 年 月 日 时 分 秒 纳秒
System.out.println(ldt); // 2025-03-01T12:46:30.962569600
// 获取日期时间对象中的信息
int year = ldt.getYear(); // 年
int month = ldt.getMonthValue(); // 月
int day = ldt.getDayOfMonth(); // 日
int hour = ldt.getHour(); // 时
int minute = ldt.getMinute(); // 分
int second = ldt.getSecond(); // 秒
int nano = ldt.getNano(); // 纳秒
System.out.println(year); // 2025
System.out.println(month); // 3
System.out.println(day); // 1
System.out.println(hour); // 12
System.out.println(minute); // 46
System.out.println(second); // 30
System.out.println(nano); // 962569600
// 直接修改某个信息:withYear、withMonth、withDayOfMonth、withHour、withMinute、withSecond、withNano
LocalDateTime ldt2 = ldt.withHour(22);
System.out.println(ldt2); // 2025-03-01T22:46:30.962569600
LocalDateTime ldt3 = ldt.withMinute(12);
System.out.println(ldt3); // 2025-03-01T12:12:30.962569600
System.out.println(ldt); // 2025-03-01T12:46:30.962569600
// 把某个信息加多少: plusYears、plusMonths、plusDays、plusHours、plusMinutes、plusSeconds、plusNanos
LocalDateTime ldt4 = ldt.plusMinutes(20);
System.out.println(ldt4); // 2025-03-01T13:06:30.962569600
LocalDateTime ldt5 = ldt.plusDays(2);
System.out.println(ldt5); // 2025-03-03T12:46:30.962569600
System.out.println(ldt); // 2025-03-01T12:46:30.962569600
// 把某个信息减多少: minusYears、minusMonths、minusDays、minusHours、minusMinutes、minusSeconds、minusNanos
LocalDateTime ldt6 = ldt.minusHours(20);
System.out.println(ldt6); // 2025-02-28T16:46:30.962569600
LocalDateTime ldt7 = ldt.minusMonths(2);
System.out.println(ldt7); // 2025-01-01T12:46:30.962569600
System.out.println(ldt); // 2025-03-01T12:46:30.962569600
// 获取指定日期时间的LocalDateTime对象: public static LocalDateTime of(...)
LocalDateTime ldt8 = LocalDateTime.of(2024, 5, 20, 13, 14, 1);
System.out.println(ldt8); // 2024-05-20T13:14:01
// 判断2个日期时间对象:是否相等,在前还是在后: equals、isBefore、isAfter
System.out.println(ldt8.equals(ldt7)); // false
System.out.println(ldt8.isAfter(ldt7)); // false
// 获取日期时间对象中的日期和时间对象
LocalTime lt = ldt.toLocalTime();
System.out.println(lt); // 12:46:30.962569600
LocalDate ld = ldt.toLocalDate();
System.out.println(ld); // 2025-03-01
// 合并日期对象和时间对象
LocalDateTime ldt10 = LocalDateTime.of(ld, lt);
System.out.println(ldt10); // 2025-03-01T12:46:30.962569600
ZoneID、ZonedDateTime
ZoneID
:时区id(洲名/城市名:Asia/Shanghai、Asia/Chongqing,国家名/城市名:America/New_York...)
// 获取系统默认的时区: public static ZoneId systemDefault()
ZoneId zoneId = ZoneId.systemDefault();
System.out.println(zoneId); // Asia/Shanghai
// 获取所有的时区: public static Set<String> getAvailableZoneIds()
System.out.println(ZoneId.getAvailableZoneIds());
// 把某个时区id封装成ZoneId对象: public static ZoneId of(String zoneId)
ZoneId zoneId1 = ZoneId.of("America/New_York");
// 获取某个时区的ZonedDateTime对象: public ZonedDateTime now(ZoneId zone)
ZonedDateTime now = ZonedDateTime.now(zoneId1);
System.out.println(now); // 2025-03-01T00:20:33.615444800-05:00[America/New_York]
ZonedDateTime now1 = ZonedDateTime.now(Clock.systemUTC());
System.out.println(now1); // 2025-03-01T05:20:33.615444800Z
// 获取系统默认时区的ZonedDateTime对象: public static ZonedDateTime now()
ZonedDateTime now2 = ZonedDateTime.now();
System.out.println(now2); // 2025-03-01T12:20:33.615444800+08:00[Asia/Shanghai]
Instant
// 获取当前时间的时间戳对象(不可变对象)
Instant now = Instant.now();
// 获取总秒数
long second = now.getEpochSecond();
System.out.println(second); // 1740836108
// 获取不够1秒的纳秒数
int nano = now.getNano();
System.out.println(nano); // 639253900
System.out.println(now); // 2025-03-01T13:35:31.639253900Z
Instant instant = now.plusNanos(111);
// Instant对象的作用:做代码的性能分析,或者记录用户的操作时间点
Instant now1 = Instant.now();
// 代码执行
Instant now2 = Instant.now();
DateTimeFormatter
格式化器,用于时间的格式化、解析
// 创建一个时间日期格式化对象
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 对时间进行格式化
LocalDateTime now = LocalDateTime.now();
System.out.println(now); // 2025-03-02T14:37:30.648196800
String rs = formatter.format(now);
System.out.println(rs); // 2025-03-02 14:37:30
String rs2 = now.format(formatter);
System.out.println(rs2); // 2025-03-02 14:37:30
// 对字符串进行解析:一般使用LocalDateTime.parse
String dataStr = "2025-03-02 14:37:30";
LocalDateTime ldt = LocalDateTime.parse(dataStr, formatter);
System.out.println(ldt); // 2025-03-02T14:37:30
Period、Duration
LocalDate start = LocalDate.of(2025, 3, 1);;
LocalDate end = LocalDate.of(2025, 3, 31);
// 创建Period对象,封装两个日期对象
Period period = Period.between(start, end);
// 通过Period对象获取两个日期对象相差的信息
int year = period.getYears();
int month = period.getMonths();
int day = period.getDays();
System.out.println(year); // 0
System.out.println(month); // 0
System.out.println(day); // 30
// 得到Duration对象
LocalDateTime start = LocalDateTime.of(2025, 3, 1, 0, 0, 0);
LocalDateTime end = LocalDateTime.of(2025, 3, 31, 0, 0, 0);
Duration duration = Duration.between(start, end);
// 获取两个时间对象相差的信息
long days = duration.toDays();
long hours = duration.toHours();
long minutes = duration.toMinutes();
long seconds = duration.getSeconds();
long millis = duration.toMillis();
long nanos = duration.toNanos();
System.out.println(days); // 30
System.out.println(hours); // 720
System.out.println(minutes); // 43200
System.out.println(seconds); // 2592000
System.out.println(millis); // 2592000000
System.out.println(nanos); // 2592000000000
Arrays类
Comparable、Comparator
// 方式一
public class Student implements Comparable<Student> {
private String name;
private int age;
private double height;
// 重写compareTo方法
@Override
public int compareTo(Student o) {
// if(this.age > o.age) {
// return 1;
// } else if(this.age < o.age) {
// return -1;
// } else {
// return 0;
// }
return this.age - o.age; // 升序
// return o-age - this.age; // 降序
}
public Student() {
}
public Student(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
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 double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
}
Student[] students = new Student[4];
students[0] = new Student("张三", 18, 1.75);
students[1] = new Student("李四", 19, 1.80);
students[2] = new Student("王五", 20, 1.85);
students[3] = new Student("赵六", 21, 1.90);
Arrays.sort(students);
System.out.println(Arrays.toString(students)); // [Student{name='张三', age=18, height=1.75}, Student{name='李四', age=19, height=1.8}, Student{name='王五', age=20, height=1.85}, Student{name='赵六', age=21, height=1.9}]
// 方式二
Student[] students = new Student[4];
students[0] = new Student("张三", 18, 1.75);
students[1] = new Student("李四", 19, 1.80);
students[2] = new Student("王五", 20, 1.85);
students[3] = new Student("赵六", 21, 1.90);
// Arrays.sort(students);
// System.out.println(Arrays.toString(students)); // [Student{name='张三', age=18, height=1.75}, Student{name='李四', age=19, height=1.8}, Student{name='王五', age=20, height=1.85}, Student{name='赵六', age=21, height=1.9}]
Arrays.sort(students, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
// if(o1.getHeight() > o2.getHeight()) {
// return 1;
// } else if(o1.getHeight() < o2.getHeight()) {
// return -1;
// }
// return 0;
return Double.compare(o1.getHeight(), o2.getHeight()); //升序
// return Double.compare(o2.getHeight(), o1.getHeight()); //降序
}
});
System.out.println(Arrays.toString(students));
Lamabda表达式
public static void main(String[] args) {
// Animal a = new Animal(){
// @Override
// public void eat() {
// System.out.println("狗在吃东西");
// }
// };
// a.eat();
// 使用Lambda表达式并不能简化全部匿名内部类的写法,智能简化函数式接口的匿名内部类
// Animal a = () -> {
// System.out.println("狗在吃东西");
// };
// a.eat();
// Swimming s = new Swimming() {
// @Override
// public void swim() {
// System.out.println("狗在游泳");
// }
// };
// s.swim();
Swimming s = () ->{
System.out.println("狗在游泳");
};
s.swim();
}
}
interface Swimming{
void swim();
}
abstract class Animal {
public abstract void eat();
}
Arrays.sort(students, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return Double.compare(o1.getHeight(), o2.getHeight());
}
}
// Lambda表达式
Arrays.sort(Students,(Student o1, Student o2) -> {
return Double.compare(o1.getHeight(), o2.getHeight());
});
// 参数类型可以省略不写
Arrays.sort(Students,(o1, o2) -> {
return Double.compare(o1.getHeight(), o2.getHeight());
});
// 如果方法体代码只有一行代码,可以省略大括号不写,同时要省略分号。如果这行代码是return语句,也必须去掉return不写
Arrays.sort(Students,(o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight()));
方法引用
进一步简化Lambda表达式
静态方法的引用
类名::静态方法
如果某个Lambda表达式里只是调用一个静态方法,并且前后参数的形式一致,就可以使用静态方法引用
// Arrays.sort(Students,(o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight()));
// Arrays.sort(Students,(o1, o2) -> CompareBydata.compareByHeight(o1, o2)); Lambda表达式
Arrays.sort(Students, CompareByData::compareByAge); // 静态方法引用
// 静态方法
public class CompareBydata{
public static int compareByHeight(Student o1, Student o2){
return o1.getHeight() - o2.getHeight();
}
}
实例方法的引用
对象名::实例方法
如果某个Lmabda表达式里只是调用一个实例方法,并且前后参数的形式一致,就可以使用实例方法引用
// Arrays.sort(Students,(o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight()));
CompareBydata compare = new CompareBydata();
// Arrays.sort(Students,(o1, o2) -> compare.compareByHeightDesc(o1, o2)); Lambda表达式
Arrays.sort(Students, compare::compareByHeightDesc); // 实例方法引用
// 实例方法
public class CompareBydata{
pubilc int compareByHeightDesc(Student o1, Student o2){
return o1.getHeight() - o2.getHeight();
}
}
特定类型方法的引用
类型::方法
如果某个Lambda表达式里只是调用一个实例方法,并且前面参数中的第一个参数是作为方法的主调,后面的所有参数都是作为该实例方法的入参的,则此时就可以使用特定类型的方法引用
String[] names = {"body", "angela", "Andy", "dlei", "caocao", "Babo", "jack", "Cici"};
// Arrays.sort(names, new Comparator<String>() {
// @Override
// public int compare(String o1, String o2) {
// return o1.compareToIgnoreCase(o2);
// }
// });
// Arrays.sort(names, (o1, o2) -> o1.compareToIgnoreCase(o2) );
Arrays.sort(names, String::compareToIgnoreCase); // 特定类型的方法引用
System.out.println(Arrays.toString(names));
}
构造器引用
类名::new
如果某个Lambda表达式里只是在创建对象,并且前后参数情况一致,就可以使用构造器引用
异常
public static void main(String[] args) {
// try {
// saveAge(160);
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
try {
saveAge2(160);
} catch (AgeIllegalException e) {
throw new RuntimeException(e);
}
}
// 编译时异常
public static void saveAge2(int age) throws AgeIllegalException{
if(age > 0 && age < 150){
System.out.println("年龄保存成功: " + age);
}else {
// 用一个异常对象封装这个问题
// throw 抛出这个异常对象
// throws 用在方法上,抛出方法内部的异常
throw new AgeIllegalException("/age is illegal, your age is " + age);
}
}
// 运行时异常
public static void saveAge(int age){
if(age > 0 && age < 150){
System.out.println("年龄保存成功: " + age);
}else {
// 用一个异常对象封装这个问题
// throw 抛出这个异常对象
throw new AgeIllegalRuntimeException("/age is illegal, your age is " + age);
}
}
// 必须让这个类继承自RuntimeException,才能成为一个运行时异常类
public class AgeIllegalRuntimeException extends RuntimeException{
public AgeIllegalRuntimeException(String message) {
super(message);
}
public AgeIllegalRuntimeException() {
}
}
// 必须让这个类继承自Exception,才能成为一个编译时异常类
public class AgeIllegalException extends Exception{
public AgeIllegalException(String message) {
super(message);
}
public AgeIllegalException() {
}
}
// 异常报错
Exception in thread "main" java.lang.RuntimeException: ren.imuzi0221.hello.AgeIllegalRuntimeException: /age is illegal, your age is 160
at ren.imuzi0221.hello.Exceptiontest1.main(Exceptiontest1.java:8)
Caused by: ren.imuzi0221.hello.AgeIllegalRuntimeException: /age is illegal, your age is 160
at ren.imuzi0221.hello.Exceptiontest1.saveAge(Exceptiontest1.java:18)
at ren.imuzi0221.hello.Exceptiontest1.main(Exceptiontest1.java:6)
集合
Collection
Collection集合概述
ArrayList<String> list = new ArrayList<>(); // 有序 可重复 有索引
list.add("java");
list.add("java");
list.add("java");
list.add("java");
list.add("java");
System.out.println(list); // [java, java, java, java, java]
HashSet<String> set = new HashSet<>(); // 无序 不重复 无索引
set.add("java1");
set.add("java2");
set.add("java1");
set.add("java2");
set.add("java3");
System.out.println(set); // [java3, java2, java1]
Collection常用方法
Collection<String> c = new ArrayList<>();
// public boolean add(E e) // 添加元素,返回值是boolean类型
c.add("java1");
c.add("java1");
c.add("java2");
c.add("java2");
c.add("java3");
System.out.println(c); // [java1, java1, java2, java2, java3]
// public void clear() // 清空集合
c.clear();
System.out.println(c); // []
// public boolean isEmpty() // 判断集合是否为空,返回值是boolean类型
System.out.println(c.isEmpty()); // true
// public int size() // 返回集合中元素的个数,返回值是int类型
System.out.println(c.size()); // 0
// public boolean contains(Object o) // 判断集合中是否包含指定元素,返回值是boolean类型
System.out.println(c.contains("java1")); // false
// public boolean remove(E e) // 删除指定元素,如果有多个重复元素默认删除前面的第一个,返回值是boolean类型
// c.remove("java1");
// public Object[] toArray() // 将集合转换为数组,返回值是Object[]
Object[] arr = c.toArray();
String[] arr2 = c.toArray(new String[c.size()]);
// 把一个集合的全部数据倒入到另一个集合中
Collection<String> c2 = new ArrayList<>();
c2.add("java1");
c2.add("java2");
Collection<String> c3 = new ArrayList<>();
c3.add("java3");
c3.add("java4");
c2.addAll(c3); // 把c3的全部数据倒入到c2中
System.out.println(c2); // [java1, java2, java3, java4]
System.out.println(c3); // [java3, java4]
Collection遍历方式
迭代器
Collection<String> c = new ArrayList<>();
c.add("java1");
c.add("java2");
c.add("java3");
c.add("java4");
System.out.println(c); // [java1, java2, java3, java4]
Iterator<String> it = c.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
增强for循环
Collection<String> c = new ArrayList<>();
c.add("java1");
c.add("java2");
c.add("java3");
c.add("java4");
System.out.println(c); // [java1, java2, java3, java4]
for (String ele : c) {
System.out.println(ele);
}
String[] name = {"java1", "java2", "java3", "java4"};
for (String ele : name) {
System.out.println(ele);
}
Lambda
Collection<String> c = new ArrayList<>();
c.add("java1");
c.add("java2");
c.add("java3");
c.add("java4");
System.out.println(c); // [java1, java2, java3, java4]
c.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
c.forEach(s -> System.out.println(s)); // lambda表达式
c.forEach(System.out::println); // 方法引用
集合中存储的是元素对象的地址
List集合
List集合支持的遍历方式:
· for循环
· 迭代器
· 增强for循环
· Lambda表达式
List<String> list = new ArrayList<>();
list.add("java");
list.add("java2");
list.add("java3");
// for循环
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// 迭代器
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// 增强for循环(foreach)
for (String s : list) {
System.out.println(s);
}
// Lambda表达式
list.forEach(s -> System.out.println(s));
ArrayList
ArrayList集合适合的应用场景:
适合根据索引查询数据,比如根据随机索引取数据,或者数据量不是很大时。
不适合数据量大的同时,又要频繁的进行增删操作。
LinkedList
应用场景:
· 设计队列
· 设计栈
Set集合
Set<Integer> set = new HashSet<>(); // 一行经典代码 无序 不重复 无索引
Set<Integer> set1 = new LinkedHashSet<>(); // 有序 不重复 无索引
Set<Integer> set2 = new TreeSet<>(); // 可排序(升序) 不重复 无索引
set.add(666);
set.add(555);
set.add(555);
set.add(888);
set.add(888);
set.add(777);
set.add(777);
set1.add(666);
set1.add(555);
set1.add(555);
set1.add(888);
set1.add(888);
set1.add(777);
set1.add(777);
set2.add(666);
set2.add(555);
set2.add(555);
set2.add(888);
set2.add(888);
set2.add(777);
set2.add(777);
System.out.println(set); // [888, 777, 666, 555]
System.out.println(set1); // [666, 555, 888, 777]
System.out.println(set2); // [555, 666, 777, 888]
HashSet
LinkedHashSet
TreeSet
并发修改异常
ArrayList<String> list = new ArrayList<>(); // 有序 可重复 有索引
list.add("王麻子");
list.add("小李子");
list.add("李爱花");
list.add("张全蛋");
list.add("晓李");
list.add("李玉刚");
// 使用迭代器,使用集合自身的remove方法会出现ConcurrentModificationException异常(并发修改异常)
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String name = iterator.next();
if (name.contains("李")) {
// iterator.remove(); // 正确做法
list.remove(name); // 报错
}
}
System.out.println(list);
// 使用增强for循环,使用集合自身的remove方法会出现ConcurrentModificationException异常(并发修改异常)
// for (String s : list) {
// if (s.contains("李")) {
// list.remove(s); // 报错
// }
// }
// System.out.println(list);
// 使用Lambda表达式,使用集合自身的remove方法会出现ConcurrentModificationException异常(并发修改异常)
// list.forEach(s -> {
// if (s.contains("李")) {
// list.remove(s); // 报错
// }
// });
// System.out.println(list);
// // 使用for循环遍历集合并删除集合中带李子的名字
// for (int i = 0; i < list.size(); i++) {
// String name = list.get(i);
// if (name.contains("李")) {
// list.remove(name);
//// i--; // 删除后索引要减一,防止漏删
// }
// }
// // 出现BUG导致漏删
// System.out.println(list); // [王麻子, 李爱花, 张全蛋, 李玉刚]
Collections类
可变参数
定义在方法、构造器的形参列表中,格式是:数据类型...参数名称;
使用可变参数在方法调用时候不传数据给它;也可以传一个或者同时传多个数据;也可以传一个数组给它,常常用来灵活的接收数据
public static void main(String[] args) {
test(); //不传数据
test(1); //传一个数据
test(1,2,3,4,5,6,7,8,9,10); //传多个数据
test(new int[]{1,2,3,4,5,6,7,8,9,10}); //传数组
}
// 注意事项: 一个形参列表中,只能有一个可变参数
// 可变参数必须放在形参列表的最后面
//
public static void test(int...nums){
// 可变参数在方法内部会被当做数组来处理
System.out.println(nums.length);
System.out.println(Arrays.toString(nums));
//0
//[]
//1
//[1]
//10
//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
//10
//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}
Collections工具类
Map
Map集合概述
Map<String, Integer> map = new HashMap<>(); // 无序,不重复,无索引
Map<String, Integer> map1 = new LinkedHashMap<>(); // 有序,不重复,无索引
Map<String, Integer> map2 = new TreeMap<>(); // 可排序(升序),不重复,无索引
map.put("java", 1);
map.put("java", 2); // 后面重复的数据会覆盖前面的数据(键)
map.put("java1", 3);
map.put("java2", 3);
map.put(null,null);
map.put(null,1);
map1.put("java", 1);
map1.put("java", 2); // 后面重复的数据会覆盖前面的数据(键)
map1.put("java1", 3);
map1.put("java2", 3);
map1.put(null,null);
map1.put(null,1);
map2.put("java", 5);
map2.put("java", 8); // 后面重复的数据会覆盖前面的数据(键)
map2.put("java1", 3);
map2.put("java2", 3);
// map2.put(null,null); // TreeMap不允许键为null
// map2.put(null,1);
System.out.println(map); // {null=1, java=2, java2=3, java1=3}
System.out.println(map1); // {java=2, java1=3, java2=3, null=1}
System.out.println(map2); // {java=8, java1=3, java2=3}
Map常用方法
Map<String, Integer> map = new HashMap<>();
map.put("手表", 100);
map.put("手表", 220);
map.put("手机", 2);
map.put("Java", 2);
map.put(null, null);
System.out.println(map); // {null=null, 手表=220, 手机=2, Java=2}
// 获取集合大小: pubilc int size()
System.out.println(map.size()); // 4
// 清空集合: pubilc void clear()
// map.clear();
// System.out.println(map); // {}
// 判断集合是否为空: pubilc boolean isEmpty()
// System.out.println(map.isEmpty()); // true
// 根据键获取值: pubilc V get(Object key)
System.out.println(map.get("手表")); // 220
// 删除指定键值对: pubilc V remove(Object key)
// System.out.println(map.remove("手表")); // 220
// 判断集合中是否包含指定键: pubilc boolean containsKey(Object key)
System.out.println(map.containsKey("手表")); // true
// 判断集合中是否包含指定值: pubilc boolean containsValue(Object value)
System.out.println(map.containsValue(100)); // false
// 获取Map集合的全部键: pubilc Set<K> keySet()
System.out.println(map.keySet()); // [null, 手表, 手机, Java]
// 获取Map集合的全部值: pubilc Collection<V> values()
System.out.println(map.values()); // [null, 220, 2, 2]
// 把其他Map集合中的数据添加到当前Map集合中: pubilc V putAll(Map<? extends K, ? extends V> m)
Map<String, Integer> map2 = new HashMap<>();
map2.put("Alaska", 100);
map2.put("Gulf", 220);
map2.put("imuzi", 2);
map.putAll(map2); // 把map2中的数据添加到map中
System.out.println(map2); // {Alaska=100, Gulf=220, imuzi=2}
System.out.println(map); // {null=null, 手表=220, 手机=2, Java=2
Map遍历方式
键找值
Map<String, Double> map = new HashMap<>();
map.put("蜘蛛精", 162.5);
map.put("蜘蛛精", 169.8);
map.put("紫霞", 165.8);
map.put("至尊宝", 169.5);
map.put("牛魔王", 183.6);
System.out.println(map); // {至尊宝=169.5, 牛魔王=183.6, 紫霞=165.8}
// 获取Map集合的全部键
Set<String> keys = map.keySet();
System.out.println(keys); // [至尊宝, 牛魔王, 紫霞]
// 遍历全部的键,根据键获取其对应的值
for (String key : keys) {
// 根据键获取值
Double v = map.get(key);
System.out.println(key + "的身高是" + v);
// 蜘蛛精的身高是169.8
// 牛魔王的身高是183.6
// 至尊宝的身高是169.5
// 紫霞的身高是165.8
}
键值对
Set<Map.Entry<String, Double>> entries = map.entrySet();
for (Map.Entry<String, Double> entry : entries) {
System.out.println(entry.getKey() + "的身高是" + entry.getValue());
}
Lambda
map.forEach((k,v) -> System.out.println(k + "的身高是" + v));
HashMap
LinkedHashMap
TreeMap
Stream流
Stream流可以用于操作集合或者数组的数据
Stream流大量的结合了Lambda的语法风格,提供了一种更加强大,更加简单的方式操作集合或者数组中的数据,代码更简洁,可读性更好。
List<String> names = new ArrayList<>();
Collections.addAll(names,"张三丰","张无忌", "赵敏", "周芷若", "小昭", "李青萝", "殷素素", "黄衫女");
System.out.println(names);
// 找出姓张,且是3个字的名字,存入到一个新集合中去
List<String> list = new ArrayList<>();
for (String name : names) {
if (name.startsWith("张") && name.length() == 3) {
list.add(name);
}
}
System.out.println(list); // [张三丰, 张无忌]
// 使用Stream流来实现
List<String> list1 = names.stream().filter(name -> name.startsWith("张")).filter(name -> name.length() == 3).collect(Collectors.toList());
System.out.println(list1); // [张三丰, 张无忌]
// 获取List集合的Stream流
List<String> names = new ArrayList<>();
Collections.addAll(names,"张三丰","张无忌", "赵敏", "周芷若", "小昭", "李青萝", "殷素素", "黄衫女");
Stream<String> stream = names.stream();
// 获取Set集合的Stream流
Set<String> set = new HashSet<>();
Collections.addAll(set,"张三丰","张无忌", "赵敏", "周芷若", "小昭", "李青萝", "殷素素", "黄衫女");
Stream<String> stream1 = set.stream();
stream1.filter(s -> s.startsWith("张")).forEach(System.out::println);
// 获取Map集合的Stream流
Map<String, Double> map = new HashMap<>();
map.put("蜘蛛精", 162.5);
map.put("蜘蛛精", 169.8);
map.put("紫霞", 165.8);
map.put("至尊宝", 169.5);
map.put("牛魔王", 183.6);
Set<String> key = map.keySet();
Stream<String> ks = key.stream();
Collection<Double> values = map.values();
Stream<Double> vs = values.stream();
Set<Map.Entry<String, Double>> entries = map.entrySet();
Stream<Map.Entry<String, Double>> kvs = entries.stream();
kvs.filter(s -> s.getKey().contains("牛")).forEach(e -> System.out.println(e.getKey() + "的身高是" + e.getValue()));
// 获取数组的Stream流
String[] arr = {"张三丰","张无忌", "赵敏", "周芷若", "小昭", "李青萝", "殷素素", "黄衫女"};
Stream<String> stream2 = Arrays.stream(arr);
Stream<String> arr1 = Stream.of(arr);
多线程
线程创建
方式一:继承Thread类
注意事项:
· 启动线程必须是调用start
方法,不是调用run
方法
· 不要把主线程任务放在启动子线程之前(不然永远都是主线程的任务先跑完)
// 让子类继承Thread线程类
public class MyThread extends Thread{
// 重写Thread的run方法
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程Mythread输出:" + i);
}
}
}
public static void main(String[] args) {
// 创建MyThread线程类的对象代表一个线程
Thread t = new MyThread();
// 启动线程(自动执行run方法)
// t.run(); 子线程先跑了,不是多线程
t.start(); // 注册子线程
for (int i = 0; i < 5; i++) {
System.out.println("主线程main输出:" + i);
}
//主线程main输出:0
//主线程main输出:1
//主线程main输出:2
//子线程Mythread输出:0
//主线程main输出:3
//子线程Mythread输出:1
//子线程Mythread输出:2
//主线程main输出:4
//子线程Mythread输出:3
//子线程Mythread输出:4
}
方式二:实现Runnable接口
// 定义一个任务类,实现Runnable接口
public class MyRunnable implements Runnable{
// 重写runnable的run方法
@Override
public void run() {
// 线程要执行的任务
for (int i = 0; i < 5; i++) {
System.out.println("子线程输出---->" + i);
}
}
}
public static void main(String[] args) {
// 创建任务对象
Runnable target = new MyRunnable();
// 把任务对象交给一个线程对象处理
new Thread(target).start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程main输出---->" + i);
}
}
//子线程输出---->0
//子线程输出---->1
//子线程输出---->2
//主线程main输出---->0
//子线程输出---->3
//主线程main输出---->1
//子线程输出---->4
//主线程main输出---->2
//主线程main输出---->3
//主线程main输出---->4
Runnable target = new Runnable() {
@Override
public void run() {
// 线程要执行的任务
for (int i = 0; i < 5; i++) {
System.out.println("子线程输出---->" + i);
}
}
};
// 把任务对象交给一个线程对象处理
new Thread(target).start();
// 简化一:匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
// 线程要执行的任务
for (int i = 0; i < 5; i++) {
System.out.println("子线程输出---->" + i);
}
}
}).start();
// 简化二:Lambda表达式
new Thread(() -> {
// 线程要执行的任务
for (int i = 0; i < 5; i++) {
System.out.println("子线程输出---->" + i);
}
}).start();
方式三:实现Callable接口
前两种创建方式,重写的run
方法都不能直接返回结果
// 让这个类实现Callabl接口
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n) {
this.n = n;
}
// 重写call方法
@Override
public String call() throws Exception {
int sum = 0;
// 描述线程任务,返回线程执行的结果
for (int i = 0; i <= n; i++) {
sum += i;
}
return "线程求出了1-" + n + "的值是: " + sum;
}
}
public static void main(String[] args) throws Exception {
// 创建一个Callable的对象
Callable<String> call = new MyCallable(100);
// 把Callable的对象封装成一个FutureTask对象(任务对象)
// 未来任务对象的作用:
// 是一个任务对象,实现了Runnable对象
// 可以在线程执行完毕之后,用未来任务对象调用get方法获取线程执行完毕后的结果
FutureTask<String> f1 = new FutureTask<>(call);
// 把任务对象交给Thread对象
new Thread(f1).start();
// 获取线程执行完毕后返回的结果
// 注意:如果执行到这里,假如上面的线程还没有执行完毕,这里的代码会暂停,等待上面线程执行完毕后才会获取结果
System.out.println(f1.get()); // 线程求出了1-100的值是: 5050
}
常用方法
线程安全
// 账户类
public class Account {
private double money;
public Account() {
}
public Account(double money) {
this.money = money;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public void drawMoney(double money) {
String name = Thread.currentThread().getName();
if(this.money >= money){
System.out.println(name + "来取钱" + money + "成功");
this.money -= money;
System.out.println(name + "来取钱后,余额剩余:" + this.money);
}else {
System.out.println(name + "来取钱,余额不足");
}
}
}
// 子线程类
public class DrawThread extends Thread{
private Account acc;
public DrawThread(Account acc, String name){
super(name);
this.acc = acc;
}
@Override
public void run() {
acc.drawMoney(100000);
}
}
// main线程
public static void main(String[] args) {
Account acc = new Account(100000);
new DrawThread(acc,"小明").start(); // 小明
new DrawThread(acc,"小红").start(); // 小红
}
// 输出:
// 小红来取钱100000.0成功
// 小明来取钱100000.0成功
// 小红来取钱后,余额剩余:0.0
// 小明来取钱后,余额剩余:-100000.0 出现线程安全问题
线程同步
线程同步的常见方案:
· 加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来。
同步代码块
作用:把访问共享资源的核心代码给上锁,以此保证线程安全。
// 同步代码块
synchronized{
}
public void drawMoney(double money) {
String name = Thread.currentThread().getName();
// synchronized ("lock") { // 此时锁是唯一的,不合理,会对所有对象生效
synchronized (this) { // 实例方法建议使用this作为锁对象
if(this.money >= money){
System.out.println(name + "来取钱" + money + "成功");
this.money -= money;
System.out.println(name + "来取钱后,余额剩余:" + this.money);
}else {
System.out.println(name + "来取钱,余额不足");
}
}
}
public static void test(){
synchronized (Account.class){ // 静态方法是用类名.class作为锁
}
}
同步方法
作用:把访问共享资源的核心方法给上锁,以此保证线程安全。
原理:同步方法其底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
修饰符 synchronized 返回值类型 方法名称(形参列表){
}
public synchronized void drawMoney(double money) {
String name = Thread.currentThread().getName();
if(this.money >= money){
System.out.println(name + "来取钱" + money + "成功");
this.money -= money;
System.out.println(name + "来取钱后,余额剩余:" + this.money);
}else {
System.out.println(name + "来取钱,余额不足");
}
}
Lock锁
可以创建锁对象进行加锁和解锁,更灵活、更方便、更强大。
Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock
来构建Lock
锁对象。
// 创建一个锁对象
private final Lock lk = new ReentrantLock();
......
public void drawMoney(double money) {
String name = Thread.currentThread().getName();
try {
lk.lock(); // 加锁
if(this.money >= money){
System.out.println(name + "来取钱" + money + "成功");
this.money -= money;
System.out.println(name + "来取钱后,余额剩余:" + this.money);
}else {
System.out.println(name + "来取钱,余额不足");
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lk.unlock(); // 解锁
}
}
线程池
什么是线程池?
· 线程池是一种可以复用线程的技术
不是用线程池的问题?
· 用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了又要创建新的线程,而创建新线程的系统开销往往很大,当请求过多时,会产生大量的线程,严重影响系统的性能
线程池可以控制线程和任务的数量,重复利用这些线程来处理任务,不会因为线程或任务过多而导致系统资源耗尽引起系统瘫痪。
创建线程池对象:
· 方式一: 使用ExecutorService
的实现类ThreadPoolExecutor
创建一个线程池对象
· 方式二: 使用Executors
(线程池的工具类)调用方法返回的不同特点的线程池对象
corePoolSize
:指定线程池的核心线程数量
对于计算密集型任务:核心线程数量 = CPU核数 + 1
对于I/O密集型任务:核心线程数量 = CPU核数 * 2
maximumPoolSize
: 指定线程池的最大线程数量
keepAliveTime
:指定临时线程的存活时间(只有当核心线程满了的同时任务队列也满,才会创建临时线程)
unit
:指定临时线程存活时间的时间单位(秒、分、时、天)
workQueue
:指定线程池的任务队列 new ArrayBlockingQueue<>()
基于链表实现,不限制任务队列大小 new LinkedBlockingQueue<>()
基于数组实现,可以控制任务队列大小
threadFactory
:指定线程池的线程工厂(负责创建线程)Executors.defaultThreadFactory()
可以创建默认线程工厂
handler
:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的情况下,新任务来了该如何处理)
线程池处理Runnable任务
// 定义一个任务类,实现Runnable接口
public class MyRunnable implements Runnable{
// 重写runnable的run方法
@Override
public void run() {
// 线程要执行的任务
System.out.println(Thread.currentThread().getName() + " ==> 输出666");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) {
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
new ThreadPoolExecutor.AbortPolicy());
Runnable target = new MyRunnable();
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
pool.execute(target); // 复用前面的核心线程
pool.execute(target); // 只有当核心线程满了的同时任务队列也满,才会创建临时线程
// pool.shutdown(); // 等线程池的任务全部执行完毕后,再关闭线程池
// pool.shutdownNow(); // 立即关闭线程池
// 线程池里的线程本身就是用来复用的,一直存活,不应该死亡
// 或者使用shutdown()、shutdownNow()方法来结束线程池
//pool-1-thread-1 ==> 输出666
//pool-1-thread-3 ==> 输出666
//pool-1-thread-2 ==> 输出666
//pool-1-thread-2 ==> 输出666
//pool-1-thread-1 ==> 输出666
}
什么时候会创建临时线程?
· 只有当核心线程满了(都在忙)的同时任务队列也满,才会创建临时线程
// 定义一个任务类,实现Runnable接口
public class MyRunnable implements Runnable{
// 重写runnable的run方法
@Override
public void run() {
// 线程要执行的任务
System.out.println(Thread.currentThread().getName() + " ==> 输出666");
try {
Thread.sleep(Integer.MAX_VALUE); // 让他睡眠更长的时间,以此来占用住线程
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
// other code...
Runnable target = new MyRunnable();
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
pool.execute(target); // 如果前面的核心线程在忙,后面的任务开始在任务队列中排队(1/4),不会创建新的临时线程
pool.execute(target); // 如果前面的核心线程在忙,后面的任务开始在任务队列中排队(2/4),不会创建新的临时线程
pool.execute(target); // 如果前面的核心线程在忙,后面的任务开始在任务队列中排队(3/4),不会创建新的临时线程
pool.execute(target); // 如果前面的核心线程在忙,后面的任务开始在任务队列中排队(4/4),不会创建新的临时线程
pool.execute(target); // 临时线程被创建
pool.execute(target); // 临时线程被创建
pool.execute(target); // 此时核心线程和临时线程都在忙,触发拒绝策略(线程池创建的是AbortPolicy())策略
// Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task ren.imuzi0221.thread.MyRunnable@506e1b77 rejected from java.util.concurrent.ThreadPoolExecutor@6e8cf4c6[Running, pool size = 5, active threads = 5, queued tasks = 4, completed tasks = 0]
// 输出
//pool-1-thread-3 ==> 输出666
//pool-1-thread-4 ==> 输出666
//pool-1-thread-5 ==> 输出666
//pool-1-thread-2 ==> 输出666
//pool-1-thread-1 ==> 输出666
线程池处理Callable任务
// 让这个类实现Callabl接口
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n) {
this.n = n;
}
// 重写call方法
@Override
public String call() throws Exception {
int sum = 0;
// 描述线程任务,返回线程执行的结果
for (int i = 0; i <= n; i++) {
sum += i;
}
return Thread.currentThread().getName() + "线程求出了1-" + n + "的值是: " + sum;
}
}
public static void main(String[] args) throws Exception {
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
new ThreadPoolExecutor.CallerRunsPolicy());
Future<String> f1 = pool.submit(new MyCallable(100));
Future<String> f2 = pool.submit(new MyCallable(200));
Future<String> f3 = pool.submit(new MyCallable(300));
Future<String> f4 = pool.submit(new MyCallable(400));
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
//pool-1-thread-1线程求出了1-100的值是: 5050
//pool-1-thread-2线程求出了1-200的值是: 20100
//pool-1-thread-3线程求出了1-300的值是: 45150
//pool-1-thread-3线程求出了1-400的值是: 80200
}
Executors
工具类实现线程池:
并发、并行、生命周期
进程:
· 正在运行的程序(软件)就是一个独立的进程。
· 线程是属于进程的,一个进程中可以同时运行多个线程。
· 进程中的多个线程是并发和并行执行的。
并发:
· 进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPUI会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉就是这些线程在同时执行,这就是并发。
并行:
· 在同一个时刻上,同时有多个线程在被CPU调度执行。(例8H16C的CPU可以同时处理16个线程)
生命周期:
· Java总共定义了6种线程状态。
NEW
:新建状态
RUNNABLE
:可运行状态
BLOCKED
:锁阻塞状态
WATTING
:计时等待状态
TIMED_WATTING
:无限等待状态
TERMINATED
:终止状态
悲观锁、乐观锁
悲观锁:一上来就加锁,每次只能一个线程进入,访问完毕后再解锁。(线程安全、性能较差)
乐观锁:一上来不上锁,线程同时执行,等要出现线程安全的时候才开始控制。(线程安全,性能较好)
· 整数修改的乐观锁:原子类AtomicInteger
实现
public static void main(String[] args) {
Runnable target = new MyRunnable();
for (int i = 0; i < 100; i++) {
new Thread(target).start();
}
}
// 定义一个任务类,实现Runnable接口
public class MyRunnable implements Runnable{
//private static int count;
private AtomicInteger count = new AtomicInteger(); // 原子类
// 重写runnable的run方法
@Override
public void run() {
// 线程要执行的任务
for (int i = 0; i < 100; i++) {
// synchronized (this) { // 悲观锁
// System.out.println(Thread.currentThread().getName() +
// "count =========>" + ++count);
System.out.println(Thread.currentThread().getName() +
"count =========>" + count.incrementAndGet()); // 乐观锁
// }
}
}
}
Java高级
单元测试
public class StringUtil {
public static void printNumber(String name){
if(name == null){
System.out.println(0);
return;
}
System.out.println("名字长度是: " + name.length());
}
public static int getMaxIndex(String data){
if(data == null){
return -1;
}
return data.length();
//return data.length() - 1;
}
}
/**
* 测试类
*/
public class StringUtilTest {
// 公开、无返回值、无参数列表
@Test // 测试方法注解
public void testPrintNumber(){
StringUtil.printNumber("admin");
StringUtil.printNumber(null);
}
@Test // 测试方法注解
public void testlGetMaxIndex(){
int index = StringUtil.getMaxIndex("admin");
int index1 = StringUtil.getMaxIndex(null);
System.out.println(index);
System.out.println(index1);
// 断言机制:预测业务方法的结果
Assert.assertEquals("业务逻辑存在BUG", 4, index);
//java.lang.AssertionError: 业务逻辑存在BUG
//预期:4
//实际:5
}
}
反射
获取类:
// 方式一:Class c1 = 类名.class
Class c1 = Student.class;
System.out.println(c1.getName()); // ren.imuzi0221.reflect.Student --全类名
System.out.println(c1.getSimpleName()); // Student -- 简名
// 方式二:调用Class提供的方法 public static Class forName(String package)
Class c2 = Class.forName("ren.imuzi0221.reflect.Student");
System.out.println(c1 == c2); // true 拿到的是同一个学生对象
// 方式三:调用Object提供的方法 public Class getClass(); Class c3 = 对象.getClass()
Student s1 = new Student();
Class c3 = s1.getClass();
System.out.println(c2 == c3); // true 拿到的是同一个学生对象
获取类的构造器:
public class TestConstrucutor {
@Test
public void testGetConstructors(){
// 反射第一步:先得到这个类的Class对象
Class c = Cat.class;
// 获取类的全部构造器
// Constructor[] constructors = c.getConstructors(); 获取全部public修饰的构造器
Constructor[] constructors = c.getDeclaredConstructors(); // 获取全部构造器(只要存在就能得到)
// 遍历数组中的每个构造器对象
for (Constructor constructor : constructors) {
System.out.println(constructor.getName() + "--->"
+ constructor.getParameterCount());
}
//ren.imuzi0221.reflect.Cat--->0
//ren.imuzi0221.reflect.Cat--->2
}
@Test
public void testGetConstructor() throws Exception {
Class c= Cat.class;
// Constructor constructor = c.getConstructor(); // 获取某个public修饰的构造器
Constructor constructor = c.getDeclaredConstructor(); // 获取某个构造器(只要存在就能得到)
Cat cat = (Cat) constructor.newInstance(); // 初始化对象返回
constructor.setAccessible(true); // 禁止检查访问权限(破坏封装性)
System.out.println(cat); // Cat{name='null', age=0}
System.out.println(constructor.getName() + "--->"
+ constructor.getParameterCount());
//ren.imuzi0221.reflect.Cat--->0
Constructor constructor1 = c.getDeclaredConstructor(String.class, int.class);
Cat cat2 = (Cat) constructor1.newInstance("叮当猫", 3); // 初始化对象返回
constructor1.setAccessible(true); // 禁止检查访问权限(破坏封装性)
System.out.println(cat2); // Cat{name='叮当猫', age=3}
System.out.println(constructor1.getName() + "--->"
+ constructor1.getParameterCount());
//ren.imuzi0221.reflect.Cat--->2
}
}
获取类的成员变量:
public class TestField {
@Test
public void testGetFields() throws Exception {
Class c = Cat.class;
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName() + "--->" + field.getType());
}
// name--->class java.lang.String
// age--->int
// a--->int
// COUNTRY--->class java.lang.String
Field name = c.getDeclaredField("name");
System.out.println(name.getName() + "--->" + name.getType()); // name--->class java.lang.String
Cat cat = new Cat();
name.setAccessible(true); // 禁止访问控制权限
name.set(cat, "卡菲猫");
System.out.println(cat); // Cat{name='卡菲猫', age=0}
System.out.println(name.get(cat)); // 卡菲猫
}
}
获取类的成员方法:
public class TestMethod {
@Test
public void testGetMethods() throws Exception {
Class c = Cat.class;
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName() + "--->"
+ method.getParameterCount() + "--->"
+ method.getReturnType());
//getName--->0--->class java.lang.String
//run--->0--->void
//toString--->0--->class java.lang.String
//setName--->1--->void
//getAge--->0--->int
//setAge--->1--->void
//eat--->0--->void
//eat--->1--->class java.lang.String
}
Method run = c.getDeclaredMethod("run");
System.out.println(run.getName() + "--->"
+ run.getParameterCount() + "--->"
+ run.getReturnType());
//run--->0--->void
Cat cat = new Cat();
run.setAccessible(true);
run.invoke(cat); // 🐱猫跑的贼快
Method eat = c.getDeclaredMethod("eat", String.class);
System.out.println(eat.getName() + "--->"
+ eat.getParameterCount() + "--->"
+ eat.getReturnType());
eat.setAccessible(true);
System.out.println(eat.invoke(cat, "🐟鱼")); // 🐱猫最爱吃:🐟鱼
//eat--->1--->class java.lang.String
}
}
评论区