运算符

位运算符

运算符 运算 范例
<< 左移,在一定范围内,每向左移一位,相当于 *2 3 << 2 = 3 x 2 x 2 = 12
>> 右移,在一定范围内,每向右移一位,相当于 /2 3 >> 1 = 3 / 2 = 1
>>> 无符号右移 3 >>> 1 = 3 /2 = 1
& 与运算 6 & 3 = 2
| 或运算 6 | 3 = 7
^ 异或运算 6 ^ 3 = 5
~ 取反运算 ~6 = -7
1
2
3
4
5
6
7
<< 空位补0,被移除的最高位丢弃,空缺位补0
<< 被移位的二进制最高位是0,右移后,空缺位补0,最高位是1,空缺位补1
>>> 被移位二进制最高位无论是0还是1,空缺位都补0
& 二进制位进行与运算,只有1&1时的结果是1,其他都是0
| 二进制位进行或运算,只有0|0时的结果是0,其他都是1
^ 相同二进制位进行^运算,结果是0;1^1=0,0^0=0,不用的二进制位^运算的结果是1;1^0=1,0^1=1
~ 正数取反,各二进制码按补码各位取反;负数取反,各二进制码按补码各位取反

面向对象

  • 面向过程(POP)和面向对象(OOP)

    二者都是一种思想,面向对象是相对于面向过程而言的,面向过程,强调的是功能行为,以函数为最小单位,考虑怎么做。面向对象,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。

  • 面向对象的三大特征

    • 封装
    • 继承
    • 多态

面向对象的核心概念:类和对象

类是对一类事物的描述,是抽象的,概念上的定义

对象是实际存在的该类事物的每个个体,因而也称为实例。


对象创建和使用:内存解析

  • ==堆==( Heap),此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。

  • 通常所说的栈( Stack),是指虚拟机==栈==。虚拟机栈用于存储局部变量等。局部变量表存放了编译期可知长度的各种基本数据类型( boolean、 byte、char 、 short 、 int 、 float 、 long 、double ) 、对象引用( reference类型,它不等同于对象本身,是对象在堆内存的首地址)。方法执行完,自动释放。

  • 方法区( Method Area),用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。


方法的重载

重载的概念:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数、参数类型或顺序不同即可

重载的特点:与返回值类型无关,只看参数列表,且参数列表必须不同。调用时根据方法参数列表的不同来区别。


可变个数的形参的方法

可变个数的形参必须声明在末尾,一个方法中只能声明一个可变形参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MethodArgsTest {

public static void main(String[] args) {
MethodArgsTest.show();
MethodArgsTest.show("A","B");
}

// 可变形参的使用
public static void show(String ... strs){
for (String str : strs) {
System.out.println(str);
}
}

// 不可共存
// public static void show(String[] strs){}
}

方法参数的值传递机制

Java中的参数如何传入方法呢?

Java里方法的参数传递方式只有一种,值传递。即将实际参数的值得副本(复制品)传入方法内,而参数本身不受影响。

  • 形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参
  • 形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参

权限修饰符

封装性的体现需要权限修饰符来配合。private,缺省(default),protected,public

修饰符 类内部 同一个包 不同包子类 同一个工程
private
缺省
protected
public

this关键字

在Java中,this关键字比较难理解,它的作用和词义很接近。

  • 它在方法内部使用,即用这个方法所属对象的引用
  • 它在构造器内部使用,表示该构造器正在初始化的变量

this表示==当前对象==,可以调用类的属性、方法和构造器


supper关键字

supper可以理解为:父类的

supper可以用来调用父类的属性,构造器和方法

static关键字

当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,==某些特定的数据在内存空间里只有一份==。

静态变量(类变量):我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过的。

  • 静态变量随着类的加载而加载。可以通过”类.静态变量”的方式进行使用

  • 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。

静态变量和实例变量的内存解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Chinese{
String name;
String age;
static String nation;
}

class StaticTest{

public static void main(String[] args) {
Chinese.nation = "中国";
Chinese c1 = new Chinese();
c1.name = "姚明";
c1.age = "40";
c1.nation = "CHN";
Chinese c2 = new Chinese();
c1.name = "马龙";
c1.age = "30";
c1.nation = "CHINA";
}
}

image-20220727160316274

单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 饿汉式
class Blank{

// 1.私有化构造器
private Blank(){}

// 2.内部创建类的对象
// 4.要求此对象也必须是静态的
private static Blank instance = new Blank();

// 3.提供公共的静态方法,返回类的对象
public static Blank getInstance() {
return instance;
}
}

// 懒汉式
class Blank1{

// 1.私有化构造器
private Blank1(){}

// 2.声明当前类对象,没有初始化
// 4.要求此对象也必须是静态的
private static Blank1 instance = null;

// 3.提供公共的静态方法,返回类的对象
public static Blank1 getInstance() {
if (instance == null)
return new Blank1();
return instance;
}
}

区分饿汉式和懒汉式

  • 饿汉式

    坏处:对象加载时间过长

    好处:线程安全

  • 懒汉式

    好处:延迟对象的创建

    坏处:目前写法线程不安全

代码块

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person{
//属性
private String name;
private String age;
// 构造器
public Person(){}
// 方法
void show(){}
// 代码块
{

}
}

代码块的作用:

  • 用来初始化类、对象
  • 只能使用static来修饰。因此分为静态代码块和非静态代码块
    • 静态代码块随着类的加载而执行,只执行一次
    • 非静态代码块随着对象的创建而执行,每创建一次对象执行一次

final关键字

final表示最终的,可以用来修饰类,方法,变量

final修饰类,不能被继承

final修饰方法,不可重写

final修饰变量,不可修改,此时的”变量”称为常量,可以考虑的赋值方式显示赋值,代码块赋值,构造器赋值。除此之外,final还可以修饰局部变量

static fianl 修饰属性:全局常量

抽象类和抽象方法

随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计的非常重写,以至于它没有具体的实例,这样的类叫做抽象类。

==abstract==表示“抽象的”,可以用来修饰类和方法。

abstract修饰类

  • abstract修饰的类不能实例化
  • 抽象类中一定有构造器
  • 开发中,都会提供抽象类的子类,让子类实例化,完成相关操作

abstract修饰方法

  • 抽象方法只有声明,没有方法体。
  • 包含抽象方法的类一定是一个抽象类。反之,抽象类中可以没有抽象方法
  • 若子类重写了父类中的所有抽象方法后,子类才可以实例化。若没有重写父类(包括间接父类)中所有的抽象方法,则子类也是一个抽象类。

abstract使用的注意点:

abstract不能用来修饰:属性、构造器等结构

abstract不能用来修饰私有方法、静态方法、final的方法、final的类

模板方法的设计模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class TemplateTest {

public static void main(String[] args) {
SubTemplate subTemplate = new SubTemplate();
subTemplate.spendTime();
}
}

abstract class Template{

public void spendTime(){
long start = System.currentTimeMillis();
code(); // 钩子,类似于回调方法
long end = System.currentTimeMillis();
System.out.println("所需要的时间为:"+(end-start));
}

public abstract void code();
}

class SubTemplate extends Template{

@Override
public void code() {
for (int i = 0; i < 1000; i++) {
boolean isFlag = true;
for (int j = 2;j<Math.sqrt(i);j++){
if (i % j == 0){
isFlag = false;
break;
}
}
if (isFlag){
System.out.println(i);
}
}
}
}

例子:

编写工资系统,实现不同类型员工(多态)的按月发放工资。如果当月某个Employee对象的生日,则该员工工资增加100

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class MyDate {
private int year;
private int month;
private int day;

public MyDate(){};

public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}

public int getYear() {
return year;
}

public void setYear(int year) {
this.year = year;
}

public int getMonth() {
return month;
}

public void setMonth(int month) {
this.month = month;
}

public int getDay() {
return day;
}

public void setDay(int day) {
this.day = day;
}

@Override
public String toString() {
return year + "年" + month + "月" + "日";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public abstract class Employee {
private String name; //年
private int number; //月
private MyDate myDate; //日

public abstract double earnings();

public Employee(String name, int number, MyDate myDate) {
this.name = name;
this.number = number;
this.myDate = myDate;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getNumber() {
return number;
}

public void setNumber(int number) {
this.number = number;
}

public MyDate getMyDate() {
return myDate;
}

public void setMyDate(MyDate myDate) {
this.myDate = myDate;
}

@Override
public String toString() {
return "name='" + name + ", number=" + number +", myDate=" + myDate;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class SalariedEmployee extends Employee{

private double monthlySalary; // 月工资

public SalariedEmployee(String name, int number, MyDate birthday){
super(name,number,birthday);
}

public SalariedEmployee(String name, int number, MyDate birthday,double monthlySalary){
super(name,number,birthday);
this.monthlySalary = monthlySalary;
}

public double getMonthlySalary() {
return monthlySalary;
}

public void setMonthlySalary(double monthlySalary) {
this.monthlySalary = monthlySalary;
}

@Override
public double earnings() {
return monthlySalary;
}

@Override
public String toString() {
return "SalariedEmployee["+super.toString()+"]";
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class HourlyEmployee extends Employee{

private int wage;// 没小时的工资
private int hour;// 月工作的小时数

public HourlyEmployee(String name, int number, MyDate myDate) {
super(name, number, myDate);
}

public HourlyEmployee(String name, int number, MyDate myDate,int wage,int hour) {
super(name, number, myDate);
this.hour = hour;
this.wage = wage;
}

@Override
public double earnings() {
return wage * hour;
}

@Override
public String toString() {
return "HourlyEmployee["+super.toString()+"]";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class PayrollSystem {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入当前月份");
int month = sc.nextInt();

Employee[] emp = new Employee[2];
emp[0] = new SalariedEmployee("马森",1002,new MyDate(1992,2,28),10000);//月工资
emp[1] = new HourlyEmployee("cjz",1007,new MyDate(2001,9,26),60,240);// 按小时计费

for (Employee employee : emp) {
double salay = employee.earnings();
System.out.println(employee);
if ((month == employee.getMyDate().getMonth())){
System.out.println("生日快乐,工资+100");
salay += 100;
}
System.out.println("月工资为:"+ salay);
}
}
}

interface关键字

接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个“是不是”的关系,而接口实现则是“能不能”的关系。

image-20220308225454660

接口的使用

  • 接口使用interface来定义

  • Java中,接口和类是并列的两个结构

  • 定义接口中的成员

    • JDK7之前:只能定义全局常量和抽象方法

      全局常量:public static final的,书写时可以省略不写

      抽象方法:public abstract的,

    • 还可以定义静态方法、默认方法

  • javak开发中,接口通过让类实现(implements)方式来使用

    • 如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化
    • 如果实现类没有覆盖接口中的所有抽象方法,则此实现类仍然为一个抽象类

接口的应用:静态代理模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* 接口的应用:代理模式
*/
public class NetWorkTest {
public static void main(String[] args) {
Server server = new Server();
ProxyServer proxyServer = new ProxyServer(server);
proxyServer.browse();
}
}

// 网络
interface NetWork{
void browse();
}

// 被代理类
class Server implements NetWork{
@Override
public void browse() {
System.out.println("真实的服务器网络");
}
}
// 代理类
class ProxyServer implements NetWork{

private NetWork work;

public ProxyServer(NetWork work){
this.work = work;
}

public void check(){
System.out.println("联网之前的检查工作");
}

@Override
public void browse() {
check();
work.browse();
}
}

接口中还可以定义静态方法和默认方法

1
2
3
4
5
6
7
8
9
10
interface CompareA{
// 静态方法
public static void method1(){
System.out.println("11");
}
// 默认方法
public default void method2(){
System.out.println("北京");
}
}
  • 接口中定义的静态方法,只能由接口调用

  • 通过实现类的对象,可以调用接口中的默认方法;如果重写了接口的默认方法,调用的是实现类的默认方法

  • 如果子类或实现类 继承了父类和接口中同名同参数的方法,那么字类没有重写此方法的请求下,默认调用的是父类的同名同参数的方法。—类优先原则

  • 在子类(或实现类)中调用父类或或接口的方法

    1
    2
    3
    4
    // 调用父类的
    super.method();
    // 调用接口的
    接口.super.method();

内部类

  • Java允许将一个类A放到类B中,则类A就是内部类,类B就是外部类

  • 内部类的分类:成员内部类(静态,非静态)、局部内部类(方法内,代码块内,构造器内)

    • 成员内部类

      一方面,作为外部类的成员

      • 调用外部类的结构,非静态调用外部类方法通过“对象.this.方法()”调用
      • 可以被static修饰
      • 可以被4种不同的权限修饰符修饰

      另一方面,做为一个类

      • 类内可以定义属性构造器等
      • 可以被final修饰,表示此类不能被继承
      • 可以被abstract修饰,不能被实例化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class InnerClassTest {

public static void main(String[] args) {
// 调用静态成员内部类
Person.Bird bird = new Person.Bird();
bird.Fay();
// 调用非静态成员内部类
Person p = new Person();
Person.Dog dog = p.new Dog();
dog.show();
}
}

class Person{
String name;
int age;

public void method1(){}

class Dog{
String name;

public void show(){
Person.this.method1();
}
}

static class Bird{
String name;

public void Fay(){
// 不可以调用非静态结构
// Person.method1();
}
}
}

异常

异常:在Java语言中,将程序执行中发生的不正常情况称为“异常”。(开发过程中的语法错误和逻辑错误不是异常)

Java程序在执行过程中所发生的异常时间分为两类:

  • Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误,资源耗尽等严重情况。StackOverflowError栈溢出,OutOfMemoryError堆溢出。
  • Exception:其他因编程错误或偶然的外在因素导致一般性问题,可以使用针对性的代码进行处理。

对于这些错误,一般有两种解决方法:一是遇到错误就终止程序的运行。另一种方法是由程序在编写程序时,就考虑到错误的检测、错误信息的提升,以及错误的处理。

捕获错误最理想是在编译期间,但有的错误只有在允许时才会发生。

异常分为编译时异常和允许时异常

  • 编译时异常
  • 运行时异常
    • NullPointerException 空指针异常
    • ArrayIndexOutOfBoundsException 数组角标越界
    • ClassCaseException 类型转换异常
    • NumberFormatException 格式化异常
    • InputMismatchException 输入不匹配异常

自定义异常类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 1.继承与现有的异常结构
* 2.提供全局常量:serialVersionUID
*/
public class MyException extends RuntimeException{

static final long serialVersionUID = 3748921734890127847L;

public MyException() {
}

public MyException(String message) {
super(message);
}
}

多线程

基本概念:程序,进程,线程

  • 程序

    是为了特定任务、用某种语言编写的一组指令的集合。是指一段静态的代码,静态对象

  • 进程

    是程序的一次执行过程,或是正在允许的一个程序。是一个动态的过程:有它自身的产生和消亡的过程。—生命周期

    进程做为资源分配的单位,系统在允许时会没个进程分配不同的内存区域

  • 线程

    进程可进一步细化为线程,是一个程序内部的一条执行路径。

    若一个进行同一时间并行多个线程,就是支持多线程的。

使用多线程的优点

  • 提高应用程序的响应。对应图形化界面更有意义,可增强用户体验
  • 提高计算机系统CPU的利用率
  • 改善程序结构。将即长又复杂的进程分为多个线程,独立运行,利于理解和修改

何时需要多线程

  • 程序需要同时执行两个或多个任务
  • 程序需要实现一些需要等待的任务,如用户输入,文件读写操作,网络操作、等等。
  • 需要一些后台运行的程序时

并行和并发

单核CPU下,线程还是串行执行的。操作系统有一个组件叫做任务调度器,将CPU的时间片分给不同的线程使用。

  • 一般将这种线程轮流使用CPU的作法称为并发。concurrent。
  • 多核CPU下,每个核都可以调度运行线程,这时候线程是可以并行的。
image-20250304194132754

线程的创建和使用

方式一:继承Thread类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 多线程的创建
* 方式一:继承于Thread类
* 1.创建一个了类继承于Thread类
* 2.重写Thread类的run()方法 -->将此线程的操作声明在run方法中
* 3.创建Thread类的子类的对象
* 4.通过此类调用start()方法
*/

class MyThread extends Thread{
// 线程的执行体
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0){
System.out.println(i);
}
}
}
}

public class ThreadTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
// 线程开始执行
myThread.start();
}
}

方式二:实现Runnable接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* 创建多线程
* 方式二:实现Runnable接口的类
* 1.创建一个实现了Runnable接口的类
* 2.实现接口中的run方法
* 2.创建实现类的对象
* 3.将此对象做为参数传递到Thread类的构造器中,创建Thread类对象
* 4.通过Thread类调用此对象
*/

class RunnableTest implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0){
System.out.println(i);
}
}
}
}

public class MyRunnable{
public static void main(String[] args) {
RunnableTest runnableTest = new RunnableTest();
Thread t1 = new Thread(runnableTest);
Thread t2 = new Thread(runnableTest);
t1.start();
t2.start();
}
}

比较创建线程的两种方式

开发中优先选择:实现Runnable接口

  1. 实现的方式没有类的单继承的局限性
  2. 实现的方式更适合来处理多个线程有共享数据的情况
  3. Thread实现了Runnable接口

JDK5.0新增创建线程方式

方式三:实现Callable接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class NewThreadTest implements Callable<Integer>{

@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
if (i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}

public class CallableTest {

public static void main(String[] args) {
NewThreadTest newThreadTest = new NewThreadTest();
FutureTask<Integer> futureTask = new FutureTask<Integer>(newThreadTest);
new Thread(futureTask).start();
try {
// get()方法的返回值即为FutureTask构造器参数Callable实现类重写的call()方法的返回值
Integer o = futureTask.get();
System.out.println("总和为:"+o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}

与Runnable接口相比

  1. 相比run()方法可以有返回值
  2. 方法可以抛出异常
  3. 支持泛型返回值
  4. 需要借助FutureTask类,比如获取返回结果

方式四:使用线程池创建线程

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

思路:提前好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建和销毁,实现重复利用。

好处:

  1. 提高响应速度(减少创建新线程的时间)
  2. 减低资源消耗(重复利用线程池中的线程,不需要每次都创建)
  3. 便于线程管理
    • corePoolSize:核心池大小
    • maximumPoolSize:最大线程数
    • keepAliveTime:线程没有任务时最多保持多长时间后会停止

Thread类的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 启动线程,并执行run()方法
void start();
// 线程在被调度时执行的操作
run();
// 返回线程的名称
String getName();
// 设置线程的名称
void setName();
// 返回当前线程。在Thread子类中就是this,通常用于主线程和Runable实现类。
static Thread currentThread();
// 释放释放当前CPU的执行权,礼让线程
yield();
// 在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b执行完成(main中调用a线程的join方法,主线程等待a线程执行完成)
join();
// 等待线程运行结束,最多等待n毫秒
join(long n);
// 让当前线程睡眠(阻塞)指定的millitime毫秒数,
sleep(long millitime);
// 判断当前线程是否存活
isAlive();
// 获取线程状态
getStatus();
// 打断线程
interrup();
// 判断是否被打断,不会清除打印标记
isInterrupted();
// 判断是否被打断,会清除打印标记
interrupted();
// 线程是否存活
isAlive();

线程的调度

1
2
3
4
// 返回线程的优先级
getPriority();
// 设置线程的优先级
setPriority(int newPriority);

说明

高优先级的线程要比低优先级线程的执行权,

image-20250304213650317

线程的生命周期

JDK中用Thread.State类定义了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象.JAVA语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:

  • 新建:当一个线程类或其子类的对象被声明并创建时,新生的线程对象处于新建状态

  • 就绪:处于新建状态的线程被启动()后,将进入线程队列等待cpu时间片,此时它已具备了运行的条件,只是没分配到cpu资源

  • 运行:当就绪的线程被调度并获得cpu资源时,便进入运行状态,运行()方法定义了线程的操作和功能

  • 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出cpu并临时中止自己的执行,进入阻塞状态

  • 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

image-20220311231206859

线程的同步

方式一:同步代码块

1
2
3
synchronized(同步监视器){
// 需要同步的代码
}

说明

  • 操作共享数据的代码,极为需要被同步的代码
  • 共享数据:多个线程共同操作的变量
  • 同步监视器,俗称:锁。任何一个类的对象都可以充当锁

要求多个线程必须共用同一把锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private Integer ticket = 100;
Object obj = new Object();

@Override
public void run() {
while(true){
synchronized (obj){
if (ticket > 1){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket --;
System.out.println(Thread.currentThread().getName()+"抢到票,票还剩下"+ticket);
}else{
break;
}
}
}
}
}

class MyRunnable{
public static void main(String[] args) {
RunnableTest runnableTest = new RunnableTest();
Thread t1 = new Thread(runnableTest);
Thread t2 = new Thread(runnableTest);
t1.setName("窗口1");
t2.setName("窗口2");
t1.start();
t2.start();
}
}

方式二:同步方法

1
2
3
private synchronized void show(){
// 方法体
}

同步方法仍然设计到同步监视器,只是不需要我们显式声明

非静态的同步方法,同步监视器是:this

静态的同步方法,同步监视器是:当前类本身

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class RunnableTest implements Runnable{

private Integer ticket = 100;

@Override
public void run() {
while(true) {
show();
if (ticket.equals(0)){
break;
}
}
}

private synchronized void show(){
if (ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到票,票还剩下"+ticket);
ticket--;
}else {
return;
}
}
}

class MyRunnable{
public static void main(String[] args) {
RunnableTest runnableTest = new RunnableTest();
Thread t1 = new Thread(runnableTest);
Thread t2 = new Thread(runnableTest);
t1.setName("窗口1");
t2.setName("窗口2");
t1.start();
t2.start();
}
}

线程安全的懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 懒汉式
class Blank1{

// 1.私有化构造器
private Blank1(){}

// 2.声明当前类对象,没有初始化
// 4.要求此对象也必须是静态的
private static Blank1 instance = null;

// 同步方法
// 3.提供公共的静态方法,返回类的对象
public static synchronized Blank1 getInstance() {
if (instance == null)
return new Blank1();
return instance;
}

// 同步代码块
public static synchronized Blank1 getInstance() {
if (instance == null){
synchronized(Blank1.class){
if (instance == null)
return new Blank1();
return instance;
}
}
}
}

死锁

  1. 不同的线程分别在占用对方需要的同步资源不放弃,都在等对方放弃需要的同步资源,就形成了线程的死锁
  2. 出现死锁后不会出现一次,不会出现提升,只是所有的线程都处于堵塞状态,无法继续

解决方法

  1. 专门的算法、原则
  2. 尽量减少同步资源的定义
  3. 尽量避免嵌套同步

Lock锁

解决线程安全问题的方式三:Lock锁 —JDK5.0新增

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class RunnableTest implements Runnable {

private Integer ticket = 100;

// 1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();

@Override
public void run() {
while (true) {
try {
// 2.调用lock
lock.lock();
if (ticket > 1) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(Thread.currentThread().getName() + "抢到票,票还剩下" + ticket);
} else {
break;
}
} finally {
// 3.调用解锁
lock.unlock();
}
}
}
}

class MyRunnable {
public static void main(String[] args) {
RunnableTest runnableTest = new RunnableTest();
Thread t1 = new Thread(runnableTest);
Thread t2 = new Thread(runnableTest);
t1.setName("窗口1");
t2.setName("窗口2");
t1.start();
t2.start();
}
}

synchronized和Lock锁的异同?

相同:都可以解决多线程的安全问题

不同:synchronized机制在执行玩相应的同步代码以后,自动释放同步监视器,Lock锁需要手动开启,手动关闭

线程的通信

1
2
3
4
5
6
// 一旦执行此方法,当前线程就会进入堵塞状态,并释放同步监视器
wait();
// 一旦执行此方法,就会唤醒被wait的线程。如果有多个线程被wait,就会唤醒优先级高的那个。
notify();
// 唤醒所有被wait的线程
notifyAll();
  • wait()、notify()、notifyAll()必须使用在同步代码块或同步方法中
  • 三个方法调用者必须是同步代码块或同步方法中的同步监视器

sleep()方法和wait()方法的区别?

相同: 一旦执行此方法,都可以使得当前线程进入堵塞状态

不同:

  1. 两个方法声明的位置不同:sleep()在Thread类中,wait()声明在Object类中
  2. 调用的要求不同:sleep()可以在任何场景中使用,wait()必须在同步代码块或同步方法中使用
  3. 关于是否释放同步监视器:sleep()不会释放同步监视器,wait()会释放同步监视器

生产者和消费者的问题

生产者(Productor)将产品交给店员(CLerk),而消费者(Customer)从店员处产品,店员一次只能持有固定数量的产品(比如:20)。如果生产者试图生产更多的产品,店员叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来联走产品.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// 店员
class Clerk{

private Integer productCount = 0;

// 生产产品
public synchronized void producerClerk() {
if (productCount < 20){
System.out.println(Thread.currentThread().getName()+"开始生产第"+(++productCount)+"个产品");
// 生产产品后就释放同步监视器,通知消费者可以消费产品了
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

// 消费产品
public synchronized void consumerClerk() {
if (productCount > 0){
System.out.println(Thread.currentThread().getName()+"开始消费第"+(productCount--)+"个产品");
notify();
}else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

// 生产者
class Producer implements Runnable{
private Clerk clerk;

public Producer(Clerk clerk) {
this.clerk = clerk;
}

@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始生产产品....");
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.producerClerk();
}
}
}

// 消费者
class Consumer implements Runnable{
private Clerk clerk;

public Consumer(Clerk clerk) {
this.clerk = clerk;
}

@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始消费产品....");
while (true){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumerClerk();
}
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
Thread producerThread = new Thread(producer);
Thread consumerThread = new Thread(consumer);
Thread consumerThread2 = new Thread(consumer);
producerThread.setName("生产者");
consumerThread.setName("消费者1");
consumerThread2.setName("消费者2");
producerThread.start();
consumerThread.start();
consumerThread2.start();
}
}

涉及线程池的API

JDK5.0提供了线程池相关API:==ExecutorService==和==Executors==

  • ExecutorService

    真正的线程池接口。常见子类ThreadPoolExecutor

    1
    2
    3
    4
    5
    6
    // 执行任务/命令,没有返回值,一般用来执行Runnable
    void execute(Runnable command);
    // 执行任务,有返回值,一般用来执行Callable
    <T> Future<T> submit(Callable<T> task);
    // 连接池
    void shutdown();
  • Executors:工具类、线程池的工厂

    1
    2
    3
    4
    5
    6
    7
    8
    // 创建一个可根据需要创建新线程的线程池
    Executors.newCachedThreadPool();
    // 创建一个可重用固定线程数的线程池
    Executors.newFixedThreadPool(n);
    // 创建一个只有一个线程的线程池
    Executors.newSingleThreadExecutor();
    // 创建一个线程池,它可安排在给定延迟后允许命令或定期执行
    Executors.newScheduledThreadPool(n);

常用类

String类

  1. String类:代表字符串。Java程序中的所有字符串字面值(如“abc”)都作为此类的实例实现。底层使用char[] value存储。

  2. String 是一个final类,代表不可变的字符序列

  3. String 实现了Serializable接口:代表字符串是支持序列化的

    实现了Comparable接口:表示String可以比较大小

演示String的不可变性

1
2
3
4
5
6
7
8
9
10
11
@Test
public void test1(){
String s1 = "abc";//字面量的定义方式
String s2 = "abc";
System.out.println(s1 == s2);//true,判断地址值
s1 += "def";
System.out.println(s1==s2);//false
String replace = s1.replace("a", "g");
System.out.println(s1);// abcedf
System.out.println(replace); //gdcdef,常量池中新建的
}

不可变的体现:

  1. 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。

  2. 当对现有的字符串进行链接操作时,也需要重写指定内存区域赋值,不能使用原有的value进行赋值

  3. 当调用S提让的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值。


  • 通过字面量方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。
  • 字符串常量池中是不会存储相同内存的字符串

String对象的创建

1
2
3
4
5
6
7
8
String str = "hello";
// 本质上this.value = new char[0];
String str = new String();
// this.value = original.value;
String str = new String(String original);
// this.value = Arrays.copyOf(value,value.length);
String str = new String(char[] a);
String str = new String(char[] a,int startIndex,int count);

image-20220314223108059

1
2
3
4
5
6
7
8
9
10
11
@Test
public void test2(){
String s1 = "JavaSE";
String s2 = "JavaSE";
String s3 = new String("JavaSE");
String s4 = new String("JavaSE");
System.out.println(s1 == s2);// true
System.out.println(s1 == s3);// false
System.out.println(s1 == s4);// false
System.out.println(s3 == s4);// false
}

面试题:String s = new String(“abc”);方式创建对象,在内存中创建了几个对象?

两个:一个是堆空间中的new结构,一个是常量池中的数据”abc”。

Stirng 的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// 返回字符串的长度,value.length
int length();
// 返回某索引处的字符,return value[inedex]
char charAt(int index);
// 判断是否是空字符串,return value.length == 0
boolean isEmpty();
// 使用默认语言环境,将String中所有字符转换为小写
String toLowerCase();
// 使用默认语言环境,将String中所有字符转换为大写
String toUpperCase();
// 返回字符串的副本,忽略前部和尾部空白
String trim();
// 比较字符串内容是否相等
boolean equals(Object obj);
// 与equals()方法类似,忽略大小写
boolean equalslgnoreCase(String anotherString);
// 将指定字符串链接导此字符串的结尾
String concat(String str);
// 比较两个字符串的大小
int compareTo(String anotherString);
// 返回一个新的字符串,此字符串是从beginIndex截取导最后的一个子字符串
String substring(int beginIndex);
// 返回一个新的字符串,此字符串是从beginIndex开始截取到endIndex(不包含,左闭右开)的一个子字符串
String subString(int beginIndex,int endIndex);
// 判断此字符串是否是以指定后缀结束
boolean endsWith(String suffix);
// 判断此字符串是否以指定的前缀开始
boolean startsWith(String prefix);
// 判断此字符串从指定索引开始的子字符串是否以指定前缀开始
boolean startsWith(String prefix,int toffset);
// 当且仅当此字符串包含指定的char值序列时,返回true
boolean contains(CharSequence s);
// 返回指定子字符串在此字符串第一次出现的索引位置,没有返回-1
int indexOf(String str);
// 返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
int indexOf(String str,int fromIndex);
// 返回指定子字符串在此字符串中最右边出现处的索引
int lastIndexOf(String str);
// 返回指定子字符串在此字符串中最后一次出现的索引,从指定的索引开发反向搜索
int lastIndexOf(String str,int fromIndex);
// 返回一个新的字符串,它是通过newChar替换此字符串中所有的oldChar得到的
String replace(char oldChar,char newChar);
// 使用指定的字面值替换序列替换此字符串所有匹配字面值模板序列的子字符串。
String replace(CharSequence target,CharSequence replacement);
// 使用给定的replacement替换此字符串所有匹配给定的正则表达式的子字符串。
String replaceAll(String regex,String replacement);
// 使用给定的replacement替换此字符串匹配给定的正则表达式的第一个子字符串。
String replaceAll(String regex,String replacement);
// 告知此字符串是否匹配给定的正则表达式。
boolean matches(String regex);
// 根据给定的正则表达式的匹配拆分此字符串
String[] split(Stirng regex);
// 根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中
String[] split(Stirng regex,int limit);

String 和其他结构的转换

String –> 基本数据类型,调用包装类的parseXxx()方法

基本数据类型 –> Stirng,调用String.valueOf()方法。

1
2
3
4
5
6
7
@Test
public void test3(){
String str = "123";
// 转int,其他基本数据类型类似
int num = Integer.parseInt(str);
String s = String.valueOf(num);
}

Stirng –> char[],调用Stirng的toCharArray()方法

char[] – > String,调用Stirng的构造器

1
2
3
4
5
6
@Test
public void test4(){
String str = "123";
char[] value = str.toCharArray();
String s = new String(value);
}

Stirng –> byte[],调用String的getBytes();

byte[] –> String,调用String的构造器

1
2
3
4
5
6
@Test
public void test5(){
String str = "123";
byte[] bytes = str.getBytes();
String s = new String(bytes);
}

StirngBuffer和StringBuilder

String、StringBuffer和StringBuilder三者的异同

String:不可变的字符序列;底层用char[]存储

StringBuffer:可变的字符序列;线程安全的,效率偏低;底层用char[]存储

StringBuilder:可变的字符序列;线程不安全的,效率高,JDK1.5新增;底层用char[]存储

效率对比:StringBuilder > StringBuffer > String

==源码分析==

1
2
3
4
String str = new String();// char[] value = new char[];
String str = new String("abc");// char[] value = new char[]{'a','b','c'};
StringBuffer sb1 = new StringBuffer();// char value = new char[16];
StringBuffer sb1 = new StringBuffer("abc");// char value = new char["abc".length()+16];

扩容问题:如果要添加的数据底层数组盛不下了,那就扩容底层的数组。

1
2
3
4
5
6
7
8
9
10
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}

默认情况下扩容为原来容量的2倍+2,同时将原有数组中内容复制到新的数组中

==常用方法==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 添加内容,拼接字符串
StringBuffer append(Xxx);
// 删除指定位置的内容
StringBuffer delete(int start,int end);
// 位置替换为str
StringBuffer replace(int start,int end,String str);
// 在指定位置插入str
StringBuffer insert(int offset,String str);
// 把当前字符序列反转
StringBuffer reverse();
// 回指定子字符串在此字符串第一次出现的索引位置,没有返回-1
int indexOf(String str);
// 截取一个字符串,从start位置开始,到end(不包括)位置结束
String substring(int start,int end);
// 获取字符串的长度
int length();
// 返回某处索引的字符
char charAt(int n);
// 替换某处索引的字符
void setCharAt(int n,char ch);

常用类String算法题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// 获取两个字符串的最大相同子串,只有一个最大相同子串的情况
public String getMaxString(String str1,String str2){
if (str1 != null && str2!= null){
String maxString = (str1.length() >= str2.length()) ? str1 : str2;
String minString = (str1.length() < str2.length()) ? str1 : str2;

int minSize = minString.length();
// 从小的字符串开始依次减少位数进行判断;
for (int i = 0; i < minSize; i++) {
for (int x = 0,y = minSize - i; y <= minSize; x++,y++){
String subStr = minString.substring(x, y);
// 从maxString查看是否有subStr字符
if (maxString.contains(subStr)){
return subStr;
}
}
}
}
return null;
}
// 优化:获取两个字符串的最大相同子串,有多个最大相同子串的情况
public String[] getMaxString1(String str1,String str2){
if (str1 != null && str2!= null){
StringBuffer sBuffer = new StringBuffer();
String maxString = (str1.length() >= str2.length()) ? str1 : str2;
String minString = (str1.length() < str2.length()) ? str1 : str2;

int minSize = minString.length();
// 从小的字符串开始依次减少位数进行判断;
for (int i = 0; i < minSize; i++) {
for (int x = 0,y = minSize - i; y <= minSize; x++,y++){
String subStr = minString.substring(x, y);
// 从maxString查看是否有subStr字符
if (maxString.contains(subStr)){
sBuffer.append(subStr+",");
}
}
// 当集合中中存在相同子串的时候不用进行后面的判断了
if (sBuffer.length() != 0){
break;
}
}
return sBuffer.toString().replaceAll(",$","").split("\\,");
}
return null;
}
// 优化:获取两个字符串的最大相同子串,有多个最大相同子串的情况
public List<String> getMaxString2(String str1, String str2){
if (str1 != null && str2!= null){
ArrayList<String> strs = new ArrayList<String>();
String maxString = (str1.length() >= str2.length()) ? str1 : str2;
String minString = (str1.length() < str2.length()) ? str1 : str2;

int minSize = minString.length();
// 从小的字符串开始依次减少位数进行判断;
for (int i = 0; i < minSize; i++) {
for (int x = 0,y = minSize - i; y <= minSize; x++,y++){
String subStr = minString.substring(x, y);
// 从maxString查看是否有subStr字符
if (maxString.contains(subStr)){
strs.add(subStr);
}
}
// 当集合中中存在相同子串的时候不用进行后面的判断了
if (strs.size() != 0){
break;
}
}
return strs;
}
return null;
}

==注意==

1
2
3
4
5
6
7
8
9
10
@Test
public void test02(){
String str = null;
StringBuffer sb1 = new StringBuffer();
sb1.append(str);
System.out.println(sb1.length()); //4
System.out.println(sb1); //"null"
StringBuffer sb2 = new StringBuffer(str);// null指针异常
System.out.println(sb2);
}

常用类jdk1.8新日期时间类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/**
* jdk1.8之前
* Calender日历类(抽象类)的使用
*/
@Test
public void DateTest(){
// 1.实例化
// 方式一:调用其静态方法Calendar.getInstance()
// 方式二:使用其子类(GregorianCalendar)的对象
Calendar instance = Calendar.getInstance();
// 常用方法
// get()
int days = instance.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
// set()
instance.set(Calendar.DAY_OF_MONTH,10);
System.out.println(instance.get(Calendar.DAY_OF_MONTH));
// add()
instance.add(Calendar.DAY_OF_MONTH,2);
System.out.println(instance.get(Calendar.DAY_OF_MONTH));
instance.add(Calendar.DAY_OF_MONTH,-5);
System.out.println(instance.get(Calendar.DAY_OF_MONTH));
// getTime():日历类 --> 日期类Date
Date time = instance.getTime();
SimpleDateFormat simple = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(simple.format(time));
// setTime():日期类Date --> 日历类
instance.setTime(new Date());
System.out.println(instance.get(Calendar.DAY_OF_MONTH));
}

/**
* LocalDate,LocalTime,LocalDateTime的使用
*/
@Test
public void testLocalDateTime(){
// now()获取当前日期,时间,日期+时间
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDate);
System.out.println(localTime);
System.out.println(localDateTime);

// of() 设置制定年,月,日,时,分,秒
LocalDateTime of = LocalDateTime.of(2020, 2, 8, 13, 35, 6);
System.out.println(of);

// getXxx():获取相关属性
System.out.println(localDateTime.getDayOfYear()); //今天是这一年的多少天
System.out.println(localDateTime.getMonth()); //月份,几月
System.out.println(localDateTime.getYear()); //年
System.out.println(localDateTime.getMonthValue()); //月,几号
System.out.println(localDateTime.getDayOfMonth()); //日
System.out.println(localDateTime.getHour()); //小时
System.out.println(localDateTime.getMinute()); //分钟
System.out.println(localDateTime.getSecond()); //秒
System.out.println(localDateTime.getDayOfWeek()); //星期几

//withXxx():设置相关属性
LocalDateTime localDateTime1 = localDateTime.withDayOfMonth(10); //设置几号,1-31
LocalDateTime localDateTime2 = localDateTime.withMonth(10); //设置几月,1-12
LocalDateTime localDateTime3 = localDateTime.withDayOfYear(365); //设置今年的第几天,取值1-365
System.out.println(localDateTime);
System.out.println(localDateTime1);
System.out.println(localDateTime2);
System.out.println(localDateTime3);

//plusXxx()\minusXxx():加日期\减日期
LocalDateTime localDateTime4 = localDateTime.plusMonths(3);
System.out.println();
}
  • 计算世界世界的主要标准
    • UTC
    • GMT
    • CST
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 时间戳
*/
@Test
public void testInstant(){
Instant instant = Instant.now();//少8个小时
System.out.println(instant);
//添加时间偏移量
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime);
//获取1970年1月1号0时0分0秒至今的毫秒数,Date类的getTime()
long milli = instant.toEpochMilli();
System.out.println(milli);
// 根据毫秒数获取Instant对象 -->Date类的setTime()方法
Instant instant1 = Instant.ofEpochMilli(1644302483969L);
System.out.println(instant1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* DateTimeFormatter 格式化或解析日期、时间
* 类似于SimpleDateFormat
*/
@Test
public void testFormat(){
// 方式一:预定义的标准格式
DateTimeFormatter formatter1 = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
LocalDateTime localDateTime = LocalDateTime.now();
// format():日期-->字符串
String str1 = formatter1.format(localDateTime);
System.out.println(localDateTime); //2022-02-08T15:47:27.924
System.out.println(str1); //2022-02-08T15:47:27.924
// parse():字符串-->日期
TemporalAccessor parse = formatter1.parse("2022-02-08T15:46:34.603");
System.out.println(parse); //{},ISO resolved to 2022-02-08T15:46:34.603

// 方式二:本地相关的格式
// 适用于LocalDateTime: FormatStyle.SHORT / FormatStyle.LONG / FormatStyle.MEDIUM
DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
// JDK11中,使用FormatStyle.LONG会报错
// 需要加上 DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG)
// .withZone(ZoneId.systemDefault())
String str2 = formatter2.format(localDateTime);
/*
FormatStyle.SHORT --> 22-2-8 下午3:57
FormatStyle.LONG --> 2022年2月8日 下午03时58分54秒
FormatStyle.MEDIUM --> 2022-2-8 15:59:25
*/
System.out.println(str2);

// 适用于LocalDate: FormatStyle.FULL / FormatStyle.SHORT / FormatStyle.LONG / FormatStyle.MEDIUM
DateTimeFormatter formatter3 = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
String str3 = formatter3.format(LocalDate.now());
/*
FormatStyle.FULL --> 2022年2月8日 星期二
*/
System.out.println(str3);

// 方式三:自定义格式
DateTimeFormatter formatter4 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String str4 = formatter4.format(LocalDateTime.now());
System.out.println(str4); //2022-02-08 16:12:38
TemporalAccessor parse1 = formatter4.parse("2022-02-08 16:11:43");
System.out.println(parse1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 设置时区信息
*/
@Test
public void testZone(){
// 获取所有时区的信息
Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
for (String availableZoneId : availableZoneIds) {
System.out.println(availableZoneId);
}
// 获取指定时区的时间
LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of("Asia/Aden"));
System.out.println(localDateTime); //2022-02-08T11:25:05.318
// 获取带时区的日期和时间
ZonedDateTime now = ZonedDateTime.now();
// 获取指定时区的日期和时间
ZonedDateTime now1 = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
System.out.println(now); // 2022-02-08T16:25:05.321+08:00[Asia/Shanghai]
System.out.println(now1); // 2022-02-08T16:25:05.322+08:00[Asia/Shanghai]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Duration:计算两个“时间”间隔,以秒和纳秒为单位
*/
@Test
public void testDuration(){
LocalDateTime localDateTime1 = LocalDateTime.now();
LocalDateTime localDateTime2 = LocalDateTime.of(2022,2,9,20,0,0);
// between(): 静态方法,返回Duration对象,比较两个时间的间隔
Duration between = Duration.between(localDateTime1, localDateTime2);
System.out.println(between.toDays()); // 间隔多少天
System.out.println(between.toHours()); // 间隔多少个小时
System.out.println(between.toMinutes()); // 间隔多少分钟
System.out.println(between.getSeconds()); // 两个时间间隔多少秒
System.out.println(between.toMillis()); // 间隔多少毫秒
}
1
2
3
4
5
6
7
8
9
10
11
12
/**
* Period:用于计算两个“日期”间隔,以年、月、日衡量
*/
@Test
public void testPeriod(){
LocalDate localDate1 = LocalDate.now();
LocalDate localDate2 = LocalDate.of(2022,2,9);
Period period = Period.between(localDate1, localDate2);
System.out.println(period.getYears()); // 间隔多少年
System.out.println(period.getMonths()); // 间隔多少月
System.out.println(period.getDays()); // 间隔多少日
}
  • Date和LocalDateTime的转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
步骤:
1.拿到要转换的Date对象
2.将Date对象转换成为Instant对象
方法:
Date对象.toInstant()
3.将瞬时对象转换成为LocalDateTime对象
方法:
LocalDateTime.ofInstant(瞬时对象,时区);
/**
*相关代码
*/
//将Date对象转换为LocalDateTime
Date date = new Date();
Instant instant = date.toInstant();
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());


步骤:
1,拿到要转换的LocalDateTime对象
2,将LocalDateTime对象转换成为时区对象
方法:
LocalDateTime对象.atZone(时区);//得到时区对象
3.2中得到的时区对象转换成为瞬时对象
方法;
时区对象.toInstant()//得到瞬时对象
4.通过Date提供的静态方法将3中得到的瞬时对象转换为我们需要的Date对象
方法:
Date date = Date.from(瞬时对象);
/**
*相关代码
*/
//将LocalDateTime对象转换为Date对象
LocalDateTime dateTime = LocalDateTime.now();
ZonedDateTime zonedDateTime = dateTime.atZone(ZoneId.systemDefault());
Instant instant2 = zonedDateTime.toInstant();
Date date2 = Date.from(instant2);

比较器

java实现排序的方式有两种

  • 自然排序:java.lang.Comparable

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    public class Goods implements Comparable{

    private String name;
    private double price;

    // 省略有参无参,getter,setter

    @Override
    public String toString() {
    return "Goods{" +
    "name='" + name + '\'' +
    ", price=" + price +
    '}';
    }

    // 实现Comparable接口重写compareTo(obj)方法
    @Override
    public int compareTo(Object o) {
    if (o instanceof Goods){
    Goods goods = (Goods) o;
    if (this.price > goods.price)
    return 1;
    else if (this.price < goods.price)
    return -1;
    else
    return this.name.compareTo(goods.name);
    }
    throw new RuntimeException("传入的数据类型不一致");
    }
    }

    @Test
    public void testComparable(){
    Goods[] goods = new Goods[4];
    goods[0] = new Goods("笔记本",6788.67);
    goods[1] = new Goods("鼠标",83);
    goods[2] = new Goods("键盘",129.9);
    goods[3] = new Goods("鼠标2号",83);
    Arrays.sort(goods);
    System.out.println(Arrays.toString(goods));
    }
  • 定制排序:java.util.Comparator

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @Test
    public void testComparator(){
    Goods[] goods = new Goods[5];
    goods[0] = new Goods("笔记本",6788.67);
    goods[1] = new Goods("笔记本",6900);
    goods[2] = new Goods("鼠标",83);
    goods[3] = new Goods("键盘",129.9);
    goods[4] = new Goods("鼠标2号",83);
    // 先根据名称进行排序从低到高排序,然后在根据价格从高到低排序
    Arrays.sort(goods, new Comparator<Goods>() {
    @Override
    public int compare(Goods o1, Goods o2) {
    if (o1.getName().equals(o2.getName())){
    return -Double.compare(o1.getPrice(),o2.getPrice());
    }else{
    return o1.getName().compareTo(o2.getName());
    }
    }
    });
    System.out.println(Arrays.toString(goods));
    }

其他常用类的使用

System

image-20220208214358716

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void test(){
String javaVersion = System.getProperty( "java.version");
System.out.println("java的version: " + javaVersion);
String javaHome = System.getProperty("java.home");
System.out.println("java的home:" +javaHome);
String osName = System.getProperty( "os.name");
System.out.println("os的名称:"+osName);
String osVersion = System.getProperty("os.version");

System.out.println("os的版本:"+osVersion);
String userName = System.getProperty( "user.name");
System.out.println("user的name:" + userName);
String userHome = System.getProperty ( "user. home");
System.out.println( "user的home:" + userHome);
String userDir = System.getProperty ("user.dir");
System.out.println( "user的dir:" + userDir);
}

BigInteger和BigDecimal

BigInteger


BigDecimal

1. 构造器

1
2
3
4
5
6
7
8
// 创建一个具有参数所指定整数值的对象
BigDecimal(int);
// 创建一个具有参数所指定双精度值的对象
BigDecimal(double);
// 创建一个具有参数所指定长整数值的对象
BigDecimal(long);
// 创建一个具有参数所指定以字符串表示的数值的对象(推荐方式)
BigDecimal(String);
1
2
3
4
5
6
7
8
9
10
BigDecimal bg1 =new BigDecimal(0.1);
System.out.println("a values is:"+bg1);
System.out.println("=====================");
BigDecimal bg2 =new BigDecimal("0.1");
System.out.println("b values is:"+bg2);
System.out.println("=====================");
System.out.println(bg1 == bg2);//false
System.out.println(bg1.equals(bg2));//false
System.out.println("=====================");
System.out.println(BigDecimal.valueOf(0.1));
  1. 参数类型为double的构造方法的结果有一定的不可预知性。有人可能认为在Java中写入newBigDecimal(0.1)所创建的BigDecimal正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值)

  2. String 构造方法是完全可预知的:写入 newBigDecimal(“0.1”) 将创建一个 BigDecimal,它正好等于预期的 0.1。因此,比较而言, 通常建议优先使用String构造方法

  3. 如果要将一个double转称BigDecimal时,使用BigDecimal类所提供的静态方法valueOf(double)

2. 常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// BigDecimal对象中的值相加,返回BigDecimal对象
add(BigDecimal);
// BigDecimal对象中的值相减,返回BigDecimal对象
subtract(BigDecimal);
// BigDecimal对象中的值相乘,返回BigDecimal对象
multiply(BigDecimal);
// BigDecimal对象中的值相除,返回BigDecimal对象
divide(BigDecimal);
// 将BigDecimal对象中的值转换成字符串
toString();
// 将BigDecimal对象中的值转换成双精度数
doubleValue();
// 将BigDecimal对象中的值转换成单精度数
floatValue();
// 将BigDecimal对象中的值转换成长整数
longValue();
// 将BigDecimal对象中的值转换成整数
intValue();
// 比较大小,
bg1.compareTo(bg2);

举例:

1
2
3
4
5
6
7
8
9
10
@Test
public void test02(){
BigDecimal bg1 = new BigDecimal(0.1);
BigDecimal bg2 = new BigDecimal("0.1");
// i = -1,表示bg1l小于bg2
// i = 0,表示bg1ld等于bg2
// i = 1,表示bg1l大于bg2
int i = bg1.compareTo(bg2);
System.out.println(i);
}

3. 格式化

由于NumberFormat类的format()方法可以使用BigDecimal对象作为其参数,可以利用BigDecimal对超出16位有效数字的货币值,百分值,以及一般数值进行格式化控制。

以利用BigDecimal对货币和百分比格式化为例。首先,创建BigDecimal对象,进行BigDecimal的算术运算后,分别建立对货币和百分比格式化的引用,最后利用BigDecimal对象作为format()方法的参数,输出其格式化的货币值和百分比。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void test03(){
NumberFormat currency = NumberFormat.getCurrencyInstance(); //建立货币格式化引用
NumberFormat percent = NumberFormat.getPercentInstance(); //建立百分比格式化引用
percent.setMaximumFractionDigits(3); //百分比小数点最多3位

BigDecimal loanAmount = new BigDecimal("15000.48"); //贷款金额
BigDecimal interestRate = new BigDecimal("0.008"); //利率
BigDecimal interest = loanAmount.multiply(interestRate); //相乘
System.out.println("贷款金额:\t" + currency.format(loanAmount));//贷款金额: ¥15,000.48
System.out.println("利率:\t" + percent.format(interestRate));//利率: 0.8%
System.out.println("利息:\t" + currency.format(interest));//利息: ¥120.00
}

4. 注意事项

除法会出现的异常

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void test04(){
BigDecimal bg1 = new BigDecimal("3.3213");
BigDecimal bg2 = new BigDecimal("2.43214");
// 报异常:java.lang.ArithmeticException: Non-terminating decimal expansion;
// no exact representable decimal result.
// 错误写法:除不尽,需要指定保留小数位
// System.out.println(bg1.divide(bg2));
// 保留两位小数,四舍五入
System.out.println(bg1.divide(bg2,2,BigDecimal.ROUND_HALF_UP));
// 设置保留小数
// bg1.setScale(2);
}

**注意:**BigDecimal都是不可变的(immutable)的, 在进行每一次四则运算时,都会产生一个新的对象 ,所以在做加减乘除运算时要记得要保存操作后的值。

1
2
3
4
5
6
7
8
9
@Test
public void test05(){
BigDecimal bg1 = new BigDecimal("1");
BigDecimal bg2 = new BigDecimal("9");
bg1.add(bg2);
System.out.println(bg1);//1
bg1 = bg1.add(bg2);
System.out.println(bg1);//10
}

枚举类和注解

枚举

枚举类的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/**
* jdk1.5之前的方法
* 自定义枚举类
*/
class Season{

private final String seasonName;
private final String seasonDesc;

// 私有化类构造器,并给对象赋值
private Season(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}

// 提供当前枚举类的多个对象
public static final Season SPRING = new Season("春天","春暖花开");
public static final Season SUMMER = new Season("夏天","夏日炎炎");
public static final Season AUTUMN = new Season("秋天","秋高气爽");
public static final Season WINTER = new Season("冬天","冰天雪地");

// 提供获取枚举属性的方法
public String getSeasonName() {
return seasonName;
}

public String getSeasonDesc() {
return seasonDesc;
}

@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}

/**
* jdk1.5使用的方法
* 使用enum关键字定义枚举类
* 定义的枚举类默认继承于Enum
*/
enum Season1{
SPRING("春天","春暖花开"),
SUMMER("夏天","夏日炎炎"),
AUTUMN("秋天","秋高气爽"),
WINTER("冬天","冰天雪地");

private final String seasonName;
private final String seasonDesc;

// 私有化类构造器,并给对象赋值
Season1(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}

// 提供获取枚举属性的方法
public String getSeasonName() {
return seasonName;
}

public String getSeasonDesc() {
return seasonDesc;
}
}

/**
* 枚举类的使用
* 1.枚举类的理解:类的对象只有有限个,确定的。我们称为为枚举类
* 2.当需要定义一组常量时,强烈建议使用枚举类
* 3.如果枚举类中只有一个对象,则可以做为单例模式的实现方式。
*/
@Test
public void test(){
Season1 autumn = Season1.AUTUMN;
System.out.println(autumn);
}

Enum类的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void test(){
Season1 autumn = Season1.AUTUMN;
System.out.println(autumn.toString());
// value()方法:返回所有枚举的数组
Season1[] values = Season1.values();
for (Season1 value : values) {
System.out.println(value);
}
// valueOf():返回枚举类中对象名是objName的对象,没有找到会抛出异常
Season1 spring = Season1.valueOf("SPRING");
System.out.println(spring);
}

使用enum关键字定义的枚举实现接口的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
interface Info{
void show();
}

/**
* jdk1.5使用的方法
* 使用enum关键字定义枚举类
* 定义的枚举类默认继承于Enum
*/
enum Season1 implements Info{
SPRING("春天","春暖花开"){
@Override
public void show() {
System.out.println("春天在哪里");
}
},
SUMMER("夏天","夏日炎炎") {
@Override
public void show() {
System.out.println("夏天在哪里");
}
},
AUTUMN("秋天","秋高气爽") {
@Override
public void show() {
System.out.println("秋天在哪里");
}
},
WINTER("冬天","冰天雪地") {
@Override
public void show() {
System.out.println("冬天在哪里");
}
};

private final String seasonName;
private final String seasonDesc;

// 私有化类构造器,并给对象赋值
Season1(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}

// 提供获取枚举属性的方法
public String getSeasonName() {
return seasonName;
}

public String getSeasonDesc() {
return seasonDesc;
}
}

@Test
public void test(){
Season1 autumn = Season1.AUTUMN;
System.out.println(autumn.toString());
autumn.show(); //秋天在哪里
}

注解

自定义注解

  • 注解声明为@interface

  • 内部定义成员,通常使用value表示

  • 可以指定成员的默认值,使用default定义

  • 如果自定义注解没有成员,表明这是一个标识作用

1
2
3
4
5
6
7
8
public @interface MyAnnotation {
String value() default "hello";
}

@MyAnnotation(value = "hello")
public class AnnotationTest {

}

注意:自定义注解必须配上注解的信息处理流程(使用反射)才有意义

元注解:JDK中用于修饰其他(Annotation)注解的定义,一般自定义注解都需要指明@Retention和@Target

  • @Retention

    只能用于修饰一个Annotation定义,用于指定该Annotation的生命周期,@Retention包含一个RetentionPolicy类型的成员变量,使用@Retention时必须指定该value的成员变量指定值:

    • RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释
    • RetentionPolicy.CLASS:在class文件中有效(即class文件保留),当运行java程序时,JVM不会保留注释。==默认值==
    • RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行java程序时JVM会保留注释。程序可以。程序可以通过反射获取该注释。
  • @Target

    用于修饰Annotation定义,用于指定被修饰的Annotation能用于修饰那些程序元素。

  • @Documented

    用于指定被该Annotation修饰的Annotation类将被javadoc工具提取成文档。默认情况下,javadoc是不包含注解的。

    定义为Documented的注解必须设置Retention的值为RUNTIME

  • @Inherited

    被它修饰的Annotation将具有继承性。如果某个类使用了被@Inherited修饰的Annotation,则子类将自动具有该注解。

集合

数组在存储多个数据方面的特点:

  • 一旦初始化以后长度就已经确定
  • 数组一旦定义好,其元素的类型就已经确定了

数组在存储多个数据方面的弊端:

  • 一旦初始化,长度就不可修改
  • 数组中提供的方法非常有限,对于添加,插入,删除操作非常不便,同时效率不高
  • 获取数组中实际元素的个数,数组中没有现成的属性或者方法可用
  • 数组存储的特点:有序,可重复。对于无序,不可重复的需求,不能满足

集合分为两种体系

  • Collection接口

    • List:元素有序,可重复
      • ArrayList:线程不安全,执行效率高,底层使用==Object[]==存储
      • LinkedList:对于频繁的插入和删除,使用此类比效率ArrayList高;底层使用==双向链表==存储
      • Vector:线程安全的,执行效率低,底层使用==Object[]==存储
    • Set:元素无序,不可重复
      • HashSet、LinkedHashSet、TreeSet
  • Map接口

    双列数据,保存具有映射关系“key-value对”的集合。

    HashMap、LinkedHashMap、TreeMap、HashTable、Properties

Collection

常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 添加
add(Object obj);
addAll(Collection coll);
// 获取有效个数
int size();
// 清空集合
void clear();
// 是否是空集合
boolean isEmpty();
// 是否包含某个元素
boolean contains(Object obj); // 是通过元素的equals()方法判断来判断是否是同一个元素
boolean containsAll(Collection coll); // 也是调用元素的equals()方法比较的,拿两个集合的元素挨个比较
// 删除
boolean remove(Object obj);
boolean removeAll(Collection coll);
// 取两个集合的交集
boolean retainAll(Coolection coll); //把交集的结果存在当前集合中,不影响c
// 集合是否相等
boolean equals(Object obj);
// 转成对象数组
Object toArray();
// 获取对象的哈希值
hashCode();
// 遍历,返回迭代器对象用于遍历。
iterator();

iterator()的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void test(){
Collection coll = new ArrayList();
coll.add("abc");
coll.add("12345");
// 返回一个迭代器对象,每次调用iterator()返回的都是一个新的迭代器对象
Iterator iterator = coll.iterator();
// hasNext()是否有下一个值
while (iterator.hasNext()){
// 取出迭代器 的值,指针后移在取值。remove()方法可以删除元素
System.out.println(iterator.next());
}
// NoSuchElementException 指针已经到最后了,没有值可以取了
System.out.println(iterator.next());
}

ArrayList、LinkedList、Vector三者的异同?

同:都实现了List接口,存储数据的特点相同:都是存储有序,可重复数据。

不同:见上

List

==ArrayList源码分析==

jdk1.7

  • new ArrayList()对象底层创建的是一个长度为10的Object[]数组
  • 当新添加的元素超过10时,默认情况下扩容为原来的1.5倍,将原有数据添加到新的数组中。

jdk1.8

  • new ArrayList()对象底层Object[] elementDate初始化为{},并没有创建长度为10的数组
  • 第一次调用add()方法时,底层创建了长度为10的数组,并把值添加到elementDate中

常用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 在index位置插入ele元素
void add(int index,Object ele);
// 在index位置将coll中所有元素插入进来
boolean addAll(int index,Collection coll);
// 获取指定位置元素
Object getIndex(int index);
// 获取元素首次出现的位置
int indexOf(Object obj);
// 获取元素末次出现的位置
int lastIndextOf(Object obj);
// 移除指定位置的元素,并返回此元素
Object remove(Object obj);
// 设置index位置的元素为ele
Object set(int index,Object ele);
// 返回fromIndex到toIndex位置的子集合
List subList(int fromIndex,int toIndex);

向Collection接口实现类的对象添加数据obj时,要求obj所在的类必须重写equals()方法

Set

特点: 存储无序的,不可重复数据。

以hashSet为例说明:

无序性:不等于随机性。存储的数据在底层数组中并未安装数组索引的数据添加,而是根据数据的哈希值决定的。

不可重复性:保证添加的元素按照equals()判断时 ,不能返回true即可,即相同元素只有一个

我们向hashSet中添加元素a,首先调用a所在类的hashCode()方法,计算a的哈希值,此哈希值接着通过某种算法计算出在hashSet底层数组中存放的位置(即索引位置),判断数组此位置是否已经有元素:

  • 如果此位置没有其他元素,a添加成功

  • 如果此位置上有其他元素b(或以链表形式存在的多个元素),首先比较元素a和元素b的哈希值是否相同

    • 如果不相同,则元素a添加成功
    • 如果相同,进而调用元素所在类的equals()方法
      • 如果返回ture,元素a添加失败
      • 如果返回false,元素a添加成功
  • HashSet

    作为Set的接口的主要实现类;线程不安全的;可存储null值,数组+链表

    要求:

    向Set中添加的数据,其所在类一定要重写hashCode()和equals()

    重写的hashCode()和equals()尽可能保持一致性。

    • LinkedHashSet

      做为HashSet的子类,遍历其内部数据时,可以按照添加的顺序遍历,数组+双向链表实现

      对于频繁的遍历操作,LinkedHashSet效率高于HashSet()

  • TreeSet

    可以按照添加对象的指定属性,进行排序。不可以添加不同类的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void test04(){
HashSet set = new HashSet();
Person p1 = new Person(1001, "AA");
Person p2 = new Person(1002, "BB");
set.add(p1);
set.add(p2);

p1.setName("CC");
set.remove(p1);
System.out.println(set);
set.add(new Person(1001,"CC"));
System.out.println(set);
set.add(new Person(1001,"AA"));
System.out.println(set);

/*
[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]
[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}]
[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'},
Person{id=1001, name='AA'}]
*/
}

Map

双列数据,存储Kye-Value对数据

  • HashMap

    作为Map的主要实现类,线程不安全,效率高;可以存储null的key和value

    • LinkedHashMap

      遍历map时,可以按照添加的顺序实现遍历。对于频繁的遍历操作LinkedHahMap效率比HashMap高

      原因:在原有的HashMap底层结构的基础之上,添加了一对指针,指向前一个和后一个元素。

  • TreeMap

    保证按照添加的key-value对进行排序,实现排序遍历。此时考虑的是key的排序。底层使用的是红黑树

  • HashTable

    做为Map的古老实现类,线程安全的,效率低;不可以存储null的key和value

    • Properties

      常用来处理配置文件。key和value都是String类型。


HashMap的底层

jdk7之前:数组+链表

HashMap map = new HashMap():

在实例化以后,底层创建了长度是16的一维数组Entry[] table。

map.put(key1,value1)

首先,调用key1所在类的hashCode()计算key1的哈希值,此哈希值在经过某种算法以后,得到Entry数组中的存放位置。如果此位置上的数据为空,此时的key1-value1添加成功。

如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在一个或多个数据的hash值:

  • 如果key1的哈希值与已经存在的数据哈希值都不相同,此时key1-value1添加成功。
  • 如果key1的哈希值和已经存在的某一个哈希值相同了,则继续比较key1所在类的equals()方法。
    • 如果equals()方法返回false,则key1-value1添加成功
    • 如果equals()方法返回true,使用value1替换相同key的value值

在不断添加过程中,会涉及到扩容问题,当超出临界值(要存放的位置非空时),默认扩容方式为原来的2倍,并把原来的数据复制过来

jdk8:数组+链表+红黑树

  1. new HashMap():底层没有创建一个长度为16的数组

  2. jdk8底层的数组是Node[] 不是 Entry[]

  3. 首次调用put()方法时才调用长度为16的数组

  4. 当数组的某一个索引位置上的元素以链表形式存在的个数 > 8 且当前数组的长度 > 64

    此时索引的位置上的所有数据改为红黑树存储。

常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 添加
Object put(Object key,Object value);
void putAll(Map map);
// 删除,移除指定key的key-value对,并返回value
Object remove(Object key);
// 清空当前Map的所有数据
void clear();
// 获取指定key对应的value
Object get(Object key);
// 是否包含指定key
boolean containsKey(Object key);
// 是否包含指定的value
boolean contatinsValue(Object value);
// 返回map中key-value对的个数
int size();
// 判断当前map是否为空
boolean isEmpty();
// 判断当前map和参数对象obj是否相等
boolean equals(Object obj);

// 遍历
// 返回所有key构成的set集合
Set keySet();
// 返回所有value构成的Collection集合
Collection values();
// 返回所有key-value构成的set集合
Set entrySet();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Test
public void testHashMap(){
Map map = new HashMap<>();
map.put("AA",12);
map.put("BB",72);
map.put("CC",42);
map.put("DD",62);

// 遍历所有key
Set set = map.keySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
// 遍历所有value
Collection values = map.values();
for (Object value : values) {
System.out.println(value);
}
// 遍历所有key-value
Set entrySet = map.entrySet();
for (Object o : entrySet) {
// entrySet集合中的元素都是entry
Map.Entry entry = (Map.Entry) o;
System.out.println(entry.getKey()+"-"+entry.getValue());
}
}
1
2
3
4
5
6
7
8
@Test
public void TestProperties() throws IOException {
Properties properties = new Properties();
FileInputStream fis = new FileInputStream("jdbc.properties");
properties.load(fis);
String name = properties.getProperty("name");
System.out.println(name);
}

Collections工具类

Collections是一个操作Set、List和Map的工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 反转List中元素的顺序
reverse(List);
// 对List集合元素进行随机排序
shuffle(List);
// 根据元素的自然顺序对指定List集合元素升序排序
sort(List);
// 根据指定的Comparator产生的顺序对List集合元素进行排序
sort(List,Comparator);
// 将指定list集合中的i处元素和j处元素进行交换
swap(List,int,int);

// 根据元素的自然顺序,返回给定集合中的最大值
Object max(Collection);
// 根据Comparator指定的顺序,返回集合中的最大元素
Object max(Collection,Comparator);
// 获取最小值
Object min(Collection);
Object min(Collection,Comparator);
// 返回指定集合中指定元素的出现次数
int frequency(Collection,Object);
// 将src中的内容复制到dest中,dest的长度必须>=src的长度
void copy(List dest,List src);
// 使用新值替换List对象的所有旧值
boolean replaceAll(List list,Object oldVal,Object newVal);

Collection中提供了多个synchronizedXxx()方法,该方法可将指定集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题。

1
2
3
4
5
6
synchronizedCollection(Collection<T> coll);
synchronizedList(List<T> list);
synchronizedMap(Map<K,V> map);
synchronizedSet(Set<T> set);
synchronizedSortedMap(Map<K,V> map);
synchronizedSortedSet(Set<T> set);

泛型

所谓的泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法防止及参数的类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量,创建对象时)确定(即传入实际的类型参数,也称为类型变量)。

自定义泛型结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Order<T>{

String orderName;
String orderId;

// 类的内部可以使用类的泛型
T orderDesc;

public Order() {}

public Order(String orderName, String orderId, T orderDesc) {
this.orderName = orderName;
this.orderId = orderId;
this.orderDesc = orderDesc;
}

public T getOrderDesc() {
return orderDesc;
}

public void setOrderDesc(T orderDesc) {
this.orderDesc = orderDesc;
}
}

public class SubOrder extends Order<String> {}

public class SubOrder1<T> extends Order<T>{}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void test1(){
// 如果定义了泛型类,实例化没有指定类的泛型,则认为此泛型类型为Object类型
Order order = new Order();
order.setOrderDesc("ABC");
order.setOrderDesc(12);

Order<String> stringOrder = new Order<>();
stringOrder.setOrderDesc("ABC");
// stringOrder.setOrderDesc(1);

// 如果子类在继承带泛型的父类时,指明了泛型类型。则子类实例化对象时,无需再指明泛型
SubOrder subOrder = new SubOrder();
subOrder.setOrderDesc("ABC");

SubOrder1<String> stringSubOrder1 = new SubOrder1<>();
stringSubOrder1.setOrderDesc("ABC");
}

泛型注意点:

  • 泛型不同的不能相互赋值

    1
    2
    3
    4
    5
    6
    @Test
    public void test2(){
    ArrayList<String> strings = new ArrayList<>();
    ArrayList<Integer> integers = new ArrayList<>();
    // integers = strings;
    }
  • 静态方法中不能用泛型

    1
    2
    // 错误方式
    public static void show(T orderDesc){}
  • 异常类不能声明为泛型类

  • 不能使用new T[];但可以使用(T[]) new Object[];

    1
    2
    //T[] arr = new T[10];
    T[] arr = (T[])new Object[10];

泛型方法

1
2
3
4
5
6
7
8
// 泛型方法和所属的类是不是泛型没有关系,在调用指明调用的类型
public <E> List<E> copyFromArray(E[] array){
ArrayList<E> es = new ArrayList<>();
for (E e : array) {
es.add(e);
}
return es;
}

泛型方法可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化时确定的。

  • 泛型在继承方面的体现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Test
    public void test03(){
    Object obj = null;
    String str = null;
    obj = str;

    List<Integer> integers = null;
    List<String> strings = null;
    // 此时的List<String> 和 List<Object> 不是子父类关系,而是并列关系
    // objects = strings;
    /*
    假设 objeces = strings
    integers.add("AB");导致混入String数据
    */

    List<String> list1 = new ArrayList<>();
    ArrayList<String> list2 = new ArrayList<>();
    list1 = list2;
    }
  • 通配符的使用==(?)==

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public void test04(){
    List<Object> list1 = null;
    List<String> list2 = null;

    List<?> list = null;
    list = list1;
    list = list2;
    }

    // 类A和是类B的父类, List<A> 和 List<B> 是没有关系的,他们共同的父类是List<?>
    public void print(List<?> list){
    Iterator<?> iterator = list.iterator();
    while (iterator.hasNext()){
    System.out.println(iterator.next());
    }
    }

    @Test
    public void test05(){
    List<String> list2 = new ArrayList<>();
    list2.add("AA");
    list = list2;
    // 添加:对于List<?>就不能向其内部添加数据了,除了null值外
    // list.add("AB");

    // 读取:允许读取,读取的数据为Object类型
    System.out.println(list.get(0));
    }
  • 有限制条件的通配符的使用

    • (无穷小,Number) 只允许泛型为Number及Number的子类引用调用
    • (Number,无穷大) 只允许泛型为Number及Number父类的引用调用
    • 只允许泛型为实现Comparable接口的实现类的引用调用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    public void test06(){
    // ? extends,可以是Person或Person的子类,<=
    List<? extends Person> list1 = null;
    // ? super,可以是Person或Person的父类,>=
    List<? super Person> list2 = null;

    List<Student> list3 = new ArrayList<>();
    List<Person> list4 = new ArrayList<>();
    List<Object> list5 = new ArrayList<>();

    // list1 可以接收本类和子类
    list1 = list3;
    list1 = list4;
    // list1 = list5;

    // list2 可以接收本类和父类
    // list2 = list3;
    list2 = list4;
    list2 = list5;

    // 读数据:
    list1 = list4;
    Person person = list1.get(0);
    list2 = list4;
    Object object = list2.get(0);

    // 写数据
    // list1.add(new Student());

    list2.add(new Student());
    list2.add(new Person());
    // list2.add(new Object());
    }

    帮助理解:

    读数据:

    只允许泛型为Person及Person的子类引用调用(<=Person),代表可以使用子类Student对象赋值,如果list1=list4,那么list1.get(0)取出肯定是一个Person对象,因此用其子类Student接收不行,编译报错 代表只允许泛型为Person及Person的父类引用调用(>=Person),代表可以使用父类Object对象赋值,,如果list2=list5,那么list2.get(0)取出肯定是一个Object对象,因此用其子类Person接收不行,编译报错

IO流

File类的使用

java.io.File:文件和文件目录的抽象表示形式,与平台无关

File能新建、删除、重命名文件和目录,但File不能范文文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流。

想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。

构造器:

1
2
3
4
5
6
// 以pathname为路径创建File对象,可以是绝对路径也可以相对路径
public File(String pathname);
// 以parent为父路径,child为子路径传教File对象
public File(String parent,String child);
// 根据一个父File对象和子文件路径创建对象
public File(File parent,String child);

常用方法

获取功能的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 获取绝对路径
String getAbsolutePath();
// 获取路径
String getPath();
// 获取名称
String getName();
// 获取上层文件目录路径。若无,返回null
String getParent();
// 获取文件长度,即字节数。不能获取目录长度
long length();
// 获取最后异常的修改时间,毫秒值
long lastModified();
// 获取指定目录下的所有文件或文件目录的名称数组
String[] list();
// 获取指定目录下的所有文件或者文件目录的File数组
File[] listFiles();

// File的重命名功能,把文件重命名为指定的文件路径,想要重命名成功,源文件必须存在,指定的路径不存在文件
boolean renameTo(File dest);

判断功能的方法

1
2
3
4
5
6
7
8
9
10
11
12
// 判断是否是文件目录
boolean isDirectory();
// 判断是否是文件
boolean isFile();
// 判断是否存在
boolean exists();
// 判断是否可读
boolean canRead();
// 判断是否可写
boolean canWrite();
// 判断是否隐藏
boolean isHidden();

创建和删除

1
2
3
4
5
6
7
8
// 创建文件。若文件存在,则不创建,返回false
boolean createNewFile();
// 创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在也不创建
boolean mkdir();
// 创建多层目录。如果上层目录不存在,一并创建
boolean mkdirs();
// 删除文件或文件夹
boolean delete();

过滤器

在File类的list方法中可以接受一个FilenameFiter参数,通过参数可以仅列出符合条件的文件。

FilenameFilter接口仅包含一个accept(File dir,String name)方法,该方法将依次对指定的File的子目录或对文件进行迭代,如果该方法返回回true,则list方法会列出该子目录或文件。

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void test3(){
File file = new File("E:\\javaProject\\JavaBasics");
file.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
// 返回以.java结尾的文件
return name.endsWith(".java");
}
});
System.out.println(file.getName());
}

IO流原理及流的分类

  • I/O是Input/Output的缩写,I/O技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件,网络通讯等。
    • 输入input:读取外部数据(磁盘,光盘等存储设备的数据)到程序(内存中)中。
    • 输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中
  • java程序中,对于数据的输入/输出操作及”流(stream)”的方式进行。
  • java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。

流的分类

按照操作的数据单位不同分为:字节流(8bit),字符流(16bit)

按照数据流的流向不同分为:输入流和输出流

按照流的角色不同分为:节点流,处理流

image-20220227232857067

抽象基类 字节流 字符流
输入流 InputStream Reader
输出流 OutputStream Writer

image-20220227233540682

FileReader 和 FileWriter

FileReader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* read()方法返回读入的一个字符。如果达到文件末尾,返回-1
* 读取的文件一定要存在,否则会报FileNotFoundException
*/
@Test
public void testFileReader() {
File file = new File("hi.txt");
FileReader fr = null;
try {
// 数据的读入
fr = new FileReader(file);
// 1.read():返回读入的一个字符。如果达到文件末尾,返回-1
// int data;
// while ((data = fr.read()) != -1){
// System.out.print((char) data);
// }
// 2.read(char[]):返回每次读入cbuf数组中的字符个数。如果达到文件末尾,返回-1
char[] cbuf = new char[5];
int len;
while ((len = fr.read(cbuf)) != -1){
// 错误写法,最后一次不够五个字符,会导致数组后面的字符没替换
// for (int i = 0; i < cbuf.length; i++) {
// System.out.print(cbuf[i]);
// }
// 方式一:
// for (int i = 0; i < len; i++) {
// System.out.print(cbuf[i]);
// }
System.out.print(new String(cbuf,0,len));
}
} catch (IOException e) {
e.printStackTrace();
}finally {
// 流的关闭
if (fr != null){
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

FileWriter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 输出操作,对应的File文件可以不存在。
* 如果不存在,在输出的过程中,会自动创建此文件夹
* 如果存在,FileWriter(file,false),参数2为false覆盖,true追加
*/
@Test
public void testFileWriter(){
// 1.提供File类对象,指明写出得文件夹
File file = new File("hello.txt");
// 2.提供FileWriter对象,用于数据写入
FileWriter fw = null;
try {
fw = new FileWriter(file,false);
// 3.写出操作
fw.write("is not null");
fw.write("hao are you?");
} catch (IOException e) {
e.printStackTrace();
}finally {
// 4.流的关闭
if (fw != null){
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

综合案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Test
public void testReadWriteTest(){
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader(new File("hi.txt"));
fw = new FileWriter(new File("hello.txt"));
char[] str = new char[5];
int len;// 每次读取到str数组中的个数
while ((len = fr.read(str)) != -1){
fw.write(str,0,len);// 每次写出len个字符
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (fr != null) fr.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fw != null) fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

FileInputStream 和 FileOutputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void testFileInputStream(){
FileInputStream fis = null;
try {
File file = new File("hello.txt");
fis = new FileInputStream(file);
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1){
System.out.println(new String(buffer,0,len));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fis != null) fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

综合案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Test
public void testFileInputStream(){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
File input = new File("test.pdf");
File output = new File("test1.pdf");
fis = new FileInputStream(input);
fos = new FileOutputStream(output);
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1){
fos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fis != null) fis.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fos != null) fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

处理流之一:缓冲流

作用:提高流的读取,写入的速度。能提高速度的原因是缓冲流内部提供了一个缓冲区

BufferedInputStream、BufferedOutputStream、BuffereReader、Buffered Writer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Test
public void testBufferedStream(){
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
File input = new File("test.pdf");
File output = new File("test1.pdf");
FileInputStream fis = new FileInputStream(input);
FileOutputStream fos = new FileOutputStream(output);
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
byte[] buffer = new byte[1024];
int len;
while ((len = bis.read(buffer)) != -1){
bos.write(buffer,0,len);
// 刷新
// bos.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 在关闭外层流的同时内层流也会关闭
try {
if (bos != null) bos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (bis != null) bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Test
public void testBufferedReaderWriter(){
BufferedReader br = null;
BufferedWriter bw = null;
try {
FileReader fr = new FileReader(new File("hi.txt"));
FileWriter fw = new FileWriter(new File("hello.txt"));
br = new BufferedReader(fr);
bw = new BufferedWriter(fw);
// char[] str = new char[5];
// int len;// 每次读取到str数组中的个数
// while ((len = br.read(str)) != -1){
// fw.write(str,0,len);// 每次写出len个字符
// }
String str;
// readLine():一次读取一行数据
while ((str = br.readLine()) != null){
// bw.write(str+"\n");// str中不包含换行符
bw.write(str);
bw.newLine();//提供换行操作
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (bw != null) bw.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (br != null) br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

处理流之二:转换流

转换流提供了在字节流和字符流之间的转换

  • InputStreamReade:将InputStream转换为Reader
  • OoutputStreamWriter:将Writer转换为OutputStream

image-20220228231344463

将输入的字节流转换为输入的字符流

将输出的字符流转换为输出的字节流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
public void testInputStreamReader(){
InputStreamReader isr = null;//使用系统默认的字符集
try {
FileInputStream fis = new FileInputStream("hello.txt");
// 文件存的是那种格式,读的就是那种格式
isr = new InputStreamReader(fis, "utf-8");
char[] cbuf = new char[1024];
int len;
while ((len = isr.read(cbuf)) != -1){
System.out.println(new String(cbuf,0,len));
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (isr != null) isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

综合使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 读的时候使用utf-8,写入的时候使用gbk
*/
@Test
public void test(){
InputStreamReader isr = null;//使用系统默认的字符集
OutputStreamWriter osw = null;
try {
FileInputStream fis = new FileInputStream("hello.txt");
FileOutputStream fos = new FileOutputStream("hello1.txt");
// 文件存的是那种格式,读的就是那种格式
isr = new InputStreamReader(fis, "utf-8");
osw = new OutputStreamWriter(fos,"gbk");
char[] cbuf = new char[1024];
int len;
while ((len = isr.read(cbuf)) != -1){
osw.write(cbuf,0,len);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (osw != null) osw.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (isr != null) isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

处理流之三:对象流

ObjectInputStream和ObjectOutputStream

  • 用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把java中的对象写入到数据源中,也能把对象从数据源中还原回来
  • 序列化:用ObjectOutputStream类保存基本类型数据或对象的机制
  • 反序列化:用ObjectInputStream类读取基本类型数据或对象的机制
  • ObjectInputStream和ObjectOutputStream不能序列化static和transient修饰的成员变量
  • 类需要进行序列化需要满足如下条件
    • 序列化和反序列化类必须是实现了==Serializable==或Externalizable接口。
    • 需要提供一个全局常量serialVersionUID
    • 除了当前类需要实现==Serializable==外,还必须保证其内部的所有属性也必须是可序列化的。(默认情况下基本数据类型都是可序列化的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 序列化
*/
@Test
public void test() {
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
oos.writeObject(new Person(1,"李华"));
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (oos != null) oos.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 反序列化
*/
@Test
public void test1() {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("object.dat"));
Person str = (Person) ois.readObject();
System.out.println(str);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
if (ois != null) ois.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
}

网络编程

  • 计算机网络

    把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源。
    把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源.

  • 网络编程的目的

    直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯

  • 网络编程中有两个主要问题

    如何准确地定位网络上一台或多台主机、定位主机上的特定的应用

    找到主机后如何可靠高效地进行数据传输

  • 网络通信的要素

    IP和端口号

    • IP地址:InetAddress
      • 唯一标识Internet上的计算机(通信实体)
      • 本地回环地址:127.0.0.1、主机名(localhost)
      • IP地址的分类1:IPV4和IPV6
        • IPV4:4个字节组成,4个0-255。大概42亿,30亿在北美,亚洲4亿。2011年初已经用尽。以点分十进制表示,如192.168.0.1
        • IPV6:128位(16个字节),写成8个无符号整数,每个整数用4个十六进制位表示,数之间用冒号(:)分开。
      • IP地址分类2:公网地址(万维网使用)和私有地址(局域网使用)。192.168开头的就是私有地址,范围即为192.168.0.0–192.168.255.255,专门为组织机构内部使用。
  • 端口号:端口号标识正在计算机上运行的进程(程序)

    • 不同的进程有不同的端口号
    • 被规定为一个16位的整数0~65535。
      • 端口分类:
        • 公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,Telnet占用端口23)
        • 注册端口:1024~49151。分配给用户进程或应用程序。(如: Tomcat占用端口8080,MySQL占用端口3306,Oracle占用端口1521等)。
        • 动态/私有端口:49152~65535。
      • 端口号与IP地址的组合得出一个网络套接字:Socket。

    网络通信协议

    image-20220316232851790

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
public void test01(){
try {
InetAddress inet1 = InetAddress.getByName("192.168.10.14");
System.out.println(inet1);
InetAddress inet2 = InetAddress.getByName("www.atguigu.com");
System.out.println(inet2);
// 获取IP地址
String hostAddress = inet2.getHostAddress();
// 获取域名
String hostName = inet2.getHostName();
System.out.println(hostAddress);
System.out.println(hostName);
// 获取本地IP
InetAddress localhost1 = InetAddress.getByName("localhost");
InetAddress localHost2 = InetAddress.getLocalHost();
System.out.println(localhost1);
System.out.println(localHost2);
} catch (UnknownHostException e) {
e.printStackTrace();
}
}

TCP的网络编程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/**
* 客户端发送信息给服务端,服务端显示在控制台上
*/
public class TcpTest1 {

@Test
public void client(){
Socket socket = null;
OutputStream out = null;
try {
// 创建Socket对象,指明服务器端的IP和端口号
InetAddress inet = InetAddress.getByName("localhost");
socket = new Socket(inet,8889);
// 获取一个输出流,用于输出数据
out = socket.getOutputStream();
out.write("你好,我是客户端".getBytes());
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

}

@Test
public void server(){
ServerSocket serverSocket = null;
Socket socket = null;
InputStream input = null;
ByteArrayOutputStream baos = null;
try{
// 创建服务器段的ServerSocket,指明自己的端口号
serverSocket = new ServerSocket(8889);
// 调用accept()获取客户段的socket
socket = serverSocket.accept();
input = socket.getInputStream();
// 不建议这样写,会有乱码
// byte[] data = new byte[1024];
// int len;
// while ((len = input.read()) != -1){
// String str = new String(data,0,len);
// }
// 读取流的数据
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = input.read(buffer)) != -1){
baos.write(buffer,0,len );
}
System.out.println(baos.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (baos != null) baos.close();
if (input != null) input.close();
if (socket != null) socket.close();
if (serverSocket != null) serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/**
* 发送文件给客户端保存
*/
public class TcpTest2 {

@Test
public void client(){
Socket socket = null;
OutputStream out = null;
try {
// 创建Socket对象,指明服务器端的IP和端口号
InetAddress inet = InetAddress.getByName("localhost");
socket = new Socket(inet,8889);
// 获取一个输出流,用于输出数据
out = socket.getOutputStream();
File file = new File("hello.txt");
InputStream is = new FileInputStream(file);
byte[] buffer = new byte[1024];
int len;
while (( len = is.read(buffer)) != -1){
out.write(buffer,0,len);
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

}

@Test
public void server(){
ServerSocket serverSocket = null;
Socket socket = null;
FileOutputStream out = null;
InputStream input = null;
try{
// 创建服务器段的ServerSocket,指明自己的端口号
serverSocket = new ServerSocket(8889);
// 调用accept()获取客户段的socket
socket = serverSocket.accept();
input = socket.getInputStream();
// 读取流的数据
out = new FileOutputStream("hello2.txt");
byte[] buffer = new byte[1024];
int len;
while ((len = input.read(buffer)) != -1){
out.write(buffer,0,len );
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (out != null) out.close();
if (input != null) input.close();
if (socket != null) socket.close();
if (serverSocket != null) serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

反射

Java反射机制的概述

Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助ReflectionAPI取得任何类的内部结构信息,并能直接操作任意对象的内部属性及方法。

加载玩类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这面镜子看到类的结构,所以我们形象称之为:反射

image-20220321220157228

动态语言 VS 静态语言

  1. 动态语言

    是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。

    主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。

  2. 静态语言

    与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。

Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言 的特性。

Java的动态性让编程的时候更加灵活!

JAVA反射机制提供的功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时处理注解
  • 生成动态代理

反射相关的API

  • java.lang.Class:代表一个类
  • java.lang.reflect.Method:代表类的方法
  • java.lang.reflect.Field:代表类的成员变量
  • java.lang.reflect.Constructor:代表类的构造器
  • ……

疑问:反射机制与面向对象中的封装性是不是矛盾?如何看待这两个技术。

不矛盾。封装性体现的是建议你怎么调用,反射体现的是可不可以调。

理解Class类并获取Class类实例

关于java.lang.Class类的理解

  1. 类的加载过程

    程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class)结尾。

    接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中,此过程就被称为类的加载过程。加载到内存中的类,我们就称为运行时类,此运行时类,就作为一个Class类。

    换句话说,Class的实例就对应着一个运行时类。

    加载到内存中的运行时类,会换成一段时间。在此期间之内,我们可以通过不同的方式来获取此运行时类的对象。

关于Class类的理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void test03(){
Class<Object> objectClass = Object.class;
Class<Comparable> comparableClass = Comparable.class;
Class<String[]> aClass = String[].class;
Class<int[][]> aClass1 = int[][].class;
Class<ElementType> elementTypeClass = ElementType.class;
Class<Override> overrideClass = Override.class;
Class<Integer> integerClass = int.class;
Class<Void> voidClass = void.class;
Class<Class> classClass = Class.class;

int[] a = new int[10];
int[] b = new int[100];
// 只要数组的元素类型和维度一样,就是同一个Class
System.out.println(a.getClass() == b.getClass());// true
}

获取Class实例的四种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void test02() throws ClassNotFoundException {
// 方式一:调用运行时类的属性
Class<Person> clazz1 = Person.class;
// 方式二:通过调用运行时类的对象,调用getClass()方法
Person p1 = new Person();
Class clazz2 = p1.getClass();
// 方式三:通过Class.forName(String classPath)方法
Class clazz3 = Class.forName("reflectionTest.Person");
// 方式四(了解):使用类的加载器:ClassLoader
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("reflectionTest.Person");
System.out.println(clazz1);
System.out.println(clazz2);
System.out.println(clazz3);
System.out.println(clazz4);
}

类的加载与ClassLoder的理解

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对类进行初始化。

image-20220321232731561

了解ClassLoader(类加载器)

类的加载器作用是把类(class)装载到内存的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Properties:用来读取文件
**/
@Test
public void test04() throws Exception {
Properties pros = new Properties();
// 读取配置文件的方式一:
// FileInputStream fis = new FileInputStream("jdbc.properties");
// pros.load(fis);
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("jdbc.properties");
pros.load(is);
String username = pros.getProperty("username");
String password = pros.getProperty("password");
System.out.println("username="+username+",password="+password);
}

创建运行时类的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 使用反射创建运行时类的对象
*/
@Test
public void test05(){
try {
Class<Person> clazz = Person.class;
/**
* newInstance():调用此方法创建对应运行时类的对象
* 默认调用运行时类的空参构造器,如果没有,会抛异常
* 要想此方法正常创建运行时类的对象,要求:
* 1.运行时类必须提供空参的构造器
* 2.空参构造器的访问权限要求满足
*/
Person person = clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}

反射性的体现,运行时创建类的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 创建类的对象
* @param classPath 类的全类名
* @return 指定类的对象
* @throws Exception
*/
public Object getInstance(String classPath) throws Exception {
Class<?> clazz = Class.forName(classPath);
return clazz.newInstance();
}

@Test
public void test06(){
int num = new Random().nextInt(3);
String classPath = "";
switch (num){
case 0:
classPath = "java.util.Date";
break;
case 1:
classPath = "java.lang.Object";
break;
case 2:
classPath = "reflectionTest.Person";
break;
}
try {
Object obj = getInstance(classPath);
System.out.println(obj);
} catch (Exception e) {
e.printStackTrace();
}
}

获取运行时类的完整结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
public interface MyInterface {
void info();
}

public class Creature<T> implements Serializable {

private char gender;
public double weight;

private void breath(){
System.out.println("生物呼吸");
}

public void eat(){
System.out.println("生物吃东西");
}
}

@Target({ElementType.TYPE,ElementType.METHOD,ElementType.PACKAGE})
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
String value();
}

@MyAnnotation(value = "hi")
public class Person extends Creature<String> implements Comparable<String>,MyInterface{

private String name;
int age;
public int id;

public Person() {}

private Person(String name) {
this.name = name;
}

public Person(int age, String name) {
this.age = age;
this.name = name;
}

Person(String name, int age, int id) {
this.name = name;
this.age = age;
this.id = id;
}

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 int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

private String show(String nation){
System.out.println("我的国籍是"+nation);
return nation;
}

public String display(String interests){
System.out.println("我的兴趣是"+interests);
return interests;
}

@Override
public int compareTo(String o) {
return 0;
}

@Override
public void info() {
System.out.println("我是一个人");
}
}

获取运行时属性的结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 获取运行时类的属性结构
* 权限修饰符 属性类型 属性名
*/
@Test
public void test07(){
Class<Person> clazz = Person.class;
// getFields():获取属性结构:获取当前运行时类及其父类中声明为public访问权限的属性
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println(field);
}
// clazz.getDeclaredFields():获取当前运行时类声明的所有属性(不包含父类中声明的属性)
Field[] declaredFields = clazz.getDeclaredFields();
for (Field f : declaredFields) {
System.out.println(f);
// 获取属性权限修饰符:
int modifier = f.getModifiers();
System.out.print(Modifier.toString(modifier)+"\t");
// 获取属性数据类型
Class type = f.getType();
System.out.print(type+"\t");
// 获取属性变量名
String name = f.getName();
System.out.print(name+"\t");
System.out.println();
}
}

获取运行时类的方法结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 获取类的方法结构
* @Xxx
* 权限修饰符 返回值类型 方法名(参数类型1 参数名称1, ...)throws XxxException(){}
*/
@Test
public void test08() {
Class<Person> clazz = Person.class;
// getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
Method[] methods = clazz.getMethods();
// getDeclaredMethods():获取当前运时类中的所有方法(不包含父类中)
Method[] dms = clazz.getDeclaredMethods();
for (Method m : dms) {
// 获取方法声明的注解
Annotation[] annotations = m.getAnnotations();
// 获取方法的权限修饰符
int modifiers = m.getModifiers();
// 获取返回值类型
String returnType = m.getReturnType().getName();
// 获取方法名
String name = m.getName();
// 获取形参类型
Class<?>[] parameterTypes = m.getParameterTypes();
// 获取抛出的异常
Class<?>[] exceptionTypes = m.getExceptionTypes();
}
}

获取构造器结构

1
2
3
4
5
6
7
8
@Test
public void test09(){
Class<Person> clazz = Person.class;
// getConstructors():获取当前运行时类中声明为public权限的构造器
Constructor<?>[] constructors = clazz.getConstructors();
// getDeclaredConstructors():获取当前运行时类中声明的所有构造器
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
}

获取运行时类的父类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void test10(){
Class<Person> clazz = Person.class;
// getSuperclass():获取当前运行类的父类
Class constructors = clazz.getSuperclass();
// getGenericSuperclass():获取当前运行类带泛型的父类
Type genericSuperclass = clazz.getGenericSuperclass();
// getAnnotatedSuperclass():获取当前运行类带泛型的父类的泛型
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
String typeName1 = actualTypeArgument.getTypeName();
String typeName2 = ((Class) actualTypeArgument).getName();
}
}

获取运行时类实现的接口、所在的包

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void test11(){
Class<Person> clazz = Person.class;
// 获取运行时类实现的接口
Class<?>[] interfaces = clazz.getInterfaces();
// 获取运行时类父类的接口
Class<?>[] interfaces1 = clazz.getSuperclass().getInterfaces();
// 获取运行时类所在的包
Package pack = clazz.getPackage();
// 获取运行时类声明的注解
Annotation[] annotations = clazz.getAnnotations();
}

调用运行时类的指定结构

操作运行时类的指定方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void testFiled(){
try {
Class<Person> clazz = Person.class;
Person person = clazz.newInstance();
Field name = clazz.getDeclaredField("name");
// setAccessible():保证当前属性是可以访问的
name.setAccessible(true);
/**
* 设置当前属性的值
* set():参数1:要设置属性的对象,参数2:属性的值
*/
name.set(person,"小明");
/**
* 获取当前属性的值
* get():参数1:要获取属性的对象
*/
Object o = name.get(person);
System.out.println(o);
} catch (Exception e) {
e.printStackTrace();
}
}

操作运行时类的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Test
public void testMethod(){
try {
Class<Person> clazz = Person.class;
Person person = clazz.newInstance();
/**
* 获取指定某个方法
* getDeclaredMethod():参数1:要获取指明方法的名称,参数2:指明方法的形参列表
*/
Method show = clazz.getDeclaredMethod("show", String.class);
show.setAccessible(true);
/**
* 调用方法
* invoke():参数1:方法的调用者,参数而实参
* invoke()方法的返回值就是调用方法的返回值
*/
Object chn = show.invoke(person, "CHN");
System.out.println(chn);
// 调用静态方法
Method showDesc = clazz.getDeclaredMethod("showDesc");
showDesc.setAccessible(true);
showDesc.invoke(Person.class);
} catch (Exception e) {
e.printStackTrace();
}
}

操作指定构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testConstructor(){
try {
Class<Person> clazz = Person.class;
Constructor<Person> pc = clazz.getDeclaredConstructor(String.class);
pc.setAccessible(true);
//创建运行时类的对象
Person p = pc.newInstance("Tom");
System.out.println(p);
} catch (Exception e) {
e.printStackTrace();
}
}

反射的应用:动态代理

代理设计模式的原理

使用一个代理将对象包起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象觉得是否以及何时将方法调用转到原始对象身上。

动态代理模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
interface Human{

String getBelief();

void eat(String foot);
}

// 被代理类
class SuperMan implements Human{

@Override
public String getBelief() {
return "I believe I can fly!";
}

@Override
public void eat(String foot) {
System.out.println("我喜欢吃"+foot);
}
}

/**
* 想要实现动态代理需要解决的问题?
* 问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象
* 问题二:当通过代理类的对象的方法时,如何动态的去调用被代理类中同名的方法
*/

class ProxyFactory{
// 调用此方法返回一个代理类对象
public static Object getProxyInstance(Object obj){// obj:被代理类对象
MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
// 当执行代理类中的方法时,需要动态的去执行被代理类中同名的方法
myInvocationHandler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),myInvocationHandler);
}
}

class MyInvocationHandler implements InvocationHandler{

private Object obj;

public void bind(Object obj){
this.obj = obj;
}

// 当我们通过代理类的对象,调用方法a时,就会自动调用如下方法:invoke()
// 将被代理类要执行的方法a的功能就声明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// method,即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
// obj,被代理对象。args即为传入的参数
Object returnValue = method.invoke(obj,args);
return returnValue;
}
}


public class ProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
String belief = proxyInstance.getBelief();
System.out.println(belief);
// 当执行代理类的方法时,会动态的去执行被代理类中同名的方法
proxyInstance.eat("馒头");
}
}

Java8新特性

Lambda表达式

Lambda是一个==匿名函数==,我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样传输)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java语言表达能力得到提升。

Lambda表达式的本质:作为函数式接口的实例

如果需要使用Lambda表达式,则接口必须是函数式接口(只有一个抽象方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
* Lambda表达式的使用
* 举例:(o1,o2) -> Integer.compare(o1,o2);
* 格式:
* -> :Lambda操作符
* ->左边:Lambda形参(其实就是接口中的抽象方法的新参列表)
* ->右边:Lambda体(其实就是重写抽象方法的方法体)
*/
public class LambdaTest {

@Test
public void testLambda(){
// 情况一: 无参无返回值
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("AAA");
}
};
r1.run();
Runnable r2 = () -> System.out.println("AAA");
r2.run();

// 情况二: 有一个参数但是无返回值
Consumer<String> c1 = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
c1.accept("谎言和誓言的区别是什么呢?");
Consumer<String> c2 = (String s) -> {
System.out.println(s);
};
c2.accept("一个是听的人当真了,一个是说的人当真了");

// 情况三: 数据类型可以省略,因为可以由编译器推断得出,称为类型推断
Consumer<String> c3 = (s) -> {
System.out.println(s);
};
c3.accept("一个是听的人当真了,一个是说的人当真了");

// 情况四: Lambda只需要一个参数时,小括号可以省略
Consumer<String> c4 = s -> {
System.out.println(s);
};
c4.accept("一个是听的人当真了,一个是说的人当真了");

// 情况五: Lambda需要两个以上的参数,多条执行语句,并且可以有返回值
Comparator<Integer> cp1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println(o1.equals(o2));
return o1.compareTo(o2);
}
};
Comparator<Integer> cp2 = (o1, o2) -> {
System.out.println(o1.equals(o2));
return o1.compareTo(o2);
};

// 情况六: Lambda体只有一条语句时,return与大括号若有,都可以省略
Comparator<Integer> cp3 = (o1, o2) -> o1.compareTo(o2);
}
}

总结

-> 左边:Lambda形参列表的参数类型可以省略(类型推断);如果只有一个参数括号可以省略,如果有两个以上的参数不可以省略

-> 右边:Lambda使用一对大括号进行包裹;如果执行体只有一条语句(return语句),可以省略{}return关键字。

函数式接口

什么是函数式接口?

  • 只包含一个抽象方法的接口,称为函数式接口
  • 你可以通过Lambda表达式来创建该接口的对象。(若Lambda表达式抛出一个受减异常(非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)
  • 我们可以在一个接口上使用@Functionallnterface注解,这样做可以检查它是否是一个函数式接口。同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。
  • java.util.function包下定义了丰富的函数式接口

Java内置的四大函数式接口

函数式接口 参数类型 返回类型 用途
Consumer T void 对类型为T的对象应用操作,包含方法void accept(T t)
Supplier T 返回类型为T的对象,包含方法T get()
Function<T,R> T R 对类型T的对象应用操作,并返回接口。结果为R类型的对象。包含方法:R apply(T t)
Predicate T boolean 确认类型为T的对象是否满足某约束,并返回boolean值。包含方法:boolean test(T t)

image-20220327235105414

方法引用和构造引用

方法引用

  • 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用

  • 方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。

  • 要求:==实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保存一致==(适用于情况一和情况二)

  • 格式:使用操作符”::“类或对象与方法名区分开来。

  • 具体情况使用

    • **情况一:**对象 :: 非静态方法

      1
      2
      3
      4
      5
      6
      7
      8
      @Test
      public void testLambda2(){
      Person person = new Person(23,"Tom");
      Supplier supplier = () -> person.getName();
      System.out.println(supplier.get());
      Supplier supplier1 = person::getName;
      System.out.println(supplier1.get());
      }
    • **情况二:**类 :: 静态方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      /**
      * Comparator中的 int compare(int t1,int t2)
      * Integer中的 int compare(int t1,int t2)
      */
      @Test
      public void testLambda3(){
      Comparator<Integer> c1 = (t1,t2) -> Integer.compare(t1,t2);
      System.out.println(c1.compare(12,24));
      Comparator<Integer> c2 = Integer::compare;
      System.out.println(c2.compare(12,24));
      }
    • **情况三:**类 :: 非静态方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      /**
      * Function 中的 R apply(T t)
      * Integer 中的 String getName()
      */
      @Test
      public void testLambda4(){
      Function<Person,String> f = new Function<Person, String>() {
      @Override
      public String apply(Person person) {
      return person.getName();
      }
      };
      System.out.println(f.apply(new Person(12,"TOM")));
      Function<Person,String> f1 = person -> person.getName();
      System.out.println(f1.apply(new Person(12,"TOM")));
      Function<Person,String> f2 = Person::getName;
      System.out.println(f2.apply(new Person(12,"TOM")));
      }

构造器引用

1
2
3
4
5
6
7
8
9
/**
* R get()
* Employee()
*/
@Test
public void testLambda5(){
Supplier<Person> person = () -> new Person();
Supplier<Person> person1 = Person::new;
}

Stream API

Java8中有两大最为重要的改变。第一个是Lambda表达式;另外一个则是Stream API。

Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找,过滤和映射数据等操作。**Stream API 对集合数据进行操作,就类似于使用SQL执行的数据库查询。**也可以使用Stream API来并行执行操作。简而言之。Stream提供了一种高效且易于使用的处理数据方式。

Stream和Collection的区别:Collection是一种静态的内存数据结构,而Stream是有关计算的。

操作Stream的三个步骤

创建Stream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 通过集合
*/
@Test
public void test1(){
List<Person> list = new ArrayList<>();
list.add(new Person(21,"Tom"));
list.add(new Person(23,"jira"));
list.add(new Person(29,"Jar"));
// default Stream<T> stream():返回一个顺序流
Stream<Person> stream = list.stream();
// default parallelStream<T> stream():返回一个并行流
Stream<Person> personStream = list.parallelStream();
}
1
2
3
4
5
6
7
8
9
/**
* 方式二:通过数组
*/
@Test
public void test2(){
int[] arr = new int[]{1,5,67,27,86,23};
// 调用Arrays类的static <T> Stream<T> stream(T[] arrays):返回一个流
IntStream stream = Arrays.stream(arr);
}
1
2
3
4
5
6
7
/**
* 方式三:通过Stream的of
*/
@Test
public void test3(){
Stream<Integer> integerStream = Stream.of(1, 23, 543, 62443, 262);
}
1
2
3
4
5
6
7
8
9
10
/**
* 方式四:创建无限流
*/
@Test
public void test4(){
// 迭代
Stream.iterate(0,t -> t+2).limit(10).forEach(System.out::println);
// 生成
Stream.generate(Math::random).limit(10).forEach(System.out::println);
}

中间操作

一个中间操作链,对数据源的数据进行处理

  • 筛选和分片

    1
    2
    3
    4
    5
    6
    7
    8
    // 接收Lambda,从流中排除某些元素
    filter(Predicate p);
    // 筛选通过流所生成元素的hashCode()和equals()去除重复
    distinct();
    // 截断流,使其元素不会超过给定数量
    limit(long maxSize);
    // 跳过元素,返回一个扔掉前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补。
    skip(long n);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @Test
    public void test5(){
    Person p = new Person(29, "Jar");
    List<Person> list = new ArrayList<>();
    list.add(new Person(21,"Tom"));
    list.add(new Person(23,"jira"));
    list.add(p);
    list.add(p);
    // 过滤
    list.stream().filter(e -> e.getAge() >25).forEach(System.out::println);
    System.out.println("*************************************");
    // 选取指定条数
    list.stream().limit(2).forEach(System.out::println);
    System.out.println("*************************************");
    // 跳过指定元素
    list.stream().skip(1).forEach(System.out::println);
    System.out.println("*************************************");
    // 筛选,去重
    list.stream().distinct().forEach(System.out::println);
    }
  • 映射

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // map对流中的元素进行计算和替换
    // 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
    map(Function f);
    // 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream
    mapToDouble(ToDoubleFunction f);
    // 接收一个函数做为参数,该函数会被应用到每个元素上,产生一个新的IntStream
    mapToInt(ToIntFunction f);
    // 接收一个函数做为参数,该函数会被应用到每个元素上,产生一个新的LongStream
    mapToLong(ToLongFunction f);

    // 接收一个函数作为参数,将流中的每一个值都换成另一个流,然后把所有流连接成一个流
    flatMap(Function f);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Test
    public void test6(){
    List<String> list = Arrays.asList("aa", "bb", "Cc", "dD");
    list.stream().map(str -> str.toLowerCase()).forEach(System.out::println);

    // map()和flatMap()的区别类似于List中add()和addAll()的区别
    Stream<Stream<Character>> streamStream = list.stream().map(StreamApiTest::fromStringToStream);
    streamStream.forEach(stream -> {
    stream.forEach(System.out::println);
    });
    list.stream().flatMap(StreamApiTest::fromStringToStream).forEach(System.out::println);
    }
  • 排序

    1
    2
    3
    4
    // 产生一个新流按照自然顺序排序
    sorted();
    // 产生一个新流,其中按比较强顺序排序
    sorted(Comparator com);
    1
    2
    3
    4
    5
    6
    7
    8
    @Test
    public void test7() {
    List<Integer> list = Arrays.asList(1, 23, 41, 5, 134, 56, 76, 521, -312, 66, 3, 32, -9, -34);
    // 自然排序
    list.stream().sorted().forEach(System.out::println);
    // 自定义排序
    list.stream().sorted((t1,t2) -> -t1.compareTo(t2)).forEach(System.out::println);
    }

终止操作(终端操作)

一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会在被使用

  • 匹配和查找

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 检查是否匹配所有元素
    allMatch(Predicate p);
    // 检查是否至少匹配一个元素
    anyMatch(Predicate p);
    // 检查是否没有匹配所有元素
    noneMatch(Predicate p);
    // 返回第一个元素
    findFist();
    // 返回当前流中的任意元素
    findAny();
    // 返回流中的元素总个数
    count();
    // 返回流中的最大值
    max();
    // 返回流中的最小值
    min();
    // 内部迭代
    forEach(Consumer c);
  • 归约

    1
    2
    3
    // 可以将流中元素反复结合起来,得到一个值。返回T
    reduce(T iden,BinaryOperator b);
    // 可以将流中的元素反复结合起来,得到一个值,返回Optinal<T>
    1
    2
    3
    4
    5
    6
    7
    8
    @Test
    public void test8() {
    // 计算1~10自然数的和
    List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    // 0是起始值
    Integer reduce = list.stream().reduce(0, Integer::sum);
    System.out.println(reduce);
    }
  • 收集

    1
    2
    // 将流转换成其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
    collect(Collector c);

    Collecotr接口方法的实现决定了如何对流执行收集的操作(如收集到List,Set,Map)

    image-20220328225326687

Optional类

Optional<T>类(java.util.Optional)是一个容器类,它可以保存类型T的值,代表这个值得存在。或者仅仅保存null,表示这个值不存在。原来用null表示一个值不存在,现在Optional可以更好的表达这个概念。并且可以避免空指针异常。

常用方法:

创建Optional类对象的方法

1
2
3
4
5
6
// 创建一个Optional实例,t必须不为空
Optionnal.of(T t);
// 创建一个空的Optional实例
Optionnal.empty();
// t可以为空
Optionnal.ofNullable(T t);

判断Optional容器中是否包含对象

1
2
3
4
// 判断是否包含对象
boolean isPresent();
// 如果有值,就执行Consumer接口代码的实现代码,并且该值会作为参数传给它
void ifPresent(Consumer<? super T> consumer);

获取Optional容器的对象

1
2
3
4
5
6
7
// 如果调用对象包含值,返回该值,否则抛出异常
T get();
// 如果有值则将其返回,否则返回指定的other对象
T orElse(T other);
// 如果有值则将其返回,否则返回由Supplier接口实例提供的对象
T orElseGet(Supplier<? extends T> other);
// 如果有值则将其返回,否则抛出由Supplier接口实现提供的异常