基础
输入
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String name = scanner.next();
int age = scanner.nextInt();
double weight = scanner.nextDouble();
boolean isSingle = scanner.nextBoolean();
char gender = scanner.next().charAt(0);
scanner.close();
}
}
二维数组静态初始化
int[][] arr1 = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9, 10}};
int[][] arr2 = new int[][]{{1, 2, 3}, {4, 5, 6}, {7, 8, 9, 10}};
int[][] arr3 = new int[3][3]{{1, 2, 3}, {4, 5, 6}, {7, 8, 9, 10}}; //错误,静态初始化右边new 数据类型[][]中不能写数字
length
length——数组的属性;
length()——String的方法;
size()——集合的方法;
String[] list={"a","b","c"};
System.out.println(list.length); //数组用length
String a="apple";
System.out.println(a.length()); //String用length()
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
System.out.println(list.size());//集合用size()
面向对象
super
class Person {
protected String name; // protected之后,可以被子类访问
protected int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
class Student extends Person { //继承Person类
protected int score;
public Student(String name, int age, int score) {
super(name, age); // 必须super来调用父类的构造函数
this.score = score;
}
}
static
修饰成员变量、成员方法
public class Student {
static String name; // 类变量,可以通过Student.name来访问,也可以通过对象.name来访问,所有对象都共用同一个name
int age; // 实例变量,必须通过对象.name来访问
public static void f() {} // 类方法,与类变量同理
public void f2() {} // 实例方法,与实例变量同理
private Student() {} // 构造器前面加上private就不能在其他地方构造这个对象了
}
注意事项
public class Student {
static String schoolName; // 类变量
double score; // 实例变量
// 1、类方法中可以直接访问类成员,不可以直接访问实例成员
public static void printHelloWorld() { // 类方法
// 同一个类中,访问类成员,可以省略类名不写
schoolName = "黑马";
printHelloWorld2();
// System.out.println(score); // 报错
// printPass(); // 报错
// this.printPass2(); // 报错
}
public static void printHelloWorld2() {} // 类方法
// 2、实例方法中既可以直接访问类成员,也可以直接访问实例成员
// 3、实例方法中可以出现this关键字,类方法中不可以出现this关键字
public void printPass() { //实例方法
schoolName = "黑马2";
printHelloWorld2();
System.out.println(score);
printPass2();
this.printPass2();
}
public void printPass2() {} // 实例方法
}
代码块
public class Student {
static int number = 80;
static String schoolName;
/*
静态代码块:
格式:static{}
特点:类加载时自动执行,类只会加载一次,所以静态代码块也执行一次
作用:完成类的初始化,例如:对类变量的初始化赋值
*/
static {
System.out.println("静态代码块执行了~~");
schoolName = "黑马";
}
/*
实例代码块
格式:{}
特点:每次创建对象时,执行实例代码块,并在构造器前执行
作用:和构造器一样,都是用来完成对象的初始化,例如:对实例变量进行初始化赋值(常用),可以用于记录日志,有人创建对象,就用实例代码块来记录,减少构造器重复代码
*/
{
System.out.println("实例代码块执行了~~");
System.out.println("有人创建了对象:" + this);
}
public Student() {}
}
单例设计模式
饿汉式单例
public class A {
// 2、定义一个类变量记住类的一个对象
private static A a = new A();
// 1、把类的构造器私有
private A() {}
// 3、定义一个类方法返回类的对象
public static A getObject() { // 在外部无法通过构造器来创建对象,只能通过这个类方法来返回一个对象,而且无论调用多少次,只能创建这么一个对象,因为返回的a是类变量,属于类
return a;
}
}
懒汉式单例
public class A {
// 2、定义一个类变量用于存储这个类的一个对象
private static A a;
// 1、把类的构造器私有
private A() {}
// 3、定义一个类方法,保证第一次调用时才创建一个对象,后面调用时都会用这同一个对象返回
public static A getObject() {
if (a == null) {
a = new A();
}
return a;
}
}
extends 继承
public class B extends A{}
类B用extends来继承A
权限修饰符
单继承
Java是单继承的(只能有一个爸爸),不支持多继承(一个孩子不能有多个爸爸),支持多层继承(爷爷、爸爸、孩子)
Object类
class A {}
class A extends Object {}
这俩等同,定义一个类A,默认继承自Object类
方法重写
子类重写一个参数列表一样的方法,去覆盖父类的这个方法,就是方法重写。
重写后,方法的访问,Java会遵循就近原则。
- 小技巧:用Override注解,可以让编译器检查重写的格式是否正确,代码可读性更好
- 子类重写父类方法时,访问权限必须大于或等于父类该方法的权限
- 重写的方法返回值类型,必须与被重写方法的返回值类型一样,或者范围更小
- 私有方法、静态方法不能被重写,会报错
子类访问其他成员
依照就近原则
public class A {
String name = "父类名字";
}
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); // 父类成员变量
}
}
多态
什么是多态?
多态是在继承/实现情况下的一种现象,表现为:对象多态、行为多态。
多态的前提:有继承/实现关系;存在父类引用子类对象;存在方法重写。
多态的一个注意事项:多态是对象、行为的多态,Java中的属性(成员变量)不谈多态。
多态的具体代码体现
class People{
void run(){
System.out.println("People run");
}
}
class Teacher extends People{
@Override
void run(){
System.out.println("Teacher run");
}
}
class Student extends People{
@Override
void run(){
System.out.println("Student run");
}
}
public class Test {
public static void main(String[] args) {
People p1 = new Student(); // 对象多态
p1.run(); // Student run 行为多态
People p2 = new Teacher();
p2.run(); // Teacher run
}
}
多态的好处:定义方法时,使用父类类型的形参,可以接受一切子类对象,扩展性更强,更便利
多态的缺点:无法使用子类独有的功能。
多态类型转换:
自动类型转换:父类 变量名 = new 子类();
强制类型转换:子类 变量名 = (子类)父类变量; 强制类型转换后可以解决多态无法使用子类独有功能的缺点。
final 终止
final关键字是终止的意思,可以修饰(类,方法,变量)
修饰类:该类被称为最终类,特点是不能被继承了
修饰方法:该方法成为最终方法,特点是不能被重写
修饰变量:该变量只能被赋值一次
注意:final修饰基本类型的变量,变量存储的数据不能被改变。final修饰引用类型的变量,变量存储的地址不能被改变,但地址所指向对象的内容是可以被改变的。
static final String SCHOOL_NAME= "传智教育"
static final修饰的成员变量是常量
abstract 抽象
抽象类,顾名思义,没办法实例化,无法创建对象
abstract,修饰类和方法
public abstract class A {
public abstract void run(); // 抽象方法只有方法签名,一定不能有方法体
}
抽象类不一定有抽象方法,有抽象方法的类一定是抽象类。
抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现。
一个类继承抽象类,必须重写抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
模板方法设计模式:解决代码重复的问题
模板方法设计模式应该怎么写?
- 定义一个抽象类。
- 在里面定义2个方法,一个是模板方法:放相同的代码里,一个是抽象方法:具体实现交给子类完成
建议使用final关键字修饰模板方法,防止被子类重写
abstract class People{
// 1、定义一个模板方法
final void write() { // 加个final关键字防止它被子类重写
System.out.println("开头");
// 2、模板方法并不清楚正文部分到底应该怎么写,但是它知道子类肯定要写
System.out.println(writeMain());
System.out.println("结尾");
}
// 3、设计一个抽象方法写正文,具体的实现交给子类来完成
abstract String writeMain();
}
class Student extends People{
@Override
String writeMain(){
return "学生正文";
}
}
class Teacher extends People{
@Override
String writeMain(){
return "老师正文";
}
}
public class Main {
public static void main(String[] args) {
People p1 = new Student();
People p2 = new Teacher();
p1.write();
p2.write();
}
}
interface 接口
interface关键字可以定义一个特殊的结构:接口
public interface 接口名 {
// 成员变量(接口中默认为常量)
// 成员方法(接口中默认为抽象方法)
}
注意:接口不能创建对象;接口是用来被类实现(implements)的,实现接口的类成为实现类
public class 实现类 implements 接口1, 接口2, 接口3,...{
}
一个类可以实现多个接口(接口可以理解为干爹),实现类实现多个接口,必须重写完全部接口的全部抽象方法,否则实现类需要定义成抽象类
interface B {
void testb1();
void testb2();
}
interface C {
void testc1();
void testc2();
}
class D implements B, C{
@Override
public void testb1(){}
@Override
public void testb2(){}
@Override
public void testc1(){}
@Override
public void testc2(){}
}
接口的好处:
弥补了类单继承的不足,一个类可以同时实现多个接口
class A extends Student implements Driver, Singer{}
A类的亲爹是Student,干爹是Driver和Singer
JDK8新增接口形式
public interface A{
default void test1(){}
private void test2(){}
static void test3(){}
}
1、默认方法(实例方法):使用default修饰,默认会被加上public修饰,只能使用接口的实现类对象调用
2、私有方法:必须用private修饰
3、类方法(静态方法):使用static修饰,默认会被加上public修饰,只能用接口名来调用
匿名内部类
一种特殊的内部类,不需要为这个类声明名字
new 类或接口(参数值...) {
类体(一般是方法重写);
};
new Animal() {
@Override
public void cry(){
}
};
匿名内部类本质就是一个子类,并会立即创建出一个子类对象。
匿名内部类通常作为一个参数传输给方法。
例如
public static void go(Swimming s){ // go函数传入一个Swimming类
s.swim(); // 调用swim方法
}
go(new Swimming(){ // 匿名类Swimming作为参数传输给方法
@Override;
public void swim(){
System.out.Println("狗游泳飞快");
}
});
枚举类
修饰符 enum 枚举类名{
名称1, 名称2;
其他成员
}
public enum A {
X, Y, Z; // 相当于
public void go(){
}
}
编译后得到以下
public final class A extends java.lang.Enum<A> {
public static final A X = new A();
public static final A Y = new A();
public static final A Z = new A();
public static A[] values();
public static A valueOf(java.lang.String);
}
可以得出枚举类的特点
枚举类的第一行只能罗列一些名称,这些名称都是常量,并且每个常量记住的都是枚举类的一个对象
枚举类的构造器都是私有的,因此枚举类对外不能创建对象
枚举类都是最终类,不可以被继承
枚举类中,从第二行开始,可以定义类的其他各种成员
抽象枚举
public enum A {
X(){
@Override
public void go(){...}//因为go是抽象方法,所以要重写
}, Y("张三"){
@Override
public void go(){...}
}; // 枚举了两个实例
private String name;
A(){} // 构造器默认私有的
A(String name){ // 有参构造器
this.name = name;
}
public abstract void go(); // 抽象方法,此时A是抽象类,不能直接构建对象,所以第一行不能罗列
}
泛型
泛型类
public class Array<E>{
...
}
Array<String> arr = new Array<>(); // 使用方法
public class 类名<E, T, ...>{
...
}
Array<String, Animal, ...> arr = new Array<>(); // 使用方法
public class 类名<E extends Animal>{
...
}
Array<Cat> arr = new Array<>(); // 使用方法
泛型接口
public interface A<E>{...}
泛型方法
public static <T> void test(T t){...}
举个例子
import java.util.ArrayList;
public class Main{
public static void main(String[] args){
ArrayList<Car> cars = new ArrayList<>();
go(cars);
}
// ? 通配符,在使用泛型的时候可以代表一切类型,E T K V是在定义的时候使用
// ? extends Car 可以表示Car以及Car的子类
// ? super Car 可以表示Car以及Car的父类
public static void go(ArrayList<? extends Car> cars){}
// public static <T extends Car> void go(ArrayList<T> cars){}
}
泛型擦除
泛型是工作在编译阶段的,一旦程序编译成class文件,class文件中就不存在泛型了,这就是泛型擦除。
泛型不支持基本数据类型,只能支持对象类型(引用数据类型)
ArrayList<int> list = new ArrayList<>(); // 报错
ArrayList<Integer> list = new ArrayList<>(); // 正确
ArrayList<double> list = new ArrayList<>(); // 报错
ArrayList<Double> list = new ArrayList<>(); // 正确
常用API
String
定义方法
String a = "123";
char[] chars = {'1', '2', '3'};
String b = new String(chars);
byte[] bytes = {97, 98, 99};
String c = new String(bytes); // 输出变量c,是"abc"
常用方法
String s = "abc";
s.length(); // 返回s的长度
s.charAt(i); // 返回下标i的元素
s.toCharArray(); // 返回一个char数组
s.equals("abc"); // 比较两个字符串
s.equalsIgnoreCase("ABC"); // 忽略大小写
s.substring(int startIndex, int endIndex); // 返回子串,左闭右开
s.substring(int startIndex); // 返回子串,从startIndex到末尾
s.replace("bc", "**"); // 将bc字符串替换成**
s.contains("123"); // 判断字符串中是否存在某个字符串
s.startsWith("ab"); // 判断字符串是否以某个字符串为开始
s.split() // 将字符串以某个字符串来分割,返回String数组
注意事项
String类型是不可变的,只要是以"..."给出的字符串对象,存储在常量池中,而且内容相同时只会存储一份
String s1 = "";
String s2 = "";
System.out.println(s1 == s2); // true
但是如果是用new方式创建的字符串对象,每new一次都会产生新的对象放在堆内存中
char[] chars = {'a', 'b', 'c'};
String s1 = new String(chars);
String s2 = new String(chars);
System.out.println(s1 == s2); // false
案例1
String s2 = new String("abc"); // 创建两个对象,双引号abc创建一个对象放在常量池中,new之后在堆内存中也创建一个对象
String s1 = "abc"; // 创建0个对象,因为常量池已经有abc了,所以不用创建了
System.out.println(s1 == s2); // false
案例2
String s1 = "abc"; // 存放在常量池
String s2 = "ab";
String s3 = s2 + "c"; // 运算,存放在堆内存
System.out.println(s1 == s2); // false
案例3
String s1 = "abc";
String s2 = "a" + "b" + "c"; // 在编译时会直接转化成"abc",以提高执行性能
System.out.println(s1 == s2); // true
ArrayList
ArrayList list = new ArrayList(); // 可以存放任意类型的数据
ArrayList<String> list = new ArrayList<String>(); // 指定只能存放String类型数据
list.add("123"); // 在末尾添加
list.add(1, "456"); // 在指定位置添加
list.get(1); // 获取指定位置的值
list.size(); // 返回集合中元素的个数
list.remove(1); // 删除指定位置的元素,返回被删除的元素值
list.remove("123"); // 删除指定的元素,第一次遇到的,删除成功返回true,否则false
list.set(1, "123"); // 修改指定位置的元素值,并返回修改前的元素值
Object类
Object类是所有类的父类
public String toString() 返回对象的字符串表示形式
public boolean equals(Object o) 判断两个对象是否相等,主要是为了子类重写
protected object clone()
Objects类
Objects类是一个工具类
String s1 = "123";
String s2 = "123";
s1.equals(s2); // true
Objects.equals(s1, s2); // true
String s1 = null;
String s2 = "123";
s1.equals(s2); // 报错了
Objects.equals(s1, s2); // false
Objects.equals(a, b) 先做非空判断,再比较两个对象
Objects.isNull(obj) 判断是否为null
Objects.nonNull(obj) 与上一个相反
包装类
包装类就是把基本类型的数据包装成类
基本数据类型 | 对应的包装类(引用数据类型) |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
char | Character |
float | Float |
double | Double |
boolean | Boolean |
Integer a = Integer.valueOf(12);
Integer b = 12; // 自动装箱,将int类型转成Integer类型
int c = b; // 自动拆箱,将Integer转成int
为什么要用包装类呢?
因为泛型和集合不支持基本数据类型,只能使用引用数据类型
// ArrayList<int> list = new ArrayList<>(); // 不能这么用
ArrayList<Integer> list = new ArrayList<>();
toString()
Integer a = 23;
String s = Integer.toString(a); // "23"
// String s = a.toString(); // 同上
// String s = a + ""; // 同上
System.out.println(s + 1); // 231
parseInt() parseDouble()
String s = "123";
String t = "99.1";
int a = Integer.parseInt(s); // 不建议用
int a = Integer.valueOf(s); // 建议用
double b = Double.parseDouble(t); // 不建议用
double b = Double.valueOf(s); // 建议用
System.out.println(a);
System.out.println(b);
StringBuilder
StringBuilder代表可变字符串对象,相当于容器,它里面装的字符串是可以改变的,
操作字符串效率高,例如拼接修改;如果字符串较少,或者不需要操作,以及定义字符串变量,建议用String
StringBuilder s = new StringBuilder("123"); // 可传可不传参数
s.append(456); // 增
s.append("789");
s.append(true);
s.charAt(0); // 查
s.setCharAt(0, 'd'); // 改
// 支持链式编程
// s.append(456).append("789").append(true);
System.out.println(s); // 123456789true
s.reverse(); // 反转字符串
s.length(); // 字符串长度
s.toString(); // 将StringBuilder转成String类型
StringBuffer
与StringBuilder的用法一模一样,但是StringBuilder是线程不安全的,StringBuffer是线程安全的。
StringJoiner
JDK8才有的,跟StringBuilder一样,是用来操作字符串的,也可以看成一个容器,创建之后里面的内容是可变的
// 构造器
StringJoiner s = new StringJoiner(", "); // 传入一个参数作为间隔符
StringJoiner s = new StringJoiner(", ", "[", "]"); // 传入三个参数,后两个分别是开始符号和结束符号
s.add("java"); // add添加数据,并返回数据本身
s.add("java").add("java"); // 链式操作
System.out.println(s); // "java, java, java" "[java, java, java]"
Math
方法名 | 说明 |
---|---|
public static int abs(int a) | 绝对值 |
public static double ceil(double a) | 向上取整 |
public static double floor(double a) | 向下取整 |
public static int round(float a) | 四舍五入到整数 |
public static int max(int a, int b) | 求最大值 |
public static double pow(double a, double b) | 求a的b次方 |
public static double random() | 返回[0.0, 1.0)内的随机值 |
System
System.exit(0); 人为中断程序
System.currentTimeMillis(); 返回long类型的数,表示从1970-1-1 0:0:0开始走到此刻的毫秒值,应用例子如下
long time1 = System.currentTimeMillis();
// 程序运行过程
long time2 = System.currentTimeMillis();
System.out.println(time2 - time1); // 计算程序运行时间,单位毫秒
Runtime
代表程序所在的运行环境,Runtime是单例类,对外只提供一个对象。
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException, InterruptedException {
Runtime r = Runtime.getRuntime(); // 返回与当前Java应用程序关联的运行时对象
// r.exit(0); // 终止当前运行的虚拟机
System.out.println(r.availableProcessors()); // 虚拟机能获取的处理器数
System.out.println(r.totalMemory() / 1024.0 / 1024.0 + "MB"); // Java虚拟机内存总量,单位字节,1024字节=1K
System.out.println(r.freeMemory() / 1024.0 / 1024.0 + "MB"); // Java虚拟机中可用内存量
Process p = r.exec("D:\\Microsoft VS Code\\Code.exe");// 启动某个程序,并返回代表该程序的对象
Thread.sleep(5000); // 让程序在这里暂停5秒
p.destroy(); // 关闭程序
}
}
BigDecimal
解决小数运算失真的问题
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
double a = 0.1;
double b = 0.2;
System.out.println(a + b);
// 1、把小数转换成字符串然后再转成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);
// a1.add(b1); // 加法
// a1.subtract(b1); // 减法
// a1.multiply(b1); // 乘法
// a1.divide(b1, 2, RoundingMode.HALF_UP); // 除法,保留两位小数,并四舍五入
double a2 = a1.doubleValue(); // 将BigDecimal类型转成double类型
}
}
时间API
JDK8之前的(不建议用)
Date
Date d = new Date(); // 创建一个Date对象,代表系统当前时间
System.out.println(d); // Mon Aug 28 15:23:44 CST 2023
long time = d.getTime(); // 获取时间毫秒值
System.out.println(time); // 1693207424931
// 把时间毫秒值转换成日期对象,2s之后是多少
time += 2000;
Date d2 = new Date(time);
System.out.println(d2); // Mon Aug 28 15:23:46 CST 2023
// 直接用setTime来修改
Date d3 = new Date();
d3.setTime(time);
System.out.println(d3); // Mon Aug 28 15:23:46 CST 2023
SimpleDateFormat
Date d = new Date(); // 创建一个Date对象,代表系统当前时间
System.out.println(d); // Mon Aug 28 15:21:54 CST 2023
long time = d.getTime(); // 获取时间毫秒值
System.out.println(time); // 1693207314195
// 格式化日期对象,和时间毫秒值
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss EEE a");
/*
y 年
M 月
d 日
H 小时
m 分
s 秒
EEE 星期几
a 上午/下午
*/
String rs = sdf.format(d);
String rs2 = sdf.format(time);
System.out.println(rs); // 2023-08-28 15:21:54 周一 下午
System.out.println(rs2); // 2023-08-28 15:21:54 周一 下午
String dateStr = "2022-12-12 12:12:12";
// 创建简单日期格式化对象,指定的时间格式必须与被解析的时间格式一模一样,否则会有bug
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf2.parse(dateStr)); // Mon Dec 12 12:12:12 CST 2022
一个应用案例
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Main {
public static void main(String[] args) throws ParseException {
// 案例:秒杀活动,给定开始结束时间,判断某个时间是否在开始结束范围内
String start = "2023年11月11日 0:0:0"; // 开始时间
String end = "2023年11月11日 0:10:0"; // 结束时间
String xj = "2023年11月11日 0:01:18"; // 时间1
String xp = "2023年11月11日 0:10:57"; // 时间2
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
// 把字符串转换成日期对象
Date startDt = sdf.parse(start);
Date endDt = sdf.parse(end);
Date xjDt = sdf.parse(xj);
Date xpDt = sdf.parse(xp);
// 把日期对象转换成时间毫秒值
long startTime = startDt.getTime();
long endTime = startDt.getTime();
long xjTime = startDt.getTime();
long xpTime = startDt.getTime();
// 比较大小
if (xjTime >= startTime && xjTime <= endTime) {
System.out.println("小贾秒杀成功");
} else {
System.out.println("小贾秒杀失败");
}
if (xpTime >= startTime && xpTime <= endTime) {
System.out.println("小皮秒杀成功");
} else {
System.out.println("小皮秒杀失败");
}
}
}
Calendar
Calendar now = Calendar.getInstance(); // 系统此刻时间对应的日历对象
int year = now.get(Calendar.YEAR); // 年
int days = now.get(Calendar.DAY_OF_YEAR); // 一年中的第几天
Date d = now.getTime(); // 日历中记录的日期对象
long time = now.getTimeInMillis(); // 时间毫秒值
now.set(Calendar.MONTH, 9); // 修改月份为10月
now.add(Calendar.DAY_OF_YEAR, 100); // 将日历对象中的dayofyear值加上100
JDK8之后的(建议用)
代替Date
- Instant:时间戳 / 时间线
代替SimpleDateFormat
- DateTimeFormatter:用于时间的格式化和解析
代替Calendar
- LocalDate:年、月、日
- LocalTime:时、分、秒
- LocalDateTime:年、月、日,时、分、秒
- ZoneId:时区
- ZoneDateTime:带时区的时间
其他补充
- Period:时间间隔(年、月、日)
- Duration:时间间隔(时、分、秒、纳秒)
LocalDate
import java.time.LocalDate;
public class Main {
public static void main(String[] args) {
// 获取本地日期对象
LocalDate ld = LocalDate.now();
System.out.println(ld);
// 获取日期对象中的信息 调用get
int year = ld.getYear();
int month = ld.getMonthValue();
int day = ld.getDayOfMonth();
int dayOfYear = ld.getDayOfYear();
int dayOfWeek = ld.getDayOfWeek().getValue();
// 修改某个信息 调用with (原来日期对象并没有修改,返回了一个新的对象)
LocalDate ld2 = ld.withYear(2099); // 修改年份
LocalDate ld3 = ld.withMonth(12); // 修改月份
System.out.println(ld); // 2023-08-29
System.out.println(ld2); // 2099-08-29
System.out.println(ld3); // 2023-12-29
// 把某个信息加多少 调用plus
LocalDate ld4 = ld.plusYears(2);
LocalDate ld5 = ld.plusMonths(2);
// 把某个信息减多少 调用minus
LocalDate ld6 = ld.minusYears(2);
LocalDate ld7 = ld.minusMonths(2);
// 获取指定日期的LocalDate对象 调用of
LocalDate ld8 = LocalDate.of(2019, 2, 2);
LocalDate ld9 = LocalDate.of(2019, 2, 2);
// 判断两个日期对象是否相等,在前还是在后,equals isBefore isAfter
System.out.println(ld8.equals(ld9)); // true
}
}
LocalTime
与LocalDate的用法几乎完全一致,只不过是获取时、分、秒、纳秒信息
LocalDateTime
包含了LocalDate和LocalTime的用法
不过LocalDateTime可以转换为LocalDate和LocalTime
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
public class Main {
public static void main(String[] args) {
LocalDateTime ldt = LocalDateTime.now();
LocalDate ld = ldt.toLocalDate();
LocalTime lt = ldt.toLocalTime();
LocalDateTime ldt1 = LocalDateTime.of(ld, lt);
System.out.println(ldt); // 2023-08-29T17:12:19.007914500
System.out.println(ld); // 2023-08-29
System.out.println(lt); // 17:12:19.007914500
System.out.println(ldt1); // 2023-08-29T17:12:19.007914500
}
}
总结
方法名 | 示例 |
---|---|
public static Xxxx now(): 获取系统当前时间对应的对象 | LocalDate ld = LocalDate.now(); LocalDate lt = LocalTime.now(); LocalDateTime ldt = LocalDateTime.now(); |
public static Xxxx of(): 获取指定时间的对象 | LocalDate ld = LocalDate.of(2023, 12, 12); LocalDate lt = LocalTime.of(9, 8, 59); LocalDateTime ldt = LocalDateTime.of(2023, 12, 12, 9, 8, 59); |
方法名 | 说明 |
---|---|
public int getYear() | 获取年 |
public Month getMonth() | 获取月份(January February...) |
public int getMonthValue() public int getMonth().getValue() |
获取月份(1-12) |
public int getDayOfMonth() | 获取日 |
public int getDayOfYear() | 获取当前是一年中的第几天 |
public DayOfWeek getDayOfWeek() | 获取星期几(Monday Tuesday...) |
ZoneId
代表时区Id
import java.time.Clock;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Set;
public class Main {
public static void main(String[] args) {
ZoneId zoneId = ZoneId.systemDefault(); // 获取系统的默认时区
System.out.println(zoneId.getId()); // Asia/Shanghai
Set<String> zoneIdSet = ZoneId.getAvailableZoneIds(); // 获取Java 支持的全部时区Id
System.out.println(zoneIdSet); // [Asia/Aden, America/Cuiaba, Etc/GMT+9, Etc/GMT+8, ...
ZoneId zoneId1 = ZoneId.of("America/New_York"); // 把某个时区Id封装成ZoneId对象
ZonedDateTime now = ZonedDateTime.now(zoneId1); // 获取某个时区的ZonedDateTime对象
System.out.println(now); // 2023-08-29T06:48:44.296055-04:00[America/New_York]
ZonedDateTime now1 = ZonedDateTime.now(Clock.systemUTC()); // 世界标准世界,比中国要慢8小时
System.out.println(now1); // 2023-08-29T10:48:44.298056Z
ZonedDateTime now2 = ZonedDateTime.now(); // 无参的时候返回默认时区的时间
System.out.println(now2); // 2023-08-29T18:48:44.298056+08:00[Asia/Shanghai]
}
}
Instant
通过获取Instant的对象可以拿到此刻的时间,该时间由两部分组成:从1970-01-01 00:00:00开始走到此刻的总秒数+不够1秒的纳秒数(1秒=10^9纳秒)
作用:记录代码运行时间,或者用于记录某个用户操作某个事件的时间点
传统的Date类,只能精确到毫秒,并且是可变对象
新增的Instant类,可以精确到纳秒,是不可变对象,推荐用Instant代替Date
Instant now = Instant.now(); // 获取Instant对象
now.getEpochSecond(); // 获取从1970-01-01 00:00:00到现在的总秒数
now.getNano(); // 获取不够1秒的纳秒数
DateTimeFormmater
用于时间的格式化和解析
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String[] args) {
// 创建一个日期时间格式化器对象
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 对时间进行格式化
LocalDateTime now = LocalDateTime.now();
String rs = formatter.format(now);
System.out.println(rs); // 2023-08-29 21:55:03
// 格式化时间还有一种方案
String rs2 = now.format(formatter);
System.out.println(rs2); // 2023-08-29 21:55:03
// 解析时间:一般使用LocalDateTime提供的解析方法来解析
String dataStr = "2029-01-01 00:00:00";
LocalDateTime ldt = LocalDateTime.parse(dataStr, formatter);
System.out.println(ldt); // 2029-01-01T00:00
}
}
Period
计算两个日期间隔的年数、月数、天数
import java.time.LocalDate;
import java.time.Period;
public class Main {
public static void main(String[] args) {
LocalDate start = LocalDate.of(2019, 2, 2);
LocalDate end = LocalDate.of(2020, 1, 1);
// 创建Period对象,封装两个日期对象
Period period = Period.between(start, end);
// 通过Period对象来获取两个日期对象相差的信息
System.out.println(period.getYears()); // 间隔年份
System.out.println(period.getMonths()); // 间隔月份
System.out.println(period.getDays()); // 间隔天数
}
}
Duration
计算两个时间对象相差的天数、小时数、分数、秒数、纳秒数
import java.time.Duration;
import java.time.LocalDateTime;
public class Main {
public static void main(String[] args) {
LocalDateTime start = LocalDateTime.of(2020, 2, 2, 11, 10, 10);
LocalDateTime end = LocalDateTime.of(2020, 2, 2, 11, 11, 10);
// 创建Duration对象,封装两个日期对象
Duration duration = Duration.between(start, end);
// 获取两个时间对象间隔的信息
System.out.println(duration.toDays()); // 间隔天
System.out.println(duration.toHours()); // 间隔小时
System.out.println(duration.toMinutes()); // 间隔分钟
System.out.println(duration.toSeconds()); // 间隔秒
System.out.println(duration.toMillis()); // 间隔毫秒
System.out.println(duration.toNanos()); // 间隔纳秒
}
}
Arrays类
介绍几个Arrays的API,toString、copyOfRange、copyOf、setAll、sort
import java.util.Arrays;
import java.util.function.IntToDoubleFunction;
public class Main {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
System.out.println(arr); // 输出地址[I@4eec7777
// toString 打印数组
System.out.println(Arrays.toString(arr)); // 返回数组内容 [1, 2, 3, 4, 5]
// copyOfRange 左闭右开
int[] arr2 = Arrays.copyOfRange(arr, 1, 3);
System.out.println(Arrays.toString(arr2)); // [2, 3]
// copyOf 拷贝到一个新数组,第二个参数是新数组长度
int[] arr3 = Arrays.copyOf(arr, 10);
// setAll 把数组中所有值修改
double[] prices = {60.0, 100.0, 70.1};
Arrays.setAll(prices, new IntToDoubleFunction() {
@Override
public double applyAsDouble(int value) {
return prices[value] * 0.8;
}
});
System.out.println(Arrays.toString(prices)); // [48.0, 80.0, 56.08]
// sort 排序 默认升序
Arrays.sort(prices);
System.out.println(Arrays.toString(prices)); // [48.0, 56.08, 80.0]
}
}
如何将对象进行排序呢?
方法1:让对象实现Comparable接口,然后重写compareTo方法,自己制定比较规则。(自定义排序规则Comparable)
方法2:使用下面这个sort方法,创建Comparator比较器接口的匿名内部类对象,自己制定比较规则。(自定义比较器Comparator)
public static
举例
import java.util.Arrays;
import java.util.Comparator;
public class Main {
public static void main(String[] args) {
Student[] students = new Student[4];
students[0] = new Student("小明", 2);
students[1] = new Student("小亮", 7);
students[2] = new Student("小红", 3);
students[3] = new Student("小刚", 1);
Arrays.sort(students); // 方法1
Arrays.sort(students, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
}); // 方法2
System.out.println(Arrays.toString(students)); // [小刚, 小明, 小红, 小亮]
}
}
class Student implements Comparable<Student> {
String name;
int age;
Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Student o) {
// 约定1:左边对象 大于 右边对象, 返回任意正整数
// 约定2:左边对象 小于 右边对象, 返回任意负整数
// 约定3:左边对象 等于 右边对象, 返回0
// if (this.age > o.age) {
// return 1;
// } else if (this.age < o.age) {
// return -1;
// }
// return 0;
return this.age - o.age; // 升序
// return o.age - this.age; // 降序
}
@Override
public String toString() {
return this.name;
}
}
JDK8新特性
Lambda表达式
Lambda表达式是JDK8新增的一种语法形式,作用:用于简化匿名内部类的代码写法
只能简化函数式接口的匿名内部类。
什么是函数式接口?有且仅有一个抽象方法的接口
public class Main {
public static void main(String[] args) {
Swimming s1 = new Swimming() {
@Override
public void swim() {
System.out.println("匿名内部类,学生游泳");
}
};
Swimming s2 = () -> {
System.out.println("Lambda表达式,学生游泳");
};
s1.swim();
s2.swim();
}
}
interface Swimming {
void swim();
}
像前文提到的Arrays.setAll(),也可以用Lambda表达式来改写
import java.util.Arrays;
import java.util.function.IntToDoubleFunction;
public class Main {
public static void main(String[] args) {
// setAll 把数组中所有值修改
double[] prices = {60.0, 100.0, 70.1};
// Arrays.setAll(prices, new IntToDoubleFunction() {
// @Override
// public double applyAsDouble(int value) {
// return prices[value] * 0.8;
// }
// }); // 匿名类
Arrays.setAll(prices, (int value) -> {
return prices[value] * 0.8;
}); // Lambda表达式来简化匿名内部类
System.out.println(Arrays.toString(prices)); // [48.0, 80.0, 56.08]
}
}
Arrays.sort()用Lambda改写
Arrays.sort(students, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
if (o1.age > o2.age) {
return 1;
} else if (o1.age < o2.age) {
return -1;
}
return 0;
}
}); // 匿名内部类
Arrays.sort(students, (Student o1, Student o2) -> {
if (o1.age > o2.age) {
return 1;
} else if (o1.age < o2.age) {
return -1;
}
return 0;
}); // Lambda表达式
Lambda省略规则
- 参数类型可以不写
- 如果只有一个参数,参数类型可以省略,同时()也要省略
- 如果Lambda表达式中的方法体代码只有一行代码,可以省略大括号,同时必须去掉分号,如果这行代码是return的,也必须去掉return
方法引用
静态方法的引用
类名::静态方法
如果某个Lambda表达式里只是调用一个静态方法,并且前后参数的形式一致,就可以使用静态方法引用
实例方法的引用
对象名::实例方法
如果某个Lambda表达式里只是调用一个实例方法,并且前后参数的形式一致,就可以使用实例方法引用
import java.util.Arrays;
import java.util.Comparator;
public class Main {
public static void main(String[] args) {
Student[] students = new Student[4];
students[0] = new Student("小明", 2);
students[1] = new Student("小亮", 7);
students[2] = new Student("小红", 3);
students[3] = new Student("小刚", 1);
// 原始写法,对学生类别按照年龄排序
Arrays.sort(students, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
});
// Lambda简化写法
Arrays.sort(students, (o1, o2) -> o1.age - o2.age);
// Lambda表达式调用一个静态方法
Arrays.sort(students, (o1, o2) -> CompareByData.compareByAge(o1, o2));
// 静态方法引用
Arrays.sort(students, CompareByData::compareByAge);
// Lambda表达式调用一个实例方法
CompareByData compare = new CompareByData();
Arrays.sort(students, (o1, o2) -> compare.compareByAgeDesc(o1, o2));
// 实例方法引用
Arrays.sort(students, compare::compareByAgeDesc);
System.out.println(Arrays.toString(students));
}
}
class Student {
String name;
int age;
Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return this.name;
}
}
class CompareByData {
public static int compareByAge(Student o1, Student o2){ // 静态方法,可以通过类直接调用
return o1.age - o2.age; // 升序
}
public int compareByAgeDesc(Student o1, Student o2){ // 必须实例化对象后通过对象调用
return o2.age - o2.age; // 降序
}
}
特定类型方法的引用
类型::方法
如果某个Lambda表达式里只是调用一个实例方法,并且前面参数列表中的第一个参数是作为方法的主调,后面的所有参数都是作为该实例方法的入参的,则此时就可以使用特定类型方法的引用
import java.util.Arrays;
import java.util.Comparator;
public class Main {
public static void main(String[] args) {
String[] names = { "boby","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); // 忽略大小写比较
}
});
// Lambda改写
Arrays.sort(names, (o1, o2) -> o1.compareToIgnoreCase(o2));
// 特定类型的方法引用
Arrays.sort(names, String::compareToIgnoreCase);
System.out.println(Arrays.toString(names));
}
}
构造器的引用
类名::new
如果某个Lambda表达式里只是在创建对象,并且前后参数情况一致,就可以使用构造器引用
import java.util.Arrays;
import java.util.Comparator;
public class Main {
public static void main(String[] args) {
// 创建匿名内部类对象
// CreateCar cc = new CreateCar() {
// @Override
// public Car create(String name, double price) {
// return new Car(name, price);
// }
// };
// 用Lambda表达式简化
// CreateCar cc = (name, price) -> new Car(name, price);
// 构造器引用
CreateCar cc = Car::new;
Car c = cc.create("奔驰", 49.9);
System.out.println(c);
}
}
interface CreateCar{
Car create(String name, double price);
}
class Car {
String name;
double price;
Car(String name, double price){
this.name = name;
this.price = price;
}
}
Stream流
用于操作集合或者数组的数据
获取Stream流
// 1、调用List、Set的Stream方法
List<String> names = new ArrayList<>();
// Set<String> names = new HashSet<>();
Collections.addAll(names, "张三", "李四", "王五");
Stream<String> stream = names.stream();
// 2、调用Map的Stream方法
Map<String, Double> map = new HashMap<>();
map.put("张三", 1.0);
map.put("李四", 2.0);
Set<String> keys = map.keySet();
Stream<String> ks = keys.stream(); // map的key的stream流
Collection<Double> values = map.values();
Stream<Double> vs = values.stream(); // map的value的stream流
Set<Map.Entry<String, Double>> entries = map.entrySet(); // map的键值对集合
Stream<Map.Entry<String, Double>> kvs = entries.stream(); // 键值对流
kvs.filter(e -> e.getKey().startsWith("张")).forEach(e -> System.out.println(e.getKey() + ", " + e.getValue()));
// 3、获取数组的Stream
String[] names2 = {"张三", "李四", "王五"};
Stream<String> s1 = Arrays.stream(names2);
Stream<String> s2 = Stream.of(names2);
Stream流常见的中间方法
List<Double> scores = new ArrayList<>();
Collections.addAll(scores, 88.5, 100.0, 71.5, 90.5, 40.0, 30.0);
// 需求1,找出成绩大于等于60分的数据,升序
scores.stream().filter(s -> s >= 60).sorted().forEach(System.out::println);
List<Student> students = new ArrayList<>();
students.add(new Student("张三", 26, 185.0));
students.add(new Student("张三", 50, 190.0));
students.add(new Student("李四", 27, 160.0));
students.add(new Student("王五", 16, 170.0));
// 需求2,找出年龄在[23, 30]的,降序
students.stream().filter(s -> s.age >= 23 && s.age <= 30).sorted((o1, o2) -> o2.age - o1.age).forEach(System.out::println);
// 需求3,找出身高前3高的,limit(n)取前n个
students.stream().sorted((o1, o2) -> Double.compare(o2.height, o1.height)).limit(3).forEach(System.out::println);
// 需求4,找出身高倒数2名,skip(n)跳过前n个
students.stream().sorted((o1, o2) -> Double.compare(o1.height, o2.height)).limit(2).forEach(System.out::println);
students.stream().sorted((o1, o2) -> Double.compare(o2.height, o1.height)).skip(students.size() - 2).forEach(System.out::println);
// 需求5,找出身高超过175的学生的名字,要求去除重复名字, map() 将对象用name来代替,然后用distinct() 去重
students.stream().filter(s -> s.height > 175).map(s -> s.name).distinct().forEach(System.out::println);
// concat 合并两个Stream
Stream<String> s1 = Stream.of("张三", "李四");
Stream<String> s2 = Stream.of("王五", "刘能");
Stream.concat(s1, s2).forEach(System.out::println);
Stream流的终结方法
forEach()
count()
max()
min()
collect(Collectors.toList()) 返回一个List
collect(Collectors.toSet()) 返回一个Set
collect(Collectors.toMap(a -> a.name, a -> height)) 用name作键,height作值,存入Map
toArray() 存到数组中
正则表达式(没学)
用来校验数据格式是否合法
异常
运行时异常:RuntimeException及其子类(如数组越界)
编译时异常:编译阶段就会出现错误提醒
抛出异常(throws)
在方法上使用throws关键字,可以将方法中的异常抛出去给调用者处理
方法 throws 异常1, 异常2, 异常3...{
...
}
捕获异常(try...catch)
直接捕获程序出现的异常
try{
// 监视可能出现异常的代码
}catch(异常类型1 变量){
// 处理异常
}catch(异常类型2 变量){
// 处理异常
}...
自定义运行时异常
1、定义一个异常类继承RuntimeException
2、重写构造器
3、通过throw new异常类(xxx)来创建异常对象并抛出。
public class ExceptionTest {
public static void main(String[] args) {
try{
f(1);
} catch (Exception e) {
e.printStackTrace();
System.out.println("底层出现了bug");
}
}
public static void f(int a) {
if (a > 0) {
System.out.println("正确");
} else {
// 3、通过throws new异常类(xxx)来创建异常对象并抛出。
throw new MyRuntimeException("a非法");
}
}
}
// 1、定义一个异常类继承RuntimeException
public class MyRuntimeException extends RuntimeException {
// 2、重写构造器
public MyRuntimeException() {}
public MyRuntimeException(String message) {
super(message);
}
}
自定义编译时异常
1、定义一个异常类继承Exception
2、重写构造器
3、通过throw new异常类(xxx)来创建异常对象并抛出。
集合框架
Collection
常用方法
方法名 | 说明 |
---|---|
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public void clear() | 清空集合中所有的元素 |
public boolean remove(Ee) | 把给定的对象在当前集合中删除 |
public boolean contains(object obj) | 判断当前集合中是否包含给定的对象 |
public boolean isEmpty() | 判断当前集合是否为空 |
public int size() | 返回集合中元素的个数 |
public object[ ] toArray() | 把集合中的元素,存储到数组中 |
遍历方式
迭代器
Collection<String> c = new ArrayList<>();
c.add("java1");
c.add("java2");
Iterator it = c.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
增强for
既可以遍历集合,也可以遍历数组
Collection<String> c = new ArrayList<>();
c.add("java1");
c.add("java2");
for (String s: c) {
System.out.println(s);
}
Lambda表达式
forEach()方法
Collection<String> c = new ArrayList<>();
c.add("java1");
c.add("java2");
c.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
}); // 匿名内部类
// 改写成Lambda表达式
c.forEach(s -> System.out.println(s));
// 用方法引用
c.forEach(System.out::println);
List集合
List系列集合特点:有序、可重复、有索引
List的两个实现类ArrayList、LinkedList也具有上面三个特点
ArrayList
基于数组实现
LinkedList
基于双链表实现
Set集合
Set系列集合特点:无序、不重复、无索引
HashSet:无序、不重复、无索引
LinkedHashSet:有序、不重复、无索引
TreeSet:排序、不重复、无索引
HashSet
基于哈希表实现,jdk8之后:数组+链表+红黑树
LinkedHashSet
基于哈希表实现,但是每个元素都多了个双链表机制来记录前后元素的顺序
TreeSet
基于红黑树实现排序
利用TreeSet对Student对象排序的方法
Set
可变参数
是一种特殊的形参,定义在方法、构造器的形参列表里,格式是:数据类型...参数名称
特点:可以不传数据给它、可以传一个或者同时传多个,也可以传一个数组。
好处:可以用来灵活的接受数据。
注意:可变参数在方法内部就是一个数组,一个形参列表中可变参数只能有一个,可变参数必须放在形参列表的最后面。
public class Main {
public static void main(String[] args) {
test();
test(10);
test(1,2,3);
}
public static void test(int...nums) {
System.out.println(nums.length);
System.out.println(Arrays.toString(nums));
}
}
Map
常用方法
Map<String, Integer> map = new HashMap<>();
map.put(key, value)
map.size()
map.clear()
map.isEmpty()
map.get(key)
map.remove(key)
map.containsKey(key)
map.containsValue(value)
Set<String> key = map.keySet(); // 获取全部键的集合
Collection<Integer> values = map.values(); // 获取全部values值
map1.putAll(map2); // 把map2添加到map1中
HashMap
无序、不重复、无索引
Map<String, Integer> map = new HashMap<>();
map.put("手表", 100);
map.get("手表");
LinkedHashMap
有序、不重复、无索引
TreeMap
自动根据键来排序、不重复、无索引
集合嵌套
Map<String, List<String>> map = new HashMap<>();
List<String> cities1 = new ArrayList<>();
Collections.addAll(cities1, "南京", "苏州");
map.put("江苏省", cities1);
List<String> cities2 = new ArrayList<>();
Collections.addAll(cities2, "杭州", "宁波");
map.put("浙江省", cities2);
System.out.println(map);
Deque双端队列
import java.util.Deque;
import java.util.LinkedList;
public class Main {
public static void main(String[] args) {
/*Deque本质是双端队列*/
/* 头-------------尾*/
Deque<Integer> deque = new LinkedList<>();
deque.offerFirst(3); // 头增
deque.offerLast(2); // 尾增
deque.pollFirst(); // 头删,如果为空,返回null
deque.pollLast(); // 尾删,如果为空,返回null
deque.peekFirst(); // 头查,如果为空,返回null
deque.peekLast(); // 尾查,如果为空,返回null
deque.contains(1); // 判断是否存在某个元素
deque.isEmpty(); // 判断是否为空
/*如果只当做普通队列用的话,用下面的方法,就不用指定First还是Last了*/
deque.offer(1);
deque.poll();
deque.peek();
/*如果当做栈,用下面的方法*/
deque.push(1);
deque.pop();
deque.peek();
}
}
IO流
File:操作文件
IO流:读写数据
常用方法
File对象既可以代表文件、也可以代表文件夹。
File封装的对象仅仅是一个路径名,这个路径可以是存在的,也允许是不存在的。
案例1:文件搜索
需求:从D盘中搜索QQ.exe这个文件,找到后输出其位置,并启动QQ
import java.io.File;
public class FileTest1 {
public static void main(String[] args) throws Exception {
searchFile(new File("D:/"), "QQ.exe");
}
/**
* 去目录下搜索某个文件
* @param dir 目录
* @param fileName 要搜索的目录名称
*/
public static void searchFile(File dir, String fileName) throws Exception {
if (dir == null || !dir.exists() || dir.isFile()) {
return;
}
File[] files = dir.listFiles();
if (files != null && files.length > 0) {
for (File f : files) {
if (f.isFile()) {
if (f.getName().equals(fileName)) {
System.out.println("找到了: " + f.getAbsolutePath());
// 把QQ启动
Runtime runtime = Runtime.getRuntime();
runtime.exec(f.getAbsolutePath());
}
} else {
searchFile(f, fileName);
}
}
}
}
}
案例2:删除指定的非空文件夹
分析:先把目标文件夹下的内容都删掉,再删除文件夹
import java.io.File;
public class FileTest1 {
public static void main(String[] args) throws Exception {
File dir = new File("E:\\java\\javase\\data");
deleteDir(dir);
}
public static void deleteDir(File dir) {
if (dir == null || !dir.exists()) {
return;
}
if (dir.isFile()) { // 如果是文件,那就直接删除
dir.delete();
return;
}
// dir是文件夹,需要获得里面的一级文件对象
File[] files = dir.listFiles();
if (files == null) { // null表示没有访问权限
return;
}
if (files.length == 0) { // 文件夹为空直接删除再返回
dir.delete();
return;
}
// 此时dir是个有内容的文件夹,
for (File f : files) {
if (f.isFile()) {
f.delete();
} else {
deleteDir(f);
}
}
dir.delete();
}
}
字符集、编码、解码
ASCII字符集:只有英文、数字、符号等,占1个字节。
GBK字符集:汉字占2个字节,英文、数字占1个字节。
UTF-8字符集:汉字占3个字节,英文、数字占1个字节。
import java.util.Arrays;
public class FileTest1 {
public static void main(String[] args) throws Exception {
String data = "a我b";
byte[] bytes = data.getBytes(); // 编码,默认用utf-8
System.out.println(Arrays.toString(bytes)); // [97, -26, -120, -111, 98]
byte[] bytes1 = data.getBytes("GBK"); // 用GBK编码
System.out.println(Arrays.toString(bytes1)); // [97, -50, -46, 98]
String s1 = new String(bytes); // 解码,默认用utf-8
System.out.println(s1);
String s2 = new String(bytes1, "GBK"); // 用GBK解码
System.out.println(s2);
}
}
IO流
字节输入流 InputStream(读字节数据的)
字节输出流 OutoutStream(写字节数据出去的)
字符输入流 Reader(读字符数据的)
字符输出流 Writer(写字符数据出去的)
字节流适合复制文件,不适合读写文本文件
字符流适合读写文本文件内容
IO流-字节流
FileInputStream FileOutputStream
案例:复制文件
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class FileTest1 {
public static void main(String[] args) throws Exception {
InputStream is = new FileInputStream("E:\\java\\javase\\data\\图片1.png");
OutputStream os = new FileOutputStream("E:\\java\\javase\\data\\图片2.png");
byte[] buffer = new byte[1024]; // 1KB
int len; // 记录每次读取了多少个字节
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
os.close();
is.close();
System.out.println("复制完成");
}
}
字节流非常适合做一切文件的复制操作,因为任何文件的底层都是字节,字节流做复制,是一字不漏的转移完全部字节,只要复制后的文件格式一致就没问题
释放资源的方式
try-catch-finally
try {
...
} catch (IOException e) {
e.printStackTrace();
} finally {
...
}
// 复制文件案例的try-catch-finally
import java.io.*;
public class FileTest1 {
public static void main(String[] args) {
InputStream is = null;
OutputStream os = null;
try {
is = new FileInputStream("E:\\java\\javase\\data\\图片1.png");
os = new FileOutputStream("E:\\java\\javase\\data\\图片2.png");
byte[] buffer = new byte[1024]; // 1KB
int len; // 记录每次读取了多少个字节
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
System.out.println("复制完成");
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (os != null) os.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
if (is != null) is.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
finally代码区的特点:无论try中的程序是正常执行了,还是出现了异常,最后都一定会执行finally区,除非JVM终止。
作用:一般用于在程序执行完成后进行资源的释放操作(专业级做法)。
try-with-resource
JDK7提供的更简单的资源释放方案
try(定义资源1;定义资源2;...) {
可能出现的异常;
} catch(异常类名 变量名) {
异常处理的代码;
}
import java.io.*;
public class FileTest1 {
public static void main(String[] args) {
try (
InputStream is = new FileInputStream("E:\\java\\javase\\data\\图片1.png");
OutputStream os = new FileOutputStream("E:\\java\\javase\\data\\图片2.png");
// 注意这里只能放资源
// 资源一般指的是最终实现了AutoCloseable接口
) {
byte[] buffer = new byte[1024]; // 1KB
int len; // 记录每次读取了多少个字节
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
System.out.println("复制完成");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
IO流-字符流
FileReader FileWriter
import java.io.*;
public class FileTest1 {
public static void main(String[] args) {
try (
Reader fr = new FileReader("E:\\java\\javase\\data\\a.txt");
) {
char[] buffer = new char[3]; // 每次读3个字符
int len;
while ((len = fr.read(buffer)) != -1) {
System.out.println(new String(buffer, 0, len));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
import java.io.*;
public class FileTest1 {
public static void main(String[] args) throws Exception {
try (
Writer fw = new FileWriter("E:\\java\\javase\\data\\b.txt");
// 第二个参数为true表示在后面追加写入数据
) {
fw.write('a');
fw.write("\n");
fw.write("b");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
IO流-缓冲流
缓冲流自带了8KB缓冲池,提高读写数据的性能
字节缓冲流
InputStream is = new FileInputStream("file.txt");
InputStream bis = new BufferedInputStream(is);
OutputStream os = new FileOutputStream("file.txt");
OutputStream bos = new BufferedOutputStream(os);
字符缓冲流
Reader fr = new FileReader("file.txt");
BufferedReader br = new BufferedReader(fr);
br.readLine(); // 字符缓冲流新增的,读取一行数据,没有数据就返回null
Writer fw = new FileWriter("file.txt");
BufferedWriter bw = new BufferedWriter(fw);
bw.newLine();
案例:拷贝出师表到另一个文件,恢复顺序
分析: 定义一个缓存字符输入流管道与源文件接通。
定义一个List集合存储读取的每行数据。
定义一个循环按照行读取数据,存入到List集合中去。
对List集合中的每行数据按照首字符编号升序排序。
定义一个缓存字符输出管道与目标文件接通。
遍历List集合中的每个元素,用缓冲输出管道写出并换行。
3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施行,必能裨补阙漏,有所广益。
8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏,臣不胜受恩感激。
4.将军向宠,性行淑均,晓畅军事,试用于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。
2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。
1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。
9.今当远离,临表涕零,不知所言。
6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。
7.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐托付不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。
5.亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。
import java.io.*;
import java.util.*;
public class FileTest1 {
public static void main(String[] args) {
try (
BufferedReader br = new BufferedReader(new FileReader("E:\\java\\javase\\data\\出师表.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\java\\javase\\data\\出师表2.txt"));
) {
List<String> data = new ArrayList<>();
String line;
while ((line = br.readLine()) != null) {
data.add(line);
}
// 将每行前面的序号分割出来然后排序
data.sort(Comparator.comparingInt(o -> Integer.parseInt(o.split("\\.")[0])));
System.out.println(data);
for (String ln : data) {
bw.write(ln);
bw.newLine();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
IO流-转换流
字符输入转换流
InputStreamReader
解决不同编码时,字符流读取文本内容乱码的问题
解决思路:先获取文件的原始字节流,再将其按真实的字符集编码转成字符输入流,这样字符输入流中的字符就不乱码了
import java.io.*;
public class FileTest1 {
public static void main(String[] args) {
try (
// 原始方法,由于gbk.txt是GBK编码的,所以会出现乱码
// BufferedReader br = new BufferedReader(new FileReader("src/gbk.txt"));
// 用字符输入转换流解决乱码问题
InputStream is = new FileInputStream("src/gbk.txt"); // 先获取原始字节流
Reader isr = new InputStreamReader(is, "GBK"); // 将其按真实的字符集编码转成字符输入转换流
BufferedReader br = new BufferedReader(isr); // 然后将字符输入转换流转成字符缓冲流
) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
字符输出转换流
OutputStreamWriter
import java.io.*;
public class FileTest1 {
public static void main(String[] args) {
try (
OutputStream os = new FileOutputStream("gbk.txt");
Writer osw = new OutputStreamWriter(os, "GBK");
BufferedWriter bw = new BufferedWriter(osw);
) {
bw.write("我是中国人");
} catch (Exception e) {
e.printStackTrace();
}
}
}
IO流-打印流
import java.io.*;
public class FileTest1 {
public static void main(String[] args) {
try (
PrintStream ps = new PrintStream("itheima.txt");
) {
ps.println(1);
ps.println("我爱你");
ps.println(1.2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
PrintStream和PrintWriter差不多
应用:输出重定向,主要用System.setOut(ps)
import java.io.*;
public class FileTest1 {
public static void main(String[] args) {
System.out.println("1");
System.out.println("2");
try (
PrintStream ps = new PrintStream("itheima.txt");
) {
System.setOut(ps); // 后面的输出就输出到文件中了
System.out.println("3");
System.out.println("4");
} catch (Exception e) {
e.printStackTrace();
}
}
}
IO流-数据流
DataInputStream DataOutputStream
DataOutputStream允许把数据和其类型一并写出去。
DataInputStream用于读取数据输出流写出去的数据
import java.io.*;
public class FileTest1 {
public static void main(String[] args) {
try (
DataOutputStream dos = new DataOutputStream(new FileOutputStream("itheima.txt"));
DataInputStream dis = new DataInputStream(new FileInputStream("itheima.txt"));
) {
dos.writeInt(97);
dos.writeDouble(1.0);
System.out.println(dis.readInt()); // 先写的int,就得先读int
System.out.println(dis.readDouble());
} catch (Exception e) {
e.printStackTrace();
}
}
}
IO流-序列化流
ObjectOutputStream(对象字节输出流),可以把Java对象进行序列化:把Java对象存入到文件中去。
ObjectInputStream(对象字节输入流),可以把Java对象进行反序列化:把存储在文件中的Java对象读入到内存中来。
public class User implements Serializable {
// 注意:对象如果要参与序列化,必须实现序列化接口(java.io.Serializable)
private String loginName;
private String userName;
private int age;
private transient String passWord;
// 加入transient关键字后,这个变量就不参与序列化了
}
import java.io.*;
public class Test {
public static void main(String[] args) {
try (
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.txt")); // 进行序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.txt")); // 进行反序列化
) {
User u = new User("admin", "张三", 32, "12345678");
// 序列化对象到文件中
oos.writeObject(u);
System.out.println("序列化对象成功");
User user = (User) ois.readObject(); // User{loginName='admin', userName='张三', age=32, passWord='12345678'}
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
}
}
}
IO流体系图
FileInputStream 字节输入流
FileOutputStream 字节输出流
FileReader 字符输入流
FileWriter 字符输出流
BufferedInputStream 字节缓冲输入流
字符输出转换流
OutputStreamWriter
特殊文件
Properties
是一个Map集合(键值对集合),但是我们一般不会当集合使用。
核心作用:Properties是用来代表属性文件的,通过Properties可以读写属性文件里的内容。
读取properties文件
import java.io.FileReader;
import java.util.Properties;
import java.util.Set;
public class Test {
public static void main(String[] args) throws Exception {
// 创建一个Properties对象出来
Properties properties = new Properties();
System.out.println(properties);
properties.load(new FileReader("E:\\java\\javase\\src\\users.properties"));
System.out.println(properties);
Set<String> keys = properties.stringPropertyNames(); // 获取全部键的集合
for (String key : keys) {
String value = properties.getProperty(key);
System.out.println(key + ", " + value);
}
properties.forEach((key, value) -> {
System.out.println(key + ", " + value);
});
}
}
写入properties文件
import java.io.FileWriter;
import java.util.Properties;
public class Test {
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
properties.setProperty("张无忌", "minmin");
properties.setProperty("殷素素", "cuishan");
properties.setProperty("张翠山", "susu");
properties.store(new FileWriter("E:\\java\\javase\\src\\users2.properties"), "I saved many users");
}
}
修改properties文件
import java.io.FileReader;
import java.io.FileWriter;
import java.util.Properties;
public class Test {
public static void main(String[] args) throws Exception {
// 创建一个Properties对象出来
Properties properties = new Properties();
// 读取文件
properties.load(new FileReader("E:\\java\\javase\\src\\users.properties"));
if (properties.containsKey("张无忌")) {
properties.setProperty("张无忌", "xiaozhao");
}
properties.store(new FileWriter("E:\\java\\javase\\src\\users.properties"), "success");
}
}
XML(没学)
XML(全称EXtensible Markup Language,可扩展标记语言)
日志(没学)
多线程
多线程创建
方法一:继承Thread类
1、定义子类MyThread,继承java.lang.Thread,重写run方法
2、创建MyThread对象
3、调用线程对象的start方法启动线程(启动后执行run方法)
// 线程的创建方式一: 继承Thread类
public class Main {
public static void main(String[] args) { // main方法默认是由一条主线程负责执行
Thread t = new MyThread(); // 3、创建MyThread线程类的对象代表一个线程
t.start(); // 4、启动线程,自动执行run方法
for (int i = 0; i < 5; i++) {
System.out.println("主线程main: " + i);
}
}
}
class MyThread extends Thread{ // 1、继承Thread类
@Override
public void run() { // 2、重写run方法
for (int i = 0; i < 5; i++) {
System.out.println("子线程MyThread: " + i);
}
}
}
优缺点:
- 优点:编码简单
- 缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展
注意:
- 启动线程必须用start,不能用run
- 不要把主线程放在启动子线程之前
方法二:实现Runnable接口
1、定义一个线程任务类,实现Runnable接口,重写run方法
2、创建任务对象
3、把任务对象交给Thread处理
4、start启动
public class Main {
public static void main(String[] args) {
// 3、创建任务对象
Runnable target = new MyRunnable();
// 4、把任务对象交给线程对象
Thread thread = new Thread(target);
thread.start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程:" + i);
}
}
}
class MyRunnable implements Runnable { // 1、定义一个任务类,实现Runnable接口
// 2、重写run方法
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程:" + i);
}
}
}
优缺点:
- 优点:任务类只是实现接口,还可以继承其他类、实现其他接口,扩展性更强
- 缺点:多创建一个Runnable对象(也谈不上缺点)
方法二的匿名内部类的写法
Runnable target = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程:" + i);
}
}
};
方法三:实现Callable接口
前两种线程创建方式都存在一个问题,run方法无法返回值,所以Callable解决了这个问题
1、创建任务对象
- 定义类实现Callable接口,重写call方法,封装要做的事,和要返回的数据
- 把Callable类型的对象封装成FutureTask(线程任务对象)
2、把线程任务对象交给Thread对象
3、调用Thread对象的start方法启动线程
4、线程执行完毕后,通过FutureTask对象的get方法获取线程任务执行的结果
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> call = new MyCallable(5); // 3、创建Callable对象
FutureTask<Integer> f1 = new FutureTask<>(call); // 4、把Callable对象封装成一个FutureTask对象(任务对象)
// FutureTask的作用:FuTureTask实现了Runnable对象,可以在线程执行完毕后,用get获取结果
new Thread(f1).start(); // 5、把任务对象交给Thread对象
int sum = f1.get();
System.out.println(sum);
}
}
class MyCallable implements Callable<Integer> { // 1、实现Callable接口
int n;
public MyCallable(int n) {this.n = n;}
@Override
public Integer call() { // 2、重写Call方法
int sum = 0;
for (int i = 0; i < n; i++) {
sum += i;
}
return sum;
}
}
优缺点
- 优点:线程任务类只是实现接口,可以继承类和实现接口,扩展性强;可以在线程执行完毕后获取线程执行结果
- 缺点:编码复杂一点
Thread常用方法
Thread提供的常用方法 | 说明 |
---|---|
public void run() | 线程的任务方法 |
public void start() | 启动线程 |
public String getName() | 获取当前线程名字 |
public void setName(String name) | 为线程设置名字 |
public static Thread currentThread() | 获取当前执行的线程对象 |
public static void sleep(long time) | 让当前线程睡眠多少毫秒后再执行 |
public final void join() | 让调用这个方法的线程执行完 |
Thread提供的常见构造器 | 说明 |
---|---|
public Thread(String name) | 指定名称 |
public Thread(Runnable target) | 封装Runnable对象成为线程对象 |
public Thread(Runnable target, String name) | 封装Runnable对象成为线程对象,并指定线程名称 |
线程安全
多个线程,同时访问同一个共享资源,且存在修改该资源。
线程同步
利用线程同步来解决线程安全问题,思想是让多个线程实现先后依次访问共享资源,这样就解决了安全问题。
同步代码块
作用:把访问共享资源的核心代码块给上锁,以此保证线程安全
synchronized(同步锁) {
访问共享资源的核心代码
}
原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行
同步锁注意事项:对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug
同步方法
作用:把访问共享资源的核心方法给上锁,以此保证线程安全
修饰符 synchronized 返回值类型 方法名称(形参列表) {
访问共享资源的核心代码
}
原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行
底层原理:同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。如果方法是实例方法,同步方法默认用this作为锁的对象。如果方法是静态方法,同步方法默认用类名.class作为锁的对象。
Lock锁
Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。
Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。
final Lock lk = new ReentrantLock();
lk.lock();
访问共享资源的核心代码
lk.unlock();
通常以下操作,即使核心代码有bug,也可以解锁
final Lock lk = new ReentrantLock();
try {
访问共享资源的核心代码
} catch (Exception e) {
e.printStackTrace();
} finally {
lk.lock();
}
线程通信
生产者消费者案例
import java.util.*;
public class Main {
public static void main(String[] args){
Desk desk = new Desk();
new Thread(() -> {
while (true)
desk.put();
}, "厨师1").start();
new Thread(() -> {
while (true)
desk.put();
}, "厨师2").start();
new Thread(() -> {
while (true)
desk.put();
}, "厨师3").start();
new Thread(() -> {
while (true) {
desk.get();
}
}, "吃货1").start();
new Thread(() -> {
while (true) {
desk.get();
}
}, "吃货2").start();
}
}
class Desk {
List<String> list = new ArrayList<>();
public synchronized void put() {
try {
String name = Thread.currentThread().getName();
// 判断是否有包子
if (list.size() == 0) {
list.add(name + "做的包子");
System.out.println(name + "做了个包子");
Thread.sleep(2000);
}
this.notifyAll(); // 唤醒别人
this.wait(); // 等待自己
} catch (Exception e) {
e.printStackTrace();
}
}
public synchronized void get() {
try {
String name = Thread.currentThread().getName();
if (list.size() == 1) {
System.out.println(name + "吃了" + list.get(0));
list.clear();
Thread.sleep(1000);
}
this.notifyAll();
this.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
}
线程池
创建线程池
方法一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象
方法二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize:指定线程池的核心线程的数量
maximumPoolSize:指定线程池的最大线程数量
keepAliveTime:指定临时线程的存活时间
unit:指定临时线程存活的时间单位(秒、分、时、天)
workQueue:指定线程池的任务队列
threadFactory:指定线程池的线程工厂
handler:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了该怎么处理)
临时线程什么时候创建?
新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
什么时候会开始拒绝新任务?
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。
ExecutorService核心方法
方法名称 | 说明 |
---|---|
void execute(Runnable command) | 执行任务/命令,没有返回值,一般用来执行Runnable任务 |
Future<T> submit(Callable<T> task) | 执行任务,返回未来任务对象获取线程结果,一般拿来执行Callable任务 |
void shutdown() | 等任务执行完毕后关闭线程池 |
List<Runnable> shutdownNow() | 立刻关闭,停止正在执行的任务,并返回队列中未执行的任务 |
线程池处理Runnable任务
import java.util.concurrent.*;
public class Main {
public static void main(String[] args){
// 创建3个核心线程,最大线程数是5,线程池的任务队列可以容纳4个,临时线程5-3=2
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
Runnable target = new MyRunnable();
// 前3个进入核心线程
pool.execute(target);
pool.execute(target);
pool.execute(target);
// 中间4个进入任务队列
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
// 最后2个进入临时线程
pool.execute(target);
pool.execute(target);
// 再来新任务就要拒绝了
pool.execute(target); // 抛异常
pool.shutdown(); // 等线程池的任务全部执行完,再关闭线程池
pool.shutdownNow(); // 代码执行到这里时,立即关闭线程池
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "输出666");
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
线程池处理Callable任务
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建3个核心线程,最大线程数是5,线程池的任务队列可以容纳4个,临时线程5-3=2
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
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());
}
}
class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n) { this.n = n; }
@Override
public String call() {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return Thread.currentThread().getName() + "求出了1-" + n + "的和是:" + sum;
}
}
Executors工具类实现线程池
创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它:
public static ExecutorService newFixedThreadPool(int nThreads)
创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程:
public static ExecutorService newSingleThreadExecutor()
线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了60s则会被回收掉:
public static ExecutorService newCachedThreadPool()
创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
这些方法的底层,都是通过线程池的实现类ExecutorService创建的线程池对象
并发、并行
正在运行的程序(软件)就是一个独立的进程。
线程是属于进程的,一个进程中可以同时运行很多个线程。
进程中的多个线程其实是并发和并行执行的。
并发
进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。
并行
在同一时刻,同时有多个线程在被CPU调度执行
线程生命周期
也就是线程从生到死的过程中,经历的各种状态及状态转换。
Java一共定义了6种状态,在Thread类的内部枚举类中
线程状态 | 说明 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动 |
Runnable(可运行) | 线程已经调用了start(),等待CPU调度 |
Blocked(锁阻塞) | 线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态 |
Waiting(无限等待) | 一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能唤醒 |
Timed Waiting(计时等待) | 同waiting状态,有几个方法(sleep,wait)有超时参数,调用他们将进入Timed Waiting状态 |
Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡 |
网络通信
基本通信架构
CS架构(Client客户端/Server服务端)
BS架构(Browser浏览器/Server服务端)
网络通信三要素
IP地址:设备在网络中的地址,是唯一的标识
端口:应用程序在设备中的唯一标识
协议:连接和数据在网络中传输的规则
IP地址
特殊IP地址:
127.0.0.1、localhost:代表本机IP,只会寻找当前所在的主机
InetAddress
代表IP地址,常用方法如下
名称 | 说明 |
---|---|
public static InetAddress getLocalHost() | 获取本机IP |
public static InetAddress getByName(String host) | 根据Ip地址或域名,返回一个InetAddress对象 |
public String getHostName() | 获取该IP地址对象对应的主机名 |
public String getHostAddress() | 获取该IP地址对象中的ip地址信息 |
public boolean isReachable(int timeout) | 在指定毫秒内,判断主机与该ip对应的主机是否能连通 |
import java.net.InetAddress;
public class Main {
public static void main(String[] args) throws Exception {
// 获取本机IP地址对象的主机名和地址信息
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println(ip1.getHostName());
System.out.println(ip1.getHostAddress());
// 获取指定IP或者域名的IP地址对象
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2.getHostName());
System.out.println(ip2.getHostAddress());
System.out.println(ip2.isReachable(6000));
}
}
端口号
端口标记正在计算机设备上运行的应用程序,被规定为一个16位的二进制,范围是0~65535
分类
周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占80,FTP占21)
注册端口:1024~49151,分配给用户进程或某些应用程序
动态端口:49152~65535,一般不固定分配某种进程,而是动态分配
注意:我们自己开发的程序一般使用注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错。
协议
网络上通信的设备,事先规定的连接规则,以及传输数据的规则被称为网络通信协议。
- OSI网络参考模型:全球网络互联标准
- TCP/IP网络模型:事实上的国际标准
UDP(User Datagram Protocol): 用户数据报协议;TCP(Transmission Control Protocol): 传输控制协议
UDP协议
- 特点:无连接、不可靠通信。
- 不事先建立连接,数据按照包发,一包数据包含:自己的IP、程序端口、目的地IP、程序端口和数据(限制在64KB内)等。
- 发送方不管对方是否在线,数据在中间丢失也不管,如果接收方收到数据也不返回确认,故是不可靠的。
- 通信效率高,可用于语音通话,视频直播
TCP协议
- 面向连接、可靠通信。
- TCP的最终目的:要保证在不可靠的信道上实现可靠的传输。
- TCP主要有三个步骤实现可靠传输:三次握手建立可靠连接,传输数据进行确认,四次挥手断开连接。
- 通信效率相对不高,用于网页、文件下载、支付
1、三次握手,目的是确定通信双方收发消息都是正常无问题的,可以建立可靠连接
(1)客户端 发送连接请求
(2)服务端 返回一个响应
(3)客户端 再次发出确认信息,连接建立
2、传输数据进行确认,目的是确保数据传输的可靠性。
3、四次挥手断开连接,目的是确保双方数据的收发都已经完成
(1)客户端 发出断开连接请求
(2)服务端 返回一个响应:稍等(因为服务端可能还没接受完数据呢)
(3)服务端 返回一个响应:确认断开(服务端将数据处理完后就说可以断开啦)
(4)客户端 发出正式确认断开连接
UDP通信
DatagramSocket:用于创建客户端、服务端
public DatagramSocket() | 创建客户端的Socket对象,系统会随机分配一个端口号 |
public DatagramSocket(int port) | 创建服务端的Socket对象,并指定端口号 |
public void send(DatagramPacket dp) | 发送数据包 |
public void receive(DatagramPacket p) | 使用数据包接受数据 |
DatagramPacket:创建数据包
public DatagramPacket(byte[] buf, int length, InetAddress address, int port) | 创建发出去的数据包对象 |
public DatagramPacket(byte[] buf, int length) | 创建用来接受数据的数据包 |
public int getLength() | 获取数据包,实际接收到的字节个数 |
// Server.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Server {
public static void main(String[] args) throws Exception{
System.out.println("服务端启动");
// 创建一个服务端对象,注册端口
DatagramSocket socket = new DatagramSocket(6666);
// 创建一个数据包对象,用于接受数据
byte[] buffer = new byte[1024 * 64]; // 64KB
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
// 接受客户端发来的数据
socket.receive(packet);
System.out.println("服务端接受数据");
int len = packet.getLength(); // 从字节数组中把接收到的数据打印出来,接受多少就倒出多少
String rs = new String(buffer, 0, len); // 转成String
System.out.println(rs);
System.out.println(packet.getAddress().getHostAddress());
System.out.println(packet.getPort());
}
}
// Client.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class Client {
public static void main(String[] args) throws Exception {
// 创建客户端对象
DatagramSocket socket = new DatagramSocket(7777);
// 创建数据包对象封装要发出去的数据
byte[] bytes = "我是客户端".getBytes();
DatagramPacket packet = new DatagramPacket(bytes, bytes.length,
InetAddress.getLocalHost(), 6666);
// 开始正式发送这个数据包的数据
socket.send(packet);
System.out.println("客户端数据发送完毕");
// 释放资源
socket.close();
}
}
TCP通信
- 客户端程序是通过java.net包下的Socket类来实现的
public Socket(String host, int port) | 根据指定的服务器ip,端口号请求与服务端建立连接,连接通过,就获得了客户端socket |
public OutputStream getOutputStream() | 获得字节输出流对象 |
public InputStream getInputStream() | 获得字节输入流对象 |
- 服务端是通过java.net包下的ServerSocket类来实现的
// Client.java
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws Exception {
// 创建Socket对象,并同时请求与服务器程序的连接
Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(), 8888);
// 使用socket对象调用getOutputStream()得到一个字节输出流
OutputStream os = socket.getOutputStream();
// 把低级的字节输出流包装成数据输出流
DataOutputStream dos = new DataOutputStream(os);
// 写数据出去
dos.writeUTF("客户端发的消息");
// 释放资源
dos.close();
socket.close();
}
}
// Server.java
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws Exception{
// 创建ServerSocket对象,同时为服务端注册端口
ServerSocket serverSocket = new ServerSocket(8888);
// 使用ServerSocket对象,调用accept方法,等待客户端的连接请求
Socket socket = serverSocket.accept();
// 从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 把原始的字节输入流包装成数据输入流
DataInputStream dis = new DataInputStream(is);
// 使用数据输入流读取客户端发送的消息
String rs = dis.readUTF();
System.out.println(rs);
// 也可以获取客户端的IP地址
System.out.println(socket.getRemoteSocketAddress());
dis.close();
socket.close();
}
}
若要实现多个客户端通信,必须要用多线程
// Client.java
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws Exception {
// 创建Socket对象,并同时请求与服务器程序的连接
Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(), 8888);
// 使用socket对象调用getOutputStream()得到一个字节输出流
OutputStream os = socket.getOutputStream();
// 把低级的字节输出流包装成数据输出流
DataOutputStream dos = new DataOutputStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说");
String msg = sc.nextLine();
if ("exit".equals(msg)) {
System.out.println("欢迎您使用");
dos.close();
socket.close();
break;
}
dos.writeUTF(msg);
dos.flush();
}
}
}
// Server.java
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws Exception{
// 创建ServerSocket对象,同时为服务端注册端口
ServerSocket serverSocket = new ServerSocket(8888);
while (true) {
// 使用ServerSocket对象,调用accept方法,等待客户端的连接请求
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress() + "上线了");
// 把这个客户端对应的socket通信管道,交给一个独立的 线程处理
new ServerReadThread(socket).start();
}
}
}
// ServerReadThread.java
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.Socket;
public class ServerReadThread extends Thread{
private Socket socket;
public ServerReadThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
while (true) {
try {
String msg = dis.readUTF();
System.out.println(msg);
} catch (Exception e) {
System.out.println("离线了");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
TCP通信-端口转发
Java高级
单元测试
针对最小的功能单元(方法),编写测试代码对其进行正确性测试。
Junit单元测试框架
- 将Junit框架的jar包导入到项目中(IDEA集成了Junit框架,不需要我们自己手工导入)
- 为需要测试的业务类,定义对应的测试类,并为每个业务方法,编写对应的测试方法(必须:公共、无参、无返回值)
- 测试方法上必须声明@Test注解,然后在测试方法中,编写代码调用被测试的业务方法进行调试
- 开始测试:选中测试方法,右键选择“JUnit运行”,如果测试通过则是绿色,失败是红色。
package com.itheima.d1_junit;
import org.junit.Assert;
import org.junit.Test;
public class StringUtilTest {
@Test // 测试方法
public void testPrintNumber() {
StringUtil.printNumber("admin");
StringUtil.printNumber(null);
}
@Test // 测试方法
public void testGetMaxIndex() {
int index1 = StringUtil.getMaxIndex("admin");
System.out.println(index1);
// 断言机制
Assert.assertEquals("方法内部有bug", 4, index1);
}
}
注解 | 说明 |
---|---|
@Test | 测试类中的方法必须用它修饰才能成为测试方法,才能启动执行 |
@Before | 用来修饰一个实例方法,该方法会在每一个测试方法前执行 |
@After | 用来修饰一个实例方法,该方法会在每一个测试方法后执行 |
@BeforeClass | 用来修饰一个静态方法,该方法会在所有测试方法前只执行一次 |
@AfterClass | 用来修饰一个静态方法,该方法会在所有测试方法后只执行一次 |
开始执行的方法:初始化资源
执行完之后的方法:释放资源
反射
反射就是:加载类,并允许以编程的方式解剖类中的各种成分(成员变量、方法、构造器)
步骤
1、 加载类,获取类的字节码:Class对象
Class c1 = Student.class; // 获取Class对象
System.out.println(c1.getName()); // 全类名
System.out.println(c1.getSimpleName()); // 简名 Student
System.out.println(c1.getMethods());
2、获取类的构造器
Class c1 = Student.class;
Constructor[] constructors = c1.getConstructors(); // 获取public修饰的构造器
Constructor[] constructors = c1.getDeclaredConstructors(); // 获取所有声明的构造器
Constructor constructor = c1.getConstructor(); // 获取无参构造器
Constructor constructor = c1.getConstructor(String.class, int.class); // 获取有参构造器
// 打印构造器的名字以及参数数目
System.out.println(constructor.getName() + ", " + constructor.getParameterCount());
获取类构造器的作用:初始化对象返回 newInstance()
Class c1 = Student.class;
Constructor constructor = c1.getConstructor(); // 获取无参构造器
constructor.setAccessible(true); // 禁止检查访问权限
Student student = (Student) constructor.newInstance(); // 初始化对象
3、获取类的成员变量
Class c = Student.class;
Field[] fields = c.getDeclaredFields(); // 获取声明的(private protected public)成员变量
for (Field field : fields) {
System.out.println(field.getName() + ", " + field.getType());
}
获取成员变量的作用:赋值、取值 set() get()
Class c = Student.class;
Field fName = c.getDeclaredField("name"); // 获取name成员变量
// 赋值
Student student = new Student();
fName.setAccessible(true); // 禁止检查访问权限
fName.set(student, "小名"); // 为student对象的name成员变量赋值为“小名”
System.out.println(student);
// 取值
String name = (String) fName.get(student);
System.out.println(name);
4、获取类的成员方法
Class c = Student.class;
Method[] methods = c.getDeclaredMethods();
Method run = c.getDeclaredMethod("run");
Student student = new Student();
run.invoke(student);
反射的作用
得到一个类的全部成分然后操作
破坏封装性
设计框架
注解
注解就是Java代码里的特殊标记,比如@Override、@Test等,作用是:让其他程序根据注解信息来决定怎么执行程序
自定义注解
public @interface 注解名称 {
public 属性类型 属性名() default 默认值;
}
注解的原理
- 注解本质是一个接口,Java中所有注解都是继承了Annotation接口
- @注解(...):其实就是一个实现类对象,实现了该注解以及Annotation接口
元注解
元注解指的是:修饰注解的注解
注解的解析
什么是注解的解析?
就是判断类上、方法上、成员变量上是否存在注解,并把注解里的内容给解析出来
如何解析注解?
指导思想:要解析谁上面的注解,就应该先拿到谁
比如要解析类上面的注解,则应该现获取该类的Class对象,再通过Class对象解析其上面的注解
比如要解析成员方法上的注解,则应该获取到该成员方法的Method对象,再通过Method对象解析其上面的注解
Class、Method、Field、Constructor都实现了AnnotatedElement接口,它们都拥有解析注解的能力
// MyTest.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) // 元注解,声明注解的保留周期
@Target({ElementType.TYPE, ElementType.METHOD}) // 声明注解在类、方法中使用
public @interface MyTest {
String value();
double aaa() default 100;
String[] bbb();
}
// Demo.java
@MyTest(value = "类", aaa = 99.1, bbb = {"3", "4"}) // 注解修饰类
public class Demo {
@MyTest(value = "方法", aaa = 99.4, bbb = {"1", "2"}) // 注解修饰方法
public void test1() {
}
}
// AnnotationTest.java
import org.junit.Test;
import java.lang.reflect.Method;
import java.util.Arrays;
public class AnnotationTest {
@Test
public void parseClass() {
// 1、先得到Class对象
Class c = Demo.class;
// 2、解析类上的注解
if (c.isAnnotationPresent(MyTest.class)) {
// 判断c这个对象是否存在MyTest这个注解
MyTest myTest = (MyTest) c.getDeclaredAnnotation(MyTest.class); // 解析类上的注解
System.out.println(myTest.value());
System.out.println(myTest.aaa());
System.out.println(Arrays.toString(myTest.bbb()));
}
}
@Test
public void parseMethod() throws Exception {
// 1、先得到Class对象
Class c = Demo.class;
Method m = c.getDeclaredMethod("test1");
// 2、解析类上的注解
if (m.isAnnotationPresent(MyTest.class)) {
// 判断c这个对象是否存在MyTest这个注解
MyTest myTest = (MyTest) m.getDeclaredAnnotation(MyTest.class); // 解析方法上的注解
System.out.println(myTest.value());
System.out.println(myTest.aaa());
System.out.println(Arrays.toString(myTest.bbb()));
}
}
}