Java笔记
基本数据类型
数据类型 | 内存空间 | 取值范围 |
byte | 8bit | -128~127 |
short | 16bit | -32768~32767 |
int | 32bit | -2147483648~2147483647 |
long | 64bit | |
float | 32bit | 1.4E-45~3.4028235E38 |
double | 64bit |
逻辑运算符
&&:会判断先判断第一个表达式,若为false则结果为false不会再去判断第二个
&:无论什么情况都会判断这两个表达式
运算符优先级
优先级 | 描述 | 运算符 |
1 | 括号 | () |
2 | 正负号 | +,- |
3 | 一元运算符 | ++,--,! |
4 | 乘除 | *,/ |
5 | 加减 | +,- |
6 | 移位运算 | >>,>>>,<< |
7 | 比较大小 | >,<,<=,>= |
8 | 比较是否相等 | !=,== |
9 | 按位与运算 | & |
10 | 按位异或运算 | ^ |
11 | 按位或运算 | | |
12 | 逻辑与运算 | && |
13 | 逻辑或运算 | || |
14 | 三元运算符 | ?: |
15 | 赋值运算符 | = |
注释
//单行注释
/*
多行注释
*/
String类
String(char a[],int offset,int length)
char a[]={'s','t','u','d','e','n','t'};
String s=new String(a,2,4);
等价于
String s=new String("uden");
表示从第二个字符开始截取长度为4的字符串
当使用双引号直接赋值时,系统会检查该字符串在串池(StringTable)中是否存在。
不存在:创建新的 存在:复用
Double.parseDouble();
可以将String语句转换为double语句,前提是String语句是小数的格式;
Stringa="3.14";
doubleb=Double.parseDouble(a);
字符串连接
Strings1=newString("hello");
Strings2=newString("world");
Strings=s1+" "+s2;
System.out.println(s);
可以使用“+”进行连接
intbooktime=4;
floatpractice=2.5f;
System.out.println("我每天花费"+booktime+"小时"+"看书"+ (practice+booktime) +"小时上机练习");
当整形变量和浮点型变量相连时会自动调用toStrng()方法,将其转换为字符串的形,然后参与连接。
获取字符串长度
length(s)
Stringstr="We are students";
intsize=str.length();
System.out.println(size);
返回的字符串s的长度(长度包括字符串中的空格)
字符串查找
indexOf(String s)
Stringstr="We are students";
intsize=str.indexOf("a");
System.out.println(size);
返回参数字符串s在指定字符串中首次出现的位置
如果没有找到,则返回-1
lastindexOf(String s)
Stringstr="We are students";
intsize=str.lastIndexOf("s");
System.out.println(size);
用于返回参数字符串s最后一次出现的位置
如果没有找到,则返回-1
如果参数是空字符串"",则返回的结果与调用length()方法的结果相同
获取指定索引位置的字符
charAt(int index)
Stringstr="We are students";
charmychar=str.charAt(7);
System.out.println(mychar);
使用charAt()方法可以将指定索引处的字符返回
获取子字符串
substring(int beginIndex)
Stringstr="We are students";
Stringsubstr=str.substring(4);
System.out.println(substr);
返回的是从指定的索引位置开始截取知道该字符串结尾的字串
substring(int beginIndex,int endIndex)
Stringstr="We are students";
Stringsubstr=str.substring(4,6);
System.out.println(substr);
该方法返回的是从某一位置开始截取至某一位置结束的字串(前闭后开)
去除空格
trim()
Stringstr=" We are students ";
Stringsubstr=str.trim();
System.out.println(substr);
返回字符串的副本,忽略前导空格和尾部空格
字符串替换
replace(char oldchar,char newchar)
Stringstr=" We are students ";
Stringsubstr=str.replace("a","b");
System.out.println(substr);
用于将指定的字符或字符串替换成新的字符或字符串
判断字符串的开始与结尾
startsWith(String prefix)
Stringnum1="22045612";
booleanb=num1.startsWith("22");
System.out.println(b);
判断当前字符串的前缀是否是指定参数,返回boolean类型的值
endsWith(String suffix)
Stringnum1="22045612";
booleanb=num1.endsWith("12");
System.out.println(b);
判断当前字符串的后缀是否是指定参数,返回boolean类型的值
判断字符串是否相等
equals( String otherstr)
equalsIgnoreCase(String otherstr)
String s1 = "hello";
String s2 = "你好";
String s3 = "HELLO";
String s4 = "hello";
boolean b = s1.equals(s4);
boolean b1 = s1.equalsIgnoreCase(s3);
System.out.println(b);
System.out.println(b1);
equals()方法区分大小写
equalsIgnoreCase()方法不区分大小写
StringBuilder
概述:StringBuilder可以看成一个容器,创建之后里面的内容是可变的
作用:提高字符串的操作效率
因为使用“+”对字符串进行拼接时,他会再创建一个字符串。
普及:因为StringBuilder时java已经写好的类
java在底层对他做了一些特殊处理 打印对象不是地址值而是属性值,
经典案例(链式编程):
publicstaticvoidmain(String[] args) {
intarr[] = {1, 2, 3};
Stringstr=newString();
str=pinjie(arr);
System.out.println(str);
}
publicstaticStringpinjie(intarr[]) {
StringBuildersb=newStringBuilder("[");
for (inti=0; i<arr.length; i++) {
if(i==arr.length-1){
sb.append(arr[i]).append("]");
}else{
sb.append(arr[i]).append(",");
}
}
returnsb.toString();
}
StringJoiner
构造方法 :
public StringJoiner(间隔符号)
public StringJoiner(间隔符号,开始符号,结束符号)
字符串原理
扩展底层原理1:字符串存储的内存缘婚礼
直接赋值会复用字符串常量池中的
new出来不会复用,而是开辟一个新的空间
扩展底层原理2:==号比较的到底是什么?
基本数据类型比较数据值
引用数据类型比较地址值
扩展底层原理3:字符串拼接的底层原理
如果没有变量参与,都是字符串直接相加,变异之后就是拼接之后的结果,会复用串池中的字符串
如果有变量参与,会创建新的字符串,浪费内存
扩展底层原理4:StringBuilder提高效率原理图
所有要拼接的内容都会往StringBUilder中放,不会创建很多无用的空间,节约内存
扩展底层原理5:StringBuilder源码分析
默认创建一个长度为16的字节数组
添加的内容长度小于16,直接存
添加的内容大于16会扩容(原来的容量*2+2)
如果扩容之后还不够,以实际长度为准
数组
创建数组
int arr[]=new int[5];
int arr[]={1,2,4,6};
int arr[]=new int[]{1,2,3,45,2};
int a[][] = new int[2][4];
int a[][] = {{12,0},{45,10}};
遍历数组
int arr2[][] = {{4,3},{1,2}};
int i = 0;
for(int x[]: arr2){
i++;
int j=0;
for(int e: x){
j++;
if(i == arr2.length&&j == x.length){
System.out.print(e);
}else{
System.out.print(e+"、");
}
}
}
foreach语句中,x[]等于是将arr2中的值复制到x[]中
并且x[]不必进行初始化。
填充数组元素
fill(int[] a,int value)
int arr[] = new int[5];
Arrays.fill(arr,8);
for(int x:arr){
System.out.println(x+" ");
}
结果:8 8 8 8 8
该方法是Arrays类中的静态方法,此方法可以将指定的int值分配给int型数组的每个元素
fill(int[] a,int fromIndex,int toIndex,int value)
int arr[] = new int[7];
Arrays.fill(arr,8);
Arrays.fill(arr,2,5,1);
for(int x:arr){
System.out.print(x+" ");
}
结果:8 8 1 1 1 8 8
该方法是将fromIndex到toIndex范围内的值填充为值value,如果fromIndex==toIndex则填充范围为空
排序
sort(int arr)
int arr[] = new int[]{23,42,12,8};
Arrays.sort(arr);
for(int x:arr){
System.out.print(x+" ");
}
结果:8 12 23 42
通过Arrays类的静态方法sort()可以实现对数组的排序,sort()方法提供了多种重载形式,可对任意类型的数组进行升序排序。
复制数组
copyOf(arr,int newlength)
int arr[] = new int[]{23,42,12};
int newarr[] = Arrays.copyOf(arr,5);
for(int i = 0;i<newarr.length;i++){
System.out.print(newarr[i]+" ");
}
结果:23 42 12 0 0
Arrays类中的静态方法copyOf()可以实现对数组的复制,newlength为新数组的长度,若新数组长度大于旧数组则大于的部分填充为0,若小于则截取到满足新数组长度为止
copyOfRange(arr,int fromIndex,int tolndex)
int arr[] = new int[]{23,42,12,84,10};
int newarr[] = Arrays.copyOfRange(arr,0,3);
for(int i = 0;i<newarr.length;i++){
System.out.print(newarr[i]+" ");
}
结果:23 42 12
数组查询
binarySearch(Object[] a,Object key)
int arr[] = new int[]{23,42,12,84,10};
Arrays.sort(arr);
for(int x: arr){
System.out.print(x+" ");
}
System.out.println();
int index = Arrays.binarySearch(arr, 10);
System.out.println(index);
结果:10 12 23 42 84 0
Arrays类的binarySearch()方法可使用二分法来搜索指定数组,以获得指定对象。该方法返回搜索元素的索引值
int arr[] = new int[]{4,25,10};
Arrays.sort(arr);
int index = Arrays.binarySearch(arr,0,1,8);
System.out.println(index);
结果:-2
并不存在元素“8”在数组arr中索引在0~1的位置。由于并不存在,所以index的值为"-",如果对数组进行排序,元素"8"应该在"10"的前面,"10"的索引值是1,所以index的索引值是"-2"
引用数据类型
new的都是引用数据类型
引用数据类型在栈里存储的是地址
public static void main(String args[]){
int arr[] = {10,20,30};
System.out.println("调用前:"+arr[1]);
change(arr);
System.out.println("调用后:"+arr[1]);
}
public static void change(int arr[]){
arr[1] = 50;
}
结果:调用前:20 调用后:50
输入
Scaner sc=new Scanner(System.in);
int i=sc.nextInt();
System.out.println(i);
canner sc = new Scanner(System.in);
String cang = sc.next();
字符串输入
工具类
java.util.Arrays类即为操作数组的工具类,包含了用来操作数组(比如排序和搜索)的各种方法
1 | boolean equals(int[] a,int[] b) | 判断两个数组是否相等 |
2 | String toString(int[] a) | 输出数组信息 |
3 | void fill(int[] a,int val) | 将指定值填充到数组之中 |
4 | void sort(int[] a) | 对数组进行排序 |
5 | int binarySearch(intt[] a,int key) | 对排序后的数组进行二分法检索指定的值 |
面向对象
封装
对象代表什么,就得封装对应的数据,并提供数据对应的行为
继承
继承
特点:
java只支持单继承,不支持多继承,但支持多层继承
每个类都直接或间接的继承与Object
子类继承父类内容
构造方法 | 成员变量 | 成员方法 | |
非私有 | 不能 | 能 | 能 |
private | 没在虚方法表中则不能 | 能 | 不能 |
继承时,父类会把自己的虚方法(非private、非static、非final修饰的方法)放到虚方法表中,然后子类会将虚方法表继承到自己的虚方法表中并把自己的虚方法添加到虚方法表中,调用方法时,会优先查看虚方法表内的方法
总结
继承中成员变量访问特点:就近原则。
遇到重名
System.out.printf(name); //从局部位置开始
System.out.printf(this.name); //从本类成员位置开始找
System.out.printf(name); //从父类成员位置开始找
方法的重写
当父类的方法不能满足子类现在的需求时,需要进行方法重写
书写格式
在继承体系中,子类出现了和父类一模一样的方法声明,我们称这个子类发的方法是重写的方法。
@Override重写注解
@Override是放在重写后的方法上,校验子类重写时语法是否正确。
加上注解后如果有红色波浪线,表示语法错误。
建议重写方法都加@Override注解,代码安全,优雅!
方法重写注意事项和要求
重写方法的名称、形参列表必须与父类中的一致
子类重写父类方法时,访问权限子类必须大于等于父类
子类重写父类方法时,返回值类型子类必须小于父类
建议:重写的方法尽量和父类保持一致
只有被添加到虚方法表中的方法才能被重写
继承中:构造方法的访问特点
父类中的构造方法不会被子类继承
子类中,所有的构造方法默认先访问父类中的无参构造,再执行自己
Why?
子类在初始化的时候,有可能会使用到父类的数据,如果父类没有完成初始化,子类将无法使用父类的数据
子类初始化之前,一定要调用父类构造方法先完成父类数据空间的初始化
怎么调用父类的构造方法?
子类构造方法的第一行语句默认都是:super(),不写也存在,且必须在第一行。
如果想调用父类有参构造,必须手动写super进行调用。
多态
什么是多态
同类型的对象,表现出的不同形态
多态的表现形式
父类类型 对象名称 = 子类对象;
多态的前提
有继承/实现关系
有父类引用指向子类对象
Fu f =new Zi();
有方法重写
多态的好处
使用父类型作为参数,可以接收所有的子类对象,体现多态的扩展性与便利
多态的优势
方法中,使用父类型作为参数,可以接收所有子类对象
多态的弊端
不能调用子类的特有功能
报错原因
当调用成员方法的时候,编译看左边,运行看右边
那么在编译的时候回先检查左边的父类有没有这个方法,如果没有直接报错
解决方案
变回子类类型就可以
细节:不能瞎转,如果转换成其他类的类型,就会报错
具体实现
if(a instanceof Dog){ //判断a是不是Dog类,是则返回true
Dog b = (Dog)a;
b.lookhome();
}
引用数据类型的类型转换
自动类型转换、强制类型转换
Person p =new Student(); //自动类型转换
Student s =(Student)p; //强制类型转换
强制类型转换能解决什么问题?
可以转换成真正的子类类型,从而调用子类独有的功能
转换类型与真是对象类型不一致会报错
转换的时候用instanceof关键字进行判断
抽象
抽象方法
抽象方法:将共性的行为(方法)抽取到父类之后。由于每个子类执行的内容是不一样的,所以,在父类中不能确定具体的方法体。该方法就可以定义为抽象方法。
抽象类:如果一个类中存在抽象方法,那么该类就必须声明为抽象类
抽象类的作用
抽取共性是,无法确定方法体,就把方法定义为抽象的
强制让子类按照某种格式重写
抽象方法所在的类,必须是抽象类
抽象类和抽象方法的定义格式
抽象方法的定义格式:
public abstract 返回值类型 方法名(参数列表);
抽象类的定义格式:
public abstract class 类名 {}
抽象类和抽象方法的注意事项
抽象类不能实例化
抽象类中不一定有抽象方法,但有抽象方法的类一定是抽象类
可以有构造方法
抽象类的子类,要么重写抽象类中的所有抽象方法,要么是抽象类
接口
接口就是一种规则,是对行为的抽象。
接口的定义和使用
接口用关键字interface来定义
public interface 接口名{}
接口不能实例化
接口和类之间是实现关系,通过implements关键字表示
public class 类名 implements 接口名 {}
接口的子类(实现类):要么重写接口中的所有抽象方法,要么是抽象类
注意:
接口和类的实现关系,可以单实现,也可以多实现
public class 类名 implements 接口名1,接口名2{}
实现类还可以在继承一个类的同时实现多个接口
public class 类名 extends 父类 implements 接口名1,接口名2{}
接口中成员的特点
成员变量
只能是常量
默认修饰符:public static final
构造方法
没有
成员方法
只能是抽象方法
默认修饰符:public abstract
JDK7以前:接口中可以定义抽象方法
JDK8的新特性:接口中可以定义有方法体的方法
JDK9的新特性:接口中可以定义私有方法
接口和类之间的关系
类和类之间的关系
继承关系,只能单继承,不能多继承,但是可以多层继承
类和接口的关系
实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
接口和接口的关系
继承关系,可以单继承,也可以多继承
JDK8开始接口中新增的方法
JDK7以前:接口中只能定义抽象方法
JDK8的新特性:接口中可以定义有方法体的方法。(默认、静态)
JDK9的新特性:接口中可以定义私有方法。
JDK8以后接口中新增的方法
允许在接口中定义默认方法,需要使用关键字default修饰
作用:解决接口升级的问题
允许在接口中定义静态方法,需要用static修饰
接口中默认方法的定义格式:
格式:public default 返回值类型 方法名(参数列表){ }
范例:public default void show(){ }
接口中默认方法的注意事项:
默认方法不是抽象方法,所以不强制被重写。但是如果被重写,重写的时候去掉default关键字
public可以省略,default不能省略
如果实现了多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写。
接口中静态方法的定义格式:
格式:public static 返回值类型 方法名(参数列表){ }
范例:public static void show(){ }
接口中静态方法的注意事项:
静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
public可以省略,static不能省略
JDK9新增的方法
接口中私有方法的定义格式:
格式1:private 返回值类型 方法名(参数列表){ }
范例1:prviate void show(){ }
格式2:private static 返回值类型 方法名(参数列表){ }
范例2:private static void method(){ }
总结
接口代表规则,是行为的抽象。想要让哪个类拥有一个行为,就让这个类实现对应的接口就可以了。
当一个方法的参数是接口时,可以传递接口所有实现类的对象,这种方式称之为接口多态。
代码块
作用
用来初始化类、对象
代码块如果有修饰的话,只能使用static
静态代码块
内部可以有输出语句
随着类的加载而执行,而且只执行一次
作用:初始化类的信息
如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构
非静态代码块
内部可以有输出语句
随着对象的创建而执行
每创建一个对象,就执行一次非静态代码块
作用:可以在创建对象时,对对象的属性进行初始化
非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法
适配器设计模式
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验总结。
使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用型。
简单理解:设计模式就是各种套路
适配器设计模式:解决接口与接口实现类之间的矛盾问题。
总结
当一个接口中的抽象方法过多,但是我只要使用其中一部分的时候,就可以使用适配器设计模式
书写步骤:
编写中间类XXXAdapter 实现对应的接口
对接口中的抽象方法进行空实现
让真正的实现类继承中间类,并重写需要用的方法
为了避免其他类创建适配器类的对象,中间的适配器类用abstract进行修饰
内部类
内部类就是在一个类里面,再定义一个类
内部类表示的是外部类的一部分
内部类单独出现没有任何意义
类的五大成员:
属性、方法、构造方法、代码块、内部类
内部类的访问特点
内部类可以直接访问外部类的成员,包括私有
外部类要访问内部类的成员,必须创建对象
内部类的分类
成员内部类
静态内部类
局部内部类
匿名内部类
成员内部类
写在成员位置的,属于外部类的成员。
成员内部类可以被一些修饰符所修饰,比如:private,默认,protected,public,static等
在成员内部类里面,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量。
获取成员内部类对象
方式一:
在外部类中编写方法,对外提供内部类的对象
方式二:
直接创建格式:
外部类名.内部类名 对象名 = 外部类对象.内部类对象;
在内部类中可以使用 外部类名.this 获取对象的地址值
Car.Engine en = new Car().new Engine();
静态内部类
静态内部类只能访问外部类中的静态变量和静态方法,如果想要访问非静态的需要创建对象。
创建静态内部类对象的格式:
外部类名.内部类名 对象名 = new 外部类名.内部类名();
调用非静态方法的格式:
先创建对象,用对象调用
调用静态方法的格式:
外部类名.内部类名.方法名();
注意事项
静态内部类也是成员内部类的一种
静态内部类只能访问外部类中的静态变量和静态方法
如果想要访问非静态的需要创建对象
局部内部类
将内部类定义在方法里面就叫做局部内部类,类似于方法里面的局部变量
外界是无法直接使用,需要在方法内部创建对象并使用。
该类可以直接访问外部类的成员,也可以访问方法内的局部变量。
匿名内部类
匿名内部类本质上就是隐藏了名字的内部类,可以写在成员位置,也可以写在局部位置。
格式:
new 类名或者接口名(){
重写方法;
};
代码示例
package Se.neibulei;
public class Student {
public static void main(String[] args) {
methd(
new Animal() {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
);
}
public static void methd(Animal a){
a.eat();
}
}
package Se.neibulei;
public abstract class Animal {
public abstract void eat();
}
格式细节
包含了继承或实现,方法重写,创建对象。
整体就是一个类的子类对象或者接口的实现类对象
使用场景
当方法的参数是接口或者类是,以接口为例,可以传递这个接口的实现类对象,如果实现类只要实现一次,就可以用匿名内部类简化代码。
方法的重载
概念
在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
特点
与返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类型)。调用时,根据方法参数列表的不同来区别。
示例
//返回两个整数的和
int add(int x,int y){return x+y;}
//返回三个整数的和
int add(int x,int y,int z){return x+y+z;}
//返回两个整数的和
double add(double x,double y,double z){return x+y'}
可变个数形参
JavaSE5.0中提供了Varargs(variable number of arguments)机制,允许直接定义能和多个实参相匹配的形参、从而,可以用一种更简单的方式,来传递个数可变的实参
具体使用:
1. 可变个数形参的个数:数据类型 ... 变量名
1. 当调用可变个数形参的方法是,传入的参数个数可以使:0个,1个,2个。。。
1. 可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载
1. 可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载。换句话说,二者不能共存
1. 可变个数形参在方法的形参中,必须声明在末尾
1. 可变个数形参在方法的形参中,最多只能声明一个可变形参
show(String .. strs){}
java方法中的参数传递机制的具体表现
基本数据类型:数据值
引用数据类型:地址值(含变量的数据类型)
权限修饰符
java中的四种权限修饰符:public(公共的)、protected(受保护的)、default(默认的)、private(私有的)
修饰符 | 同一个类 | 同一个包 | 子类 | 任何地方 |
private | yes | |||
default | yes | yes | ||
protected | yes | yes | yes | |
public | yes | yes | yes | yes |
private
1.private关键字是一个权限修饰符
2.可以修饰成员(成员变量和成员方法)
3.被private修饰的成员只能在本类中才能访问
4.针对private修饰的成员变量,如果需要被其他类使用,提供相应的操作
5.提供”setXxx(参数)“方法,用于给成员变量赋值,方法用public修饰
6.提供”getXxx(参数)“方法,用于获取成员变量值,方法用public修饰
this
this的本质:代表方法调用者地址值
获取成员变量
this的作用:区分局部变量和成员变量
this调用构造器
我们在类的构造器中,可以显式的使用"this(形参列表)"方式,调用本类中的指定的其他构造器
构造器中不能通过"this(形参列表)"方式调用自己
如果一个类中有n个构造器,则最多有n-1构造器中使用了"this(形参列表)"
规定,"this(形参列表)"方式必须声明在当前构造器的首行
构造器内部,最多只能声明一个"this(形参列表)",用来调用其他构造器
package
package的使用
为了更好的实现项目中类的管理,提供包的概念
使用package声明类或接口所属的包,声明在源文件的首行
包,属于标识符,遵循标识符的命名规则、规范(xxxyyyzzz)、"见名知意"
每"."一次,就代表一层文件目录
import
import:导入
在源文件中显式的使用import结构导入指定包下的类、接口
声明在包的声明和类的声明之间
如果需要导入多个结构,则并列写出即可
如果使用的类或接口是java.lang包下定义的,则可以省略import结构
可以使用"xxx.*"的方式,表示可以导入xxx包下的所有结构
如果使用的类或接口是本包下定义的,则可以省略import结构
如果在源文件中,使用了不同包下的同名的类,则必须至少有一个类需要以全名的方式显示
import static :导入指定类或接口中的静态结构:属性或方法
super
用来调用父类的方法或变量
final
final修饰方法
表名该方法时最终方法,不能被重写
final修饰类
表名该类时最终类,不能被继承
final修饰变量
叫做常量,只能被赋值一次
常量
实际开发中,常量一般作为系统的配置信息,方便维护,提高可读性。
常量的命名规范:
单个单词:全部大写
多个单词:全部大写,单词之间用下划线隔开
细节:
final修饰的变量是基本类型:那么变量存储的数据值,不能发生改变。
final修饰的变量是引用类型:那么变量存储的地址值不能发生改变,对象内部的可以改变。
JavaBean
JavaBean,是指符合如下标准的Java类
类是公共的
里有一个无参的公共构造器
有属性,且有对应的get、set方法
包
作用
包就是文件夹。用来管理各种不同功能的java类,方便后期代码维护
包的书写规则
公司域名反写+包的作用,需要全部英文小写,见名知意。
com.ithema.domain
全类名
包名+类名
使用其他类的规则
使用同一个包中的类是,不需要导包。
使用java.lang包中的类时,不需要导包。
其他情况都需要导包
如果同时使用两个包中的同名类,需要用全类名。
集合
ArrayList
基本操作
ArrayList<String> list = new ArrayList<>();
boolean result = list.add("aaa");//添加
list.add("bbb");
list.add("ccc");
// list.remove("ccc"); //按值删除 返回Boolean
// String str = list.remove(0);//按索引删除
// System.out.println(str);
list.set(1,"eee"); //更改索引位置的值
String str1 = list.get(2);//返回索引位置的值
System.out.println(str1);
System.out.println(list);
for (int i = 0; i < list.size(); i++) {//返回集合的长度
System.out.println(list.get(i));
}
基本数据类型对应的包装类
基本数据类型 | 包装类 |
byte | Byte |
short | Short |
char | Character |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
Scanner
Scanner sc = new Scanner(System.in);
String a = sc.next();
//接收字符串 遇到空格、制表符、回车就停止接收
String a =sc.nextLine();
//接收字符串 遇到回车停止接收
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
double v = Double.parseDouble(s);
System.out.println(v+1);
static
static表示静态,是java中的一个修饰符,可以修饰成员方法,成员变量
静态变量
被static修饰的成员变量,叫做静态变量
特点:
被该类所有对象共享
不属于对象,属于类
随着类的加载而加载,优先于对象存在
调用方式:
类名调用(推荐)
对象名调用
静态方法
被static修饰的成员方法,叫做静态方法
多用在测试类和工具类中
javabean类中很少会用
调用方式:
类名调用(推荐)
对象名调用
定义: 在类中使用static修饰的静态方法会随着类的定义而被分配和装载入内存中;而非静态方法属于对象的具体实例,只有在类的对象创建时在对象的内存中才有这个方法的代码段。
注意: 非静态方法既可以访问静态数据成员 又可以访问非静态数据成员,而静态方法只能访问静态数据成员;非静态方法既可以访问静态方法又可以访问非静态方法,而静态方法只能访问静态数据方法。
原因: 因为静态方法和静态数据成员会随着类的定义而被分配和装载入内存中,而非静态方法和非静态数据成员只有在类的对象创建时在对象的内存中才有这个方法的代码段
注意事项:
静态方法只能访静态变量和静态方法
非静态方法可以访问静态变量或静态方法,也可以访问非静态的成员变量和非静态的成员方法
静态方法中是没有this关键字
类
Javabean类:用来描述一类事物的类。比如Student,Teacher,Cat
测试类:用来检查其他类是否书写正确,带有main方法的类,是程序的入口
工具类:不是用来描述一类事物的,而是帮我们做一些事情的类
设计模式
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格,以及解决问题的思考方式。设计模免去我们再思考和摸索。式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,“套路”。
单例(Singleton)设计模式
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private,这样,就不能new操作符在类的外部产生类的对象了,但在类内部扔可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法已返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。
优点
由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
应用场景
网站的计数器,一般也是单例模式实现,否则难以同步
应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
Application也是单例的典型应用
Windows的Task Manager(任务管理器)就是很经典的单例模式
Windows的Recycle Bin(回收站)也是经典的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
main()方法
使用说明
main()方法可以作为程序的入口
main()方法也是一个普通的静态方法
main()方法可以作为我们与控制台交互的方式。(之前:使用Scanner)
异常
概述
在Java语言中,将程序执行中发生的不正常情况称为“异常”(开发过程中的语法错误和逻辑错误不是异常)
Java程序在执行过程中发生的异常事件可分为两类:
Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误,资源耗尽等严重情况。比如:StackOverflowError和OOM。一般不变西针对的代码进行处理。
Exception:其他因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:
空指针访问
试图读取不存在的文件
网络连接中断
数组角标越界
异常体系结构
java.lang.Throwable
java.lang.Error:一般不编写针对性的代码进行处理
java.lang.Exception:可以进行异常的处理
编译时的异常(checked)
IOException
FileNotFoundException
ClassNotFoundException
运行时异常(unchecked,RuntimeException)
NullpointerException
ArrayIndexOutOfBoundsException
ClassCastException
NumberFormatException
InputMismatchException
ArithmeticException
异常处理
Java采用的异常处理机制,是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅,并易于维护。
两种方式
tru-catch-finally
throws+异常类型
抛抓模型
“抛”:程序在正常的执行过程中,一旦出现异常,就会在代码处生成一个对应异常类,并将此对象抛出。
一旦抛出对象以后,其后的代码就不再执行。
关于异常对象的的产生:
系统自动生成的异常对象。
手动生成一个异常对象,并抛出(throw)
“抓”:可以理解为异常的处理方式:
try-catch-finally
throws
try-catch-finally的使用
try{
//可能出现异常的代码
}catch(异常类型1 变量名1){
//处理异常的方式1
}catch(异常类型2 变量名2){
//处理异常的方式2
}catch(处理异常2 变量名3){
//处理异常的方式3
}
...
finally{
//一定会执行的代码
}
说明
finally是可选的
使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配
一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前的try-catch结构(在没有写finally的情况),继续执行其后的代码。
catch中的异常类型如果没有子父关系,则谁声明在上,谁声明在下无所谓。
catch中的异常类型如果满足子父关系,则要求子类一定声明在父类的上面。否则报错。
常用的异常对象处理方式
String getMessage()
printStackTrace()
在try结构中声明的结构变量,出了try便无法调用。
try-catch-finally结构可以嵌套
finally的使用
finally是可选的
finally中声明的是一定会被执行的代码,及时catch中又出现异常了,try中有return语句,catch中有return语句等情况。
体会
使用try-catch-finally处理编译时异常,是得程序在编译时就不再报错,但是运行时仍可能报错,相当于我们使用try-catch-finally将一个可能出现的异常,延迟到运行时出现。
开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了,针对编译时异常,我们说一定要考虑异常的处理。
开发中如何选择使用try-catch-finally,还是使用throws?
如果父类中被重写的方法设有throws方式处理异常,则子类重写的方法也不能使用throws,意味着子类重写的方法中有异常,必须使用try-catch-finally方式处理。
执行的方法a中,先后调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方式进行处理。
throws+异常类型
使用
"throws+异常类型"写在方法的声明处,指明此方法执行时,可能会抛出异常类型,一旦方法执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws异常类型时,就会被抛出,
体会:try-catch-finally:真正的将异常给处理掉了
throws的方式只是将异常抛给了方法的调用者,并没有真正的将异常处理掉。
重写方法抛出异常的规则
子类重写的方法抛出异常类型不大于父类被重写的方法抛出的异常类型。
手动抛出异常写法
public void regist(int id)throws Exception{
if(id>0){
this.id=id;
}else{
throw new Exception("您输入的数据异常!");
}
}
public static void main(String[] args) {
try {
new Student().regist(-1000);
} catch (Exception e) {
System.out.println(e.getMessage());
}finally {
System.out.println();
}
}
自定义异常类
继承于现有的异常结构,RuntimeException,Exception
提供全局常量,serialVersionUID
提供重载的构造器
内置异常类
非检查型性异常
异常 | 描述 |
ArithmeticException | 当出现异常的运算条件时,抛出此异常。例如,一个整数"除以零"时,抛出此类的一个实例。 |
ArrayIndexOutOfBoundsException | 用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。 |
ArrayStoreException | 试图将错误类型的对象存储到一个对象数组时抛出的异常。 |
ClassCastException | 当试图将对象强制转换为不是实例的子类时,抛出该异常。 |
IllegalArgumentException | 抛出的异常表明向方法传递了一个不合法或不正确的参数。 |
IllegalMonitorStateException | 抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。 |
IllegalStateException | 在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。 |
IllegalThreadStateException | 线程没有处于请求操作所要求的适当状态时抛出的异常。 |
IndexOutOfBoundsException | 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。 |
NegativeArraySizeException | 如果应用程序试图创建大小为负的数组,则抛出该异常。 |
NullPointerException | 当应用程序试图在需要对象的地方使用 null 时,抛出该异常 |
NumberFormatException | 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。 |
SecurityException | 由安全管理器抛出的异常,指示存在安全侵犯。 |
StringIndexOutOfBoundsException | 此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。 |
UnsupportedOperationException | 当不支持请求的操作时,抛出该异常。 |
检查性异常
异常 | 描述 |
ClassNotFoundException | 应用程序试图加载类时,找不到相应的类,抛出该异常。 |
CloneNotSupportedException | 当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。 |
IllegalAccessException | 拒绝访问一个类的时候,抛出该异常。 |
InstantiationException | 当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。 |
InterruptedException | 一个线程被另一个线程中断,抛出该异常。 |
NoSuchFieldException | 请求的变量不存在 |
NoSuchMethodException | 请求的方法不存在 |
异常方法
序号 | 方法及说明 |
1 | public String getMessage() 返回关于发生的异常的详细信息。这个消息在Throwable 类的构造函数中初始化了。 |
2 | public Throwable getCause() 返回一个 Throwable 对象代表异常原因。 |
3 | public String toString() 返回此 Throwable 的简短描述。 |
4 | public void printStackTrace() 将此 Throwable 及其回溯打印到标准错误流。。 |
5 | public StackTraceElement [] getStackTrace() 返回一个包含堆栈层次的数组。下标为0的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底。 |
6 | public Throwable fillInStackTrace() 用当前的调用栈层次填充Throwable 对象栈层次,添加到栈层次任何先前信息中。 |
多线程
基本概念
名词解释
程序
(program),是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
进程
(process),是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
如:运行中的QQ,运行中的MP3播放器
程序是静态的,进程是动态的
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
线程
(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
若一个进程同一时间并行执行多个线程,就是支持多线程的。
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
一个进程的多个线程共享相同的内存单元/内存地址空间->它们同一堆中分配对象,可以访问相同的变量和对象,这就使得线程间通信更简洁、高效,但多个线程操作共享的系统资源可能就会带来安全的隐患。
单核CPU与多核CPU的理解
多核CPU,请示是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。例如:虽然有多车道,但是收费站只有一个一个工作人员在收费,只有收了费才能通过,那么CPU就好比收费人员。如果某个人不想交钱,那么收费人员可以把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费。但是因为CPU时间单元特别短,因此感觉不出来。
如果是多核的话,才能更好的发挥多线程的效率(现在的服务器都是多核的)
一个Java应用程序java.exe,其实只要三个线程,main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
并行与并发
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事
使用多线程的优点
背景:以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?
多线程程序的优点:
提高应用程序的相应。对图形化界面更有意义,可增强用户体验。
提高计算机系统CPU的利用率
改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。
何时需要多线程
程序同时执行两个或多个任务
程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等
需要一些后台运行的程序时。
线程的创建和启动
Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来体现。
Thread类的特性
每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体
通过该Thread对象的start()方法来启动这个线程,而非直接调用run()
方式一:继承于Thread类
创建一个继承于Thread类的子类
重写Thread类的run()
创建Thread类的子类的对象
通过此对象调用start()
实例
class MyThread extends Thread{
@Override
public void run(){
for (int i = 0; i < 10000; i++) {
if(i%2==0){
System.out.println(i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
MyThread m1 = new MyThread();
//1.启动当前线程 2.调用当前线程的run()
m1.start();
for (int i = 0; i < 10000; i++) {
if(i%2==1){
System.out.println(i+"鸡");
}
}
}
}
Thread类的方法
void start():启动线程,并执行对象run()方法
run():线程在被调度时执行的操作
String getName():返回线程的名称
void setName(String name):设置该线程名称
startic Thread currentThread():返回当前线程。在Thread子类中就是this,通常用于主线程和runnable实现类。
static void yield():线程让步
暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
若队列中没有同优先级的线程,忽略此方法。
join():当某个程序执行流中调用其他的线程join()方法时,调用线程将被阻塞,知道join()方法加入的join线程执行完为止
低优先级的线程也可以获得执行
static void sleep(long millis):(指定时间:毫秒)
令当前活动线程在指定时间段内放弃对CPU的控制,使其他线程有机会被执行,时间到后重新排队
抛出InterruptedException异常
stop():强制线程生命期结束,不推荐使用。方法已过时。
boolean isAlive():返回boolean,判断线程是否还活着。
线程通信
wait() / notigy() /notigyAll():此三个方法定义在Object类中。
方式二:实现Runnable接口
创建一个是实现了Runnable接口的类
实现类去实现Runnable中的抽象方法:run()
创建实现类的对象
将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
通过Thread类的对象调用start()
实例
package duoxiancheng;
class MThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
if(i%2 == 0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
public class MThreadTest {
public static void main(String[] args) {
MThread mThread = new MThread();
Thread t1 = new Thread(mThread);
t1.start();
Thread t2 = new Thread(mThread);
t2.start();
}
}
比较创建线程的两种方式
开发中:优先选择:实现Runnable接口的方式
原因
实现的方式没有类的单继承性的局限性
实现的方式更适合来处理多个线程有共享数据的情况。
联系
public class Thread implements Runnable
相同点
两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
方式三:实现Callable接口
jkd5.0新增
与使用Runnable相比,Callable功能更强大些
相比run()方法,可以有返回值
call()方法可以抛出异常,被外面的操作捕获,获取异常的信息
Callable支持泛型的返回值
需要借助FutureTask类,比如获取返回结果
Future接口
可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
FutrueTask是Futrue接口的唯一的实现类
FutureTask同时实现了Runnable,Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
创建步骤
创建一个实现Callable的实现类
实现call方法,将此线程需要执行的操作声明在call()中
创建Callable接口实现类的对象
将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTak的对象
将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象那个,并调用start()
获取Callable中call方法的返回值
get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
示例
package duoxiancheng;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class NumThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
if(i%2==0){
sum+= i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
NumThread numThread = new NumThread();
FutureTask<Integer> futureTask = new FutureTask<Integer>(numThread);
Thread thread = new Thread(futureTask);
thread.start();
try {
Integer sum = futureTask.get();
System.out.println(sum);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
}
方式四:使用线程池
背景
经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理
corePoolSize:核心池的大小
maxmumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
线程池相关API
JDK5.0起提供了线程池相关API:ExecutorService和Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
<T>Future<T> submit(Callable<T> task):执行任务,有返回值,一般又来执行Callable
void shutdown():关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n):创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor:创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n):创建一个线程池,它可安排给定延迟后运行明或者定期地执行。
示例
package duoxiancheng;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class ThreadJi implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==1){
System.out.println(Thread.currentThread().getName()+": "+i);
}
}
}
}
class ThreadO implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName()+": "+i);
}
}
return sum;
}
}
public class Xianchengchi {
public static void main(String[] args) {
//提供指定数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//执行指定的线程操作,需要提供实现的Runnable接口或Callable接口
service.execute(new ThreadJi());//适用于Runnable
service.submit(new ThreadO());//适用于Callable
//关闭线程池
service.shutdown();
}
}
Java的调度方法
同优先级线程组成先进先出队列(先到先服务),使用时间片策略
对高优先级,使用优先调度的抢占式策略
线程的优先级
线程的优先级等级
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5
涉及方法
getPriority():返回线程的优先值
setPriority(int newPriority):该表线程的优先级
说明
线程创建时继承父线程的优先级
低优先级只是获得调度的概率低,并未一定是在高优先级线程之后才被调用
线程的分类
Java中的线程分为两类:一种是守护线程,一种是用户线程。
他们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
及守护线程是用来服务用户线程的,通过在start()方法前调用thred.setDaemon(true)可以把一个用户线程变成一个守护线程。
Java累计回收就是一个典型的守护线程。
若JVM中都是守护线程,当前JVM将退出
形象理解:兔死狗烹,鸟尽弓藏
线程的生命周期
Thread.Start类其定义了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Tread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间拍你,此时它以具备了运行的条件,只是没分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时终止自己的执行,进入阻塞状态
死亡:线程完成了他的全部工作或线程被提前强制性地中止或者出现异常导致结束
解决线程安全问题
例子
问题:买票过程中,出现了重票、错票-->出现了线程的安全问题
问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票
如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来,知道线程a操作完ticket,其他线程才可以开始操作ticket。这种情况及时线程a出现了阻塞,也不能被改变。
在Java中,我们通过同步机制,来解决线程的安全问题。
线程同步
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:
操作共享数据的代码,即为需要被同步的代码
共享数据,多个线程共同操作的变量。比如:ticket就是共享数据。
同步监视器,俗称:锁。任何一个类的对象那个都可以充当锁。
要求:多个线程必须要共用同一把锁
补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。在继承Therad类创建多线程的方式中,慎用this充当同步显示器,考虑使用当前类,充当同步监视器。
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
示例1
class Window2 implements Runnable {
private static int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private synchronized void show() {//同步监视器:this
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket--;
} else {
return;
}
}
}
public class WindowTest2 {
public static void main(String[] args) {
Window2 w1 = new Window2();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.start();
t2.start();
t3.start();
}
}
示例2
class Window3 extends Thread {
private static int ticket = 100;
public Window3(String name){
super(name);
}
@Override
public void run(){
while(true){
show();
}
}
private static synchronized void show(){ //同步监视器是Window3.class
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口为:"+Thread.currentThread().getName()+"票号为:"+ticket);
ticket--;
}else{
return;
}
}
}
public class WindowTest3 {
public static void main(String[] args) {
Window3 w1 = new Window3("窗口1");
Window3 w2 = new Window3("窗口二");
Window3 w3 = new Window3("窗口三");
w1.start();
w2.start();
w3.start();
}
}
同步方法的总结
同步方法仍然涉及到同步监视器,知识不需要我们显示的声明
非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身
同步的优缺点
同步的方式,解决了线程的安全问题。——好处
操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。——坏处
线程的死锁问题
死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃,自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,知识所有的线程都处于阻塞状态无法继续
我们使用同步时要避免死锁
解决方法
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步
Lock(锁)
从JDK5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常见的是ReetrantLock,可以显式加锁、释放锁。
示例
package duoxiancheng;
import java.util.concurrent.locks.ReentrantLock;
class Window4 implements Runnable{
private int ticket = 100;
//如果参数为true则,每个线程都会依次运行
private ReentrantLock Lock = new ReentrantLock(true);
@Override
public void run() {
while(true){
try{
//加锁
Lock.lock();
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("票号为:"+ticket+"售卖窗口为:"+Thread.currentThread().getName());
ticket--;
}else{
break;
}
}finally {
//释放锁
Lock.unlock();
}
}
}
}
public class WindowTest4 {
public static void main(String[] args) {
Window4 w4 = new Window4();
Thread t1 = new Thread(w4);
Thread t2 = new Thread(w4);
Thread t3 = new Thread(w4);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
面试题
synchronized与Lock的异同?
相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器。Lock需要手动启动同步(lock()),同时结束同步也需要手动的实现(unlock())
sychronized与Lock的对比
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),sychronize是隐式锁,出了作用域自动释放
Lock只有代码块锁,synchronized有代码块锁和方法锁
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
优先使用顺序
Lock->同步代码快(已经进入了方法体,分配了相应资源)->同步方法(在方法体之外)
线程的通信
方法
wait()
一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
notify()
一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就会唤醒优先级高的那个
notifyAll()
一旦执行此方法,就会唤醒所有被wait的线程
说明
wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
wait(),notify(),notifyAll()三个方法调用者必须是同步代码块或同步方法中的同步监视器
wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
面试题
sleep()和wait()的异同?
相同
一旦执行方法,都可以使得当前的线程进入阻塞状态。
不同
两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
调用的要求不同:sleep()可以在任何需要的场景下调用。wait()必须使用在同步代码块中
关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁
示例
package duoxiancheng;
//店员
class Clerk {
private int num=0;
public synchronized void add(){
if(num<20){
notify();
num++;
System.out.println(Thread.currentThread().getName()+"生产了"+num+"号");
}else{
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public synchronized void reduce(){
if(num>0){
notify();
System.out.println(Thread.currentThread().getName()+"消费了"+num+"号");
num--;
}else{
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
//生产者
class Producer extends Thread{
private Clerk clerk;
@Override
public void run(){
System.out.println(getName()+"开始生产");
while (true){
try {
sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
clerk.add();
}
}
public Producer(Clerk clerk){
this.clerk=clerk;
}
}
//消费者
class Customer extends Thread{
private Clerk clerk;
@Override
public void run(){
System.out.println(getName()+"开始消费");
while(true){
try {
sleep(20);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
clerk.reduce();
}
}
public Customer(Clerk clerk){
this.clerk=clerk;
}
}
public class ProducerTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer p1 = new Producer(clerk);
Customer c1 = new Customer(clerk);
Customer c2 = new Customer(clerk);
p1.setName("生产者1");
c1.setName("消费者1");
c2.setName("消费者2");
p1.start();
c1.start();
c2.start();
}
}
集合
Java的集合中可以分为Collection(单列集合)和map(双列集合)
体系结构
List系列集合:添加的元素是有序、可重复、有索引的
set系列集合:添加的元素是无序、不重复、无索引的
Conllection
Collection是单列集合的祖宗接口,他的功能是全部单列集合都可以继承使用的。
Collection是单列集合的顶层接口,所有方法被List和Set系列共享
方法
方法名称 | 说明 |
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public void clear() | 清空集合中所有的元素 |
public boolean remove(E e) | 把给定的对象在当前集合中删除 |
public boolean contains(Object obj) | 判断当前集合中是否包含给定的对象 |
public boolean isEmpty | 判断当前集合是否为空 |
public int size() | 返回集合中元素的个数/集合的长度 |
注意
contains方法在底层依赖equals方法判断对象是否一致,如果存的是自定义对象,没有重写equals方法,那么默认使用Object类中的equals方法进行判断,而Object类中的equals方法是依赖于地址值进行判断的,如果同姓名和同年龄,就认为是同一个学生,那么,需要在自定义的Javabean类中,重写equals方法就可以了。
遍历方式
迭代器:在遍历的过程中需要删除元素,请使用迭代器。
增强for、Lambda:仅仅想遍历,那么使用增强for或Lambda表达式。
迭代器遍历
迭代器在Java中的类是Iterator,迭代器是集合专用的遍历方式。
迭代器在遍历集合时,是不依赖索引的。
Collection集合获取迭代器
方法名称 | 说明 |
Iterator<E> iterator() | 返回迭代器对象,默认指向当前集合的0索引 |
Iterator中的常用方法
方法名称 | 说明 |
boolean hasNext() | 判断当前位置是否有元素,有元素则返回true,没有元素返回false |
E next() | 获取当前位置的元素,并将迭代器对象移向下一个位置 |
void remove() | 从迭代器指向的collection中移除迭代器返回的最后一个元素 |
示例
Collection<String> coll = new ArrayList<>();
coll.add("a");
coll.add("b");
coll.add("c");
coll.add("d");
coll.add("e");
Iterator<String> it = coll.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
注意
如果it指向空再next会报错NoSuchElementException
迭代器遍历完毕,指针不会复位
循环中只能用一次next方法
迭代器遍历时,不能用集合的方法进行增加和删除
如果需要删除,那么可以使用迭代器提供的remove方法进行删除。
添加暂时没有办法。
增强for遍历
增强for的底层就是迭代器,为了简化迭代器的代码书写的。
他是JDK5之后出现的,其内部原理就是一个Iterator迭代器
所有的单例集合和数组才能用增强for进行遍历
格式
for(元素的数据类型 变量名:数组或者集合){
}
示例
Collection<String> coll = new ArrayList<>();
coll.add("xianrendou");
coll.add("sisan");
coll.add("siyou");
for(String s:coll){
System.out.println(s);
}
Lambda表达式遍历
得益于JDK8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。
方法名称 | 说明 |
default void forEach(Consumer<? super T> action); | 结合lambda遍历集合 |
底层原理:其实也会自己遍历集合,依次得到每一个元素,把得到的每一个元素,传递给下面的accept方法,s依次表示集合中的每一个数据
示例
Collection<String> coll = new ArrayList<>();
coll.add("xianrendou");
coll.add("sisan");
coll.add("siyou");
// for(String s:coll){
// System.out.println(s);
// }
coll.forEach(s->System.out.println(s));
List
特点
有序:存和取得元素顺序一致
有索引:可以通过索引操作元素
可重复:存储的元素可以重复
方法
Collection的方法List都继承了
List集合因为有索引,所以多了很多索引操作的方法
方法名称 | 说明 |
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
例子
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add(1,"222");
// System.out.println(list.remove(0));
list.set(0,"kkk");
System.out.println(list);
System.out.println(list.get(3));
/*List<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(2);
list1.add(3);
Integer i = Integer.valueOf(1);
list1.remove(i);
System.out.println(list1);*/
列表迭代器
方法与迭代器遍历相同
额外添加了一个方法:在遍历的过程中,可以添加元素
五种遍历方式对比
迭代器遍历:在遍历的过程中需要删除元素,请使用迭代器。
列表迭代器:在遍历的过程中需要添加元素
增强for遍历 and Lambda表达式:仅仅想遍历,那么使用增强for或者Lambda表达式
普通for:如果遍历的时候想操作索引,可以用普通for
ArrayList
底层原理
利用空参创建的集合,在底层创建一个默认长度为0的数组
添加第一个元素时,底层会创建一个新的长度为10的数组
存满时,会扩容1.5倍
如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准
LinkedList
底层数据结构是双链表,查询慢,增删快,但是如果操作的是首尾元素,速度也是极快的。
LinkedList本身多了很多直接操作首尾元素的特有API。
特有方法
特有方法 | 说明 |
public void addFirst(E e) | 在该列表开头插入指定的元素 |
pulic void addLast(E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
Set系列集合
无序:存取顺序不一致
不重复:可以去重复
无索引:没有带索引的方法,所以不能使用普通fro循环,也不能通过索引来获取元素
Set集合的实现类
HashSet:无序、不重复、无索引
LinkedHashSet:有序、不重复、无索引
TreeSet:可排序、不重复、无索引
Set接口中的方法基本上与Collection的API一致。
HashSet
底层原理
HashSet集合底层采取哈希表存储数据
哈希表是一种对于增删改查数据性能都较好的结构
哈希表组成
JDK8之前:数组+链表
JDK8开始:数组+链表+红黑树
哈希值
根据hashCode方法算出来的int类型的整数
该方法定义在Object类中,所有对象都可调用,默认使用地址值进行计算
一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值
对象哈希值特点
如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
如果已经重写hashcode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞)
HashSet 底层原理
创建一个默认长度16,默认加载因子0.75(用于数组扩容)的数组,数组名table
根据元素的哈希值跟数组的长度激素三处应存入的位置
判断当前位置是否为null,如果是null直接存入
如果位置不为null,表示有元素,则调用equals方法比较属性值
一样:不存 不一样:存入数组,形成链表
JDK8以前:新元素存入数组,老元素挂在新元素下面
JDK8以后:新元素直接挂在老元素下面
但数组里面存了16*0.75=12个元素时,数组长度自动扩容为32。
JDK8以后,当链表长度大于8而且数组长度大于等于64时,当前链表就会自动转为红黑树。
注意
如果集合中存储的是自定义对象,必须要重写hashCode和equls方法
LinkedHashSet
底层原理
有序、不重复、无索引
这里的有序指的是保证存储和取出的元素顺序一致
原理:底层数据结构依然是哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序
在以后如果要数据去重,该使用哪个?
默认使用HashSet
吐过要求去重且存取有序,才使用LinkedHashSet
TreeSet
特点
不重复、无索引、可排序
可排序:按照元素的默认规则(从小到大)排序
TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
默认排序规则
对于数值类型:Integer,Double,默认按照从小到大的顺序进行排序
对于字符、字符串类型:按照字符串在ASCII码表中的数字升序进行排序。
两种比较方式
方式一
默认排序/自然排序:javabean类实现Comparable接口指定比较规则
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString(){
return "Student{name="+name+",age="+age+")";
}
@Override
public int compareTo(Student o) {
//指定排序规则
//只看年龄,我想要按照年龄的升序进行排序
return this.getAge() - o.getAge() ;
}
//this:表示当前要添加的元素//o:表示已存在红黑树中的元素//返回值:// 负数:认为要添加的元素是小的,存左边// 正数:认为要添加的元素是大的,存右边// 0:认为要添加的元素已存在,舍弃
方式二
比较器排序:创建TreeSet对象时候,传递比较器Comparator制定规则
使用原则
默认使用第一种,如果第一种不能满足,就使用第二种。
public class Student3 implements Comparable<Student3>{
private String name;
private int age;
private int chinese_score;
private int math_score;
private int english_score;
public Student3() {
}
public Student3(String name, int age, int chinese_score, int math_score, int english_score) {
this.name = name;
this.age = age;
this.chinese_score = chinese_score;
this.math_score = math_score;
this.english_score = english_score;
}
public String toString(){
return "name="+name+",all_score="+(chinese_score+math_score+english_score)+",chinese_score="+ chinese_score+ ",math_score="+math_score+",english_score="+english_score;
}
@Override
public int compareTo(Student3 o) {
int sum1 = this.getChinese_score()+this.getMath_score()+this.getEnglish_score();
int sum2 = o.getChinese_score()+o.getMath_score()+o.getEnglish_score();
int i =sum2-sum1;
i = i==0?o.getChinese_score()-this.getChinese_score():i;
i = i==0?o.getMath_score()-this.getMath_score():i;
i = i==0?o.getEnglish_score()-this.getEnglish_score():i;
i = i==0?o.getAge()-this.getAge():i;
i = i==0?o.getName().compareTo(this.getName()):i;
return i;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getChinese_score() {
return chinese_score;
}
public void setChinese_score(int chinese_score) {
this.chinese_score = chinese_score;
}
public int getMath_score() {
return math_score;
}
public void setMath_score(int math_score) {
this.math_score = math_score;
}
public int getEnglish_score() {
return english_score;
}
public void setEnglish_score(int english_score) {
this.english_score = english_score;
}
}
总结
如果想要集合中的元素可重复
用ArrayList集合,基于数组的。(用的最多)
如果想要集合中的元素可重复,而且当前的增删操作明显多于查询
用LinkedList集合,基于链表的。
如果想对集合中的元素去重
用HashSet集合,基于哈希表的。(用的最多)
如果想对集合中的元素去重,而且保证存取顺序
用LinkedHashSet集合,基于哈希表和双链表,效率低于HashSet。
如果想对集合中的元素进行排序
用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。
Map
双列集合的特点
双列集合一次需要存一对数据,分别为键和值
键不能重复,值可以重复
键和值是一一对应的,每个键只能找到自己对应的值
键+值这个整体 我们称之为"键值对"或者"键值对对象",在Java中叫做"Entry对象"
Map常见的API
Map是双列集合的顶层接口,它的功能是全部双列集合都可以继承使用的
方法名称 | 说明 |
V put(K key,V value) | 添加元素 |
V remove(Object key) | 根据键删除键值对元素 |
v get(K key) | 获取指定 key 对应对 value |
void clear() | 移除所有的键值对元素 |
boolean containsKey(Object key) | 判断集合是否包含指定的键 |
boolean contaionsValue(Object value) | 判断集合是否包含指定的值 |
boolean isEmpty | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中键值对的个数 |
细节
put
在添加数据时,如果键不存在,则直接把键值对对象添加到map集合中,方法返回null。
在添加数据时,如果键存在,则把键值对原有的值覆盖,并返回被覆盖的值
Map的遍历方式
1.键找值
Map<String,String> m = new HashMap<>();
m.put("tent","帐篷");
m.put("newspaper","报纸");
m.put("abroad","出国");
Set<String> keys = m.keySet();
for(String v:keys){
System.out.println(v+":"+m.get(v));
}
// Iterator<String> it = keys.iterator();
// while(it.hasNext()){
// String value = it.next();
// System.out.println(value+":"+m.get(value));
// }
keys.forEach(s -> System.out.println(s+":"+m.get(s)));
System.out.println(m);
2.键值对
//entrySet:返回所有键值对对象,返回一个Set集合
Set<Map.Entry<String,String>> entries = m.entrySet();
//遍历这个集合,去得到里面的每一个键值对对象(entry)
//增强for遍历
for(Map.Entry<String,String> entry:entries){
System.out.println(entry);
}
//迭代器遍历
Iterator<Map.Entry<String,String>> it = entries.iterator();
while(it.hasNext()){
Map.Entry<String,String> entry = it.next();
System.out.println(entry);
}
//lambda遍历
entries.forEach(stringStringEntry -> System.out.println(stringStringEntry));
System.out.println(m);
3.Lambda表达式
方法名称 | 说明 |
default void forEach(BiConsumer<? super k,? super V>action) | 结合lambda遍历Map集合 |
Map<String,String> m = new HashMap<>();
m.put("tent","帐篷");
m.put("newspaper","报纸");
m.put("abroad","出国");
m.forEach((k, v) -> System.out.println(k+"="+v));
System.out.println(m);
HashMap
特点
HashMap是Map里面的一个实现类
没有额外需要学习的特有方法,直接使用Map里面的方法就可以了
特点都是由键决定的:无序、不重复、无索引
HashMap跟HashSet底层原理是一样的,都是哈希表结构
总结
HashMap底层是哈希表结构的
依赖hashCode方法和equals方法保证键的唯一
如果键存储的是自定义对象,需要重写hashCode和equals方法
如果值存储自定义对象,不需要重写hashCode和equals方法
LinkedHasMap
由键决定:有序、不重复、无索引
这里的有序指的是保证存储和取出的元素顺序一致
原理:底层数据结构依然是哈希表,知识每个键值对元素又额外的多了一个双链表的机制记录存储的顺序。
例子
public static void main(String[] args) {
LinkedHashMap<String,Integer> lhm = new LinkedHashMap<>();
lhm.put("a",123);
lhm.put("a",11);
lhm.put("b",1333);
lhm.put("d",123);
System.out.println(lhm);
}
TreeMap
TreeMap跟TreeSet底层原理一样,都是红黑树结构的。
由键决定特性:不重复、无索引、可排序
可排序:对键进行排序。
注意:默认按照键的从小到大进行排序,也可以自己规定键的排序规则
代码书写两种排序规则
实现Comparable接口,指定比较规则。
创建集合时传递Comparator比较器对象,指定比较规则。
例子
l1
package gather;
import org.jetbrains.annotations.NotNull;
public class Student4 implements Comparable<Student4>{
private String name;
private int age;
public Student4() {
}
public Student4(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student4{name = " + name + ", age = " + age + "}";
}
@Override
public int compareTo(@NotNull Student4 o) {
//按照学生年龄的升序排序,年龄一样按照姓名的字母排序,同姓名年龄视为同一个人。
//this:表示当前要添加的元素
//o:表示已经在红黑树中存在的元素
//返回值:
//负数:表示当前要添加的元素是小的,存左边
//正数:表示当前要添加的元素是大的,存右边
//0:表示当前要添加的元素已存在,舍弃
int i =this.getAge()-o.getAge();
i = i==0?this.getName().compareTo(o.getName()):i;
return i;
}
}
package gather;
public class Student3 implements Comparable<Student3>{
private String name;
private int age;
private int chinese_score;
private int math_score;
private int english_score;
public Student3() {
}
public Student3(String name, int age, int chinese_score, int math_score, int english_score) {
this.name = name;
this.age = age;
this.chinese_score = chinese_score;
this.math_score = math_score;
this.english_score = english_score;
}
public String toString(){
return "name="+name+",all_score="+(chinese_score+math_score+english_score)+",chinese_score="+ chinese_score+ ",math_score="+math_score+",english_score="+english_score;
}
@Override
public int compareTo(Student3 o) {
int sum1 = this.getChinese_score()+this.getMath_score()+this.getEnglish_score();
int sum2 = o.getChinese_score()+o.getMath_score()+o.getEnglish_score();
int i =sum2-sum1;
i = i==0?o.getChinese_score()-this.getChinese_score():i;
i = i==0?o.getMath_score()-this.getMath_score():i;
i = i==0?o.getEnglish_score()-this.getEnglish_score():i;
i = i==0?o.getAge()-this.getAge():i;
i = i==0?o.getName().compareTo(this.getName()):i;
return i;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getChinese_score() {
return chinese_score;
}
public void setChinese_score(int chinese_score) {
this.chinese_score = chinese_score;
}
public int getMath_score() {
return math_score;
}
public void setMath_score(int math_score) {
this.math_score = math_score;
}
public int getEnglish_score() {
return english_score;
}
public void setEnglish_score(int english_score) {
this.english_score = english_score;
}
}
l2
public static void main(String[] args) {
TreeMap<Integer,String> tm = new TreeMap<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
tm.put(1,"泡面");
tm.put(2,"火腿肠");
tm.put(4,"辣条");
tm.put(3,"卤蛋");
System.out.println(tm);
}
Collections
java.util.Collections:是集合工具类
作用:Collectons不是集合,而是集合的工具类
Collections常用的API
方法名称 | 说明 |
public static <T> boolean addALL(Collection<T> c,T.. elements) | 批量添加元素 |
public static void shuffle(List<?> list) | 打乱List集合元素的顺序 |
public static <T> void sort(List<T> list) | 排序 |
public static <T> void sort(List<T> list,Comparator<T> C) | 根据指定的规则进行排序 |
public static <T> int binarySearch(List<T> list,T key) | 以二分法查找元素 |
public static <T> void copy(List<T> dest,List<T> src) | 拷贝集合中的元素 |
public static <T> int fill(List<T> list,T obj) | 使用指定的元素填充集合 |
public static <T> void max/min(Collection<T> coll) | 根据默认自然排序获取最大/最小值 |
public static <T> void swap(List<?> list,int i,int j) | 交换集合中指定位置的元素 |
不可变集合
创建不可变集合
特点
不可修改,或者添加、删除
不可变集合的应用场景
如果某个数据不能被修改,把他防御性地拷贝到不可变集合中是个很好的实践。
当集合对象被不可信的库调用时,不可变的形式是安全的。
简单理解:不能被别人修改集合中的内容
创建不可变集合的书写格式
方法名称 | 说明 |
static <E> List<E> of (E...elements) | 创建一个具有指定元素的List集合对象 |
static <E> Set<E> of (E...elements) | 创建一个具有指定元素的Set集合对象 |
static <K,V> Map<K,V> of(E...elements) | 创建一个具有指定元素的Map集合对象 |
例子
package gather;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class ImmutableDemo2 {
public static void main(String[] args) {
List<String> list = List.of("张三","李四","王五","赵六");
for (String s : list) {
System.out.println(s);
}
System.out.println("-----------------------------------------");
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
list.set(0,"李");
Set<String> set = Set.of("张三", "李四", "王五", "赵六");
Iterator<String> it = set.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
Map<Integer, String> map = Map.of(0, "aaa", 1, "2222", 3, "322");
Set<Integer> seet = map.keySet();
for (Integer i : seet) {
System.out.println(i+":"+map.get(i));
}
}
}
细节
键是不能重复的
Map里面的of方法,参数是有上限的,最多只能传递20个参数,10个键值对。
如果我们要传递多个键值对对象,数量大于10个,在Map接口中还有一个方法
但可以用使用如下方法传递20以上的参数
package gather;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class ImmutableDemo3 {
public static void main(String[] args) {
HashMap<String,String> hm = new HashMap<>();
hm.put("天道","雨忍");
hm.put("猿飞日斩","木叶");
hm.put("宇智波鼬","木叶");
hm.put("山椒鱼半藏","雨忍");
hm.put("宇智波佐助","木叶");
hm.put("漩涡鸣人","木叶");
hm.put("春野樱","木叶");
hm.put("纲手","木叶");
hm.put("宇智波斑","木叶");
hm.put("再不斩","雾隐");
hm.put("波风水门","木叶");
hm.put("药师兜","音忍");
Set<Map.Entry<String,String>> entries = hm.entrySet();
System.out.println(entries);
Map.Entry[] arr1 = new Map.Entry[0];
Map.Entry[] arr2 = entries.toArray(arr1);
System.out.println(arr2);
Map map = Map.ofEntries(arr2);
System.out.println(map);
Map map1 = Map.copyOf(hm);
System.out.println(map1);
// map.put("bb","22");
}
}
Stream流
作用
结合Lambda表达式,简化集合、数组的操作
使用步骤
先得到一条Stream(流水线),并把数据放上去
使用中间方法对流水线上的数据进行操作
使用终结方法对流水线上的数据进行操作
获取方法
获取方式 | 方法名 | 说明 |
单列集合 | default Stream<E> stream() | Collection的默认方法 |
双列集合 | 无 | 无法直接使用stream流 |
数组 | public static <T> Stream<T> stream(T[] array) | Arrays工具类中的静态方法 |
一堆零散数据 | public static<T> Stream<T> of(T... values) | Stream接口中的静态方法 |
中间方法
名称 | 说明 |
Stream<T> fillter(Predicate<? super T> predicate) | 过滤 |
Stream<T> limit(long maxSize) | 获取前几个元素 |
Stream<T> skiip(long n) | 跳过前几个元素 |
Stream<T> distinct() | 元素去重,依赖(hashCode和equals方法) |
Static <T> Stream<T> concat(Stream a,Stream b) | 合并a和b两个流为一个流 |
Stream<R> map(Function<T,R> mapper) | 转换流中的数据类型 |
注意:
中间方法,返回新的Stream流,原来的Stream流只能使用一次,建议使用链式编程
修改Stream流中的数据,不会影响原来或者数组中的数据
例子
public static void main(String[] args) {
//单例结合
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"a","b","c","c","c","c","c","c");
list.stream().forEach(s-> System.out.println(s));
System.out.println("------------------------------------------");
//双列集合
HashMap<String,Integer> hm = new HashMap<>();
hm.put("aaa",1);
hm.put("bbb",2);
hm.put("ccc",3);
hm.put("ddd",4);
hm.keySet().stream().forEach(s-> System.out.println(s));
hm.entrySet().stream().forEach(s-> System.out.println(s));
System.out.println("----------------------");
//数组
int[] arr = {1,2,3,4,5,6,7,8,9,10};
Arrays.stream(arr).forEach(s-> System.out.println(s));
System.out.println("---------------------------");
//一堆零散数据
Stream.of(1,2,3,4,5).forEach(s-> System.out.println(s));
}
泛型
是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查
格式:<数据类型>
注意:泛型只能支持引用数据类型
好处
统一数据类型
把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确定下来。
扩展:java中的泛型是伪泛型,(就像在门口加一个看门的)
细节
泛型中不能写基本数据类型
指定泛型的具体类型后,传递数据时,可以传入该类类型或者子类类型
如果不写泛型,类型默认是Object
泛型类
使用场景:当一个类中,某个变量的数据类型不确定时,就可以定义带有泛型的类
格式
修饰符 class 类名<类型>{
}
此处E可以理解为变量,但是不是用来记录数据的,而是记录数据的类型,可以写成T、E、K、V等
例子
public class MyArrayList<E> {
Object obj[] = new Object[10];
int size;
public boolean add(E e){
obj[size] = e;
size ++;
return true;
}
public E get(int index){
return (E)obj[index];
}
@Override
public String toString(){
return Arrays.toString(obj);
}
}
public static void main(String[] args) {
MyArrayList<String> list = new MyArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
System.out.println(list.toString());
}
泛型方法
格式
修饰符<类型> 返回值类型 方法名(类型 变量名){
}
此处类型可以理解为变量,但是不是用来记录数据的,而是记录类型的,可以写成:T、E、K、V等
例子
public class ListUtil {
private ListUtil() {
}
public static<E> void addAll(ArrayList<E> list, E e1, E e2, E e3){
list.add(e1);
list.add(e2);
list.add(e3);
}
public static<E> void addAll2(ArrayList<E> list,E...e){
for(E elment:e){
list.add(elment);
}
}
}
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
ListUtil.addAll(list,"aaa","bbb","ccc");
System.out.println(list);
ListUtil.addAll2(list,"aaa");
System.out.println(list);
}
泛型接口
格式
修饰符 interface 接口名<类型>{
}
使用泛型接口:
方式1:实现类给出具体类型
方式2:实现类延续泛型,创建对象再确定
泛型的继承和通配符
泛型不具备继承性,但是数据具备继承性
通配符
可以限定类型的范围
?也表示不确定的类型,可以进行类型的限定
? extends E:表示可以传递E或者E所有的子类类型
? super E:表示可以传递E或者E所有的父类类型
应用场景
如果我们在定义类、方法、接口的时候,如果类型不确定,就可以定义反省类、泛型方法、泛型接口
如果类型不确定,但是能知道以后只能传递某个继承体系中的,就可以使用泛型的通配符
例子
public static void main(String[] args) {
ArrayList<Ye> list1 = new ArrayList<>();
ArrayList<Fu> list2 = new ArrayList<>();
ArrayList<Zi> list3 = new ArrayList<>();
method(list1);
}
public static void method(ArrayList<? extends Ye> list){
}
常见API
应用程序接口
Math
方法名 | 说明 |
public static int abs(int a) | 获取参数绝对值 |
public static double ceil(double a) | 向上取整 |
public static double floor(double a) | 向下取整 |
public static int round(float a) | 四舍五入 |
public static int max(int a,int b) | 获取两个int值的较大值 |
public static double pow(double a,double b) | 返回a的b次幂的值 |
public static double random() | 返回值为double的随机值,范围[0.0,1.0) |
System
方法名 | 说明 |
public static void exit(int status) | 终止当前运行的java虚拟机 |
public static long currentTimeMillis() | 返回当前系统的时间毫秒值形式 |
public static void arraycopy(数据源数组,起始索引,目的地数组,起始索引,拷贝个数) | 数组拷贝 |
Runtime
表示当前虚拟机的运行环境
方法名 | 说明 |
public static Runtime getRuntime() | 当前系统的运行环境对象 |
public void exit(int status) | 停止虚拟机 |
public int availableProcessors() | 获得CPU的线程数 |
public long maxMemory() | JVM能从系统中获取内存大小(单位byte) |
public long totalMemory() | JVM已经从系统中获取总内存大小(单位byte) |
public long freeMemory() | JVM剩余内存大小(单位byte) |
public Process exec(String command) | 运行cmd命令 |
Object
方法名 | 说明 |
public String toString() | 返回对象的字符串表示形式 |
public boolean equals(Object obj) | 比较两个对象是否相等 |
protected Object clone(int a) | 对象克隆 |
结论
如果没有重写equals方法,呢么默认使用Object中的方法进行比较,比较的是地址值是否相等
一般来讲地址值对于我们意义不大,所以我们会重写,重写之后比较的就是对象内部的属性值了。
Cloneable
如果一个接口里面没有抽象方法
表示当前的接口是一个标记性的接口
现在Cloneable表示一旦实现,那么当前类的对象就可以被克隆
如果没有实现,当前类的对象就不能克隆
克隆对象细节
方法在底层会帮我们创建一个对象,并把对象中的数据拷贝过去
重写Object中的clone方法
让javabean类实现Cloneable接口
创建原对象并调用clone就可以了
BigInteger
构造方法
对象一旦创建里面的内容不能发生改变。
方法名 | 说明 |
public BigInteger(int num,Random rnd) | 获取随机大整数,范围:[0~2的num次方] |
public BigInteger(String val) | 获取指定的大整数 |
public BigInteger(String val,int radix) | 获取指定进制进制的大整数 |
小结
如果BigInteger表示的数字没有超出long的范围,可以用静态方法获取。
如果BigInteger表示的超出long的范围,可以用构造方法获取。
对象一旦创建,BigInteger内部记录的值不能发生改变
只要进行计算都会产生一个新的BigInteger对象
例子
BigInteger db1 = new BigInteger(4,new Random());
System.out.println(db1);
BigInteger db2 = new BigInteger("100");
System.out.println(db2);
BigInteger db3 = new BigInteger("100",16);
System.out.println(db3);
//静态方法获取BigInteger,内部有优化
//细节:
//1.能表示范围的比较小,只能在long的取值范围之内,如果超出就不行了
//2.在内部对常用的数字:-16 ~ 16进行了优化
//提前把-16 ~ 16先创建好BigInteger的对象,如果多次获取不会重新创建新的。
BigInteger db4 = BigInteger.valueOf(100);
System.out.println(db4);
方法
方法名 | 说明 |
public BigInteger add(BigInteger val) | 加法 |
public BigInteger subtract(BigInteger val) | 减法 |
public BigInteger multiply(BigInteger val) | 乘法 |
public BigInteger divide(BigInteger val) | 除法,获取商 |
public BigInteger[] divideAndRemainder(BigInteger val) | 除法,获取商和余数 |
public boolean equals(Object x) | 比较是否相同 |
public BigInteger pow(int exponent) | 次幂 |
public BigInteger max/min(BigInteger val) | 返回最大值/较小值 |
public int intValue(BigInteger val) | 转为int类型整数,超出范围数据有误 |
BigDecimal
作用
表示较大的小数和解决小数运算精度失真问题。
构造方法
//值一定要是字符串
BigDecimal 变量名 = new BigDecimal("小数值");
//值的范围不要超过double的范围,因为底层是通过double转换为string的
BigDecimal 变量名 = BigDecimal.valueOf(小数)
BigDecimal bd1 = new BigDecimal(0.01);
BigDecimal bd2 = new BigDecimal(0.09);
System.out.println(bd1);
System.out.println(bd2);
BigDecimal bd3 = new BigDecimal("0.01");
BigDecimal bd4 = new BigDecimal("0.09");
System.out.println(bd3);
System.out.println(bd4);
BigDecimal db5 = bd3.add(bd4);
System.out.println(db5);
BigDecimal db6 = BigDecimal.valueOf(0.01);
System.out.println(db6);
方法
方法名 | 说明 |
public static BigDecimal valueOf(double val) | 获取对象 |
public BigDecimal add(BigDecimal val) | 加法 |
public BigDecimal subtract(BigDecimal val) | 减法 |
public BigDecimal multiply(BigDecimal val) | 乘法 |
public BigDecimal divide(BigDecimal val) | 除法 |
public BigDecimal divide(BigDecimal val,精确几位,舍入模式) | 除法 |
JDK7时间-data
Date时间类
Date类是一个JDK写好的javabean类,用来描述时间,精确到毫秒。
利用空参构造创建的对象,默认表示系统当前时间。
利用有参构造创建的对象,表示指定的时间。
方法
//1.创建日期对象
Date date = new Date();
Date date = new Date(指定毫秒);
//2.修改时间对象中的毫秒值
setTime(毫秒值);
//3.获取时间对象中的毫秒值
getTime();
例子
//创建对象表示当前时间
Date d = new Date();
System.out.println(d);
//创建对象表示制定一个时间
Date d1 = new Date(0L);
System.out.println(d1);
//setTime修改时间
d1.setTime(1000L);
System.out.println(d1);
//getTime获取时间
long l = d1.getTime();
System.out.println(l);
时间相关知识点
世界标准时间:格林尼治时间/格林威治时间简称GMT
中国标准时间:时间标准时间+8小时
时间换算单位:
1秒 = 1000毫秒
1毫秒 = 1000微秒
1微秒 = 1000纳秒
JDK7时间-SimpleDateFormat
作用
格式化:把时间变成我们喜欢的格式
解析:把字符串表示的时间变成Date对象
方法
构造方法 | 说明 |
public SimpleDateFormat() | 构造一个SimpleDateFormat,使用默认格式 |
public SimpleDateFormat(String pattern) | 构造一个SimpleDateFormat,使用指定的格式 |
常用方法 | 说明 |
public final String format(Date date) | 格式化(日期对象->字符串) |
public Date parse(String source) | 解析(字符串->日期对象) |
格式化时间形式的常用的模式对应关系:
y 年
M 月
d 日
H 时
m 分
s 秒
E 星期
例子
SimpleDateFormat s = new SimpleDateFormat();
Date d = new Date(0L);
String str = s.format(d);
System.out.println(str);
SimpleDateFormat s1 = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss E");
Date d1 = new Date(0L);
String str1 = s1.format(d1);
System.out.println(str1);
String str = "2023-11-11 11:11:11";
SimpleDateFormat s1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
Date d = s1.parse(str);
System.out.println(d);
} catch (ParseException e) {
throw new RuntimeException(e);
}
JDK7时间-Calender
概述
Calendar代表了系统当前时间的日历对象,可以单独修改、获取时间中的年、月、日
细节:Calendar是一个抽象类,不能直接创建对象。
方法
方法名 | 说明 |
public static Calendar getInstance() | 获取当前时间的日历对象 |
方法名 | 说明 |
public final Date getTime | 获取日期对象 |
public final setTime(Date date) | 给日历设置日期对象 |
public long getTimeInMillis() | 拿到时间毫秒值 |
public void setTimeInMillis(long millis) | 给日历设置时间毫秒值 |
public void set(int field) | 取日历中的某个字段信息 |
public void set(int field.int value) | 修改日历的某个字段信息 |
public void add(int field,int amount) | 为某个字段增加/减少指定值 |
JDK8-Zoneld时区
方法
方法名 | 说明 |
static Set<String> getAvailableZoneIds() | 获取Java中支持的所有时区 |
static ZoneId systemDefault() | 获取系统默认时间 |
static ZoneId of(String zoneId) | 获取一个指定时区 |
例子
Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
System.out.println(availableZoneIds);
System.out.println(availableZoneIds.size());
ZoneId zoneId = ZoneId.systemDefault();
System.out.println(zoneId);
ZoneId of = ZoneId.of("Asia/Karachi");
System.out.println(of);
JDK8-Instant时间戳
方法
方法名 | 说明 |
static Instant now() | 获取当前时间的Instant对象(标准时间) |
static Instant ofXxx(long epochMilli) | 根据(秒、毫秒、纳秒)获取Instant对象 |
ZonedDateTime atZone(ZoneId zone) | 指定时区 |
boolean isXxx(Instant otherIstant) | 判断系列的方法 |
Instant minusXxx(long millisToSubtract) | 减少时间系列的方法 |
Instant plusXxx(long millisToSubtract) | 增加时间系列的方法 |
例子
Instant now = Instant.now();
System.out.println(now);
Instant instant = Instant.ofEpochMilli(0L);
System.out.println(instant);
ZonedDateTime zonedDateTime = Instant.now().atZone(ZoneId.of("Asia/Shanghai"));
System.out.println(zonedDateTime);
Instant instant1 = Instant.ofEpochMilli(1L);
Instant instant2 = Instant.ofEpochMilli(10000L);
//判断调用者代表的时间是否在参数之前
boolean b = instant1.isBefore(instant2);
System.out.println(b);
//判断调用者代表的时间是否在参数之后
boolean b1 = instant1.isAfter(instant2);
System.out.println(b1);
Instant instant3 = Instant.ofEpochMilli(0L);
Instant instant4=instant3.minusSeconds(1L);
Instant instant5 = instant3.plusSeconds(2L);
System.out.println(instant3);
System.out.println(instant4);
System.out.println(instant5);
JDK8-ZonedDateTime带时区的时间
方法
方法名 | 说明 |
static ZonedDateTim now() | 获取当前时间的ZonedDateTime对象 |
static ZonedDateTime ofXxx(...) | 获取指定时间的ZonedDateTime对象 |
ZonedDateTime withXxx(时间) | 修改时间系列的方法 |
ZonedDateTime minusXxx(时间) | 减少时间系列的方法 |
ZonedDateTime pulsXxx(时间) | 增加时间系列的方法 |
JDK8-DateTimeFormatter用于时间的格式化和解析
方法
方法名 | 说明 |
static DateTimeFormatter ofPattern(格式) | 获取格式对象 |
String format(时间对象) | 按照指定方式格式化 |
例子
ZonedDateTime time= Instant.now().atZone(ZoneId.of("Asia/Shanghai"));
DateTimeFormatter dtf=DateTimeFormatter.ofPattern("yyyy-MM-dd HH-mm-ss EE a");
System.out.println(dtf.format(time));
JDK8时间-日历类
LocalDate、LocalTime、LocalDateTime
LocalDate:年月日
LocalTime:时分秒
LocalDateTime:年月日时分秒
方法
方法名 | 说明 |
static XXX now() | 获取当前时间的对象 |
static XXX of(...) | 获取指定时间的对象 |
get开头的方法 | 获取日历中的年、月、日、时、分、秒等信息 |
isBefore,isAfter | 比较较两个LocalDate |
with开头的 | 修改时间系列的方法 |
minus开头的 | 减少时间系列的方法 |
plus开头的 | 增加时间系列的方法 |
方法名 | 说明 |
public LocalDate toLocalDate() | LocalDateTime转换成一个LocalDate对象 |
public LocalTime toLocalTime() | LocalDateTime转换成一个LocalTime对象 |
例子
LocalDate data1 = LocalDate.now();
LocalDate data2 = LocalDate.of(2022,11,11);
System.out.println(data1);
System.out.println(data2);
System.out.println(data1.getYear());
LocalDateTime time1 = LocalDateTime.now();
LocalDateTime time2 = LocalDateTime.of(2022,10,11,8,20,30);
System.out.println(time1);
System.out.println(time1.getHour());
System.out.println(time2);
System.out.println(time2.getDayOfMonth());
System.out.println(time2.withHour(10));
JDK8时间-工具类
Duration、Period、ChronoUnit
Duration:用于计算两个“时间”间隔(秒,纳秒)
Period:用于计算两个“日期”间隔(年、月、日)
ChronoUnit:用于计算两个“日期”间隔
//第二个参数减去第一个参数
方法between(时间参数1,时间参数2)
例子
LocalDateTime today = LocalDateTime.now();
System.out.println(today);
LocalDateTime briday = LocalDateTime.of(2001,10,19,0,0,0);
System.out.println(briday);
System.out.println("今年"+ ChronoUnit.DAYS.between(briday,today)/365+"岁");
Arrays
操作数组的工具类
方法
方法名 | 说明 |
public static String toString(数组) | 把数组拼接成一个字符串 |
public static int binarySearch(数组,查找的元素) | 二分查找法查找元素 |
public static int[] copyOf(原数组,新数组长度) | 拷贝数组 |
public static int[] copyOfRange(原数组,起始索引,结束索引) | 拷贝数组(指定范围) |
public static void fill(数组,元素) | 填充数组 |
public static void sort(数组) | 按默认方式进行数组排序 |
public static void sort(数组,排序规则) | 按照指定的规则排序 |
注意:使用sort(,)时,只能给引用数据类型的数组进行排序,如果数组是基本数据类型的,需要变成其对应的包装类。
包装类
基本数据类型对应的引用类型
基本数据类型 | 包装类 |
byte | Byte |
short | Short |
char | Character |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
在JDK5的时候提出了一个机制:自动装箱和自动拆箱
自动装箱:把基本数据类型会自动的变成其对应的包装类
自动拆箱:把包装类自动的编程其对象的基本数据类型
在底层,此时还会去自动调用静态方法valueof得到一个Integer对象,只不过这个动作不需要我们自己去操作了
自动装箱的动作
以后如何获取包装类对象?
不需要new,不需要调用方法,直接赋值即可。
例子
Integer i1 = 12;
Integer i2 = new Integer(12);
int i3 = i1;
System.out.println(i3+i1);
Intege成员方法
方法名 | 说明 |
public static String toBinaryStrinf(int i) | 得到二进制 |
public static String toOctalString(int i) | 得到八进制 |
public static String toHexString(int i) | 得到十六进制 |
public static int parseInt(String s) | 将字符串类型的整数转成int类型的整数 |
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
double v = Double.parseDouble(s);
System.out.println(v+1);
常见算法
Lambda表达式
函数式编程
函数式编程是一种思想特点
函数式编程思想,忽略面向对象的复杂语法,强调做什么,而不是谁去做。
而Lambda表达式就是函数式思想的体现。
标准格式
() ->{
}
( )对应着方法的形参
-> 固定格式
注意点:
Lambda表达式可以用来简化匿名内部类的书写
Lambda表达式只能简化函数式接口的匿名内部类的写法
函数式接口:有且仅有一个抽象方法的接口叫做函数式接口,接口上方可以加@Functionallnterface注解
好处
Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一个可以传递的代码,他可以写写出更简洁、更灵活的代码,作为一种更紧凑的代码风格,用Java语言的表达能力得到了提升。
Lambda的省略规则
参数类型可以省略不写
如果只有一个参数,参数类型可以省略,同时()也可以省略。
如果过哦Lambda表达式的方法体只有一行,大括号,分好,return可以省略不写,需要同时省略。
idea快捷命令
alt+ins->constructor:创建构造方法
alt+ins->setter+getter:封装变量
ctrl+"n":搜索文件
ctrl+F12:搜索方法
ctrl+"b":跟进
ctrl+"p":查看方法的参数
ctrl+alt+"t":放进某个函数中
shift+alt+下键:移动代码
ctrl+alt+L:优化代码格式