此电脑–>属性 -->高级系统设置 -->环境变量
JAVA_HOME:
C:\Program Files\Java\jdk1.8.0_171
path:
%JAVA_HOME%\bin %JAVA_HOME%\jre\bin
classpath:
.; %JAVA_HOME%\lib; %JAVA_HOME%\lib\tools.jar
MAVEN_HOME:
E:\devEn\apache-maven-3.6.1
类名首字母大写,包名项目名全小写
dir:列出当前目录下的文件以及文件夹
md:创建目录
rd:删除目录
cd:进入指定目录
cd…:回退到上一级
cd\:回退到根目录
del:删除文件
exit:退出dos命令行
定义:被java语言f赋予了特殊含义,用作专门用途的字符串
特点:关键字中所有的字母都是小写
用于定义 | 数据类型 | 的关键字 | ||
---|---|---|---|---|
class | interface | enum | byte | short |
int | long | float | double | char |
boolean | void | |||
用于定义 | 流程控制 | 的关键字 | ||
if | else | switch | case | default |
while | do | for | break | continue |
return | ||||
用于定义 | 访问权限 | 修饰符的 | 关键字 | |
private | protected | public |
用于定义类 | 函数、变量 | 的关键字 | ||
---|---|---|---|---|
abstract | final | static | synchronized | |
用于定义 | 简历实例 | 引用实例 | 的关键字 | |
new | this | super | instanceof | |
用于处理 | 异常的 | 关键字 | ||
try | catch | finally | throw | throws |
用于包的 | 关键字 | |||
package | import | |||
其他修饰 | 符的关键 | 字 | ||
native | strictfp | transient | volatile | assert |
用于定义 | 数据类型 | 值的字面值 | ||
true | false | null |
定义:现有的java版本尚未使用,但是以后的版本可能会作为关键字使用,自己命名标识符的时候要避免使用关键字和保留字。
goto、const
标识符:凡是自己可以起名字的地方都可以叫标识符
比如:类名、变量名、方法名、接口名、包名…
由英文字母大小写,数字0-9、_或$组成,其中数字不可以开头
不可以使用关键字和保留字,但是可以包含
java中严格区分大小写,长度无限制,不可包含空格
java中的名称的命名规范
包名:多单词组成是所有字母都要小写 xxyyyzzz
类名、接口类、多单词组成 首字母要大写 XxxYyyZzz
变量名、方法名:多单词使用组成,第一单词首字母小写,第二个单词开始每个首字母大写 xxYyyZzz
常量名:所有字母都大写。多单词是每个单词用下划线连接XXX_YY_ZZ
java定义的变量格式 数据类型 变量名 = 变量值;
说明:
按照声明的位置分类
成员变量 vs 局部变量
long 类型不加L,会默认为int型
float必须加f
整形常量默认类型为int 浮点型默认为double
当容量小的数据类型的变量与容量大的数据类型做运算时,结果自动提升为容量大的 数据类型
byte、char、short -->int -->long -->float -->double
特别的 :当byte、char、short三种类型的变量做运算时,结果为int型
自动类型提升运算的逆运算
需要使用强转符 ()
强制类型转换可能损失精度
byte、short和char的取值范围交叉,所以需要双向强制转换
double num1 = 2.05;
int num = (int) num1;
//num = 2
Math.random() * (n - m) + m;
//生成[m,n)区间的随机数
最高位为符号位,0为正数,1为负数,负数先除符号位按位取反得到反码,反码加一得到补码,计算机底层存储数据都是用补码
运算符 | 运算 |
---|---|
+ | 正号 |
- | 负号 |
+ | 加 |
- | 减 |
* | 乘 |
/ | 除 |
% | 取余 |
++a | 先加后用 |
a++ | 先用后加 |
–a | 先减后用 |
a– | 先用后减 |
提示:自加自减运算符谁在前边先用谁
符号:=
当“=”两侧数据类型不一致时,可以使用自动类型转换或使用强制类型转换原则进行处理,支持连续赋值
扩展赋值运算符
+= -= /= *= %=
比较运算符 | 运算 | 范例 | 结果 |
---|---|---|---|
== | 相等于 | 4==3 | false |
!= | 不等于 | 4!=3 | true |
< | 小于 | 3<4 | true |
> | 大于 | 4>3 | ture |
<= | 小于等于 | 4<=3 | false |
>= | 大于等于 | 4>=3 | true |
instanceof | 检查是否是类的对象 | “Hello” instanceof String | true |
逻辑运算符 | 运算 | 运算符 | 运算 | 运算符 | 运算 |
---|---|---|---|---|---|
& | 逻辑与 | | | 逻辑或 | ! | 逻辑非 |
&& | 短路与 | || | 短路或 | ^ | 逻辑异或 |
a | b | a&b | a&&b | a|b | a||b | !a | a^b |
---|---|---|---|---|---|---|---|
true | true | true | true | true | true | false | false |
true | false | false | false | true | true | false | true |
false | true | false | false | true | true | true | true |
false | false | false | false | false | false | true | false |
提示:
&和&&的区别
相同点1:&和&&的运算结果相同
相同点2:当符号左边是true时,二者都会执行符号右边的操作
不同点:当符号左边是false时,&继续执行右边的操作,&&不会执行右边的操作
|和||的区别
相同点1:|和||的运算结果相同
相同点2:当符号左边是false时,二者都会执行符号右边的操作
不同点:当符号左边是true时,|继续执行右边的操作,||不会执行右边的操作
运算符 | 运算 | 范例 |
---|---|---|
<< | 左移 | 3<<2=12 |
>> | 右移 | 3>>1=1 |
>>> | 无符号右移 | 3>>>1=1 |
& | 与 | 6&3=2 |
| | 或 | 6|3=7 |
^ | 异或 | 6^3=5 |
~ | 取反 | ~6=-7 |
位运算是对整数的二进制进行运算
假设有两张表 用户表 以及 权限表
来源于 Unix 文件控制权限思想
userID | name | password | promission |
---|---|---|---|
1 | 张三 | 1111 | 15 |
2 | 李四 | 2222 | 12 |
3 | 王五 | 333 | 1 |
modId | info | url | value |
---|---|---|---|
1 | 查看 | 1 | |
2 | 添加 | 2 | |
3 | 修改 | 4 | |
4 | 删除 | 8 |
//假如要判断李四是否有修改权限
if(lisi.promission & 4 != 0)
假设李四的权限是: XXXX XXXX
与4进行与运算 &: 0000 0100 //如果李四有修改权限的 4的二进制位就会变成1,
结果 : 0000 0100 //4
//原理:与运算按二进制位运算 如果两个值都是1则为1
//要给王五添加修改权限
if(wangwu.promission | 4 != 0)
假设王五的权限是: XXXX XXXX
与4进行或运算 |: 0000 0100 //如果王五没有修改权限 4的二进制位就会变成1
结果 : XXXX X1XX
//原理:或运算安二进制位 有1则为1
//删除莫个人的修改权限
if(zhangsan.promission ^ -5 != 0)
假设王五的权限是: xxxx xxxx
与4进行异或预算: 0000 0100 //如果张三有修改权限的话 4的二进制位就会变成0
结果 : XXXX X0XX
//原理:异或运算 两个都一样的情况下才会变成0
//有一个int型的数字 XXXXXXXX YYYYYYY ZZZZZZZZ WWWWWWWW
//如果想要后八位 WWWWWWWW
需要将int型 & 1111 1111
xxxx xxxx yyyy yyyy zzzz zzzz wwww wwww
& 0000 0000 0000 0000 0000 0000 1111 1111
//进行与运算 0000 0000 0000 0000 0000 0000 wwww wwww
//想要得到zzzz zzzz的值 先将int型的数字右移八位在与 1111 1111进行与运算
xxxx xxx yyyy yyyy zzzz zzzz wwww wwww >> 8
//溢出之后w位的就不要了 变成 xxxx xxxx yyyy yyyy zzzz zzzz
xxxx xxxx yyyy yyyy zzzz zzzz
& 0000 0000 0000 0000 1111 1111
//进行与运算 0000 0000 0000 0000 zzzz zzzz
拓展:
50 << 1 = 50 * 2;
import java.util.Scanner;
public class Test1{
public static void main(String[] args){
System.out.println("input");
Scanner sc = new Scanner(System.in);
int n = Integer.parseInt(sc);
check(n);
}
public static void check(int num){
if( (num & num -1) == 0 ){
//因为num 和 num-1 的二进制数是完全相反的,所以进行与运算的时候,如果是0,则代表是二的次幂
//如果不是0的话,代表这个数与减一的数有一样的,所以就不是而的次幂
System.out.println("shi");
}else{
System.out.println("fou");
}
// 8 0000 1000
// 7 0000 0111
//4 0000 0100
//3 0000 0011
//16 0001 0000
//15 0000 1111
}
}
int m = 10;
int n = 12;
String maxStr = (m > n) ? "m大" : "n 大";
//如果m = n
// String maxStr = (m > n) ? "m大" : ((m == n) ? "m = n" : "n大");
3. 凡是可以用三元运算符的低档都可以用 if -else,反之不行,如果两者都可以使用的话,优先使用 三元运算符,比较简洁高效
==:运算符
public boolean equals(Object obj){
return (this == obj);
}
Object类中定义的equals()和== 的作用是相同的,比较两个对象的地址值是否相同,,即两个引用是否指向同一个对象实体 ;
public class EqualsTest{
public static void main(String[] args){
int i = 1;
int j = 1;
double d = 1.0;
System.out.println(i == j);//true
System.out.println(i == d);//true
char c = 1;
System.out.println(i == c); //true
char c1 = 'A';
char c2 = 65;
System.out.println( c1 == c2);//true
Customer user1 = new Customer("Tom",21);
Customer user2 = new Customer("Tom",21);
System.out.println( user1 == user2);//false
String str1 = new String("Test");
String str2 = new String("Test");
System.out.println( str1 == str2);//false
System.out.println( user1.equals(user2)); //false
System.out.println( str1.equals(str2)); //true
}
}
//自动创建一个Customer类
//自动生成get 和set方法
//生成一个空参和一个带参构造器
//*************************************
@Override
public boolean equals(Object obj){
if( this == obj){
return true;
}
if( obj instanceof Customer){
Customer cust = (Customer)obj;
return this.age == cust.age && this.name.equals(cust.name);
}
return false;
}
Source --> Generate hashCode() and equals()
程序从上到下逐行的执行,中间没有任何的判断和跳转
if(条件表达式){ 执行表达式 }
if(条件表达式){ 执行表达式1 } else { 条件表达式2 }
if(条件表达式1 ){
执行表达式 1
} else if(条件表达式 2){
执行表达式 2
} else{
执行表达式 3
}
// switch 支持的数据类型有 byte short char int string enum
switch(表达式) {
case 条件1: 执行语句 1;
//break;
case 条件2: 执行语句 2;
//break;
default:执行最终条件;
//break;
}
示例:输入月份和日期,输出是2019年的第几天
import java.util.Scanner;
class SwitchCaseTest{
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
System.out.println("请输入2019年的月份");
int month = scan.nextInt();
System.out.println("请输入2019年的日期");
int day = scan.nextInt();
int sumDay = 0;
switch(month){
case 12: sumDay += 30;
//......
case 4: sumDay += 31;
case 3: sumDay += 28;
case 2: sumDay += 31;
case 1: sumDay += day;
}
System.out.println("2019年"+month+"月"+day+"日是当年的第"+sumDay+"天");
}
}
for循环
do{ … } while
while(){ … }
do -while 总是比while 多循环 一次
eg:输出100以内所有的质数
class PrimeNumber{
public static void main(String[] args){
for(int i = 2; i < 100 ; i++){
boolean isFlag = true;
for(int j = 2; j <= Math.sqrt(i);j++){
if(i % j ==0){
isFlag = false;
break; //如果被除尽,跳出
}
}
if( isFlag ){
System.out.print(i+"\t");
}
}
}
}
数组的定义:
数组(Array) 是多个同类型的数据按照一定顺序排列的,并使用 一个名字命名并通过编号的方式对这些数据进行统一的管理.
数组的特点
int[] ids;//声明
ids = new int[]{10001,1002,1003,1004};
//静态初始化
String[] names = new String[2];//动态初始化
//错误的写法
//int[] arr1 = new int[];
//int[5] arr2 = new int[5];
//int[] arr3 = new int[3]{1,2,3};
//总结:
int[] ids;//声明
ids = new int[]{10001,1002,1003,1004};
for(int i = 0; i < ids.length;i++){
....
}
//foreach用法
for(int arr: ids){
System.out.println(arr);
}
public class YangHuiTest{
public static void main(String[] args){
int yangHui[][] = new int[10][];
for(int i = 0;i<yangHui.length;i++){
yangHui[i] = new int[i + 1];
yangHui[i][0] = yangHui[i][i] = 1;
for(int j = 1; j <yangHui[i].length - 1 ;j++){
yangHui[i][j] = yangHui[i - 1][j - 1] + yangHui[i - 1][j];
}
}
//能不能用foreach遍历?
for(int i = 0;i < yangHui.length; i++){
for(int j = 0;j < yangHui[i].length;j++){
System.out.print(yangHui[i][j] + " ");
}
System.out.println();
}
//foreach用法
for(int[] out :yangHui ){
for(int inside:out){
System.out.print(inside +" ");
}
System.out.println();
}
}
}
数组元素的赋值
杨辉三角、回形数等
求数值型数组中的最大值,最小值,平均值,总和
public class RandomTest {
public static void main(String[] args){
int[] arr = new int[10];
for(int i = 0; i < arr.length;i++){
arr[i] = (int)(Math.random() * 100 - 1 + 10);
System.out.print(arr[i] +"\t");
}
System.out.println();
//求数组的最大值
int maxValue = arr[0];
for(int x : arr){
if(maxValue < x){
maxValue =x;
}
}
System.out.println("最大值为:" + maxValue);
//求最小值
int minValue = arr[0];
for(int x : arr){
if(minValue > x){
minValue =x;
}
}
System.out.println("最小值为:" + minValue);
double sum = 0.0d;
for(int x : arr){
sum += x;
}
double ave = sum / arr.length;
System.out.println("总和为:" + sum);
System.out.println("平均值:" + ave);
}
}
数组的复制、反转(线性查找,二分查找)
String[] arr = new String[]{"AA","BB","CC","DD","EE"};
//数组的复制
String[] arr1 = new String[arr.length];
for(int i = 0;i < arr1.length;i++){
arr1[i] = arr[i];
System.out.println(arr[i]);
}
//数组的反转
for(int i = 0;i < arr.length / 2;i++){
String temp = arr[i];
arr[i] = arr[arr.length - 1];
arr[arr.length - 1] = temp;
}
//线性查找
String dest = "BB";
boolean isFlag = true;
for(int i = 0;i < arr.length;i++){
if(dest.equals(arr[i])){
System.out.println("指定元素的索引位置为" + i);
isFlag = false;
break;
}
}
if(isFlag){
System.out.println("没有找到");
}
升级版的双向冒泡(鸡尾酒排序法)点击查看
//冒泡排序
int[] arr = new int[]{43,56,45,78,-12,21,-98,0,12,19};
for(int i = 0;i < arr.length - 1;i++){
for(int j = 0;j < arr.length - i - 1;j++){
if(arr[j] > arr[j + 1]){
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
public static int[] qsort(int arr[],int start,int end) {
int pivot = arr[start];
int i = start;
int j = end;
while (i<j) {
while ((i<j)&&(arr[j]>pivot)) {
j--;
}
while ((i<j)&&(arr[i]<pivot)) {
i++;
}
if ((arr[i]==arr[j])&&(i<j)) {
i++;
} else {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
if (i-1>start) arr=qsort(arr,start,i-1);
if (j+1<end) arr=qsort(arr,j+1,end);
return (arr);
}
public static void main(String[] args) {
int arr[] = new int[]{3,3,3,7,9,122344,4656,34,34,4656,5,6,7,8,9,343,57765,23,12321};
int len = arr.length-1;
arr=qsort(arr,0,len);
for (int i:arr) {
System.out.print(i+"\t");
}
}
/*//方式二*/
更高效点的代码:(TextendsComparable和SortUtil都是自己封装的类,里面重写和实现了compareTo和swap方法)
public <TextendsComparable<?superT>>
T[] quickSort(T[] targetArr,int start,int end){
inti=start+1,j=end;
T key=targetArr[start];
SortUtil<T> sUtil=new SortUtil<T>();
if(start==end)return(targetArr);
/*从i++和j--两个方向搜索不满足条件的值并交换
*
*条件为:i++方向小于key,j--方向大于key
*/
while(true){
while(targetArr[j].compareTo(key)>0)j--;
while(targetArr[i].compareTo(key)<0&&i<j)i++;
if(i>=j)break;
sUtil.swap(targetArr,i,j);
if(targetArr[i]==key){
j--;
}else{
i++;
}
}
/*关键数据放到‘中间’*/
sUtil.swap(targetArr,start,j);
if(start<i-1){
this.quickSort(targetArr,start,i-1);
}
if(j+1<end){
this.quickSort(targetArr,j+1,end);
}
returntargetArr;
}
/*//方式三:减少交换次数,提高效率/*/
private<TextendsComparable<?superT>>
voidquickSort(T[]targetArr,intstart,intend){
inti=start,j=end;
Tkey=targetArr[start];
while(i<j){
/*按j--方向遍历目标数组,直到比key小的值为止*/
while(j>i&&targetArr[j].compareTo(key)>=0){
j--;
}
if(i<j){
/*targetArr[i]已经保存在key中,可将后面的数填入*/
targetArr[i]=targetArr[j];
i++;
}
/*按i++方向遍历目标数组,直到比key大的值为止*/
while(i<j&&targetArr[i].compareTo(key)<=0){
/*此处一定要小于等于零,假设数组之内有一亿个1,0交替出现的话,而key的值又恰巧是1的话,那么这个小 于等于的作用就会使下面的if语句少执行一亿次。*/
i++;
}
if(i<j){
/*targetArr[j]已保存在targetArr[i]中,可将前面的值填入*/
targetArr[j]=targetArr[i];
j--;
}
}
/*此时i==j*/
targetArr[i]=key;//应加判断
/*递归调用,把key前面的完成排序*/
this.quickSort(targetArr,start,i-1);
/*递归调用,把key后面的完成排序*/
this.quickSort(targetArr,j+1,end);
//两个递归应加判
方法 | 作用 |
---|---|
boolean equals(int[] a,int[] b) | 判断两个数组是否相等 |
String toString(int[] a) | 输出数组信息 |
void fill(int[] a,int val) | 用指定值填充数组 |
void sort(int[] a) | 对数组排序 |
int binarySearch(int[] a,int key) | 堆排序后的数组进行二分法检索指定的值 |
数组常见异常
1.数组角标越界:ArrayIndexOutOfBoundsException
2.空指针异常:NullPointerException
二者都是一种思想,面向对象是相对于面向过程而言的。面向过程强调的是功能行为,以函数为最小对象,考虑怎么做。面向对象,将功能封装进对象,强调具备了功能的对象。以类/对象为最小单位,考虑谁来做。
面向对象三大特征
封装(encapsulation)
继承(inheritance)
多态(polymorphism)
类:属性 方法
class Person{
String name;
int age;
boolean isMale;
public void eat(){
System.out.println("人可以吃饭");
}
public void sleep(){
System.out.println("人能睡觉");
}
}
Person p = new Person();
p.name = "Tom";
p.isMale = true;
p.age = 18;
p.eat();p.sleep();
属性 vs 局部变量
在类中声明的位置不同
属性:直接定义在类的{}内
局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量
关于权限修饰符的不同
属性:可以再声明属性是,指明权限,使用权限修饰符。
局部变量:不可以使用权限修饰符。
常见的权限修饰符:private、public、缺省、protected、final
默认初始化值
属性:类的属性,根据其类型,都有默认的初始化值。
类型 | 默认值 |
---|---|
整型byte、short、int、long | 0 |
浮点型double、float | 0.0 |
字符型char | 0 |
Boolean | false |
引用数据类型类、数组、接口 | null |
局部变量:无默认值。
特别的:形参在调用的时候,赋值即可
在内存中加载的位置不一样
属性:加载到堆空间
局部变量:加载到栈空间
按照是否有形参分类
无返回值 | 有返回值 | |
---|---|---|
无形参 | void method(){} | 返回值类型 method(){} |
有形参 | void method(形参列表){} | 返回值类型 method(形参列表){} |
方法的声明:
权限修饰符 返回值类型 方法名(形参列表){
方法体
}
方法名:属于标识符,要见名知意
return关键字的使用
使用在方法中
作用:
结束方法
针对于有返回值的方法,使用return返回想要的数据。
例子1:
public class CircleTest{
public static void main(String[] args){
Circle c1 = new Circle();
c1.radius = 2;
System.out.println("圆的面积为:" + c1.findArea());
System.out.println("矩形的面积为:" + method(12,8));
}
public static void method(int m,int n){
return m * n;
}
}
class Circle{
double radius;
public double findArea(){
return Math.PI * radius * radius;
}
}
例子2:
public class StudentTest{
public static void main(String[] args){
Student[] students = new Student[20];
for(int i = 0;i < students.length;i++){
students[i] = new Student();
students[i].number = (i + 1);
students[i].state = (int)(Math.random() * 6 + 1);
students[i].score = (int)(Math.random() * (100 - 0 + 1) + 1);
}
}
}
class Student{
int number; //学号
int state; //年级
int score; //得分
}
public class InstanceTest{
public static void main(String[] args){
Phone p = new Phone();
p.sendEmail();
p.playGame();
//匿名
new Phone().sendEmail();
new Phone().playGame();
}
}
class Phone{
double price;
public void playGame(){
System.out.println("玩游戏");
}
public void sendEmail(){
System.out.println("发邮件");
}
}
定义
在同一个类中,允许存在一个以上的同名方法,只要他们的参数个数或者参数类型不同即可
public class OverLoadTest{
public static void main(String[] args){
OverLoadTest olt = new OverLoadTest();
System.out.println(olt.getSum(1.2,2.0));
//如不想声明对象,直接输出getSum();需将方法声明为静态(static)方法
//静态方法可以再没有对象实例的情况下直接调用
}
//如下的方法构成了重构
public int getSum(int i,int j){
return i + j;
}
public double getSum(double i,double j){
return i + j;
}
}
public void show(String ... strs){
//TODO
}
//调用可变个数形参时,传入参数随意个
public void show(String ... strs){}
public void show(String[] strs){}
//JDk5.0以前用数组,所以不能同时存在
public void show(int i,String ... strs){}
class Data{
int m;
int n;
}
main(){
Data data = new Data();
data.m = 10;
data.n = 20;
swap(data.m,data.n);
}
swap(){
int temp = data.m;
data.m = data.n;
data.n = temp;
}
class Animal{
//私有化属性,公有化方法
private String name;
private int age;
private int legs;
//自动化生成Getter和Setter方法
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
//当一个属性没有配套的getter、setter方法时直接用属性名当做方法名
public int legs(){//实际上的方法全名是getLegs()
return legs;
}
//JavaBean
}
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | √ | |||
缺省 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
创建对象
发生在两个有继承关系的类中,且发生在子类里边(子类认为父类的方法不适用在子类里边,对父类的方法进行重写)
子类重写的方法的权限修饰符要大于等于父类中方法的权限修饰符
重写方法的返回类型在JDk5.0以前需要一模一样,在JDK5.0以后支持斜变类型
即支持返回其父类的子类类型
子类的异常处理部分要小于等于父类的异常类型
@Override注解,表示下边被注解的方法必须要重写父类的某个方法
方法重载的条件
必须发生在同一个类体
方法名需要完全相同
方法的参数需要不同
参数类型不同
参数个数不同
参数顺序不同
方法重载对返回值类型和修饰符没有要求
方法重载的作用是为了满足用户的不同需求
二者的概念:
重写和重载的具体规则
重载:不表现为多态性
重写表现为多态性
super理解为父类的,可用来调用属性,方法,构造器
super的使用
super调用构造器
从结果来看
子类继承父类以后,就获取了父类中声明的属性或方法
创建子类的对象,在堆空间,就会加载所有父类中声明的属性
从过程来看
通过子类的构造器创建子类对象时,一定会间接或直接的调用其父类的构造器,进而调用父类的父类的构造器,,直接调用了java.lang.Object类的空参构造器为止,因为家在所有父类的结构,所以才可以看到内存中有父类的结构,子类对象才可以考虑进行调用
Person p1 = new Women();
Person p2 = new Men();
多态的使用:虚拟方法调用
有了对象的多态性以后,在编译时,只能调用父类中声明的方法,但是在运行期间,实际执行的是子类重写父类的方法,
编译看左边,执行看右边
多态是运行时行为
将Object类的克隆方法权限改大变成public的,然后用super.clone()调用父类的克隆方法,强转成要返回的类型
将要实现克隆方法的类实现允许克隆的接口**(implements Cloneable)**
在main()方法中调用,并抛出异常
public class Exec1 {
public static void main(String[] args)throws Exception{
OS android = new OS("安卓");
OS flyme = android.clone();
flyme.name = "魅族";
System.out.println(android == flyme);
}
}
class OS implements Cloneable{
String name;
public OS(String name){
this.name = name;
}
@Override
public OS clone() throws CloneNotSupportedException{
return (OS)(super.clone());
}
}
对象的遗言方法,当一个对象要被gc()回收掉的时候回主动执行的一个方法
面试题
final 和 **finalize()**的区别
雷锋和雷锋塔的区别,final是一个修饰符,表示最终的可以用来修饰类,方法,变量
finali是Object类型里边的一个方法,当对象要被回收的时候会主动执行的一个方法
public class TestToString2{
public static void main(String[] args){
Person p1 = new Person("黄晓明",35,'男');
Person p2 = new Person("baby",28,'女');
System.out.println(p1);
System.out.println(p2);
}
}
class Person{
String name;
int age;
char sex;
public Person(String name,int age,char sex){
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString(){
return name + (sex == '男' ? "\t先生" : "\t女士") + age + "岁";
}
}
重写equals()方法
public class ExecEquals1{
public static void main(String[] args){
Food f1 = new Food("猪耳朵拌黄瓜",23,"凉菜");
Food f2 = new Food("猪耳朵拌黄瓜",25,"凉菜");
System.out.println(f1.equals(f2));
}
}
class Food{
String name;
double price;
String type;
public Food(String name,double price,String type){
this.name = name;
this.price = price;
this.type = type;
}
@Override
public boolean equals(Object obj){
if(this == obj) return true;
if(obj == null) return false;
if(obj instanceof Food){
Food food = (Food) obj;
return this.name.equals(food.name) && this.type.equals(food.type);
}
return false;
}
}
将一大组数据分散成不同的小组
特征码服务于集合
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character(不存在parse方法) |
public class WrapperTest{
public static void main(String[] args){
}
@Test
public void test1(){
}
//自动装箱 基本数据类型 --> 包装类
int num1 = 10 ;
Integer in1 = num1;
//自动拆箱 包装类 --> 基本数据类型
int num2 = in1;
//基本数据类型,包装类 --->String类型
int num = 10;
//方式一 连接运算
String str1 = num + "";
//方式二 :调用String的valueOf(Xxx xxx)
float f1 = 1.3;
String str2 = String.valueOf(f1);
//String --> 基本数据类型,包装类
String str3 = "123";
int num4 = Integer.paserInt(str3);
}
例题
Object obj1 = true ? new Integer(1) : new Double(2.0);
System.out.println(ojb1);
//结果为1.0
//三元运算符运算式要保证两个结果类型一致。所以会对Integer类型的进行自动类型提升 变为Double型
//***************************
Object obj2;
if(true)
obj2 = new Inetger(1);
else
obj2 = new Double(2.0);
System.out.println(ojb2);
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println( i == j);//false
//Integer内部定义了IntegerCache的结构,在其中定义了Integer[]
//保存了-128~127范围的整数,如果用自动装箱的方式,给Integer固执的范围在这个范围内,可以直接使用数组的元素,不用new了,提高了效率
Integer m = 1;
Integer n = 1;
System.out.println( m == n);//false
Integer i = 128;
Integer j = 128;
System.out.println( x == y);//false
按照是否使用static修饰可以分为
静态变量(类变量) vs 非静态变量(实例变量)
非静态变量:创建了类的多个对象,每个对象都独立的拥有一套类的非静态属性,改变其中一个对象的非静态属性时,不会导致其他对象同样的属性修改。
静态变量:创建了类的多个对象,多个对象共享一个静态变量,当通过一个对象修改静态变量的值的时候,其他对象调用此变量是,是修改过的。
static修饰的属性
举例
System.out
Math.PI;
类变量 | 实例变量 | |
---|---|---|
类 | 可以调用 | 不可以调用 |
对象 | 可以调用 | 可以调用 |
public class StaticTest1{
public static void main(String[] args){
Chinese c1 = new Chinese();
c1.name = "张三";
c1.age = 20;
Chinese c2 = new Chinese();
c2.name = "李四";
c2.age = 45;
c2.nation = "CHN";
System.out.println(c1.nation);
}
}
class Chinese{
String name;
int age;
static String nation ;
}
静态方法 | 非静态方法 | |
---|---|---|
类 | yes | no |
对象 | yes | yes |
静态方法中只能调用静态的方法或属性
非静态方法中,既可以调用非静态的方法。也可以调用静态的方法。
public class StaticTest2{
publiv static void main(String[] args){
}
}
**static注意点 **
属性
属性可以被多个对象所共享的,不会随着对象的变化而变化的
类中的常量通常定义为static的
方法
操作静态属性的方法,通常设置为静态的
工具类中的方法,通常定义为static的,如Math,Arrarys。
用来初始化类,对象
如果要用修饰符修饰的话只能用static
final修饰的类不可以有子类,但是可以有父类,final修饰的方法不可以被覆盖,但是可以被继承,当final修饰变量的时候,一旦赋值之后就不可以更改
final修饰属性时,保护的是属性的值不受改变,当final修饰的是引用数据类型时,保护的是引用的地址不发生改变
抽象类
抽象类表示这个类不形象不具体,特点是不允许创建对象
抽象类也是类,也有构造方法,但是不可以用来创建对象,是给子类构造器首行的super()调用的
抽象方法表示这个类型一定会这个方法,但是现在给不出来具体的实现,待留给子类去实现当一个类中出现了抽象方法以后,这个类一定变成抽象方法,
public abstract Animal{
//抽象方法的特点是不能有方法体,直接()结束
public abstract void eat();
}
抽象方法里边既可以出现抽象方法又可以出现普通方法
接口相当于工业生产中的规范
接口不允许创建对象但是可以用匿名实现类
class、interface、enum、@interface在编译之后都
可以生成class文件
//如何定义一个接口
interface XXX{
//属性
//接口默认加上三个修饰符
//public static final
int x = 34;
//方法:返回类型 +方法签名
//接口默认加上两个修饰符
//public abstract
void show();
}
两两之间的关系
类和类之间:extends
类和接口之间:implements
接口和就扣之间:extends
java中的类只允许单根继承,但是接口允许多重继承,一个类在继承另外一个类的同时还可以实现多个接口
是java中的两个类型,抽象类属于类是class,接口是interface
里边边定义的属性不一样
抽象类里边定义的属性就是普通属性,最多加上一个default,
接口里边定义的属性都是静态的**[public static final]**
里边定义的方法不一样
抽象类里边既可以定义抽象方法,又可以定义普通方法
接口里边只能定义抽象方法从jdk8.0开始可以出现普通方法了
工厂模式
java中允许把一个类声明在另外一个类中
class Person{
static class Phone{
}
class PC{}
}
// Person.Phnone p = new Person.Phone();
//Person p = new Person();
//Person.PC p1 = p.new PC();
在局部内部类的方法中(show),如果调用局部内部类所声明的方法(method)的局部变量(num),要求变量必须声明成final的
public class Test{
pubilc void method(){
//JDK 7及以前需要显示的声明为final的 8及以后则不需要
final int num = 1;
class AA{
public void show(){
System.out.println(num);
}
}
}
}
如果成员内部类不是静态的,那么内部类中也不能出现静态的属性或方法
匿名内部类创建对象:
new 父类/接口(参数){
抽象方法的具体实现
}
实现单例模式
私有化构造器,避免在其他类调用构造器
内部创建静态的类的对象
提供静态的公共的方法,返回类的对象
//懒汉式
public class Singleton1{
public static void main(String[] args){
Order order1 = Order.getInstance();
Order order2 = Order.getInstance();
Order1 == Order2;//true
}
}
class Order{
//1.私有化构造器
private Order();
//2.声明当前类的对象,没有初始化
//4.此对象也必须声明为静态的(static)
private static Order instance = null;
//3.声明公共的静态的返回当前对象的方法
publicstatic synchronized Order getInstance(){
if(instance == null){
instance = new Order();
}
return instance;
}
}
//饿汉式
public class Singleton{
public static void main(String[] args){
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
bank1 == bank2;//true
}
}
class Bank{
//私有化构造器
private Bank();
//内部创建类的将静态对象
private static Bank instance = new Bank();
//提供公共的静态方法,返回类的对象
public static Bank getInstance(){
return instance;
}
}
好处 | 坏处 | |
---|---|---|
懒汉式 | 延迟对象创建 | 目前的写法是线程不安全的,用synchronized修饰即可 |
饿汉式 | 饿汉式是线程安全的 | 对象加载时间过长 |
java中的异常分为两部分
常见运行时异常 | 大概原因 | 关注点 |
---|---|---|
ArithmeticExecption(算术异常) | 一个整数除以0 | / x |
NegativeArraySizeExecption | ||
异常的处理:抓抛模型
过程一:“抛”,程序在执行的过程中,一旦出现异常,就会在异常代码出生成一个对应的异常类对象,并将此对象抛出,一单抛出一场对象以后,后边的代码就不再执行
过程二:“抓”,可以理解为异常的处理方式。①try - catch - finally ② throws + 异常类型
catch一般会有getMessage()he printStackTrace()一般用这个
try{
//可能出现异常的代码
}catch(异常类型1 变量名){
//处理异常的方式1
}catch(异常类型2 变量名){
//处理异常的方式3
}finally{
//可选的,写不写都可以,写了的话就是一定会执行的代码
}
//JDK 7之后可以用 | 连接
try{
//可能出现的代码
}catch(异常类型1 | 异常类型2 e){
}
try保卫可能会出现异常的代码
catch捕获异常
finally一定会被执行的代码
范围小的异常要放在上边,
①使用try-catch-finally处理编译时异常,使得程序在编译时不在报异常,但是运行是可能报错,相当于讲一个编译时异常延迟到运行时出现了
②由于运行时异常比较常见,所以通常不针对运行时异常编写try- catch - finally,但是针对编译时异常一定要处理
③**finally里永远不要出现return语句,否则try-catch中间的return就不会执行**
如何选择使用哪一种异常处理机制
①如果父类中被重写的方法没有throws方式处理异常,那么子类重写的方法也不能使用throws,所以子类重写的方法只能用try - catch
②执行的方法a中,先后有调用另外的方法,而且是递进关机,建议使用throws方法处理,执行的方法a建议使用try-catch
①系统自动生成的异常对象
②手动生成一个异常对象并抛出**(throw)**
public class MyException extends RuntimeException{
static final long serialVersionUID = -12178941256195L;
public MyException(){
}
public MyException(String msg){
super(msg);
}
}
try{
n.close();
}catch(Exception e){
e.printStackTrace();
}finally{
try{
m.close();
}catch(Exception e){
e.printStackTrace();
}finally{
try{
o.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
当一个类的对象只有有限个,是确定的,我们称此类为枚举类
当需要定义一组常量时,建议使用枚举类
如果枚举类只有一个对象,则可以作为单例模式的实现方式
JDK5.0以前,自定义枚举类
class Season{
//声明Season对象的属性:private final修饰
private final String name;
private final String desc;
//2.私有化构造方法,并给对象赋值
private Season(String name,String desc){
this.name = name;
this.desc = desc;
}
//3.提供当前枚举类的多个对象,声明为public static final
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 getName(){
return name;
}
public String getDesc(){
return desc;
}
}
class Test{
@Test
public void test(){
Season spring = Season.SPRING;
System.out.println(spring);
}
}
JDK5.0.可以使用enum关键字定义枚举类
public class Test{
@Test
public void test(){
System.out.println(Season1.SUMMER);
}
}
enum Season1{
//1.提供枚举类的多个对象,对象之间用 , 隔开 最后一个用 ;
SPRING("春天","春暖花开"),
SUMMER("夏天","夏日炎炎"),
AUTUMN("秋天","秋高气爽"),
WINTER("冬天","冰天雪地");
private final String name;
private final String desc;
public String getName(){
return name;
}
public String getDesc(){
return desc;
}
}
Season1[] v = Season1.values();
for(Season1 s: v){
System.out.println(s);
}
valueOf()
//valueOf(String objName) 返回美剧类中对象名是objName的对象
//如果没有对应的objName的枚举类对象,则抛出异常
System.out.println(Season1.valueOf("WINTER"));
跟普通类一样的实现方法
interface info{
void show();
}
enum t implements info{
w,A;
@Override
public void show(){}
}
每个对象都重写方法
interface info{
void show();
}
enum t implements info{
W{
@Override
public void show(){}
},
A{
@Override
public void show(){}
};
}
生成文档相关的注解
注解 | 作用 |
---|---|
@author | 标明开发该类的作者,多个作者用,分开 |
@version | 标明该类模块的版本 |
@see | 参考 |
@since | 从哪个版本开始 |
@param | 对方法中某个参数的说明,没有参数不能写 |
@return | 对返回值的说明,void 不能写 |
@exception | 对可能抛出的异常说明,没有显式的用throws抛出异常的不能写 |
其中@return @param @exception 只能用于方法
@param 格式要求 @param 形参名 形参类型 形参说明
@return 格式要求 @return 返回值类型 返回值说明
@execption 格式要求 @execption 异常类型 异常说明
@param @execption 可以并列多个
在编译时进行格式检查(JDK内置的三个基本注解)
注解 | 含义 |
---|---|
@Override | 限定重写父类方法,只能用于方法 |
@Deprecated | 表示修饰的元素已经过时了 |
@SuppressWarnings | 抑制编译器警告 |
如何自定义注解
//声明为@interface
public @interface AnnotationTest{
String[] value();
}
//自定义注解通常会指明两个元注解,一折Retention指明生命周期,一个Target指明能够声明哪些结构
JDK中的四种元注解
Retention、Target、Documentd、Inherited
是对现有注解进行解释说明的注解
- @Retention:指明注解的生命周期,SOURCE \ CLASS (默认行为) \ RUNTIME ,只有声明为RUNTIME生命周期的注解,才能通过反射获取
- @Target: 指明注解可以声明可以哪些结构
- @Documented:表示所修饰的注解在javadoc解析时保留下来
- @Inherited 被修饰的注解有继承性
如何创建迭代器
Iterator<类型> i = list.iterator();
使用迭代器遍历集合
for(Iterator<类型> i = list.iterator();i.hasNext();){
}
//for循环不通用
//foreach遍历集合的同时删除集合里边的元素,会报错CME 并发修改异常
//所以需要用迭代器自己的remove()方法
如何创建一个迭代器
Iterator<泛型> car = list.iterator();
//<>里边的泛型一定要和集合的泛型保持一致
hasNext()
判断集合是不是还有下一个元素、
next()
取出下一个元素
remove()
删除集合里边的元素
如何创建集合对象
//jdk5.0以前 默认装Object[]
ArrayList list = new Array();
//jdk5.0之后 可以加泛型
ArrayList<String> list = new ArrayList<String>();
//7.0 后边的泛型可以省略
Array<Integer> list = new ArrayList<>();
如何往集合添加元素
//一次添加一个元素
list.add(44);
list.add(45);
//一次添加多个元素 集合名字 元素
Collections.addAll(list,22,22,33,4,5,5);
如何得到集合的大小
list.size();
如何得到某一个元素
list.get(下标);
判断集合是否包含一个元素
list.contains(元素);
如何遍历集合
for(int i = 0;i < list.size();i++){
System.out.println(list.get(i));
}
for(Integer x: list){
System.out.println(x);
}
使用迭代器遍历集合
//hadNext()判断迭代器后边是否还有元素
for(Iterator<泛型> iter = list.iterator;car.hasNext();){
//next()去除先一个元素
car.next();
}
remove()
remove方法有两个,一个传下标,另外一个传元素,判断两个元素是不是一样的看元素底层的equals()方法。==具体看传入的参数的equals();==传入的参数会主动调用自己的equals()方法和集合里的每一个对象作比较
//一次只可以删除一个对象
contains()
比较集合是否包含指定元素也是用的equals
clear()
清空集合用clear();
面试题
Collection 和Collections 的区别
Collection 是所有单值类型集合的父接口 :interface
Collections 是集合的工具类 :class
集合扩容
当创建一个数组对象的时候需要确定数组的大小
ArrayList底层是基于Object[]数组实现的,集合里边存几个元素时根据ArrayList的构造方法决定的
①ArrayList list = new ArrayList(100);传入多少就开辟多少
②ArrayList list = new ArrayList();不传参数,底层默认开辟10块空间
集合会自己扩容,所以不用担心不够用
JDK6.0及以前 x * 3 / 2 + 1
JDK7.0及以后 x + (x >> 1 )
//把集合扩容到指定的空间
list.ensureCapacity(300);
//减少集合空间
list.trimToSize();
手写集合
public class Exec1{
public static void main(String[] args){
AList list = new AList();
list.add("123");
list.add("456");
list.add(666);
System.out.println(list.size());
System.out.println(list.contains(new Integer(666)));
list.remove(0);
list.add("999");
System.out.println(list);
list.remove("999");
System.out.println(list);
//前后都要加泛型
AList<Integer> list1= new AList<>();
CollectionsTest.addAll(list1,123,123,234,543,7657);
System.out.println(list);
AList<Teacher> t = new AList<>();
t.add("赵信");
System.out.println(t);
t.remove("赵信");
System.out.println(t.size());
}
}
class CollectionsTest{
public static void addAll(AList<Integer> list,Integer ... obj){
for(Integer data: obj){
list.add(data);
}
}
}
class AList<E>{
//数组用来存放元素
private Object[] data;
//元素个数
private int size;
//有参构造方法,用户传进来集合大小
public AList(int x){
if(x < 0){
System.out.println("ArrayIndexOutOfBoundsException:" + x);
}
data = new Object[x];
}
//无参构造方法,默认为10
public AList(){
this(10);
}
//得到集合大小
public int size(){
return size;
}
//得到元素
public Object get(int x){
return data[x];
}
//添加元素
public void add(Object obj){
//判断如果集合满了,就进行扩容
if(data.length == size){
Object[] temp = new Object[size + (size >> 1)];
System.arraycopy(data,0,temp,0,size);
data = temp;
}
data[size] = obj;
size++;
}
//删除元素,按照下标进行删除
public void remove(int x){
/*
删除指定下标的元素,相当于把指定下标以后的元素复制到指定下标处,
复制完成之后元素个数减一
*/
System.arraycopy(data,x + 1,data,x,size - x - 1);
size--;
}
//删除元素,按照指定元素删除
public void remove(Object obj){
for(int i = 0; i < size;i++){
//挨个遍历数组,找到一样的就删除吊
if(obj.equals(data[i])){
remove(i);
}
}
}
//判断集合里边是否包含指定元素
public boolean contains(Object obj){
if(obj == null) return false;
for(int i = 0;i < size;i++){
if(obj.equals(data[i])){
return true;
}
}
return false;
}
@Override
public String toString(){
String str = "[";
for(int i = 0; i < size -1;i++){
str = str + data[i] + ",";
}
return str + data[size - 1] + "]";
}
}
class Teacher{
String name
public Teacher(String name){
this.name = name;
}
@Override
public String toString(){
return name;
}
}
addAll()
ArrayList<Integer> list = new ArrayList<>();
ArrayList<Integer> test = new ArrayList<>();
Collectoins.addAll(list,1,2,3,4,5,6);
//直接把集合塞到另外一个集合里边
test.addAll(list);
面试题
Vector 和ArrayList的区别
同步线程不同
Vector同一时间允许一个线程进行访问,效率较低但是不会出现并发错误
ArrayList同一时间允许多个线程进行访问,效率较高,但是可能会出现并发错误
从JDk5.0之后
扩容机制不同
ArrayList:分版本
JDK6.0及以前 x * 3 / 2 + 1
JDK7.0及以后 x + (x >> 1)
Vetor:分构造方法
Vetor(10) -> 2倍扩容 10 - 20 -30
Vetor(10,3) -> 定长扩容 10 -13 -16
出现版本不同可答可不答
Vetor : 1.0
ArrayList: 1.2
面试题
LinkedList和ArrayList之间的区别
LinkedList和ArrayList的底层数据结构不同,导致优劣不同
ArrayList | LinkedList | |
---|---|---|
底层数据结构 | 数组 | 链表 |
优点 | 随机查找、遍历较快 | 添加删除元素效率较高 |
缺点 | 添加删除元素效率低 | 随机查找、遍历效率慢 |
添加元素
Stack<Integer> list = new Stack<>();
list.push(666);
拉出元素
System.out.println(list.pop());
Set集合修改元素的步骤
public class RemoveTest{
public static void main(String[] args){
Set<Integer> set = new HashSet<>();
Collections.addAll(set,11,22,33,44,55);
//1.创意一个临时的 同类型集合
Set<Integer> temp = new HashSet<>();
for(Iterator<Integer> car = set.iterator();car.hasNext();){
if(car.next() == 55){
//2.删除原有的元素
car.remove();
//3。吧修改后的元素放到临时集合中
temp.add(45);
}
}
//4.把修改之后的元素放回老集合中
set.addAll(temp);
}
}
HashSet的用法与ArrayList的用法基本一样但是所有跟下标有关的方法都不可以使用了
包含get()、remove()、for()遍历集合
如何创建对象
HashSet<Integer> set = HashSet<>();
遍历集合
for(Integer x : set){
System.out.println(x);
}
for(Iterator<Iterger> car = set.iterator();car.hasNext();){
System.out.println(set.next());
}
HashSet的唯一性
唯一:内存里的同一个对象,不会添加多次
“唯一”:将两个不同的对象视为相等的对象取决于***hashCode()*** 和***equals()***
HashSet会根据传入对象的hashCode()得到的哈希码来决定具体分到哪一个组,如果两个对量的哈希码值是一样的话,才会调用equals来判断两个对象是不是一个对象,如果equals返回两个对象是一个对象的话,就不可以重复添加
当两个对象的哈希码值一样的时候,有3 种情况
①内存里的同一个对象、不可以重复添加
②视为相等对象、会调用equals()方法来是不是同一个对象
③重码
适用的方法有 add()、remove()、contains();
拓展
addAll
import java.util.*;
public class Exec1{
public static void main(String[] args){
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"张三","李四","李四","张三","王五");
HashSet<String> e = new HashSet<>();
//将另外一个集合里边所有的东西撞到HashSet里边
e.addAll(list);
System.out.println(e);
System.out.println(e.size());
}
}
可以按照添加对象的指定属性进行排序
向 TreeSet 中添加数据,要求是**相同类的对象**
与 TreeMap 一样采用红黑树的存储结构
有序,查询速度比List块
compareTo方法一样的返回0
在使用TreeSet的时候应该尽量保证compareTo方法有可能返回0
否则Tree Set集合的 add 方法用于不会认定有重复元素,无法保证唯一
同时TreeSet的 remove 方法也不乏删除元素 ,
add()、remove()、contains()、都是以来与compareTo()返回值是0的
如果需求就是compareTo方法就是无法返回0的,必须借助迭代器的删除方法
class User implements Comparable{
private String name;
private int age;
//get set 方法
public User(String name,int age){
this.name = name;
this.age = age;
}
//重写hashCode 和 equals 方法
@Override
public int compareTo(Object o){
if(o instanceof User){
User u = (User)o;
return this.getName().compareTo(u.getName());
}else{
throw new RuntimeException("输入的类型有误");
}
}
}
class TreeSetTest{
@Test
public void test(){
TreeSet<User> t = new TreeSet<>();
t.add("Tom",22);
t.add("Lee",20);
t.add("Wangwu",18);
}
}
//for Iterator
//lambda表达式
set.forEach(System.out::println);
set.forEach((x) -> System.out.println(x);)
//得到第一个元素
set.first();
//做后一个元素
set.last();
//pollFirst()选出并移除第一个元素
public class MapTest{
public static void main(String[] args){
Map map = new HashMap();
}
}
默认容量是 16
默认加载因子是 0.75 也就是扩容的临界值是12
put() 添加
//添加元素
map.put("AA",123);
put() 修改
//修改元素
map.put("AA",1234);
putAll()
//将一个map添加到另外一个map中
Map m = new HashMap();
m.putAll(map);
remove() 移除
//remove(Object key)移除参数放key
System.out.println(map.remove("CC"));
clear() 清空数据
map.clear();
get() 获取指定 key 的value
System.out.println(map.get("AA"));
//当没有填入的参数的时候返回null
containsKey / containsValue
//判断当前map是否包含指定的key
System.out.println(map.containsKey("AA"));//true
//判断当前map是否包含指定的key
System.out.println(map.containsValue(123));//true
isEmpty() 判断当前map是否为空
System.out.println(map.isEmpty());
对元数据的操作
keySet() 遍历map里所有的key
Set set = map.keySet();
for(Iterator i = set.iterator();i.hasNext()){
System.out.println(i.next());
}
values() 遍历map里所有的value
Collection c = map.values();
for(Object o : c){
System.out.println(c);
}
entrySet() 遍历所有的 key - value
Set e = map.entrySet();
for(Iterator i = e.iterator();i.hasNext();){
//entrySet 集合中的元素都是 entry
Map.Entry e = (Map.Entry)(i.next());
System.out.println(e.getKey() + "--" + e.getValue());
}
getKey()
//获取记录的键
System.out.println(e.getKey());
getValue()
//获取对应的值
System.out.println(e.getValue());
setValue()
//修改值
e.setValue();
无论我们使用keySet()、values()、entrySet()所得到的的都不是一个新的集合
12.lambda表达式遍历Map集合
map.forEach((k,v) -> System.out.println(k));
map.forEach((k,v) -> System.out.println(v));
map.forEach((k,v) -> System.out.println(k + ":" + v));
面试题:HashMap的底层实现原理
JDK 7以前
HashMap map = new HashMap();
- 在实例化以后,底层创建了一个长度为16的一维数组 Entry[] table
map.put(key1,value1);
- 调用 key1 所在类的 hashCode() 计算哈希值,在一定处理后(& 15),用来确定在Entry 数组中的存放位置。
- 如果此位置上没有数据,此时 key1 - value1 添加成功(添加的是value),
- 如果该位置有位置,意味着此位置有一个或多个数据,比较 key1 和已经存在的 key 的哈希值
如果 key1 的哈希值与已经存在的数据的哈希值都不相同,就添加成功
如果和以及存在的某一个相同,就判断equals,返回 false 就添加成功 返回 true 使用value 1 替换相同 key 的value值
JDK8底层实现有所不同
- new HashMap() : 底层没有创建一个长度为16的数组
- JDK 8 底层的数组是 Node[] 而非 Entry[]
- 首次调用 put() 方法,底层创建长度为 16 的数组
- JDK 7 底层结构只有数组 + 链表 JDK 8底层数据结构:数组 + 链表 + 红黑树
当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前的数组长度 > 64 时,此时此索引位置上的所有数据改为使用红黑树存储
@Test
public void test(){
Map map = new LinkedHashMap();
map.put(1,"AA");
map.put(1,"BB");
map.put(1,"CC");
System.out.println(map);
Map map1 = new HashMap();
map1.putAll(map);
System.out.println(map1);
}
class Test{
@Test
public void test(){
TreeMap map = new TreeMap();
map.put(new User("Tom",23),98);
map.put(new User("Jerry",21),91);
map.put(new User("Jack",20),78);
map.put(new User("Rose",22),58);
//compareTo自然排序
Set e = map.entrySet();
for(Iterator car = e.iterator();car.hasNext();){
Map.Entry et = (Map.Entry)(car.next());
System.out.println(et.getKey() + "--" + et.getValue());
}
}
@Test
public void t2(){
TreeMap map = new TreeMap(new Comparator(){
@Override
public int compare(Object o1,Object o2){
if(o1 instanceof User && o2 instanceof User){
User u1 = (User)o1;
User u2 = (User)o2;
return Integer.compareTo(u1.getAge(),u2.getAge());
}
}
});
}
}
class User implements Comparable{
private String name;
private int age;
public User(String name,int age){
this.name = new name;
this.age = new age;
}
//重写 toString(),equals(),compareTo()方法
}
public class PropretiesTest{
Propreties pros = new Propreties();
FileInputStream fis = new FileInputStream("jdbc.properties");
pros.load(fis);
String name = pros.getProperty("name");
String pwd = pros.getProperty("password");
}
//配置文件
/*
name=zhaojinhui
password=123456
*/
配置文件出现中文乱码解决方法
Setting – > File Encodings --> √ Transparent native-to-ascii conversion
方法 | 作用 |
---|---|
reverse(List) | 反转List中元素的顺序 |
shuffle(List) | 对List进行随机排序 |
sort(List) | 根据元素自然顺序队List里元素升序排列 |
sort(List,Comparator) | 根据指定的比较规则排序 |
swap(List,int i,int j) | 将List集合 i 处和 j 处元素交换 |
方法 | 作用 |
---|---|
max(Collection) | 根据自然顺序,返回最大值 |
max(Collection,Comparator) | 根据定制顺序返回最大值 |
min(Collection) | 根据自然顺序返回最小值 |
min(Collection,Comparator) | 根据定制顺序返回最小值 |
frequency(Collection,Object) | 返回指定元素出现的次数 |
copy(List i,List j) | 将 j 中的内容复制到 i 中 |
replaceAll(List i,Object o,Object n) | 用 n 替换 i 集合里边的 o |
public class Test{
public static void main(String[] args){
ArrayList<Integer> arr = new ArrayList<>();
Collections.addAll(arr,1,2,3,4,5);
List dest = Arrays.asList(new Object[arr.size()]);
System.out.println(dest.size());
}
}
List oldList = new ArrayList();
List newList = Collections.synchronizedList(oldLiset);
自然排序 Comparable接口的使用
像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个队想的方法
重写compareTo(obj) 的规则:
如果当前对象 this 大于形参对象 obj ,则返回正整数
小于,返回负整数
等于,返回0
对于自定义类,如果需要排序,可以让自定义类实现Comparable接口,重写compareTo(obj)在方法中指明如何排序
public class CompareTest{
@Test
public void test(){
Goods[] arr = new Goods[5];
arr[0] = new Goods("lenovo",34);
arr[1] = new Goods("huawei",65);
arr[2] = new Goods("dell",14);
arr[3] = new Goods("xiaomi",43);
arr[4] = new Goods("ausu",43);
Arrays.sort(arr);
}
}
class Goods implements Comparable{
String name;
double price;
public Goods(String name,double price){
this.name = name;
this.price = price;
}
//指明按照什么方式排序
@Override
public int compareTo(Object o){
if(o instanceof Goods){
Goods g = (Goods)o;
if(this.price > g.price){
return 1;
}else if(this.price < g.price){
return -1;
}else{
return this.name.copareTo(g.name);
}
}
throw new RunTimeException("传入的数据类型不一致!");
}
}
定制排序
- **当元素的类型没有实现Comparable接口而有不方便修改代码,或者实现了Comparable接口排序规则不适合当前的操作,name可以考虑使用Camparator排序,**强行队多个对象进行整体排序的比较
- 可以将Comparator传递到 sort 方法,从而允许在排序上实现精确控制(如Collections.sort 或Arrays.sort)
@Test
public void test(){
Arrays.sort(arr,new Comparator<Goods>(){
//按照产品名称从低到高
@Override
public int compare(Goods i,Goods j){
if(i instanceof Goods && j instanceof Goods){
Goods g1 = (Goods)i;
Goods g2 = (Goods)j;
if(g1.getName().equals(g2.getName())){
return Double.compare(g1.getPrice(),g2.getPrice());
}else{
return g1.getName().compareTo(g2.getName());
}
}
}
});
}
测试类
@Data
public class Test implements Comparator<Test>{
private Integer orders;
}
Lambda式简化版1
import iava.util.*;
public class TestComparator{
public static void main(String[] args){
List<Integer> list = new ArrayList<>();
Collections.addAll(list,22,33,77,14,44,55,66);
Collections.sort(list,(x,y) -> x - y);
}
}
Lambda简化版2
import iava.util.*;
public class TestComparator{
public static void main(String[] args){
List<Test> list = new ArrayList<>();
COllections.addAll(list, ...);
Comparator<Test> comp = Comparator.comparing(Test::getOrders);
Collections.sort(list,comp);
}
Lambda不当人简化版简化版3
list.sort(Comparator.comparing(Test::getOrders));
public class DAO<T>{
//表的共用操作
//增删改查
}
public class StudentDAO extends DAO<Student>{
//只能操作某一个表的DAO
}
//当返回值类型不确定的时候可以使用泛型方法
public <E> e test(){}
List<Object> list = null;
List<String> li = null;
//不可以这么使用,此时的list和li不具有子父类关系
//list = li;
@Test
public void test(){
List<Object> list1 = null;
List<String> list2 = null;
List<?> list = null;
list = list1;
list = list2;
}
//List> 只可以添加null 其他都不可以添加
? extends A:
G extends A> 可以作为 G 和 G 谷类的,其中B 是 A 的子类
? super A:
G extends A> 可以作为 G 和 G 谷类的,其中B 是 A 的父类
public class Person{}
class Student extends Person{}
class Test{
@Test
public void test(){
List<? extends Person> list = null;
List<? super Person> list2 = null;
List<Student> s = null;
List<Person> p = null;
List<Object> o = null;
list = s;
list = p;
//不可以
//list = o;
//不可以
//list2 = s;
list2 = p;
list2 = o;
}
}
创建 File 类的对象
//构造器一 File(String filePath)
//相对路径
File file1 = new File("Hello.txt");
//绝对路径
File file2 = new File("D:\\Study\\java\\java培训详细版.md");
//使用常量的方式
//"D:" + File.separator + "Study" +File.separator + "java" File.separator + "java培训详细版.md"
//构造器二 File(String parentPath,String childPath)
File file3 = new File("D:\\Study\\java","Test");
//构造器三 File(File parentFile,String childPath)
File file4 = new File(file3,"tset.txt");
获取绝对路径 getAbsolutePath()[一等优先级]
//public String getAbsolutePath()
System.out.println(file1.getAbsolutePath());
获取相对路径 getPath()[一等优先级]
//public String getPath()
System.out.println(file1.getPath());
获取名称 getName()[一等优先级]
//public String getName()
System.out.println(file1.getName());
获取上层文件目录路径,没有返回null getParent()[一等优先级]
//public String getParent()
System.out.println(file1.getParent());
获取文件字节总数 length()[一等优先级]
//public long length()
System.out.println(file1.length());
获取最后一次修改时间 毫秒值 lastModified()[一等优先级]
//public long lastModified()
System.out.println(new Date(file1.lastModified());
以下两个方法适用于文件目录
获取指定目录下所有文件或文件目录的名称数组 list()[特等优先级]
//public String[] list()
File file4 = new File("D:\\study");
String[] list = file.list();
for(String s : list){
System.out.println(s);
}
获取指定目录下的所有文件或目录的File数组listFiles()[特等优先级]
//public File[] listFiles()
File[] fs = file4.listFiles();
for(File f : fs){
System.out.println(f);
}
得到当前系统的所有根目录listRoots()[特等优先级]
//public static listRoots
File[] fs = File.listRoots();
把文件重命名为指定的文件路径 renameTo()[高危]
//public boolean renameTo(File dest)
File f1 = new File("hello.txt");
File f2 = new File("D:\\Study\\hello.txt");
boolean re = f1.renameTo(f2);
System.out.println(re);
/*
要想保证是true,需要 f1 在硬盘中存在的,且 f2 不能再硬盘中存在
*/
2. File类的判断功能
是否是文件目录 isDirectory()[一等优先级]
//public boolean isDirectory()
File f1 = new File("hello.txt");
System.out.println(f1.isDirectory());//false
是否是文件 isFile()[一等优先级]
System.out.println(f1.isFile());
是否存在 exists()[一等优先级]
System.out.println(f1.exists());
是否可读 canRead()
System.out.println(f1.canRead());
是否可写 canWrite()
System.out.println(f1.canWrite());
是否隐藏 isHidden()
System.out.println(f1.isHidden);
3. File类的创建删除功能
方法 | 作用 |
---|---|
createNewFile() | 创建文件,如果存在返回false不进行创建 |
mkdir() | 创建文件目录,存在则不创建 |
mkdirs()[高危] | 创建文件目录,如果上一级目录不存在,一并创建 |
delete()[高危] | 删除文件或文件夹,不经过回收站 |
File file = new File("hi.txt");
if(!file.exists()){
file.createNewFile();
}else{
file.delete();
}
File file1 = new File("D:\\io\\io1 ");
file1.mkdir();
file1.mkdirs();
例题
判断指定目录下是否有.jpg文件,如果有输出文件名
import java.io.*;
public class JpgTest{
public static void main(String[] args){
File srcFile = new File("D:\\Study");
String[] fileNames = srcFile.list();
for(String name : fileNames){
if(name.endsWith(".jpg")){
System.out.println(name);
}
}
}
}
遍历指定目录下所有文件名称,包括文件目录中的文件
import java.io.*;
public class FileTest{
public static void main(String[] args){
File dir = new File("D:\\Study\\Yitu");
printSubFile(dir);
}
private static void printSubFile(File dir){
File[] subFiles = dir.listFiles();
for(File f : subFiles){
if(f.isDirectory()){
printSubFile(f);
}else{
System.out.println(f.getAbsolutePath());
}
}
}
}
流的分类
抽象基类 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
访问字符串 | StringReader | StringWriter | ||
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferrdReader | BufferedWriter |
转换流 | InputStreamReader | OutputStreamWriter | ||
对象流 | ObjectInputStream | ObjectOutputStream | ||
**** | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter |
打印流 | PrintStrem | PrintWriter | ||
推回输入流 | PushbackInputStream | PushbackReader | ||
特殊流 | DataInputStream | DataOutputStream |
try-catch-finally
import java.io.*;
public class TestFileCopy{
public static void main(String[] args) throws IOException{
FileInputStream fis = null;
FileOutputStream fos = null;
try{
fis = new FileInputStream("DNF - 痛苦之村列瑟芬.mp3");
fos = new FileOutputStream("DNF - 痛苦之村列瑟芬1.mp3");
byte[] data = new byte[3 << 20];
int len;
while((len = fis.read(data)) != -1){
fos.write(data,0,len);
}
//太慢了
/*
while((data = fis.read()) != -1){
fos.write(data);
}
*/
}catch(IOException e){
e.printStackTrace();
}finally{
try{
fis.close();
}catch(IOException e){
e.printStackTrace();
}finally{
try{
fos.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
}
}
TWR 7.0新特性[超级重要]
import java.io.*;
public class TestFileCopyWithTWR{
public static void main(String[] args){
try(FileInputStream fis = new FileInputStream("1.jpg");FileOutputStream fos = new FileOutputStream("2.png")){
byte[] data = new byte[1024];
int len;
while((len = fis.read(data)) != -1){
fos.write(data,0,len);
}
}catch(IOException e){
e.printStackTrace();
}
}
}
FileOutputStream的构造方法
FileOutputStream( File f / String name) 这种模式如果有文件则会把源文件删除,在创建新的文件。想要在源文件的基础上追加文件则需要用 FileOutputStream(File f,true)
- read() 返回读入的一个字符,如果达到文件末尾,则返回 -1
- 为了保证流资源一定会执行关闭操作,并需用try-catch-finally捕获异常
import java.io.*;
public class FileReaderTest{
@Test
public void test(){
File file = null;
FileReader fr = null;
try{
//1.实例化 File 类的对象,指明要操作的文件
file = new File("hi.txt");
//2.提供具体的流
fr = new FileReader(file);
//3.数据的读入
int data = fr.read();
while(data != -1){
System.out.print((char)data);
data = fr.read();
}
/*
int data;
while((data = fr.read()) != -1){
System.out.println((char)data);
}
*/
}catch(IOExecption){
e.printStackTrace();
}finally{
try{
//4.流的关闭
if(fr != null)
fr.close();
}catch(IOExecption){
e.printStackTrace();
}
}
}
//对 read() 操作升级,使用 read 的重载方法
@Test
public void testFileReader1{
File file = null;
FileReader fr = null;
try{
//1.File 类的实例化
file = new File("Hi.txt");
//2.FileReader流的实例化
fr = new FileReader(file);
//3.读入操作
//read(char[cbuf]):返回每次读入 cbuf数组中的字符串个数,如果达到文件末尾返回-1
char[] cbuf = new char[5];
int len;
while((len = file.read(cbuf)) != -1){
//方式一
for(int i = 0;i < len;i++){
System.out.print(cbuf[i]);
}
//方式二
String str = new String(cbuf,0,len);
System.out.print(str);
}
}catch(IOExecption e){
e.printStackTrace();
}finally{
//4.资源的关闭
}
}
}
说明
- 输出操作,对应的 File 可以不存在,并不会报异常
File 对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建这个对象
如果存在:①如果流使用的构造器是 FileWriter(file ,false) / FileWriter(file) ,对原有文件覆盖②如果使用的是 FileWriter(file,true) 不会对原有文件覆盖,而是在原有文件基础上追加内容
@Test
public void testFileWriter(){
File file = null;
FileWriter fw = null;
try{
//1. 提供 File 类对象,指明写出到的文件
file = new File("Hi1.txt");
//2.提供 FileWriter 的对象,用于数据写出
fw = new FileWriter(file,false);
//3.写出的操作
fw.write("Hello World!");
}catch(IOExecption e){
e.printStackTrace();
}fianlly{
fw.close();
}
}
使用 FileWriter 和 FileReader 实现文本文件的复制
import java.io.*;
//不能用字符流处理图片文件
public class TestFileCopy{
@Test
public void test(){
FileReader fr = null;
FileWriter fw = null;
try{
//1.创建 File 类的对象,指明读入和写出的文件
File srcFile = new File("Hei.txt");
File destFile = new File("Hi.txt");
//2.创建输入流和输出流的对象
fr = new FileReader(srcFile);
fw = new FileWriter(destFile);
//3.数据输入输出操作
char[] c = new char[5];
int len;//记录每次读出来的字符个数
while((len = fr.read(c)) != -1){
//每次写出 len 个字符
fw.write(c,0,len);
}
}catch(IOExecption e){
e.printStackTrace();
}finally{
//4.资源的关闭
try{
if(fw != null)
fw.close();
}catch(IOExecption){
e.printStackTrace();
}finally{
try{
if(fr != null)
fr.close();
}catch(IOExecption){
e.printStackTrace();
}
}
}
}
}
对于文本文件**(.txt .java .c .cpp)**使用字符流处理,对于非文本文件使用字节流处理
实现非文本文件的复制
import java.io.*;
public class BufferedTest{
@Test
public void BUfferedTest(){
FileInuputStream fis = null;
FileInuputStream fis = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try{
//1.造 File 对象
File file1 = new File("hi.jpg");
File file2 = new File("hei.jpg");
//2.造流
//2.1 造节点流
fis = new FileInputStream(file1);
fis = new FileOutputStream(file2);
//2.2造缓冲流
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
//3.读取写入
byte[] b = new byte[10];
int len;
while((len = bis.read(b)) != -1){
bos.writer(b,o,len);
}
}finally{
//4.资源关闭
//先关闭外层,再关内层
bos.close();
bis.close();
//关闭外层流的同时,内层流会自动关闭
fw.close();
fr.close();
}
}
}
- 都属于**字节流、过滤流**
- 作为过滤流,是为了给原本的节点流添加读写基本数据类型的功能的
- 过滤流不能直接连接文件,只能连接其他的流
- **DataInputStream **不再以-1作为读取结束的标志。如果到了文件结果还尝试进行读取会导致EOFException -> EndOfFileException
import java.io.*;
public class TestDataStream{
public static void main(String[] args)throws Exception{
//我们想要把人物等级保存下来~等到明年春暖花开日 再继续游戏~
/*
int level = 7365;
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.data"));
dos.writeInt(level);
dos.close();
*/
DataInputStream dis = new DataInputStream(new FileInputStream("data.data"));
int x = dis.readInt();
dis.close();
System.out.println(x);
}
}
- 同样是**字节流、过滤流**
- 为了给原本的节点流添加读取对象的功能的
- 如果到达文件结尾会触发EOFException
注意点
想要持久化一个对象 必须先要序列化这个类型
如果想要将A类的对象写出到磁盘当中 则A类必须实现序列化接口
implements Serializable
而如果A类当中还有其它引用类型的属性 则这些属性的类型
也要实现序列化接口 否则A对象持久化会出现异常
如果某些属性 无关紧要 不需要保存到文件当中
可以使用transient修饰
transient = 短暂的 转瞬即逝的 = 不参与持久化的
在写出一个对象的时候 transient的属性 将直接写出null
所以这个属性的类型 也就不需要序列化了~
import java.io.*;
import java.text.*;
public class TestObjectStream2{
public static void main(String[] args){
Wish myWish = new Wish("能早点在一起上课~网络教学就是坑!有个屁用!","5v");
System.out.println(myWish);
//请许下自己的愿望 并且将愿望保存在磁盘文件当中
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("月光宝盒.data"))){
oos.writeObject(myWish);
}catch(Exception e){
e.printStackTrace();
}
}
}
class Wish implements Serializable{//愿望
String content;//愿望的内容
String name;//许愿人姓名
long time;//许下愿望的时间
public Wish(String content,String name){
this.content = content;
this.name = name;
time = System.currentTimeMillis();
}
@Override
public String toString(){
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd");
return content + "\n\t\t\t" + name +"\n\t\t\t" + f.format(time);
}
}
Reader / Writer 是字符流的抽象基类
这两个类只能用来读取文本文件,但是字节流通用语所有文件
.txt .java .html .js .css .jsp .asp .php等
用法用FileInputStream / FileOutputStream类似
BufferedReader / BufferedWriter
- 给原本的流添加一个变长的缓冲空间,从而实现以行为单位的读取
- 文件结束返回null
import java.io.*;
public class TestBufferedWriter{
public static void main(String[] args)throws Exception{
//春眠不觉晓 处处闻啼鸟 夜来风雨声 花落知多少
BufferedWriter bw = new BufferedWriter(new FileWriter("鹅鹅鹅.txt"));
bw.write("鹅鹅鹅");
bw.newLine();//能够写出当前操作系统所匹配的换行标识
bw.write("鹅项向天歌");
bw.newLine();
bw.write("鹅毛浮绿水");
bw.newLine();
bw.write("鹅可能死了");
bw.close();
}
}
PrintWiter
PrintWriter 和BufferedWriter 相比的优势
- PrintWriter既可以当做节点流 也可以当做过滤流,构造方法允许传入 File对象 / String路径 / 流!
- PrintWriter既可以连接字节流 又可以连接字符流,构造方法既可以传入 FileWriter 也可以传入 FileOutputStream
- 当做节点流使用的时候 构造方法第二个参数可以指定字符编码
new PrintWriter(“鹅鹅鹅.txt”,“utf-8”);- 当做过滤流使用的时候 构造方法第二个参数可以指定自动清空缓冲
new PrintWriter(new FileWriter(“abc.txt”),true);//autoFlush- 它的println() = write() + newLine()
import java.io.*;
public class TestPrintWriter{
public static void main(String[] args)throws Exception{
PrintWriter pw = new PrintWriter("春晓.txt");
pw.println("春眠不觉晓");//write()+newLine();
pw.println("处处闻啼鸟");
pw.println("夜来风雨声");
pw.print("花落知多少");
pw.close();
}
}
public class InputStreamReaderTest{
public static void main(String[] args) throws IOExecption{
FileInputStream fis = new FileInputStream("Test.txt");
InputStreamReader isr = new InputStreamReader(fis);
char[] c = new char[10];
int len;
while((len = isr.read(c)) != -1){
String str = new String(c,0,len);
System.out.println(str);
}
isr.close()
}
}
程序(Program)
为了完成特定的任务、用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象
进程(process)
是程序的一次执行过程,或是正在运行的程序,是一个动态的过程,有自身的生命周期
线程(thread)
进程可以进一步细分为线程,是一个程序内部的一条执行路径
- 若一个进程同一时间并行执行多个线程,就是支持多线程的
- 线程最为调度和执行的单位,每个线程都拥有独立的运行栈和程序计数器(pc),线程切换的开销小
- 一个进程的多个线程共享相同的内存单元/内存地址空间,他们从同一个堆空间中分配对象,可以访问相同的变量和对象,使得线程间的通信更简洁、高效,但是多个线程操作共享的系统资源时可能会带来安全隐患
方式一:继承Thread类
①创建一个类继承于Thread类
②重写***Thread***的***run()***方法 -->将此线程要执行的操作声明在***run()***中
③创建一个***Thread***类的子类对象
④通过此对象调用***start()***,第一步启动当前线程,第二部调用当前线程的 run()
public 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){
//创建Thread类的子类对象
MyThread t1 = new MyThread();
//通过此线程调用start()方法
t1.start();
}
}
方式二:实现Runnable接口
①创建一个实现了 Runnable接口的类
②实现 Runnable 中的抽象方法:run()
③创建实现类的对象,将此对象作为参数传递到 Thread 类的构造器中,创建 Thread 类的对象
④通过 Thread 类的对象调用 start()
/**
创建三个窗口同时卖100张票
*/
class MThread implements Runnable{
private int ticket = 100;
@Override
public void run(){
//TODO
while(true){
synchronized(this){
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
ticket--;
}else{
break;
}
}
}
}
}
public class Test{
public static void main(String[] args){
MThread m = new MThread();
Thread t1 = new Thread(m);
Thread t2 = new Thread(m);
Thread t3 = new Thread(m);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
比较两种创建多线程的方式
开发中: 优先选择 实现 Runnable 接口的方式。原因:① 实现的方式没有类的单继承性的局限性 ② 实现的方式更适合来处理多线程有共享数据的情况
联系 : public class Thread implements Runnable
相同点: 两种方法都需要重写 run() 将线程要执行的操作声明在 run() 中。
例题:创建两个线程一个打印100以内奇数,一个打印偶数
public class ThreadTest {
public static void main(String[] args){
new Thread(){
@Override
public void run(){
for(int i = 0; i < 100;i++){
if(i % 2 == 1){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}.start();
new Thread(){
@Override
public void run(){
for(int i = 0; i < 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}.start();
}
}
start()
启动当前线程,调用当前线程的 run()
run()
通常需要重写Thread类中的方法,将创建的线程要执行的操作声明在此方法中
currentThread()
静态方法,返回执行当前代码的线程
getName()
获取当前线程的名字
同样的也有setName()方法
yield()
释放当前CPU的执行权
join()
在主线程a中调用线程b的 join()。此时线程a进入阻塞状态,直到线程b完全执行完,a才会结束状态
sleep()
让当前线程休眠多少毫秒
线程的最大优先级是10,最小为1,默认为5
获取和设置当前线程的优先级
getPriority()h获取当前的优先级
setPriority()设置当前线程的优先级
控制线程相关方法
setPriorty(inyt)[不建议使用的]
setPriority(int) 可以设置现成的优先级别,可选范围是 1 - 10 ,默认级别为5,线程的优先级别越高,抢到时间片的**概率越高,并不代表一定抢到时间片**
static sleep(long)
使当前线程休眠多少毫秒,会从运行态变成阻塞态
static yield()
当前线程放弃已经持有的时间片,允许其他线程执行
join()
当前线程邀请其他线程先执行,谁在运行中,join就在谁的体内
注意:
1. 线程章节所有的静态方法,不要关注是谁调用的方法,而要关注谁的线程体内;
2. 这个章节所有主动进入阻塞状态的方法,都出要进行异常处理。因为他们都有throws InterruputedException的声明,这是一个非运行时异常,必须出来
线程的其他方法
setName() / getName()
设置和得到线程的名字
static activeCount()
得到程序中所有活跃线程的总数[就绪 + 运行 + 阻塞]
setDaemon()
设置线程成为守护线程;守护线程是为了给其他线程提供服务的,当程序中只有守护进程的时候,守护进程会自动结束
守护线程要设置成while(true);必须按在调用 start() 方法之前就设置成守护线程
interrupt()
中断线程的阻塞状态
static currentThread()
- 在主方法中使用相当于得到了主线程的线程对象
- 在 run() 调用的其他方法中,用来获取当前的线程是谁
- 不应该直接出现在 run() 方法中,因为返回的线程相当与this
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明
①操作共享数据的代码,即为需要被同步的代码
②共享数据: 多个线程共同操作的变量,如ticket
③同步监视器,俗称锁。任何一个类的对象都可以充当锁, 要求:多个线程必须共用一把锁④在实现 Runnable 接口的创建多线程的方式中,可以考虑使用 this 充当同步监视器,在继承Thread的方式中,慎用
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,可以将此方法声明成同步的
修改懒汉式
class Bank{
private static Bank instance = null;
public static Bank getInstance(){
if(instance == null){
synchronized(Bank.class){
if(instance == null){
instance = new Bank();
}
}
}
return instance;
}
}
方式三:Lock(锁) ---- JDK5.0新增
Lock构造方法是可以传参指定是否创建公平锁
new ReentrantLock( true ) 按照先来后到 默认false
class Window implements Runnable{
private int ticket = 100;
//1.实例化ReentrantLock对象
private ReentrantLock lock= new ReentrantLock();
@Override
public void run(){
while(true){
try{
//2.调用锁定方法lock()
lock.lock();
if(ticket > 0){
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("卖票" + ticket);
ticket--;
}
}finally{
//3.调用解锁方法unlock()
lock.unlock();
}
}
}
}
**synchronized和Lock区别**
> ==相同点:==
> 两者都可以解决线程安全问题
>
> ==不同点:==synchronized机制在执行完相应的同步带吗后,自动的释放同步监视器,Lock需要手动的启动同步用完释放
>
> 建议顺序 Lock--同步代码块--同步方法
>
涉及到的三个方法
wait() : 使当前线程进入阻塞状态,并释放同步锁
notify() : 唤醒被 wait() 的线程,如果有多个线程同时阻塞,就优先唤醒优先级高的,一样时随机唤醒。
notifyAll() : 唤醒所有阻塞的线程①这三个方法只能出现在同步代码块或者同步方法中;
②且调用者必须是同步代码块或同步方法中的同步监视器
③这三个方法是定义在Object类中的
例子:使用两个线程交替打印1-100
class Number implements Runnable{
private int num = 1;
@Override
public void run(){
while(true){
synchronized(this){
notify();
if(num <= 100){
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
try{
wait();
}catch(InterruptedException){
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
public class Test{
public static void main(String[] args){
Number num = new Number();
Thread t1 = new Thread(num);
Thread t2 = new Thread(num);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
面试题:sleep() 和wait() 的异同
①相同点: 一旦执行方法,都可以使当前的线程进入阻塞状态
②不同点:
- 两个方法的声明位置不同:Thread类中声明sleep(),Object类中声明wait()
- 调用要求不同:sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块或者同步方法中
- 是否释放同步监视器:如果两个方法都是用在同步代码块或者同步方法中,sleep()不会释放同步监视器,wait()则会。
生产者和消费者问题
生产者(Productor) 将产品交给店员(Clerk)。而消费者从店员处取走产品,店员一次只能持有固定数量的产品(20),如果生产者使徒生产更多的产品,店员会让生产者停一下,如果店里有空位置放了,店员会通知生产者继续生产,如果没有产品了。会告诉消费者等一下,有了会通知缴费者取走商品
public class ProductTest{
public static void main(String[] args){
Clerk clerk = new Clerk();
Producer p1 = new Producer(clerk);
Customer c1 = new Customer(clerk);
p1.setName("生产者");
c1.setName("消费者");
p1.start();
c1.start();
}
}
class Clerk{
//产品数量
private int num = 0;
//生产产品
public synchronized void produceProduct(){
if(num < 20){
num++; System.out.println(Thread.currentThread().getName() + "开始生产第" + num + "个产品");
notify();
}else{
try{
wait();
}catch(InterriptedExceptione){
e.printStackTrace();
}
}
}
//消费产品
public synchronized void sonsumeProduct(){
if(num > 0){
System.out.println(Thread.currentThread().getName() + "开始消费第" + num + "个产品");
num--;
notify();
}else{
try{
wait();
}catch(InterriptedExceptione){
e.printStackTrace();
}
}
}
}
class Producer implements Runnable{
//生产者
private Clerk clerk;
public Producer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run(){
System.out.println("生产者开始生产产品...");
while(true){
clerk.produceProduct();
}
}
}
class Customer implements Runnable{
//消费者
private Clerk clerk;
public Customer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run(){
System.out.println("消费者开始消费产品...");
while(true){
clerk.consumeProduct();
}
}
}
与使用 Runnable相比,使用 Callable功能更强大
- 相比 run() 方法,可以有返回值
- 方法可以抛出异常
- 支持泛型的返回类型
- 需要借助 FutureTask 类,比如获取返回结果
class NumThread implements Callable{
@Override
public Object call() throws Exception{
int sum = 0;
for(int i = 1;i <= 100;i++){
if(i % 2 == 0){
sum += i;
}
}
return sum;
}
}
public class Test {
public static void main(String[] args){
NumThread num = new NumThread();
FutureTask ft = new FutureTesk(num);
new Thread(ft).start();
System.out.println(ft.get());
}
}
创建一个实现Callable的实现类
实现 call() 方法,将次线程要执行的操作声明在call() 方法中
创建Callable实现类的对象
将此对象传递到FutureTask构造器中,创建FutureTask对象
将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象并调用start()
提前创造号多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁的创建销毁、实现重复利用。
好处
- 提高了响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要每次创建)
- 便于线程管理
public class ThreadPool{
public static void main(String[] args){
//提供指定数量的线程
ExecutorService es = Executors.newFixedThreadPool(10);
//适合适用于Runnable
es.execute(new NumThread);
//适合使用于Callable
//es.submit();
//关闭线程池
es.shutdown();
}
}
public class TestString{
public statuc void main(String[] args){
String s1 = new String("a");
String s2 = new String("a");
//false
System.out.println(s1 == s2);
String s3 = "a";
String s4 = "a";
//true
System.out.println(s3 == s4);
}
}
不new的话,如果是第一次遇见这个字符串,就会在常量池创建一个引用,从始至终就创建了一次,如果再有新的字符串创建,优先在常量池查找
和长度有关的方法
序号 | 返回类型 | 方法签名 | 作用 |
---|---|---|---|
1 | int | length() | 得到一个字符串里边的字符个数 |
和数组有关的方法
序号 | 返回类型 | 方法签名 | 作用 |
---|---|---|---|
2 | byte[] | getBytes() | 将一个字符串转换成字节数组 |
3 | char[] | toCharArray() | 将一个字符串转换成字符数组 |
4 | String[] | split(String) | 将一个字符串按照指定的内容劈开 |
和判断内容有关的方法
序号 | 返回类型 | 方法签名 | 作用 |
---|---|---|---|
5 | boolean | equals(String) | 区分大小写的判断两个字符串的内容是否一模一样 |
6 | boolean | equalsIgnoreCase(String) | 忽略大小写的比较两个字符串的内容是否一模一样 |
7 | boolean | contains(String) | 判断一个字符串里面是否出现了某个指定内容 |
8 | boolean | startsWith(String) | 判断一个字符串是否以指定的内容开头 |
9 | boolean | endsWith(String) | 判断一个字符串是否以指定的内容结尾 |
和改变内容有关的
序号 | 返回类型 | 方法签名 | 作用 |
---|---|---|---|
10 | String | toUpperCase() | 将一个字符串全部转换成大写 |
11 | String | toLowerCase() | 将一个字符串全部转换成小写 |
12 | String | replace(String,String) | 将一个字符串里面出现的某个内容全部替换成指定的内容 |
13 | String | replaceAll(String,String) | 将一个字符串里面出现的某个内容全部替换成指定的内容 【支持正则表达式】 |
14 | String | replaceFirst(String s1,String s2) | 将字符串中第一次出现的s1换第二个s2 |
15 | String | trim() | 去除字符串前后的空格 |
16 | String | substring(int x,int y) | 截取x到y - 1的字符串 |
17 | String | substring(int x) | 截取 x 到最后的字符串 |
和位置有关的方法
序号 | 返回类型 | 方法签名 | 作用 |
---|---|---|---|
18 | char | charAt() | 找到指定下标处对应的一个元素 |
19 | int | indexOf(String) | 找到某个内容第一次出现的下标 |
20 | int | lastIndexOf(String) | 找到某个内容最后一次出现的下标 |
***String***和***StringBuffer、StringBuilder***之间的区别
String类创建对象的时候正好够用,但是StringBuffer以及StringBuilder在创建对象的时候会预留16块缓冲区,为了防止追加连接的时候效率过低
public class TestString{
public static void main(String[] args){
String s = new String("");
long t1 = System.currentTimeMillis();
for(int i = 0;i <= 100000;i++){
s += i;
}
long t2 = System.currentTimeMillis();
System.out.println(t2 - t1);
StringBuffer bf = new StringBuffer("");
long t3 = System.currentTimeMillis();
for(int i = 0;i <= 100000;i++){
bf.append(i);
}
long t4 = System.currentTimeMillis();
System.out.println(t4 -t3);
}
}
***StringBuffer***和***StringBuilder***之间的区别
当 StringBuffer 空间被占满了,还要新增数据的话,会进行扩容,公式(原有长度 + 新增字符串长度) * 2
StringBUffer的常用方法
增加:append
append(xxx); 用于字符串拼接
删除:delete
delete(int start, int end); 用于删除指定位置的内容
修改 setCharAt / replace
setCharAt(int n,char c);把指定下标的内容替换成指定字符
replace(int start, int end ,String str);把[start,end)位置的内容替换成str
查询:charAt
与String用法一样
插入:insert
insert(int offset, xxx);在指定位置插入xxx
反转:reverse
把当前字符序列逆转
返回当前时间到1970年1月1日0:0:0之间的毫秒数,通常称之为时间戳
java.util.Date
|----- java.sql.Date 对应的是数据库中的日期类型的变量
构造方法:Date()
Date date1 = new Date();
//显示当前的年月日时分秒
System.out.println(date1.toString());
//获取当前Date对象对应的时间戳
System.out.println(date1.getTime());
java.sql.Date如何实例化对象
//import java.sql.Date;
java.sql.Date d = new java.sql.Date(12312421414L);
如何把 util.Date 对象转换成 sql.Date 对象
//强转
Date d2 = new java.sql.Date(12312412L);
java.sql.Date d3 = (java.sql.Date)d2;
//使用getTime()
Date d4 = new Date();
java.sql.Date d5 = new java.sql.Date(d4.getTime());
public class Test{
public static void main(String[] args){
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
//格式化 日期到字符串
String format1 = sdf.format(date);
System.out.println(format1);
//解析 字符串到日期
//要求字符串必须是符合SimpleDateFormat格式的
Date date2= sdf.parse("2020-02-06 17:48:13");
System.out.println(date2);
}
}
public class Test{
public static void main(String[] args){
//now():获取当前的日期
LocalDate lc = LocalDate.now();
/*
时间用LocalTime
日期加时间 LocalDateTime
*/
//of()
LocalDateTme ldt = LocalDateTime.of(2020,2,6,17,56,48);
/*
其他方法
getXXX()
*/
LocalDateTme ldt2 = LocalDateTime.withDayOfMonth(2);
}
}
public class {
public static void main(String[] args){
Instant in = Instant.now();
}
}
DateTimeFormatter dtf = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
LocalDateTime lc = LOcalDateTime.now();
System.out.println(dtf.format(lc));
TCP
UDP
将数据、源、目的封装成数据包,不需要建立连接
每个数据包的大小限制在64K以内
发送时不管对方是否准备好,接收方收到也不确认,因此不安全的
可以进行广播发送,发送数据结束时,无需释放资源,开销小,速度快
public class TCPTest{
public static void main(String[] args) throws IOException{
InetAddress inet = InetAddress.getByName("127.0.0.1");
Socket socket = new Socket(inet,9999);
OutputStream os = socket.getOutputStream();
os.write("这是客户端".getBytes());
os.close();
socket.close();
}
@Test
public void server(){
ServerSocket ss = new ServerSocket(9999);
Socket socket = ss.accept();
InputStream is = socket.getInputStream();
byte[] data = new byte[1024];
int len;
while((len = is.read(data) != -1){
String str = new String(data,0,len);
System.out.println(str);
}
System.out.println(socket.getInetAddress().getHostAddress());
}
}
问题
反射是不是跟封装性有矛盾?
Lambda是一个**匿名函数,可以理解为一段可以传递的代码**。使用它可以写出更简洁灵活的代码。
示例一:
@Test
public void test(){
Comparator<Integer> com1 = new Comparator<Integer>(){
@Overrdie
public int compare(Integre i1.Integer i2){
return Integer.compare(i1,i2);
}
}
}
//使用Lambda表达式
Comparator<Integer> com2 = (i1.i2) -> Integer.compare(i1,i2);
//使用
Compare<Integer> com3 = Integer::compare;
举例:(i1,i2) -> Integer.compare(i1,i2);
-> lambda操作符
左边lambda形参列表
右边lambda体,抽象方法的实体
懒得写,用的多了就会了
网址:http://www.jetbrains.com/idea/download/#section=windows
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-quMFafoG-1593855136188)(D:\Study\java\QQ截图20200202001250.png)]
https://blog.csdn.net/qq_38963960/article/details/89552704
Ctrl + F 在当前文件进行文本查找 (必备)
Ctrl + R 在当前文件进行文本替换 (必备)
Ctrl + Y 删除光标所在行 或 删除选中的行 (必备)
Ctrl + D 复制光标所在行 或 复制选择内容,并把复制内容插入光标位置下面 (必备)
Ctrl + W 递进式选择代码块。可选中光标所在的单词或段落,连续按会在原有选中的基础上再扩展选中范围 (必备)
Ctrl + E 显示最近打开的文件记录列表
Ctrl + N 根据输入的 类名 查找类文件
Ctrl + G 在当前文件跳转到指定行处
Ctrl + J 插入自定义动态代码模板
Ctrl + P 方法参数提示显示
Ctrl + Q 光标所在的变量 / 类名 / 方法名等上面(也可以在提示补充的时候按),显示文档内容
Ctrl + U 前往当前光标所在的方法的父类的方法 / 接口定义
Ctrl + B 进入光标所在的方法/变量的接口或是定义出,等效于 Ctrl + 左键单击
Ctrl + K 版本控制提交项目,需要此项目有加入到版本控制才可用
Ctrl + T 版本控制更新项目,需要此项目有加入到版本控制才可用
Ctrl + H 显示当前类的层次结构
Ctrl + O 选择可重写的方法
Ctrl + I 选择可继承的方法
Ctrl + + 展开代码
Ctrl + - 折叠代码
Ctrl + / 注释光标所在行代码,会根据当前不同文件类型使用不同的注释符号 (必备)
Ctrl + [ 移动光标到当前所在代码的花括号开始位置
Ctrl + ] 移动光标到当前所在代码的花括号结束位置
Ctrl + F1 在光标所在的错误代码出显示错误信息
Ctrl + F3 调转到所选中的词的下一个引用位置
Ctrl + F4 关闭当前编辑文件
Ctrl + F8 在 Debug 模式下,设置光标当前行为断点,如果当前已经是断点则去掉断点
Ctrl + F9 执行 Make Project 操作
Ctrl + F11 选中文件 / 文件夹,使用助记符设定 / 取消书签
Ctrl + F12 弹出当前文件结构层,可以在弹出的层上直接输入,进行筛选
Ctrl + Tab 编辑窗口切换,如果在切换的过程又加按上delete,则是关闭对应选中的窗口
Ctrl + Enter 智能分隔行
Ctrl + End 跳到文件尾
Ctrl + Home 跳到文件头
Ctrl + Space 基础代码补全,默认在 Windows 系统上被输入法占用,需要进行修改,建议修改为 Ctrl + 逗 号 (必备)
Ctrl + Delete 删除光标后面的单词或是中文句
Ctrl + BackSpace 删除光标前面的单词或是中文句
Ctrl + 1,2,3…9 定位到对应数值的书签位置
Ctrl + 左键单击 在打开的文件标题上,弹出该文件路径
Ctrl + 光标定位 按 Ctrl 不要松开,会显示光标所在的类信息摘要
Ctrl + 左方向键 光标跳转到当前单词 / 中文句的左侧开头位置
Ctrl + 右方向键 光标跳转到当前单词 / 中文句的右侧开头位置
Alt + enter 例如 new Person(),按了之后前边会自动加Person person;
Alt + Shift + z 包含代码块
1.自动导包设置
Setting --> Editor --> Auto Import
Insert Import on paste 改成All
并把下边Add 和Optimize勾选上
Setting --> Editor --> Appearance
show Line number
show method separators 这个是每个方法体结束之后会有一个灰色的虚线,方便看方法体结束的
Setting --> Editor --> Code completion Case sensitive 改成None
Setting --> Editor --> File and Code Templates Includes --> File Header
/**
@author zhaojinhui
@create ${YEAR}-${MONTH}-${DAY} ${TIMe}
*/
Setting --> Editor --> File Encoding UTF-8
Setting --> Build --> Compiler
***Build project***和***Compiler independent***需要勾选
Setting --> edtior --> General -->Postfix Compiler
edtior ->Live Templates 这个里边可以自定义模板比如自定义数据库连接啊,多线程创建啊
Key promoter 提示快捷键
JRebel 更改不需要重新部署
Lombok 不需要写get、set方法
AiXCode 智能提示
FindBugs-IDEA FindBugs[^FindBugs]: https://blog.csdn.net/zhaohonghan/article/details/88996697
String Manipulation字符串转换插件
选中当前工程 --> Build path --> Add libraries -->JUint --> 下一步
创建Java类,进行单元测试
此时的java类要求: ①此类是public的 ② 此类提供公共的无参构造器
声明的单元测试要求.
①方法的权限是public ②没有返回这没有形参
在测试上需要声明注解@Test,并导入org.junit.Test包
声明玩测试方法以后,可以在方法体内测试相关的代码,写完之后,双击单元测试方法名,run as -->JUnit Test
1. 如果执行结果没有异常是绿条,否则红条
[^FindBugs] : [^FindBugs]: https://blog.csdn.net/zhaohonghan/article/details/88996697
https://jadyer.cn/2016/04/20/idea-config
LoanHelper:22