Java笔记

Java

Java基础

//单行注释
/*
多行注释 注意 注释不会被编译
但是 注释中不可以出现
*/
变量的命名规则
java中的变量命名只可以使用数字 字母 _ $
不可以使用数字开头
中文变量名可以通过编译 但是不建议使用
如果在编译的时候出现了GBK不可以映射字符的问题
需要将当前文件的编码修改为ANSI(GBL编码)
见名知义 驼峰命名法
在java中 变量名 方法名使用小驼峰
类名 使用大驼峰
类名首字母必须大写 java中自带的关键字不可以作为类名

int studentage=28;
String studentName="kun";

基本数据类型

4整型 2浮点型 一种布尔型 1种字符型

byte short int long
byte 8位 -128~127
short 16位 -2^15 ~ 2^15-1
int 32位 -2^31 ~2^31-1
long 63位 -2^
63 ~ 2^63-1

int a = 0x11;

数据类型的范围

    System.out.println(Short.MIN_VALUE);
    System.out.println(Short.MAX_VALUE);
    System.out.println(Integer.MIN_VALUE);
    System.out.println(Integer.MAX_VALUE);
    System.out.println(Long.MIN_VALUE);
    System.out.println(Long.MAX_VALUE);

java中 整型数据的字面量值 默认为int类型;

类型转换

大类型转小类型 强制类型转换
小类型转大类型 隐式类型转换
byte a = (byte)200;
System.out.println(a);
long l = 21474836481L;
System.out.println(l);

字符型 只能存储一个字符
char a = '坤';
char b = 'm';
char c = 97;
char d = '\u0065';
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(d);
byte b =(byte)'a';
System.out.println(b);	
浮点型 float double

二进制科学计数法

float 32位 第一位符号位 指数部分为8位 尾数部分为23位
double 64位 第一位符号位 指数部分为11位 尾数部分为52位
float 类型数值范围远大于int 但是精度比int小
double类型数值范围远大于long 但是精度比long小
默认所有小数类型的字面量按照double类型存储
float a = 1.0f;
所有的浮点型计算 本身是实数计算
计算机只能存储整数 浮点型的存储实际存储的是约束
浮点型的计算会比较慢并且可能出现误差
BigDecimal —> decimal

布尔类型 boolean true false

4 2 1 1 整浮字布
String a =”abc”;
运算符 + - * / % ++ –

    int i=1;
    int a=(i++)-(i--)+(--i)-(++i);//-2

//表达式的值  1    2      0     1
//i的值    	 2   1       0     1
    int b=(++i)-(i++)+(i--)-(--i);//2
//表达式的值  2    2     3      1
//i的值    	2     3     2      1
    System.out.println(i);
    System.out.println(a);
    System.out.println(b);

关系运算符

< > <= >= != ==

与或非 逻辑运算符
& |
&& || 会出现短路的情况
如果运算符左边的表达式结果能决定最终结果 右边的表达式就不会被执行 会被跳过 这种现象叫做短路现象

    int a=1;
    int b=2;
    System.out.println((a++>b++)&&(a++<b++));
    System.out.println(a);
    System.out.println(b);
位运算符
& 按位与运算
    1010
    0100
    0000
    System.out.println(10&4);
    0
| 按位或运算
    1010
    0100
    1110
    System.out.println(10|4);
    15
^ 按位异或
    1010
    0100
    1110
    System.out.println(10|4);
    14
~ 按位取反
    +01111
    -10000
    System.out.println(~15);
    -16
    左移 高位溢出 低位补0
    111100
    System.out.println(15<<2);
    70
    右移 小数点不动 所有位右移
    1111.00
    11.11
    System.out.println(15>>2);
    3

三元表达式 if else的简写版

    int age=18;
    String result=(age>=18)?"老登":"未成年";

顺序结构 分支结构 循环结构

    String name="jzy";
    if(name.equals("jzy")){
        System.out.println("tui!Jerk");
    }
    System.out.println("你好,"+name);
    String name="zsy";
    if(name.equals("zsy")){
    System.out.println("你是一个好人");
    }else if(name.equals("jzy")){
        System.out.println("怎么还是你这个Jerk,tui!");
    }else{
        System.out.println("what can i say");
    }

switch case

switch的key类型不可以是long类型
每一个case语句之后都需要有一个break

    String key="A";
    switch(key){
        case "A":
        System.out.println("太对了哥");
        break;
        case"B":
        System.out.println("不是哥们");
        break;
        case"C":
        System.out.println("啊对对对");
        default:
        System.out.println("?");
    }

循环 while循环 do while循环 for循环

1-100和
    int i=1;
    int result=0;
    while(i<=100){
        result+=i;
        i++;
    }
    System.out.println(result);
do while 不管条件是否满足 do后面的语句至少会执行一次
    int i=1;
    int result=0;
    do{
        result+=i;
        i++;
    }while(i<=100);
    System.out.println(result);
    
    int result=0;
    for(int i=0;i<=100;i++){
        result+=i;
    }
    System.out.println(result);
    System.out.println(i);//无效i
continue break

continue跳出本次循环 继续到下一次循环
break 直接结束指定层循环 如果不指定结束哪一层 默认结束当前层
求1-100中的所有奇数 如果找到 打印它

    for int(i=1;i<=100;i++){
        if (i%2==0){
            continue;
        }
            System.out.println(i);
    }
    //遍历10-99的所有两位数 如果发现个位*十位等于40 则打印找到 并停止查找
    
    for(int i=10;i<=100;i++){
        int g=i%10;
        int s=i/10;
        if(g*s==40){
            System.out.println("找到了,数字是:"+i);
            break;
        }
    }

遍历1-9的数字 如果两个数字的和等于两个数字的积,则打印这个数字 并且结束循环

    loop:
    for(int i=1;i<10;i++){
        for(int j=1;j<10;j++){
            if((i+j)==(i*j)){
                System.out.println("找到了,数字是:"+i);
                break loop;
            }
        }
    }

打印内容

println 允许打印空内容 打印结尾自带换行
print() 不允许打印空内容 打印结尾不会自带换行
printf() 支持格式化打印
占位符 %d 匹配一个整型
%s 匹配一个字符串
%f 匹配一个浮点型
%c 匹配一个字符型
%b 匹配一个布尔值类型
%n 无需匹配 直接表示换行符

    String name="jzy";
    int age=22;
    float height=179.9f;
    char gender='男';
    boolean isMarried=false;
    System.out.println("大家好,我叫"+name+",今年芳龄为"+age+",性别为"+gender+",我的婚姻的布尔值为"+isMarried+",我的身高为"+height+"cm,希望大家喜欢我");
    System.out.printf("大家好,我叫%s,今年芳龄为%d,性别为%c,我的婚姻的布尔值为%b,我的身高为%f,希望大家喜欢我%n",name,age,gender,isMarried,height);

数组

基本java内存分配
递归算法 方法的进出栈
数组的长度不可改变
数组中所存放的数据类型是统一的
数组初始化 有两种 静态初始化 动态初始化
下面这种写法是数组的动态初始化 只提供数组元素的数量
而不提供具体的元素
数组元素会有一个默认值 默认值和数组的类型相关
byte short int long 0
float double 0.0
boolean false
char \u0000
数组的元素通过index进行访问 是基0的

    int[] arr=new int[10]; 
    //arr[3]=1;
    //System.out.println(arr[3]);
    //数组遍历有两种方式 第一种是传统的for循环
    for(int i=0;i<arr.length;i++){
        System.out.print(arr[i]+"\t");
    }
    //增强for循环(Iterator迭代器)遍历 语法题
    //增强for循环只能用于遍历 不可以用于元素的赋值和修改
    for(int i:arr){
        System.out.print(i+"\t");
    }

静态初始化

在数组声明的时候就定义好所有的数组元素
数组的长度会根据你定义的元素数量自动确定

    int[] arr=new int[]{1,3,5,7};
    int[] arr={1,3,5,7};
    for(int i:arr){
        System.out.print(i+"\t");
    }

java 内存分配青春版

    int[] arr=new int[10];
    arr[1]=1;
    arr[2]=2;
    int[] arr1=arr;//指向的arr地址
    arr1[0]=3;
    arr1[1]=4;
    System.out.print(arr[0]);
    System.out.print(arr[1]);

现在有如下一个数组:
int oldArr[]={1,3,4,5,0,0,6,6,0,5,4,7,6,7,0,5};
要求将以上数组中的0项去掉,将不为0的值存入一个新的数组,
生成新的数组为:
int newArr[]={1,3,4,5,6,6,5,4,7,6,7,5};

    int oldArr[]={1,3,4,5,0,0,6,6,0,5,4,7,6,7,0,5};
    int count=0;
    for(int i:oldArr){
        if(i!=0){
            count++;
        }
    }
    
    int newArr[]=new int[count];
    int j=0;
    
    for(int i:oldArr){
        if(i!=0){
            newArr[j++]=i;
        }
    }
    
    for(int i:newArr){
        System.out.print(i+"\t");
    }

排序算法

简单选择排序

在要排序的一组数中 选出最小的一个和第一个位置的进行交换
再在剩下的数中找到最小的和第二个交换 依次类推

    int[] arr={1,3,2,5,4,7,6,8,9};
    //定义变量的记录位置
    int position=0;
    for(int i=0;i<arr.length;i++){
        int j=i+1;
        position=i;
        int temp=arr[i];
        for(;j<arr.length;j++){
            if(arr[j]<temp){
                temp=arr[j];
                position=j;
            }
        }
        arr[position]=arr[i];
        arr[i]=temp;
    }

冒泡排序

在要排序的一组数中 对当前还未排序号的范围内的所有数对相邻数进行比较
和交换 让较大的下沉 较小的上冒

    int[] arr={9,3,2,5,4,7,6,8,1};
    int temp=0;
    for(int i=0;i<arr.length-1;i++){
        for(int j=0;j<arr.length-1-i;j++){
            if(arr[j]>arr[j+1]){
                temp=arr[j];
                arr[j]=arr[j+1];
                arr[j+1]=temp;
            }
        }
    }
    for(int i:arr){
        System.out.print(i+"\t");
    }

二维数组 矩阵

静态初始化

    int[][] arr={
        {1,2,3},
        {4,5,6,7},
        {8,9,10,13,15}
    };
    System.out.print(arr[0][0]);//1
    System.out.print(arr[2][3]);//13

动态初始化

    int[][] arr=new int[3][];
    arr[0]=new int[]{1,2,3};
    arr[1]=new int[]{4,5,6,7};
    arr[2]=new int[]{8,9};
    for(int i=0;i<arr.length;i++){
        for(int j=0;j<arr[i].length;j++){
            System.out.print(arr[i][j]+"\t");
        }
        System.out.println();
    }

递归算法

求1-10的和

sum(10);
public static int sum(int n){
        if(n==1){
            return 1;
        }
        return n+sum(n-1);
    }

斐波那契数列

1 1 2 3 5 8 13 21 34 55

1
2
3
4
5
6
7
8
9
System.out.println(sum(10));
System.out.println(crazyRabbit(50));
public static int crazyRabbit(int n){
if(n==0||n==1){
return 1;
}
return crazyRabbit(n-1)+crazyRabbit(n-2);
}

静态方法

方法的修饰符一定包含static
方法中有参数 返回值 的概念 方法可以没有参数
方法可以没有返回值 但是需要使用void表示没有返回值
静态方法通过类进行调用 可以在当前类中调用静态方法 可以省略类名

        HelloWorld.eat();
        eat();
        eatSth("中苑美食城二楼难吃的瓦香鸡米饭");
        
        public static void eat(){
        System.out.println("爱吃中苑老食堂");
    }
    
    public static void eatSth(String sth){
        System.out.println("爱吃"+sth);
        }

java中不可以出现方法名和参数完全相同的方法

方法重载

在调用方法的时候 方法名称相同 调用方法的参数不同(数量不同 类型不同 顺序不同)
可以实现调用不同的方法 编译时多态
方法的返回值 需要声明返回值类型 并且 谁调用方法 谁获取返回值

返回值的参数传递

引用类型传递的是对象的内存地址
基本数据类型传递的是值 String作为引用类型 做的也是值传递

    String result=runLeg(80);
    SYstem.out.println(result);
    learn();
    
    public static void learn(){
        sleep();
        System.out.println("学习5分钟");
    }

面向对象

继承

继承特点

1.子类继承了父类的所有内容
2.子类还可以拥有自己特有的内容
Person叫做父类 超类 基类
其他类叫做子类 派生类
共性抽取

继承优点:

提高了代码的复用性 提供了开发的效率
继承的出现让类和类之间产生了关系 提供了多态的前提

java中只支持单继承 但可以嵌套继承

Class A extends B
Class B extends C

抽象类

如果父类当中的方法不确定如何表达如何进行方法的具体实现,那么这个方法就一个是一个抽象方法
具有抽象方法的类 就应该是抽象类

子类在继承抽象类之后 必须重写父类的所有抽象方法 如果不重写 编译无法通过

抽象类的特点

1.抽象类无法创建对象 只能通过子类创建对象
2.子类必须重写父类的所有抽象方法
3.抽象类的成员变量 构造函数以及非抽象方法 都是为了给子类进行继承

super的用法

1.在子类构造函数中调用指定父类构造函数 如果没写 则默认使用super()调用父类的无参构造
2.在子类中调用指定的父类方法
3.在子类中访问父类的成员变量

如果子类中出现了和父类方法名称以及参数一致的方法,子类中的方法会覆盖父类中的方法,这种现象叫做方法重写

@override出现
1.标记的前方法重写了父类方法
2.如果当前方法没有重写父类方法 编译不通过

继承的总结

当一个类继承了另一个类 则子类拥有父类的全部属性和方法
如果不想使用父类的方法 则可以通过重写的方式覆盖父类的方法
如果父类的属性是private 则子类无法直接访问 可以间接访问

接口

在jdk7中 接口中只能声明常量
jdk8之后 接口中还可以声明静态方法和默认方法
jdk9之后 接口中还可以额外包含私有方法

接口的优点

解决了不能多继承的弊端 实现了多实现

接口和抽象类的区别:

相同点

都可以定义常量和抽象方法
都位于继承的顶端
都不能直接创建对象

区别

1.抽象类为部分方法提供实现 避免子类重复实现 提高了代码的复用性 接口只能包含抽象方法(jdk8可以设置静态和默认 jdk9私有)
2.抽象类在设计中表示的是某一个业务应该具有的功能 是一种is..a的关系
3.接口是一个规范 一个规范描述的是子类应该具有的功能 到那时不提供实现 由子类自己实现 是一种like..a的关系
4.抽象类中可以定义成员方法 成员变量 构造方法 接口不可以
5.接口中的所有常量必须在命名的时候全部大写 如果出现了多个单词 使用下划线连接

多态

一个对象拥有多种形态 叫做对象的多态性

里氏替换原则:任何一个需要父类对象出现的地方都可以使用子类对象进行替换

父类引用指向子类对象

//动态绑定(运行时多态)
Animal a=new Cat();
a.eat();
a=new Dog();
a.eat();
 Animal a=new Dog();
 a.eat();
//程序健壮性判断
((Dog)a).fightHouse();
((Cat)a).backFlip();//运行出错
if(a instanceof Cat){
    Cat c=(Cat)a;
    c.backFlip();
}
//通过多态实现类型的拓展
Animal[] animals = new Dog[10];
animals[0] = new Dog();
animals[1] = new Cat();

利用多态 对外可以只暴露父类和接口
让使用者只能调用接口中定义的方法

关键字

final

不可变修饰符

如果修饰类 则表示类不可以被继承
如果修饰方法 方法不可以被重写
如果修饰基本类型的变量 变量的值不可以更改
如果修饰引用类型的变量 引用不可以更改

static

静态修饰符

String name;
int sno;
static int classNo=525;

1.静态属性优先于对象存在 不可以在静态方法或代码块中访问this/super
2.同一个类中 静态属性只能访问静态属性 而不能访问非静态属性

访问修饰符

private

同类中

(default)

同类中、同包类(子类和非子类)

protected

同类中、同包类(子类和非子类)、不同包子类

public

同类中、同包类(子类和非子类)、不同包子类、不同包且非子类

系统类

常用类

String、StringBuffer、StringBuilder

String

不可变字符串 String一旦内容被修改 就一定是创建了新的字符串
String底层存储使用的是char数组

abc char[] value{'a','b''c'}

当传入一个char数组作为构造函数的参数进行初始化的时候,String并没有选择直接使用传入的数组地址进行value属性的赋值,而是复制了一个和传入参数数组元素相同的新数组,避免了传入参数的数组元素改变导致String字符改变的情况出现。

substring和concat
1
2
3
4
String str="abcdefg";
System.out.println(str.substring(2,5));//cde
System.out.println(str.concat("hij"));//abcdefghij

String支持java中的常量池:

当String是通过=直接赋值,而不是new的时候,字符串会被放到常量池中,常量池中是不会有重复的字符串的

String a ="abc";
String b ="abc";
String c = new String("abc");
char[] ch={'a','b','c'};
String d = new String(ch);
String e ="a"+"b"+"c";
String s1="a";
String s2="b";
String s3="c";
String f=s1+s2+s3;

System.out.println(a.equals(b));//true
System.out.println(a==b);//true
System.out.println(a==c);//false
System.out.println(a==d);//false
System.out.println(a==e);//true
System.out.println(a==f);//false
+连接符
String s1 = "a";
String s2 = "b";
String s3 = "c";
String s4 = s1+s2+s3;

通过该方法实现而不违反String不可变原则

String s1 = "a";
String s2 = "b";
String s3 = "c";
(new StringBuilder()).append(s1).append(s2).append(s3).toString();

StringBuilder

是线程不安全的,但效率提高了

比较可变字符串值方法需要toString()比较存放地址

1
2
3
4
StringBuilder sb1=new StringBuilder("a");
StringBuilder sb2=new StringBuilder("a");
System.out.println(sb1.toString().equals(sb2.toString()));

StringBuffer

是现成安全的,牺牲了性能

其他系统常用类

Scanner、日期相关、数字计算相关、封装类

Scanner

import java.util.Scanner;

Scanner sc = new Scanner(System.in);

Random

Random r=new Random();
int randomNum=r.nextInt(100)+100;//0-99,区间左闭右开
int randomNum=r.nextInt(9)*20;//获取0-180

封装类

封装类为基本数据类型而生的类

short-Short 
byte-Byte 
long-Long
double-Double 
boolean-Boolean 
int-Integer
char-Character

基本数据类型转换为封装类型叫装箱

int i=1;//数据类型
Integer i=1;//封装类型
Integer i=new Integer(1);//引用类型

封装类型转换为数据类型叫做拆箱

Integer j=1;
int j=1;

在某些场景中,装箱和拆箱是自动完成的,不需要开发者手动进行类型转换

Integer i=1;
int j=1;
i==j//true

4种整型的封装类支持使用常量池,但是有范围 -128-127

Integer j=1;
Integer k=1;
j==k//true

Integer l=128;
Integer m=128;
l==m//false

封装类不能直接用equals()比较,需要转换为相同的数据类型

Math

数学对象

日期相关类

Date

默认打印系统时间

Date d=new Date();
System.out.println(d);//Wed Nov 13 16:15:20 CST 2024

可以打印计算机时间原点

Date d=new Date(0);
System.out.println(d);//Thu Jan 01 08:00:00 CST 1970

打印距离时间原点经过的毫秒数

System.out.println(d.getTime());//1731485842849

SimpleDateFarmat

y年 M月 d日 H24小时 h12小时制 m时 s秒 S毫秒

//将一个日期格式的数据类型转换成字符串
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d=new Date();
        String s=sdf.format(d);
        Print.print(s);
        //2024-11-13 16:23:08
//将一个字符串转换为日期格式类型
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String str="2022-02-02 22:22:22 222";
        try {
            Date d=sdf.parse(str);
            System.out.println("转换的日期对象为:"+d);
        } catch (ParseException e) {
            e.printStackTrace();
        }

日历类Calender

可以和日期类型进行转换

//实例化一个日历对象
Calendar c=Calendar.getInstance();
//通过日历对象得到日期对象
Date d=c.getTime();
Date d2=new Date(0);
//将日历的时间设置为d2对应的日期
c.setTime(d2);
add方法

在当前时间的基础上向未来或者从前调整时间

Calendar c=Calendar.getInstance();
Date now=c.getTime();
System.out.println(now);
//获取下个月的今天
c.add(Calendar.MONTH,1);
System.out.println(c.getTime());
//获取去年的今天
c.setTime(now);
c.add(Calendar.YEAR,-1);
System.out.println(c.getTime());
set方法

设置指定时间

//获取上个月的第三天
c.setTime(now);
c.add(Calendar.MONTH,-1);
c.set(Calendar.DATE,3);
System.out.println(c.getTime());

内部类

匿名内部类、静态内部类、非静态内部类、局部内部类、内部接口

匿名内部类

USB usb=new USB(){
    public void open() {
        System.out.println("连接驱动");
    }
};
usb.open();
System.out.println(usb.getClass().getName());
System.out.println(usb.getClass()==Class.forName("test.Test$1"));

异常处理

Throwable、Exception、Error

什么是异常?

运行时发生的错误叫异常,处理异常的行为叫做异常处理。一旦发生异常,程序会突然终止。

当一个程序出现错误

1.语法错误 没通过编译
2.运行时错误(主要处理的异常类型)
3.逻辑错误 bug

java通过面向对象的方式来处理异常

在方法的运行过程中,如果出现了异常,这个方法就会产生改异常的一个对象,并将对象交给JVM,开发人员通过try{}代码来包含可能出现异常的代码,通过catch{}带捕捉可能出现的异常对象

public void say(int i){
        int[] arr={1};
        try {
            System.out.println(1 / i);
        }catch (ArithmeticException e){
            System.out.println("除数不能为0");
            e.printStackTrace();//输出堆栈的跟踪信息
        }try{
            System.out.println(arr[1]);
        }catch (ArrayIndexOutOfBoundsException e){
            System.out.println("数组越界");
            e.printStackTrace();
        }
        System.out.println("由于我们做了异常处理,代码发生异常之后,之后的代码也可以正常执行");
    }

finally

finally代码块的代码都会执行且优先级最高

public void what(int i){
        try{
            System.out.println(1/i);
        }catch(Exception e){
            System.out.println("捕捉到异常");
            e.printStackTrace();
        }finally {
            System.out.println("finally代码块的代码都会执行");
        }
    }

throw

主动创造异常
出现在方法内

try{
        Test t = null;
        if(t == null){
            throw new NullPointerException();
        }
    }catch(Exception e){
        e.printStackTrace();
    }

throws

抛出异常
出现在方法的声明中

public void what()throws ClassNotFoundException{
        can();
    }
    public void can() throws ClassNotFoundException{
        i();
    }
    public void i()throws ClassNotFoundException{
        Class.forName("com.iweb.test4.Test");
    }

    public static void main(String[] args) {
        try {
            new TestThrows().what();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

自定义异常

public class JerkException extends Throwable {
    public JerkException(String message){
        super(message);
    }
}

String name="jzy";
        try {
            if(name.equals("jzy")){
                throw new JerkException("终于找到你了,渣男,tui!");
            }
        }catch (JerkException e){
            e.printStackTrace();
        }

代码分层(规范)

pojo层

用于存放非规则实体类

view层

用于存放操作视图

service层

用于存放服务层面的接口类

controller层

用于存放控制类

util层

用于存放工具类

DAO层

用于存放底层实现方法接口

集合

List Set Map

1.扩容相关的操作由集合类自己实现
2.提供了丰富的方法,简化了操作的流程
3.性能一定没有原生数组好

Collection 单列集合

集合方法

Collection a=new ArrayList();
a.add("a");//添加
a.add("b");
a.add("c");
System.out.println(a);
a.remove("b");//去除
System.out.println(a);
System.out.println(a.contains("c"));//检查包含
a.clear();//清空
System.out.println(a.isEmpty());//检查为空
Collection b=new ArrayList();
Collection c=new ArrayList();
b.add("a");
b.add("b");
b.add("c");
c.add("a");
c.add("b");
System.out.println(b.containsAll(c));//检查b是否全包含c
b.addAll(c);//全加进去
b.removeAll(c);//移除b中和c相同的元素
System.out.println(b.size());//b的尺寸

范型

Collection<String> a = new ArrayList();
a.add("a");
a.add(1);//error
a.add(new Object());//error

动态范型

Person<Dog> person=new Person<>();
person.setFriend(new Dog());
person.getFriend().eat();
Person<Cat> yzx=new Person<>();
yzx.setFriend(new Cat());
yzx.getFriend().sleep();
public class Person<T> {
    String name;
    int age;
    T friend;
    public T getFriend(){
        return friend;
    }
    public void setFriend(T friend){
        this.friend = friend;
    }
}
public class Dog {
    public void eat() {
        System.out.println("吃老八");
    }
}
1
2
3
4
5
6
public class Cat {
public void sleep(){
System.out.println("猫吃完就睡");
}
}

List

ArrayList LinkedList Vector
数组 链表 栈 队列

List接口共性

1.有序集合(存储和取出的元素顺序相同)
2.允许存储重复的元素
3.有下标,可以使用普通的for循环进行遍历

ArrayList

数组

遍历方法
List<String> stringlist = new ArrayList<>();
stringlist.add("lxy晚上提问");
stringlist.add("jzy笑了,晚上也提问");
stringlist.add("小王车没了,麻了");
//1.传统for循环
for(int i=0;i<stringlist.size();i++){
    System.out.print(stringlist.get(i)+"\t");
}
System.out.println();
//2.增强for循环
for(String s:stringlist){
    System.out.print(s+"\t");
}
System.out.println();
//3.迭代器遍历
Iterator<String> iterator = stringlist.iterator();
while(iterator.hasNext()){
    String element = iterator.next();
    System.out.print(element+"\t");
}
System.out.println();
删除指定元素
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
list.add("f");
list.add("g");
Iterator it=list.iterator();
while(it.hasNext()){
    String element = (String) it.next();
    if(element.equals("c")){
        //list.remove(element);//数量异常
        it.remove();
    }
}



//普通for循环(不推荐)
for(int i=0;i<list.size();i++){
    if(list.get(i).equals("c")){
        list.remove(list.get(i));
    }
}
LinkedList

链表
存在下标,可以在开发中使用,但实际上还是使用地址查询,查询速度不如数组快

Queue

单向链表

Queue<String> queue=new LinkedList<>();
queue.offer("a");
queue.offer("b");
queue.offer("c");
System.out.println(queue.poll());//取出并删除
System.out.println(queue.peek());//取出
System.out.println(queue);//遍历打印
Deque

双向链表

Deque<String> queue=new LinkedList<>();
queue.offer("a");
queue.offer("b");
queue.offer("c");
queue.offerFirst("d");//开头插入
queue.offerLast("e");//结尾插入
System.out.println(queue);
System.out.println(queue.pollFirst());//取出并删除开头元素
System.out.println(queue.pollLast());//取出并删除结尾元素
System.out.println(queue);
System.out.println(queue.peekFirst());//取出开头元素
System.out.println(queue.peekLast());//取出结尾元素
System.out.println(queue);

Set

HashSet TreeSet LinkedHashSet

Set接口共性

1.不允许存储重复元素
2.没有下标,无法通过传统for循环遍历
3.不保证存储顺序和读取顺序一致

HashSet

底层是HashMap => hash散列

Set<String> set = new HashSet<>();
set.add("a");
set.add("b");
set.add("b");
set.add("b");
set.add("c");
set.add("d");
Iterator<String> it = set.iterator();
while (it.hasNext()) {
    String element = it.next();
    System.out.println(element);
}
TreeSet

底层是TreeMap=>红黑树(排序二叉树)
可以对存入的数据进行自动排序,排序是有要求的,要么泛型类型实现了Comparable接口,要么在创建TreeSet的时候传入一个Comparator对象。

定义数据的排序基准 比较器

public class Stu implements Comparable<Stu> {
    private String name;
    private int age;
    private int height;
    @Override
    public int compareTo(Stu o) {
        if(this.age>o.age){
            return 1;
        }else if(this.age<o.age){
            return -1;
        }else{
            return 0;
        }
    }
}
------------------
TreeSet<Stu> stuTreeSet=new TreeSet<>();
stuTreeSet.add(new Stu("jzy",18,180));
stuTreeSet.add(new Stu("jzy",19,189));
stuTreeSet.add(new Stu("jzy",20,187));
stuTreeSet.add(new Stu("jzy",12,185));
System.out.println(stuTreeSet);
public class Teacher {
    private String name;
    private int age;
    private int height;
}
---------------------
public class TeacherAgeAscComparator implements Comparator<Teacher> {
    @Override
    public int compare(Teacher o1, Teacher o2) {
        if(o1.getAge() > o2.getAge()){
            return 1;
        }else if(o1.getAge() < o2.getAge()){
            return -1;
        }else {
            return 0;
        }
    }
}
-------------------
Comparator<Teacher> c=new TeacherAgeAscComparator();
TreeSet<Teacher> teacherTreeSet=new TreeSet<>(c);
teacherTreeSet.add(new Teacher("王老师",18,188));
teacherTreeSet.add(new Teacher("王老师",16,188));
teacherTreeSet.add(new Teacher("王老师",19,188));
teacherTreeSet.add(new Teacher("王老师",40,188));
teacherTreeSet.add(new Teacher("王老师",39,188));
System.out.println(teacherTreeSet);
方法
TreeSet<Integer> ts = new TreeSet<>();
ts.add(3);
ts.add(5);
ts.add(1);
ts.add(2);
ts.add(4);
System.out.println(ts.ceiling(3));//>=
System.out.println(ts.higher(3));//>
System.out.println(ts.floor(3));//<=
System.out.println(ts.lower(3));//<

LinkedHashSet

存读有序
读取顺序和存储顺序一致

Collection方法工具类

sort

该工具类的sort方法进行排序是有要求的
要么排序的集合泛型类型实现了Comporable接口,要么在调用sort方法的时候传入一个Comporator对象

List<Teacher> list=new ArrayList<>();
list.add(new Teacher("王老师",18,188));
list.add(new Teacher("王老师",16,188));
list.add(new Teacher("王老师",19,188));
list.add(new Teacher("王老师",40,188));
list.add(new Teacher("王老师",39,188));
Comparator<Teacher> c=new TeacherAgeAscComparator();
Collections.sort(list,c);
System.out.println(list);
shuffle

打乱随机

ArrayList<Integer> list = new ArrayList<>();
for(int i = 1; i <= 10; i++) {
    list.add(i);
}
System.out.println(list);
Collections.shuffle(list);
System.out.println(list);
reverse

倒置

Collections.reverse(list);
System.out.println(list);
swap

交换位置

Collections.swap(list,3,7);
System.out.println(list);
rotate

移位

Collections.rotate(list,3);
System.out.println(list);

Map 双列集合 key value

HashMap TreeMap HashTable CancurrentHashMap(手撕HashMap)

HashMap

底层是Hash散列

Map<Key,Value> map=new HashMap<>();
map.put("jerk","jzy");
map.put("feiyangyang","whw");
map.put("西格玛","tjh");
map.put("西格玛","whw");
System.out.println(map.get("西格玛"));//Key
System.out.println(map);

key可以是null,value也可以是null

map.put(null,"hetui");
map.put(null,"a?");
System.out.println(map);
三种遍历方式
根据增强for循环遍历

根据keySet遍历

for(String key:map.keySet()){
    System.out.println(key+":"+map.get(key));
}
Iterator<String> keySet = map.keySet().iterator();
while(keySet.hasNext()){
    String key = keySet.next();
    System.out.println(key+":"+map.get(key));
}

根据entrySet遍历

for(Map.Entry<String,String> entry:map.entrySet()){
    System.out.println(entry.getKey()+":"+entry.getValue());
}
Iterator<Map.Entry<String,String>> iterator = map.entrySet().iterator();
while(iterator.hasNext()){
    Map.Entry<String,String> entry = iterator.next();
    System.out.println(entry.getKey()+":"+entry.getValue());
}

TreeMap

默认是升序排列所有的键值对 根据key排序
需要key所对应的类实现Comparable接口,或者提供Comparator作为构造函数的参数
底层是二叉树(红黑树)。

方法
TreeMap<Integer, String> treemap = new TreeMap<>();
treemap.put(2, "b");
treemap.put(5, "e");
treemap.put(3, "c");
treemap.put(1, "a");
treemap.put(4, "d");
System.out.println(treemap);
System.out.println(treemap.lowerEntry(3));
System.out.println(treemap.lowerKey(3));
System.out.println(treemap.higherEntry(3));
System.out.println(treemap.higherKey(3));
System.out.println(treemap.floorEntry(3));
System.out.println(treemap.floorKey(3));
System.out.println(treemap.ceilingEntry(3));
System.out.println(treemap.ceilingKey(3));
System.out.println(treemap.firstKey());
System.out.println(treemap.lastKey());
System.out.println(treemap.firstEntry());
System.out.println(treemap.lastEntry());

HashTable

key和value不能为空

CancurrentHashMap

线程安全且性能好,其他跟HashMap类似

学习集合的目标

1.会使用集合存储数据
2.关于遍历集合,能够取出数据
3.掌握每种集合的特性

集合框架的学习方式

1.学习顶层接口中共性方法,因为父接口的所有方法子类必须交继承
2.使用顶层,不管接口如何定义,最终实现一定是落在底层的实现类中实现

Hash

hash数列 Tree 树 二叉排序树 中序遍历(红黑树)

集合泛型使用

<>List<XX> xx = new ArrayList<XX>(); 动态泛型 T E

单元测试

测试返回值是否与预期一致

static UserService userservice;
//@After
//@AfterClass
@BeforeClass
public static void init(){
    System.out.println("初始化完成");
    userservice = new UserService();
}
@Test
public void test1(){
    //希望验证login的返回值是否和预期一致
    Assert.assertTrue(userservice.login());
}
@Test
public void test2(){
    Assert.assertNotNull(userservice.getDfault());
}

文件类与IO流

File

1.使用绝对路径创建文件对象

File file=new File("C:/Users/Administrator/Desktop/作业/艾瑞/JavaDemo/jzy.txt");
System.out.println(file);

2.相对路径 相对于当前项目的路径创建文件对象

File file1=new File("C:/Users/Administrator/Desktop/作业/艾瑞/JavaDemo/jzy.txt");
System.out.println(file);

3.基于指定目录作为当前文件的父目录

File file2=new File("C:/Users/Administrator/Desktop/作业/艾瑞/JavaDemo");
File file3=new File(file2,"b.txt");
System.out.println(file3.getAbsolutePath());

方法

exists

检查文件是否存在

System.out.println(file.exists());
isDirectory

检查文件是否为目录

System.out.println(file.isDirectory());
isFile

检查文件是否为目录

System.out.println(file.isFile());
length()

获取文件的大小

System.out.println(file.length());
获取和设置文件的修改时间
long time=file.lastModified();
Date d=new Date(time);
System.out.println(d);
//设置文件修改时间
file.setLastModified(0);
文件重命名
File newfile=new File("C:/Users/Administrator/Desktop/作业/艾瑞/JavaDemo/zsy.txt");
file.renameTo(newfile);
System.out.println(file);
获取文件父目录名称
String parent=file.getParent();
System.out.println(parent);
获取父目录的File对象
File parentFile=file.getParentFile();
System.out.println(parentFile);
list()

以字符串数组的方式 返回当前目录下所有文件的文件名称

String[] list=parentFile.list();
System.out.println(Arrays.asList(list));
listFile()

以File数组的方式 返回当前目录下所有文件的文件对象<NEWLINE>

File[] files=parentFile.listFiles();
mkdir

创建文件对象路径所对应的目录 如果该目录的父目录不存在 则无法创建

File mkdir=new File("C:/Users/Administrator/Desktop/作业/艾瑞/JavaDemo/cz");
mkdirs

创建文件对象路径所对应的目录 如果父类不存在则一并创建

mkdir.mkdirs();
creatNewFile()

创建一个空白文件,在创建文件之前需要保证文件的父目录存在

file.getParentFile().mkdirs();
file.createNewFile();
Scanner sc = new Scanner(System.in);
System.out.println("请输入目录:");
String directory = sc.nextLine();
File file=new File(directory);
for(int i=0;i<100;i++){
    File f=new File(directory,Sort.getContent());
    f.createNewFile();
}
delete

删除文件
无法删除有文件的目录文件,目录必须为空才能删除

deleteOnExit()

在虚拟机退出时删除文件 一般用于临时文件的删除

I/O流

I input
O output
流 Stream

InputStream OutputStream

字节流

FileInputStream
 @Test
    public void testInputStream() {
        try{
            //创建文件对象
            File file=new File("C:/Users/Administrator/Desktop/作业/艾瑞/JavaDemo/ljy.txt");
            //创建基于文件对象的字节输入流
            FileInputStream fis=new FileInputStream(file);
            //准备一个字节数组 字节数组的长度和要读取的字节数一致
            byte[] b=new byte[(int)file.length()];
            //将数据从字节输入流中读取到数组中
            fis.read(b);
            //遍历读取结果
            for(byte b1:b){
                System.out.print(b1+"\t");
            }
            //使用完成之后需要对流进行关闭节约资源
        }catch(IOException e){
            e.printStackTrace();
        }
    }
FileOutputStream
public void testInputStream() {
        try{
            //创建文件对象
            File file=new File("C:/Users/Administrator/Desktop/作业/艾瑞/JavaDemo/ljy.txt");
            //创建基于文件对象的字节输入流
            FileInputStream fis=new FileInputStream(file);
            //准备一个字节数组 字节数组的长度和要读取的字节数一致
            byte[] b=new byte[(int)file.length()];
            //将数据从字节输入流中读取到数组中
            fis.read(b);
            //遍历读取结果
            for(byte b1:b){
                System.out.print(b1+"\t");
            }
            //使用完成之后需要对流进行关闭节约资源
        }catch(IOException e){
            e.printStackTrace();
        }
    }
文件下载
public void testDownloadFile(){
        HttpURLConnection connection=null;
        InputStream is=null;
        FileOutputStream fos=null;
        String urlPath="https://dldir1.qq.com/qqfile/qq/QQNT/Windows/QQ_9.9.16_241112_x64_01.exe";
        try{
            //定义下载链接对象(统一资源定位符)
            URL url=new URL(urlPath);
            //获取httpURLConnection对象
            connection=(HttpURLConnection)url.openConnection();
            //根据链接获取对应的输入流
            is=connection.getInputStream();
            //定义下载文件的文件对象
            File file=new File("C:/Users/Administrator/Desktop/作业/艾瑞/JavaDemo/"+urlPath.substring(urlPath.lastIndexOf("/")+1));
            //定义对应的输出流
            fos=new FileOutputStream(file);
            //定义缓存数组
            byte[] buffer=new byte[1024];
            int len;
            while((len=is.read(buffer))!=-1){
                fos.write(buffer,0,len);
            }
            System.out.println("下载完毕!");
        }catch(Exception e){
            e.printStackTrace();
        }
    }

字符流

FileReader
public void testFileReader(){
        File f=new File("C:/Users/Administrator/Desktop/作业/艾瑞/JavaDemo/ljy.txt");
        try(FileReader fr=new FileReader(f)){
            char[] c=new char[(int)f.length()];
            fr.read(c);
            for(char c1:c){
                if(c1=='\u0000'){
                    break;
                }
                System.out.print(c1);
            }
        }catch(IOException e){
            e.printStackTrace();
        }
    }
FileWriter
public void testFileWriter(){
        File f=new File("C:/Users/Administrator/Desktop/作业/艾瑞/JavaDemo/ljy.txt");
        try(FileWriter fw=new FileWriter(f,true)){
            String str="爱笑的男生运气不太差哦";
            fw.write(str.toCharArray());
        }catch(IOException e){
            e.printStackTrace();
        }
    }

缓存流

java帮我们封装了缓存的操作,减少了IO操作的次数,提高了磁盘性能

BufferedReader
@Test
    public void testBufferedReader(){
        File f=new File("C:/Users/Administrator/Desktop/作业/艾瑞/JavaDemo/ljy.txt");
        try(FileReader fr=new FileReader(f)){
            BufferedReader br=new BufferedReader(fr);
            while(true){
                String line=br.readLine();
                if(line==null){
                    break;
                }
                System.out.println(line);
            }
        }catch(IOException e){
            e.printStackTrace();
        }
    }
PrintWriter

只能操作文本

@Test
    public void testPrintWriter(){
        File f=new File("C:/Users/Administrator/Desktop/作业/艾瑞/JavaDemo/ljy.txt");
        try(FileWriter fw=new FileWriter(f,true);
        PrintWriter pw=new PrintWriter(fw)){
            pw.println("whw觉得今天作业甚少" );
            pw.println("管老师懂了");
            pw.println("这就超级加倍");
            //pw.flush();
        }catch(IOException e){
            e.printStackTrace();
        }
    }

数据流

用于需要进行标准格式化输入输出的场景,即指定数据类型传输 后续会在socket编程中使用到

DataInputStream

数据输入流只能读取由数据输出流所写入的数据,否则就会出现EOFException

@Test
    public void testDataInputStream(){
        File f=new File("C:/Users/Administrator/Desktop/作业/艾瑞/JavaDemo/ljy.txt");
        try(FileInputStream fis=new FileInputStream(f);
        DataInputStream dis=new DataInputStream(fis)){
            System.out.println(dis.readUTF());
            System.out.println(dis.readBoolean());
            System.out.println(dis.readInt());
        }catch (IOException E){
            E.printStackTrace();
        }
    }
DataOutputStream
@Test
    public void testDataOutputStream(){
        File f=new File("C:/Users/Administrator/Desktop/作业/艾瑞/JavaDemo/ljy.txt");
        try(FileOutputStream fos=new FileOutputStream(f);
            DataOutputStream dos=new DataOutputStream(fos)){
            dos.writeUTF("whw觉得今天作业很少");
            dos.writeBoolean(true);
            dos.writeInt(100);
        }catch(IOException e){
            e.printStackTrace();
        }
    }

对象流

序列化

将Java对象转化为特殊的二进制字节码或者字符串的过程

反序列化

将特殊的二进制字节码合作和字符串还原成Java对象的过程

ObjectOutputStream
ObjectInputStream

JDBC

java用于数据库可以和java连接的一套接口规范
JDBC java database connection

常用接口

Connection

JDBC 第一步
加载驱动

Class.forName("com.mysql.jdbc.Driver");
System.out.println("驱动加载成功");

第二步
获取连接

String jdbcurl="jdbc:mysql://localhost:3307/nuist?characterEncoding=utf8";
String username="root";
String password="a12345";
Connection c = DriverManager.getConnection(jdbcurl, username, password);
System.out.println("获取连接成功");

第三步
获取编译语句 并准备sql语句

Statement s = c.createStatement();
String sql =" insert into student(name,age)values('yzx',18)";

第四步 语句
执行insert delete update 使用execute方法
如果执行查询语句 使用executeQuery

s.execute(sql);

Statement 编译语句对象存在sql注入和效率低下的问题
先传参 再编译

select * from user where username ='%s' and password = 'a' or 1=1;

PreparedStatement 预编译语句 可以有效防止Sql注入攻击问题
先编译 再传参 你传入的所有参数 不管是否包含sql语句的关键字 都只会被当做字符串进行处理
Statement由于先传参再编译的特性 导致哪怕相同的sql语句 都需要重复编译
假设要连续执行1w条insert语句 Statement就会编译1w次
如果是PreparedStatement 只需要编译一次

try(Connection c=DBUtil.getConnection();
    PreparedStatement ps=c.prepareStatement(sql);){
    ps.setInt(1,id);
    //结果集 类似于数据库的游标
    ResultSet rs=ps.executeQuery();
    if(rs.next()){
    s.setId(rs.getInt("id"));
    s.setName(rs.getString(2));
    s.setAge(rs.getInt("age"));
    return s;
    }
}catch (Exception e){
    e.printStackTrace();
}

规范

ORM object relation model
一个实体类映射数据库的一张表
一个属性映射表中的一个字段
java中的一个实体类对象映射表中的一行数据
java中的一个集合 例如List<Student>映射表中的多行数据

线程

单核单CPU

单一进程 轮询机制

多线程

线程对象创建方式
1.继承Thread类
2.实现Runnable接口
3.实现Collable接口
4.实现Future接口
5.从线程池中后驱线程对象
6.使用匿名内部类快速创建线程对象

继承Thread类

public class TestThread extends Thread {
    public TestThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("老师作业太多了,能不能加一点。来自:"+getName());
        }
    }
}

线程对象在进入到启动状态之后会自动调用run方法
但是开发者不可以使用t1.run()的方式调用线程的run方法 这样的调用会失去线程特性
java中 所有的线程需要被JVM分配运行资源才可以自动执行run方法
所有的线程需要调用start方法来进入就绪态
只有就绪态的线程 才有机会被JVM分配运行资源并执行线程run方法

public static void main(String[] args) {
TestThread t1=new TestThread("王宏伟");
TestThread t2=new TestThread("靳振宇");
TestThread t3=new TestThread("李佳雨");
t1.start();
t2.start();
t3.start();
System.out.println("main方法对应的主线程运行结束");
}

实现Runnable接口

Runnable的实现类不可以直接用于创建线程 需要借用Thread类进行实例化

public class TestThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("太好了,牢管让我少做20题牛客网,我是:"+Thread.currentThread().getName());
        }
    }
}

public class Test {
    public static void main(String[] args) {
        TestThread tt1 = new TestThread();
        Thread t1 = new Thread(tt1);
        TestThread tt2 = new TestThread();
        Thread t2 = new Thread(tt2);
        TestThread tt3 = new TestThread();
        Thread t3 = new Thread(tt3);
        t1.setName("李佳雨");
        t2.setName("靳振宇");
        t3.setName("王宏伟");
        t1.start();
        t2.start();
        t3.start();

    }
}

使用匿名内部类创建线程对象

本质还是继承Thread 换了个写法

public static void main(String[] args) {
    Thread t1 = new Thread() {
        public void run() {
            for (int i = 1; i <= 10; i++) {
                System.out.println("牛客网+20题,我是" + this.getName());
            }
        }
    };
    t1.setName("王宏伟");
    Thread t2 = new Thread() {
        public void run() {
            for (int i = 1; i <= 10; i++) {
                System.out.println("牛客网+20题,我是" + this.getName());
            }
        }
    };
    t2.setName("李佳雨");
    t1.start();
    t2.start();
}

进程休眠

sleep方法会使当前线程暂停运行并且放弃CPU资源
其他线程可以在其sleep的期间内获取CPU的调度资源

public class Test {
    public static void main(String[] args) {
        Thread t1=new Thread(){
            @Override
            public void run() {
                int times=0;
                while(true){
                    try{
                        System.out.println("李佳雨要休眠了");
                        Thread.sleep(100);
                        System.out.println("李佳雨苏醒了");
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread t2=new Thread(){
            @Override
            public void run() {
                while(true){
                    System.out.println("我是徐亮,我竞争到CPU资源了");
                }
            }
        };
        t1.start();
        t2.start();
        System.out.println("主线程结束");
    }
}    

join

类似于插队
被join的线程需要等待其他线程运行结束之后 才会继续运行

public class Test {
    public static void main(String[] args) {
        Thread t1=new Thread(){
            public void run(){
                for(int i=1;i<=10;i++){
                    System.out.println(getName()+"在执行线程方法");
                }
            }
        };
        t1.setName("李佳雨");
        Thread t2=new Thread(){
            public void run(){
                for(int i=1;i<=10;i++){
                    System.out.println(getName()+"在执行线程方法");
                }
            }
        };
        t2.setName("王宏伟");
        t1.start();
        t2.start();
        try {
            //t1线程join了主线程
            //在t1线程运行结束之前 主线程不会运行
            //直到t1线程运行结束 主线程才会从join的位置继续往下运行
            t1.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("主线程执行完毕");
    }
}

优先级

优先级更高的线程有更大的几率优先执行
但是需要在样本数量足够多的情况下才能体现

public class Test {
    public static void main(String[] args) {
//        System.out.println(Thread.MAX_PRIORITY);
//        System.out.println(Thread.MIN_PRIORITY);
        //线程的优先级必须在线程进入到就绪态之前设置才有效
        Thread t1 = new Thread() {
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("徐亮开始run");
                }
            }
        };
        Thread t2 = new Thread() {
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("黄厚宏开始run");
                }
            }
        };
        t1.setPriority(1);
        t2.setPriority(10);
        t1.start();
        t2.start();
    }
}

让步

public class Test {
    public static void main(String[] args) {
        Thread t1 = new Thread(){
            public void run(){
                for(int i=1;i<=10;i++){
                    System.out.println("王宏伟run起来了"+i);
                }
            }
        };
        Thread t2 = new Thread(){
            public void run(){
                for(int i=1;i<=10;i++){
                    System.out.println("李佳雨run起来了"+i);
                    //线程让步
                    Thread.yield();
                }
            }
        };
        t1.start();
        t2.start();
    }
}

守护

守护线程会在所有其他费守护线程运行结束之后停止运行
GC的本质是守护线程

public class Test {
    public static void main(String[] args) {
        Thread t1 = new Thread(){
            public void run(){
                int times=0;
                while(true){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println("守护线程已经执行了"+(++times)+"秒");
                }
            }
        };
        Thread t2 = new Thread(){
            public void run(){
                for(int i=1;i<=10;i++){
                    System.out.println("我是线程t2,我运行的循环次数为:"+i);
                }
            }
        };
        //将t1标记为守护线程
        t1.setDaemon(true);
        t1.start();
        t2.start();
        //主线程sleep20000ms 延迟主线程运行时间
        System.out.println("主线程开始sleep");
        try{
            Thread.sleep(20000);
        }catch(Exception e){
            e.printStackTrace();
        }
        System.out.println("主线程运行结束");
    }
}

t1为t2和主线程的守护线程,在t2和主线程跑完前t1会一直跑,即使是死循环t1也会在t2和主线程跑完后结束

线程安全

悲观锁

public class Ticket {
    private Integer ticketNo;
}
public class TicketShop {
    private List<Ticket> ticketBox;
    public TicketShop() {
        //对票箱进行初始化 并且装满票
        ticketBox = new ArrayList<>();
        for (int i = 1; i < 6; i++) {
            ticketBox.add(new Ticket(i));
        }
    }
    public synchronized Ticket getTicket() {
        if(ticketBox.isEmpty()){
            return null;
        }
        Ticket targetTicket = ticketBox.get(0);
        ticketBox.remove(targetTicket);
        return targetTicket;
    }
}
public class IKUNThread extends Thread {
    private TicketShop ticketShop;

    public IKUNThread(TicketShop ticketShop, String name) {
        super(name);
        this.ticketShop = ticketShop;
    }
    
    public void run() {
        synchronized (ticketShop) {
            Ticket ticket = ticketShop.getTicket();
            if (ticket != null) {
                System.out.println("ikun" + getName() + "抢到了" + ticket);
            }
    }
}
public class Test {
    public static void main(String[] args) {
        TicketShop ticketShop = new TicketShop();
        List<IKUNThread> IKUNThreads = new ArrayList<>();
        for(int i = 1; i <= 500; i++){
            IKUNThreads.add( new IKUNThread(ticketShop,"IKUN"+i));
        }
        for(IKUNThread ikunThread : IKUNThreads){
            ikunThread.start();
        }
    }
}

ticketShop是多线程所竞争的资源对象,被多线程竞争的资源对象也叫做同步对象
每一个同步对象都有一个锁,在总线程在进入到synchronized代码块之后就会获得这个同步对象的锁
锁会在线程执行完被synchronized锁修饰的方法运行完成之后被释放
其他线程在没有持有锁的情况下无法进入到synchronized代码块中,这种情况叫被阻塞

线程的交互

三个方法 wait notify notifyAll
这三个方法并不是通过线程调用的,而是通过同步对象调用的。而任意对象都可以定义为同步对象,所以这三个线程交互的方法都定义在Object类中

wait方法的作用是让占有着当前同步对象锁的线程 进入等待状态并且释放锁
wait方法一般只出现在synchronized代码块中
处于wait状态的线程如果没有同步对象调用notify方法唤醒 就会一直处于wait状态

notify作用是通知一个被当前同步对象wait的线程苏醒过来

notifyAll作用是通知所有被当前同步对象wait的线程全部苏醒过来

蛮王大招

public class Monster {
    public String name;
    public int hp;

    public synchronized void recover() {
        hp += 1;
        System.out.println("为"+name+"回复了一点生命值。当前生命值为:"+hp);
        this.notify();
    }

    public void hurt() {
        synchronized (this) {
            if(hp==1){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            hp -= 1;
            System.out.println("对"+name+"造成了一点伤害。当前生命值为:"+hp);
        }
    }
}
public class Test {
    public static void main(String[] args) {
        Monster monster = new Monster();
        monster.name="哥布林";
        monster.hp=500;
        Thread t1 =new Thread(){
            public void run(){
                while(true){
                    monster.hurt();
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread t2 =new Thread(){
            public void run(){
                while(true){
                    monster.recover();
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t1.start();
        t2.start();
    }
}

生产者消费者模型

public class Egg {
    private Integer id;
}
/*
需要提供一个容器进行装蛋
提供一个取蛋和装蛋的方法
 */
public class Basket {
    int index;
    //定义蛋容器
    Egg[] arrEgg=new Egg[10];
    //装蛋方法
    public synchronized void pushEgg(Egg egg) {
        while(index==arrEgg.length){
            //如果篮子满了,则让装蛋的哥哥线程进入wait状态
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.notify();
        //将当前蛋放入到篮子中
        arrEgg[index]=egg;
        index++;
    }
    public synchronized Egg popEgg() {
        while(index==0){
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.notify();
        index--;
        return arrEgg[index];
    }
}
public class Producer implements Runnable {
    Basket bt;
    public Producer(Basket bt) {
        this.bt = bt;
    }
    public void run() {
        //假设哥哥一共就下20个蛋
        for (int i = 0; i < 20; i++) {
            Egg egg = new Egg(i);
            bt.pushEgg(egg);
            System.out.println("哥哥新下了一个蛋:"+egg);
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Consumer implements Runnable {
    Basket bt;
    public Consumer(Basket bt) {
        this.bt = bt;
    }
    public void run() {
        //先假设单一ikun一次性也取20个蛋
//        for(int i=0;i<20;i++){
            Egg egg = bt.popEgg();
            System.out.println("ikun拿到了哥哥的蛋"+egg);
//        }
    }
}
public class Test {
    public static void main(String[] args) {
        Basket basket = new Basket();
        Producer p1=new Producer(basket);
//        Consumer c1=new Consumer(basket);
        new Thread(p1).start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
//        new Thread(c1).start();
        List<Runnable> consumers = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            consumers.add(new Consumer(basket));
        }
        for(Runnable r : consumers) {
            new Thread(r).start();
        }
    }
}

线程池

线程池的思路和生产者很接近
规定生产者负责踢出任务 消费者负责记录任务
1.准备一个任务容器
2.一次性启动10个消费者线程(固定数量)
3.刚开始的时候任务容器是空的,所有的消费者线程wait在这个任务容器上
4.直到生产者线程向这个任务容器中丢了一个任务,就会有一个对应的消费者线程被唤醒
5.消费者线程取出任务,并且执行,执行之后继续wait,等待下一次任务的来临

这个模型中,消费者线程是不需要创建新的,而是循环使用已经存在的消费者线程

public class ThreadPool {
    //线程池的大小
    int threadPoolSize;
    //定义任务容器 任务容器中的任务 也应该是线程类型
    LinkedList<Runnable> tasks = new LinkedList<>();

    public ThreadPool() {
        //初始化线程池的大小
        threadPoolSize = 10;
        //启动所有的消费者线程
        synchronized (tasks) {
            for (int i = 0; i < threadPoolSize; i++) {
                new TaskConsumeThread("消费者线程" + i).start();
            }
        }
    }

    //提供add方法 用于消费者向线程池中添加任务
    public void add(Runnable r) {
        synchronized (tasks) {
            tasks.add(r);
            tasks.notifyAll();
        }
    }

    //定义内部类 用来表示消费者线程
    class TaskConsumeThread extends Thread {
        public TaskConsumeThread(String name) {
            super(name);
        }

        //定义内部类的成员变量 用于表示当前所执行的任务
        Runnable task;

        @Override
        public void run() {
            System.out.println(this.getName() + "启动了");
            //保证消费者线程可以永久运行
            while (true) {
                //保证对象线程安全
                synchronized (tasks) {
                    //做容器是否为空的检查
                    while (tasks.isEmpty()) {
                        //保证被唤醒的线程从wait出继续运行的时候 能够重新判断任务容器是否为空
                        //直到任务容器非空的时候 才会脱离循环
                        try {
                            tasks.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //当任务容器不为空 脱离循环之后 从任务容器中取出一个任务 并进行消费
                    task = tasks.removeLast();
                    //唤醒其他被wait的线程;
                    tasks.notifyAll();
                }
                System.out.println(this.getName() + "正在执行任务");
//                new Thread(task).start();
                task.run();
            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        //实例化线程池对象
        ThreadPool pool = new ThreadPool();
        //定义若干个任务线程 用于后续添加到线程池中
        for (int i = 0; i < 5; i++) {
            Runnable task = new Runnable() {
                public void run() {
                    System.out.println("我是一个任务线程,我莫得感情");
                }
            };
            //添加任务到线程中 交给线程池提供的消费者线程进行处理
            pool.add(task);
        }

    }
}
public class TestThread {
    public static void main(String[] args) {
        ThreadPool threadPool = new ThreadPool();
        int sleep=1000;
        while(true){
            threadPool.add(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程池执行任务");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            //向线程池中丢入任务 并且不断减少投递任务的间隔
            try {
                Thread.sleep(sleep);
                sleep=sleep>100?sleep-100:100;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

数据库连接池

传统JDBC Connection连接创建带来的问题

1.连接的创建和关闭都是非常消耗性能
2.单一数据库同时支持的连接总数是有限的

如果多线程的并发量特别大,数据库的连接总数就会消耗光
后续线程发起的数据库连接就会失败

数据库连接池的原理和线程池类似
预先准备一定数量的Connection连接对象
当有用户需要使用的时候 从连接池取出一个连接使用
当用户用完之后 将连接归还
如果连接池的连接被借用完了 则其他需要连接的线程wait
直到有空余连接 再notify唤醒这些线程

public class ConnectionPool {
    List<Connection> cs = new ArrayList<>();
    int size = 0;

    public ConnectionPool(int size) {
        this.size = size;
        init();
    }

    public void init() {
        try {
            Class.forName("com.mysql.jdbc.Driver");
            for (int i = 0; i < size; i++) {
                Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3307/nuist?characterEncoding=utf8", "root", "a12345");
                cs.add(c);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public synchronized Connection getConnection() {
        while(cs.isEmpty()){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        Connection c = cs.remove(0);
        return c;
    }
    public synchronized void returnConnection(Connection c) {
        cs.add(c);
        this.notifyAll();
    }
}
public class Test {
    //初始化一个有三条连接的数据库连接池
    //创建100个线程 每个线程都会从连接池中借用连接
    //并且在用完之后归还连接 在拿到连接之后 执行一个耗时1s的sql语句
   static class WorkThread extends Thread {
        private ConnectionPool cp;

        public WorkThread(String name, ConnectionPool cp) {
            super(name);
            this.cp = cp;
        }

        public void run() {
            Connection c = cp.getConnection();
            System.out.println(this.getName() + ":获取到一个连接,并开始工作");
            try (PreparedStatement ps = c.prepareStatement("select * from student")) {
                ps.executeQuery();
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            cp.returnConnection(c);
        }
    }

    public static void main(String[] args) {
        ConnectionPool cp=new ConnectionPool(3);
        for (int i = 0; i < 100; i++) {
            new WorkThread("线程"+i,cp).start();
        }
        //实际开发的时候我们使用阿里的druid连接池 只需要我们进行配置 不需要再去操心连接的创建和关闭
    }
}

Lock接口

悲观锁的另一种实现方式,和synchronized类似
但是比synchronized更加灵活,可以指定锁对象,并且可以释放锁对象,
可以不阻塞线程,可以不加锁。

public class Test {
    public static String now() {
        return new SimpleDateFormat("HH:mm:ss").format(new Date());
    }

    public static void log(String msg) {
        System.out.printf("%s %s %s %n", now(), Thread.currentThread().getName(), msg);
    }

    public static void main(String[] args) {
        //实例化Lock对象
        Lock lock = new ReentrantLock();
        Thread t1 = new Thread() {
            public void run() {
                try {
                    log("线程启动");
                    log("视图获取对象锁");
                    lock.lock();
                    log("获取锁成功,并执行5s的业务操作模拟");
                    Thread.sleep(5000);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    log("释放对象锁");
                    lock.unlock();
                }
            }
        };
        Thread t2 = new Thread() {
            public void run() {
                try {
                    log("线程启动");
                    log("视图获取对象锁");
                    lock.lock();
                    log("获取锁成功,并执行5s的业务操作模拟");
                    Thread.sleep(5000);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    log("释放对象锁");
                    lock.unlock();
                }
            }
        };
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        try {
            Thread.sleep(2000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        t2.start();
    }
}
public class Test {
    public static String now() {
        return new SimpleDateFormat("HH:mm:ss").format(new Date());
    }

    public static void log(String msg) {
        System.out.printf("%s %s %s %n", now(), Thread.currentThread().getName(), msg);
    }

    public static void main(String[] args) {
        //lock对象也可以实现线程交互
        //synchronized 使用的是wait notify notifyAll
        //我们需要先通过Lock对象获取一个特殊的Conditon对象
        //调用该对象的await signal signAll方法
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        Thread t1 = new Thread() {
            public void run() {
                try {
                    log("线程启动");
                    log("试图获取锁");
                    lock.lock();
                    log("获取锁成功,并且进行5s的业务模拟操作");
                    Thread.sleep(5000);
                    log("临时释放锁,并且等待");
                    condition.await();
                    log("重新获取锁,并且进行5s的业务模拟操作");
                    Thread.sleep(5000);
                } catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    log("释放锁");
                    lock.unlock();
                }
                log("线程结束");
            }
        };
        Thread t2 = new Thread() {
            public void run() {
                try {
                    log("线程启动");
                    log("试图获取锁");
                    lock.lock();
                    log("获取锁成功,并且进行5s的业务模拟操作");
                    Thread.sleep(5000);
                    log("唤醒等待中的线程");
                    condition.signal();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    log("释放锁");
                    lock.unlock();
                }
                log("线程结束");
            }
        };
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

总结

Lock接口和synchronized区别

1.synchronized是JVM提供的锁 是内置关键字,而Lock是JDK提供的锁,synchronized是隐式锁,Lock是显式锁
2.Lock可以选择性地获取锁,如果获取不到可以放弃 synchronized是不可以的。借助Lock的该特性,可以很好地规避死锁。但是synchronized必须通过谨慎和良好的设计,才能减少死锁发生
3.synchronized在发生异常或者代码块解释的时候,会自动释放锁。但是Lock必须手动释放,如果忘记释放锁,一样会产生死锁。

原子类

原子性操作

AtomicInteger atomicInteger = new AtomicInteger(5);
atomicInteger.getAndIncrement();
ConcurrentHashMap<String,String> concurrentHashMap=new ConcurrentHashMap<>();

volatile

1.保证变量可见性

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
public class Whw extends Thread {
private volatile boolean flag=false;
public boolean isFlag() {
return flag;
}

@Override
public void run() {
try {
Thread.sleep(1000);
}catch (Exception e) {
e.printStackTrace();
}
flag=true;
System.out.println("flag="+flag);
}
}
public class Test {
public static void main(String[] args) {
Whw jerk = new Whw();
jerk.start();
while(true){
if(jerk.isFlag()){
System.out.println("小王有点东西");
break;
}
}
}
}

2.禁止指令重排序
编译器优化的重排序
指令级并行的重排序
内存系统的重排序
as-if-serial 内存屏障 happens-before

创建对象
1.分配内存空间
2.调用构造函数 初始化成员变量值
3.将内存地址返回给引用
1 2 3->1 3 2

Java设计模式

单例模式

三大特点
1.私有构造方法
2.静态的当前类自己的成员变量
3.提供public的获取实例的方法

1.懒汉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class SingleTon {
private static SingleTon instance;
private SingleTon(){
}
public static SingleTon getInstance(){
if(instance == null){
instance = new SingleTon();
}
return instance;
}
}
public class Test {
public static void main(String[] args) {
SingleTon s1=SingleTon.getInstance();
SingleTon s2=SingleTon.getInstance();
System.out.println(s1==s2);//true
}
}

2.饿汉
不存在线程安全问题

public class SingleTon {
    private static SingleTon instance=new SingleTon();
    private SingleTon(){
    }
    public static SingleTon getInstance(){
        return instance;
    }
}

3.双重检查锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SingleTon {
//volatile负责禁止指令重排序
private volatile static SingleTon instance;
private SingleTon() {}
public static SingleTon getInstance() {
if (instance == null) {
synchronized (SingleTon.class) {
if (instance == null) {
instance = new SingleTon();
}
}
}
return instance;
}
}

4.静态内部类(仅知道)

volatile和synchronized区别
volatile适用于以下场景:
某个属性被多个线程所共享
1.其中一个线程修改了改属性,其他线程可以立刻拿到修改后的值,类似于触发器,实现轻量级的数据同步
2.volatile的所有读写操作都是无锁的,不能替代synchronized。正因为无锁,可以节约获取锁盒释放锁的性能,但是不能保证原子性
3.volatile只能作用于变量,并且禁止指令重排序
4.volatile可以使long和double类型的赋值具有原子性
5.volatile在双重检查锁单例模式中,不仅保证了变量的可见性,也禁止了指令重排序,保证了单例的安全性

乐观锁

本质是一种并发控制机制

基本思想
不对数据写入操作加锁,而是在更新数据的时候检查自读取依赖数据是否被其他线程修改。如果数据没有被其他线程修改,则更新成功,否则充实或者进行其他处理

乐观锁的优点在于适合读多写少的场景,但是在并发场景下容易出现大量的重试操作,从而影响性能

乐观锁作用
1.减少锁的竞争
在并发场景中使用乐观锁,所有读写操作都需要加锁,而乐观锁通过假设数据修改很少发生,减少了锁的使用
2.提高系统的吞吐量
减少了锁的使用,多个线程可以同时进行读操作,只有在写操作的时候才会进行版本号的检查,从而提高系统的吞吐量
3.防止数据丢失
通过版本号的检查机制,可以检测到并发写操作

public class OptimisticLock {
    //乐观锁的版本号
    private AtomicInteger version = new AtomicInteger();
    //提供获取版本号的方法
    public int getVersion() {
        return version.get();
    }
    //使用CAS操作来更新版本号
    public boolean compareAndSet(int expectVersion, int newVersion) {
        return version.compareAndSet(expectVersion, newVersion);
    }
}
public class TestLock {
    //创建一个乐观锁的对象
    private static OptimisticLock lock = new OptimisticLock();

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(new Worker(),"线程"+i).start();
        }
    }
    static class Worker implements Runnable {
        @Override
        public void run() {
            int version;
            boolean success;
            do {
                //获取当前版本号
                version = lock.getVersion();
                //使用sleep模拟业务操作
                try {
                    System.out.println(Thread.currentThread().getName() + ":正在执行业务操作...当前版本号为:" + version);
                    Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                //使用CAS操作更新版本号
                success=lock.compareAndSet(version, version+1);
                if(!success){
                    System.out.println(Thread.currentThread().getName()+":版本号冲突,正在重试...");
                }
            } while (!success);
            System.out.println(Thread.currentThread().getName()+"版本号成功更新到了"+(version+1));
        }
    }
}

Socket编程

c/s
client/server
提供强大的功能支持
用户使用成本相对较高(客户端需要更新)

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
public class Server {
public static void main(String[] args) throws IOException {
//创建服务器端 并且指定服务器端的对应窗口
ServerSocket serverSocket=new ServerSocket(8888);
System.out.println("正在8888端口进行监听");
Socket s=serverSocket.accept();
//通过客户端对象获取输入输出流
InputStream is=s.getInputStream();
DataInputStream dis=new DataInputStream(is);
OutputStream os=s.getOutputStream();
DataOutputStream dos=new DataOutputStream(os);
Scanner sc=new Scanner(System.in);
while(true){
String msg= dis.readUTF();
System.out.println("客户端说:"+msg);
String str=sc.nextLine();
dos.writeUTF(str);
}
}
}
public class Client {
public static void main(String[] args) throws Exception {
//新建一个客户端socket对象 并且指定客户端锁连接的服务器端的ip地址和端口
//客户端默认由客户端所在设备随机分配一个端口
Socket s=new Socket("127.0.0.1",8888);
//获取输入输出流
OutputStream os=s.getOutputStream();
DataOutputStream dos=new DataOutputStream(os);
InputStream is=s.getInputStream();
DataInputStream dis=new DataInputStream(is);
Scanner sc=new Scanner(System.in);
while(true){
String str=sc.nextLine();
dos.writeUTF(str);
String msg=dis.readUTF();
System.out.println("收到服务器消息:"+msg);
}
}
}

b/s
browser/server
满足常规的功能需求
使用成本低(有浏览器就行)
不能实现较为复杂的功能(例如大型游戏)

反射

反射提出了一种思想:万物都是对象
类可以是对象,方法、成员变量、方法参数、构造函数等等都可以是对象

类对象 Class
每一个类所对应的类对象 全局唯一(在类加载的时候自动创建,但是只会创建一次)

类对象的三种获取方式

1.通过class方式直接获取

Class class1=Test.class;

2.通过Class.forName()方法获取

Class class2=Class.forName("com.iweb.test.test1.Test");

3.通过该类的对象获取

Class class3=new Test().getClass();

不管通过哪种方式获取的类对象 都是同一个对象(每一个类的类对象全局只加载一次)

System.out.println(class1==class2);

反射可以在不使用new的情况下创建对象

public class Test {
    public static void main(String[] args) throws Exception {
        //传统new创建对象的方式
        Student s=new Student();
        //反射方式创建对象
        //1.获取学生类的反射对象
        Class sClass=Class.forName("com.iweb.test.test2.Test");
        //2.获取无参构造方法的反射对象
        Constructor<Student> c=sClass.getConstructor();
        //3.借助无参构造方法的反射对象进行对象的实例化
        Student s2=c.newInstance();
    }
}

配置优于实现
约定优于配置

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
//配置文件
className=com.iweb.test.test3.Dog
//代码
public interface Animal {
void eat();
}

public class Dog implements Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
public class Person implements Animal{
@Override
public void eat() {
System.out.println("人吃饭");
}
}
public class AnimalUtil {
public static Animal getInstance() throws Exception {
//获取配置文件的文件对象
File animalFile = new File("C:\\idea_workspace\\maven_base\\d1128_java14\\src\\main\\java\\com\\iweb\\test\\test3\\animal.properties");
//实例化Properties类对象 该类专门用于做配置文件的读取
Properties animalProperties = new Properties();
//让Properties类对象读取配置文件
animalProperties.load(new FileInputStream(animalFile));
//获取配置文件中的属性
String className=(String)animalProperties.get("className");
//获取反射对象
Class animalClass = Class.forName(className);
//获取该对象对应的构造函数的反射对象
Constructor<Animal> c = animalClass.getConstructor();
//返回Animal的子类对象
return c.newInstance();
}

}
public class Test {
public static void main(String[] args)throws Exception {
Animal a=AnimalUtil.getInstance();
a.eat();
}
}

反射还可以用于方法调用

public class TJLUtil {
    public void late(){
        System.out.println("tjl迟到了,没有想好理由");
    }
    public void late(String reason){
        System.out.println("tjl迟到了,理由是:"+reason);
    }
}
public class Test {
    public static void main(String[] args) throws Exception {
        //获取工具类的反射对象
        Class tjlUtilClass=Class.forName("com.iweb.test.test4.TJLUtil");
        //获取工具类的构造函数的反射对象
        Constructor<TJLUtil> tjlUtilConstructor=tjlUtilClass.getConstructor();
        //通过反射实例化 得到一个工具类对象
        TJLUtil tjl=tjlUtilConstructor.newInstance();
        //获取工具类中的指定方法(无参)的反射对象
        Method lateMethod=tjlUtilClass.getMethod("late");
        //执行方法
        lateMethod.invoke(tjl);
        //获取工具类中的指定方法(有参)的反射对象
        //在获取有参数的方法的反射对象的时候 需要提供参数类型对应的反射对象
        Method lateMethodWithArg=tjlUtilClass.getMethod("late",String.class);
        //执行这个有参数的late方法
        lateMethodWithArg.invoke(tjl,"请大家喝蜜雪冰城");
    }
}

注解

注释和代码结合的产物 标签

妈妈的字条 冰箱上
to:小王
留言:冰箱里有饭菜,不要点外卖

系统注解

@Override 重写方法

public class Test {
    @Override
    public String toString() {
        return null;
    }
}

@Deprecated 弃用的方法

@SupperessWarnings 抑制警告

public class Test {
    @SuppressWarnings("all")
    public static void main(String[] args) {
        int a=1;
    }
}

@FunctionakInterface 函数式接口
如果接口中有且只有一个抽象方法 那么这个接口就叫做函数式接口
(用于lamda匿名表达式和流式计算)
@Resource 等价于spring的@Autowired 用于对象的自动装配

元注解

用来注解注解的注解

ElementType.TYPE 表示当前注解可以作用于类上
ElementType.MYTHED 表示当前注解可以作用于方法上
ElementType.FIELD 表示当前注解可以作用于成员变量上
ElementType.PARAMETER 表示当前注解可以作用于形参上
ElementType.CONSTRUCTOR 表示当前注解可以作用于构造方法上
ElementType.LOCAL_VARIABLE 表示当前注解可以作用于局部变量上
ElementType.ANNOTATION_TYPE 表示当前注解可以作用于注解上
ElementType.PACKAGE 表示当前注解可以作用于包上

@Target({METHOD,TYPE})
当前注解是一个运行时注解 直到代码运行之后 才会获得注解中的相关信息
RetentionPolicy.SOURCE
表示当前注解只保留在源文件中 编译成class之后自动消失
RetentionPolicy.CLASS
表示当前注解保留在class文件中 但是在代码运行的时候就会自动消失
RetentionPolicy.RUNTIME
表示当前注解保留在class文件中 并且在代码运行的时候也会保留 这种生命周期的设置方便我们通过反射获取注解的值

@Repeatable(jdk1.8新增)当没有该元注解修饰的时候 注解只能在同一个位置出现一次 当某个自定义注解有该元注解修饰的时候,可以在同一个位置出现多次

自定义注解

每一个自定义注解需要通过元注解来定义注解的使用范围和生命周期

@Target({METHOD,TYPE})
表示当前注解可以用于类上或方法上

@Retention(RetentionPolicy.RUNTIME)
当前注解是一个运行时注解 直到代码运行之后,才会获得注解中的相关信息

@Inherited
当前定义的注解在使用的时候,可以被使用该注解的类的子类自动继承

@Documented
当前注解支持使用javadoc生成相关文档

@JDBCConfig

public @interface JDBCConfig {
    String ip();
    int port() default 3306;
    String database();
    String encoding();
    String username();
    String password();
}
@JDBCConfig(ip="localhost",database = "nuist",username = "root",password = "a12345",encoding = "utf8")
public class DBUtil {
    static {
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    public static Connection getConnection()throws Exception {
        //先获取注解对象
        JDBCConfig config=DBUtil.class.getAnnotation(JDBCConfig.class);
        String ip=config.ip();
        int port=config.port();
        String database=config.database();
        String username=config.username();
        String password=config.password();
        String url=String.format("jdbc:mysql://%s:%d/%s?characterEncoding=%s",ip,port,database,config.encoding());
        return DriverManager.getConnection(url,username,password);
    }
}
public class Test {
    public static void main(String[] args) throws Exception {
        System.out.println(DBUtil.getConnection());
    }
}

枚举类

限制参数类型

public enum Season {
    SPRING, SUMMER, AUTUMN, WINTER;
}
public class Test {
    public static void chooseSeason(Season season) {
        switch (season) {
            case SPRING:
                System.out.println("春天");
                break;
            case SUMMER:
                System.out.println("夏天");
                break;
            case AUTUMN:
                System.out.println("秋天");
                break;
            case WINTER:
                System.out.println("冬天");
                break;
            default:
                System.out.println("输入的季节名称有误");
        }
    }

    public static void main(String[] args) {
        chooseSeason(Season.SPRING);
    }
}

Lambda表达式

本质是匿名方法 将方法作为参数进行传递的编程思想
java在运行时候 会将lambda匿名表达式还原匿名内部类
我们最终传递给方法的参数 本质还是StudnetChecker接口的实现类对象

实现的接口只有一个抽象方法

public class Student {
    private Integer age;
    private String name;
}
@FunctionalInterface
public interface StudentChecker {
    public boolean check(Student student);
}
public class Test {
    public static void main(String[] args) {
        Random r=new Random();
        List<Student> stus=new ArrayList<Student>();
        for(int i=0;i<100;i++){
            stus.add(new Student(r.nextInt(100),"student"+i ));
        }
        //使用匿名内部类方式实现StudentChecker接口进行实例化
/*        StudentChecker checker=new StudentChecker() {
            @Override
            public boolean check(Student student) {
                return student.getAge()>=80;
            }
        };
        filter(stus,checker);*/
        //lambda匿名表达式写法
        filter(stus,student -> student.getAge()<80);
    }
    private static void filter(List<Student> stus,StudentChecker checker){
        for(Student stu:stus){
            if(checker.check(stu)){
                System.out.print(stu+"\t");
            }
        }
    }
}
public class Student {
    private Integer age;
    private String name;
}
@FunctionalInterface
public interface StudentChecker {
    public boolean check(Student student);
}
public class Test1 {
    public static void main(String[] args) {
        Random r=new Random();
        List<Student> stus=new ArrayList<Student>();
        for(int i=0;i<100;i++){
            stus.add(new Student(r.nextInt(100),"student"+i ));
        }
        StudentChecker sc=new StudentChecker() {
            @Override
            public boolean check(Student student) {
                return student.getAge()<80;
            }
        };
        //1.使用匿名内部类过滤
        filter(stus,sc);
        //2.使用lambda表达式过滤
        filter(stus,student->student.getAge()<80);
        //3.在lambda表达式中使用静态方法
        filter(stus,student->Test1.checkStudent(student));
        //4.直接引用静态方法
        filter(stus,Test1::checkStudent);
        //5.直接引用对象方法 这种方式要求先实例被引用方法的对象
        Test1 t=new Test1();
        filter(stus,t::ckStudent);
        //6.引用容器中对象的方法 用于进行结果的过滤
        filter(stus,student->student.matched());
        filter(stus,Student::matched);
        //7.通过六十计算的方式进行数据的筛选和操作
        //Stream是一系列的元素 类似于集合
        //管道指的是一系列的流式操作
        //管道分为3个部分
        //1.管道源 在当前demo中 指的是stus集合
        //2.中间操作 每一个中间操作(例如filter) 会返回一个Stream 如果你后续
        //还有其他中间操作 可以继续进行调用
        //3.结束操作 当结束操作执行之后 流相当于就被用光了 无法被操作
        //结束操作必然是流的最后一个操作 结束操作不会返回Stream 而是会返回int float String或者是Collection
        //或者是我们这里所写的forEach 什么都不返回 一般只在结束操作中进行遍历相关的操作
        //特别地 流式计算中涉及到的Stream 和我们前面学习的I/O流的Stream 没有任何关系
        stus.stream()
                .filter(s->s.getAge()<80)
                .filter(s->s.getAge()>60)
                .forEach(s->System.out.println(s+"\t"));
        //所有的Collection集合都支持直接获取管道源
        //数组是没有Stream方法 需要使用特殊方法转换
        
        //当管道源是集合的时候 直接遍历
        stus.stream().forEach(s -> System.out.println(s));
        //当管道源是数组的时候 需要转换之后遍历
        Student[] ss = stus.toArray(new Student[stus.size()]);
        Arrays.stream(ss).forEach(s -> System.out.println(s));
        //中间操作
        //filter 匹配
        //distinct 去重(equals方法结果判断是否相同)
        //sorted 自然排序
        //sorted(Comparator<T>) 按照指定的比较器进行排序
        //limit 截取(保留部分数据)
        //skip 忽略
        //转换为其他形式的流
        //mapToDouble 转换为double流
        //map 转换为任意类型的流
        stus.stream().filter(s->s.getAge()<80).forEach(s->System.out.println(s));
        //利用distinct进行去重 重复判断的标准时equals方法比较的结果
        stus.stream().distinct().forEach(s->System.out.println(s));
        //也可以按照指定的方式进行排序 按照年龄排序
        //sorted中 如果不写lambda表达式 并且管道源中的数据所属的类实现了Comparable接口
        //则按照定义的规则进行排序
        //如果sorted方法中有lambda表达式 则相当于是用lambda写了一个临时的Comparator接口的实现类的重写方法
        stus.stream().
                sorted((s1,s2)->s1.getAge()>=s2.getAge()?1:-1).
                forEach(s->System.out.println(s));
        //也可以保留集合的指定数据
        stus.stream().limit(3).forEach(s->System.out.println(s));
        //也可以忽略前3个
        stus.stream().skip(3).forEach(s->System.out.println(s));
        //中间操作可以有多个 每一层方法调用完之后都会返回Stream对象 所以可以连续调用 这种方法调用也叫做链式调用
        stus
                .stream()
                .sorted((s1,s2)->s1.getAge()>= s2.getAge()?1:-1)
                .limit(10)
                .skip(5)
                .filter(s->s.getAge()>=20)
                .forEach(s->System.out.println(s));
        //结束操作 当进行结束操作的时候 流就被用光了 无法继续操作 所以必须是流操作的最后一步
        //forEach是遍历每一个元素
        //toArray 转换成数组
        //min<Comparator<T>> 取最小元素
        //max<Comparator<T>> 取最大元素
        //count() 总数
        //findFirst 获取第一个元素
        //返回一个数组
        Object[] ss1=stus.stream().toArray();
        Student minAgeStudent=
                stus.stream()
                        .min((s1,s2)->s1.getAge()-s2.getAge())
                        .get();
        //返回年龄最高的学生
        Student maxAgeStudent=stus.stream()
                .max((s1,s2)->s1.getAge()- s2.getAge())
                .get();
        //也可以用于获取流中等数据的总数
        long count=stus.stream().filter(s->s.getAge()>20).count();
        //获取流中数据的第一条数据
        Student firstStudent=
                stus.stream().findFirst().get();
    }
    public boolean ckStudent(Student student){
        return student.getAge()<80;
    }
    public static Boolean checkStudent(Student s){
        return s.getAge()<80;
    }

    private static void filter(List<Student> stus,StudentChecker checker){
        for(Student stu:stus){
            if(checker.check(stu)){
                System.out.print(stu+"\t");
            }
        }
    }
}

流式计算

通过流式计算的方式进行数据的筛选和操作
Stream是一系列的元素 类似于集合
管道指的是一系列的流式操作

管道分为3个部分
1.管道源 在当前demo中 指的是stus集合
2.中间操作 每一个中间操作(例如filter) 会返回一个Stream,如果你后续还有其他中间操作,可以继续进行调用
3.结束操作 当结束操作执行之后 流相当于就被用光了 无法被操作 结束操作必然是流的最后一个操作 结束操作不会返回Stream 而是会返回int float String或者是Collection
或者是我们这里所写的forEach 什么都不返回 一般只在结束操作中进行遍历相关的操作
特别地 流式计算中涉及到的Stream 和我们前面学习的I/O流的Stream 没有任何关系

stus.stream()
        .filter(s->s.getAge()<80)
        .filter(s->s.getAge()>60)
        .forEach(s->System.out.println(s+"\t"));

所有的Collection集合都支持直接获取管道源
数组是没有Stream方法 需要使用特殊方法转换*/
当管道源是集合的时候 直接遍历

stus.stream().forEach(s -> System.out.println(s));

当管道源是数组的时候 需要转换之后遍历

Student[] ss = stus.toArray(new Student[stus.size()]);
Arrays.stream(ss).forEach(s -> System.out.println(s));

中间操作
filter 匹配
distinct 去重(equals方法结果判断是否相同)
sorted 自然排序
sorted(Comparator<T>) 按照指定的比较器进行排序
limit 截取(保留部分数据)
skip 忽略
转换为其他形式的流
mapToDouble 转换为double流
map 转换为任意类型的流

stus.stream().filter(s->s.getAge()<80).forEach(s->System.out.println(s));

利用distinct进行去重 重复判断的标准时equals方法比较的结果

stus.stream().distinct().forEach(s->System.out.println(s));

也可以按照指定的方式进行排序 按照年龄排序
sorted中 如果不写lambda表达式 并且管道源中的数据所属的类实现了Comparable接口,则按照定义的规则进行排序
如果sorted方法中有lambda表达式 则相当于是用lambda写了一个临时的Comparator接口的实现类的重写方法

stus.stream().
        sorted((s1,s2)->s1.getAge()>=s2.getAge()?1:-1).
        forEach(s->System.out.println(s));

也可以保留集合的指定数据

stus.stream().limit(3).forEach(s->System.out.println(s));

也可以忽略前3个

stus.stream().skip(3).forEach(s->System.out.println(s));

中间操作可以有多个 每一层方法调用完之后都会返回Stream对象 所以可以连续调用 这种方法调用也叫做链式调用

stus
        .stream()
        .sorted((s1,s2)->s1.getAge()>= s2.getAge()?1:-1)
        .limit(10)
        .skip(5)
        .filter(s->s.getAge()>=20)
        .forEach(s->System.out.println(s));

结束操作 当进行结束操作的时候 流就被用光了 无法继续操作 所以必须是流操作的最后一步

forEach是遍历每一个元素
toArray 转换成数组
min<Comparator<T>> 取最小元素
max<Comparator<T>> 取最大元素
count() 总数
findFirst 获取第一个元素

返回一个数组

Object[] ss1=stus.stream().toArray();
Student minAgeStudent=
        stus.stream()
                .min((s1,s2)->s1.getAge()-s2.getAge())
                .get();

返回年龄最高的学生

Student maxAgeStudent=stus.stream()
        .max((s1,s2)->s1.getAge()- s2.getAge())
        .get();

也可以用于获取流中等数据的总数

long count=stus.stream().filter(s->s.getAge()>20).count();

获取流中数据的第一条数据

Student firstStudent=
        stus.stream().findFirst().get();