java基础语法
JAVA简介
JVM:java虚拟机
JRE:Java 运行时环境,包含java虚拟机(jvm)+JavaSE标准类库,如果只需要运行java程序,下载并安装JRE即可
JDK:Java 开发工具包,JDK 包含了JRE。
javac:编译器,将源程序转成字节码
java:运行编译后的java程序(.class后缀的)
java平台:JavaSE 开发桌面程序,JavaEE 开发Web程序,JavaME开发安卓程序
一、IDEA快捷键
快捷输出:sout System.out.println();
快捷主方法:psvm
fori for循环
itli List迭代
快捷复制当前行到下一行:ctrl+d
快捷导入包:Alt+Enter
快捷换行或者添加分号:ctrl+shift+回车
快捷生成get,set方法和toString:alt+insert
快捷查看源码:Ctrl+鼠标左键
单页面查找某个属性或者单词:ctrl+f
接上面修改选中的单词: ctrl+R
撤销操作:ctrl+z
全局查找替换:ctrl+shift+f/r
快速查找类或者文件:ctrl+shift+n
模糊查找,查找所有:ctrl+shift+a
IntelliJ IDEA生成get/set有2种方式: alt+insert
快速左右移动光标:ctrl+←→方向键
单行注释:ctrl+/
多行注释/**/:ctrl+shift+/
自动接受变量:ctrl+回车
二、数据类型和变量
数据类型分为基本数据类型和引用数据类型(数组和类,接口等)
字符串String不属于基本数据类型,他是类
整型:
byte(字节型,1字节)
short(短整型,2字节)
int(整型,4字节)
long(长整型.8字节)
浮点型:
float(单精度浮点型,4字节) 类型定义时在后面加F: flaot a=30F;
double(双精度浮点型,8字节) 默认double,也可以加D
字符型:
char(字符型,2字节) 类型定义时加单引号 char a=‘高’
布尔型:
boolean (1字节) 只能定义true和false
转义字符:/ 开头的
类型转换:
箭头指向的方向是可以自动转换,相反方向需要强制类型转换,虚线表示精度可能会丢失。强制类型转换会丢失数据
java1.7版本后数字之间可以加下划线没有任何影响
比如:int money=10_0000_0000;
1B(byte,字节)=8(bit,位)位是计算机储存最小单位,字节是计算机处理数据的基本单位,一个汉字为两个字节,一个英文字母为一个字节,一个字节可以存放最大数是255
1kb=1024B
1m=1024kb;
1G=1024m
例题:
long a=123456789L;
int b=123,i=200;
short c=10;
byte d=8;
a+b+c+d //结果是long型
b+c+d //结果是int型
b/(double)i //结果是double型
三、变量命名规范:
类成员:首字母小写加驼峰原则
常量:大写字母加下划线
类名:首字母大写加驼峰原则;
方法:首字母小写加驼峰原则
包机制:一般用公司域名倒置作为包名:
例如www.baidu.com包名为com.baidu.www
四、运算符
与&& 或|| 非!
int a=10,b=20;
Systm.out.println(“”+a+b);//输出结果1020
Systm.out.println(a+b+“”);//输出结果30,原因是运算顺序不同
int a=80;
String type=a>60?“及格”:“不及格”;
Systm.out.println(type);//输出结果为及格
boolean flag=true;
if(flag){}和if(flag==true){}这两个判断是完全相等的
Animal a=new Animal("花花",2);
Animal b=new Animal("花花",2);
boolean flag=a.equals(b);//输出结果为false,比较地址
System.out.println(a==b);//输出结果为false
String a=new String("hello");
String b=new String("hello");
flag=a.equals(b);//输出结果为true,因为String类重写了equals方法,只是比较字符串内容
System.out.println(a==b);//输出结果为false
重写equls方法
//在Animal中重写equals方法,Animal中有name和Month属性
public boolean equals(Animal obj){
if(obj==null)//传入参数为空直接放回false
return false;
//如果name和month相等返回true
if(this.getName().equals(obj.getName())&&
(this.getMonth()==obj.getMonth()))
return true;
else
return false;
}
五、JavaDoc(生成自己的java文档)
方法一、 找到你要生成的类的保存路径,运行cmd控制台,打开项目文件夹运行并生成类文档输入:javadoc -encoding UTF-8 -charset UTF-8 生成的类名.java
回车运行后找到该项目文件夹打开后运行index.html
方法二、 用idea直接生成
六、方法和排序
Scanner(用户交互)
Scanner scanner=new Scanner(System.in);
String str=scanner.nextLine();//从键盘接收String类型的字符
scanner.close();//关闭,IO流养成习惯,用完close
if(scanner.hasNextInt()){} //判断输入的是否是整数
while(scanner.hasNexDouble()) //循环判断输入的数是否是双精度的数
可变参数:
一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。任何普通参数必须在它之前声明。在指定参数类型后面加一个省略号(…)例如:
public static void main(String[] args) {
printMax(34,56,2,3,5);
printMax(new double[]{1,2,3});//可变参数本质就是一个数组
}
//比较大小,输出最大值
public static void printMax(double...numbers){
if(numbers.length==0){
System.out.println("参数错误");
return;}
//存最大值
double result=numbers[0];
//排序,比较大小
for(int i=1;i<numbers.length;i++){
if(numbers[i]>result){
result=numbers[i];
}
}
System.out.println("最大值为:"+result);
}
递归:
//传入的值为3,f(3)*f(2)*f(1),到f(1)时开始把值进行返回1,再返回2,再返回3
public static int f(int n){
if(n==1){
return 1;
}else{
return n*f(n-1);
}
}
遍历数组
for(int i:arr){
System.out.println(i);
}
冒泡排序
比较相邻的元素。如果第一个比第二个大,就交换他们两个,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。重复上面的步骤,直到没有任何一对数字需要比较。
int [] a= {45,23,56,78,15,89};
int b;
for (int i : a) {
System.out.print(i+" ");
}
for(int i=0;i<a.length-1;i++) {
for(int j=0;j<a.length-i-1;j++) {
if(a[j]>a[j+1]) {
b=a[j];
a[j]=a[j+1];
a[j+1]=b;
}
}
}
System.out.println();
for (int i : a) {
System.out.print(i+" ");
}
七、继承(extendx)和多态
1.子类继承父类需要重写所有抽象方法
2.如果父类重写构造方法子类也需要重写构造方法
this()调用无参构造方法,必须放在方法体的第一行。
静态成员生命周期:
类加载时产生,销毁时释放,生命周期长。
方法重写:
在满足继承关系的子类中,方法名、参数列表与父类相同,访问修饰符大于等于父类的方法。
方法重载:
在同一个类中,方法名相同,参数个数、顺序、类型不同的方法。
方法执行顺序:
单例模式:
目的:使得类的一个对象成为该类系统中的唯一实例
优点:在内存中只有一个对象,节省内存空间,避免频繁的创建销毁对象,提高性能。
缺点:扩展困难,如果实例化后的对象长期不使用,系统默认为系统垃圾进行回收,造成对象状态丢失。
定义:一个类有且仅有一个实例,并且自动实例化向整个系统提供
代码实现:
//饿汉式:类加载的时候直接初始化对象。空间换时间,线程安全
public class StringletonOne{
//1.创建类中私有构造
private StringletonOne(){
}
//2.创建该类型的私有静态实例
private static StringletonOne instance=new StringletOne():
//3.创建公有静态方法返回静态实例对象
public static StringletonOne getInstance(){
return instance;
}
}
//懒汉式:类加载并不直接初始化,第一次使用时进行实例化。时间换空间,存在线程风险
public class StringletonTow{
//1.创建类中私有构造
private StringletonTow(){
}
//2.创建该类型的私有静态实例
private static StringletonTow instance=null;
//3.创建公有静态方法返回静态实例对象
public static StringletonTow getInstance(){
if(instance==null)
instance=new StringletTow();
return instance;
}
}
多态
1.编译时多态:方法重载。
2.运行时多态:java运行时系统根据调用该方法的实例的类型来决定调用那个方法。
//向上转型,隐式转型,自动转型。父类引用指向子类实例,不能调用子类特有的方法
//父类的静态方法无法被子类重写,所以只能调用父类原有的静态方法
Animal a=new Cat();
//向下转型,强制类型转换,可以调用子类特有的方法
Cat b=(Animal)a;
//instanceof运算符,返回true,false。用于判断左边的对象是否是右边类的实例
if(a instanceof Cat)
**抽象类(abstract)**不允许实例化,可以通过向上转型,指向子类实例。
子类没有重写父类所有的抽象方法,则也要定义为抽象类。
抽象类可以没有抽象方法。
抽象方法所在的类一定是抽象类。
抽象方法不能被static,final,private所修饰。
**接口(interface)**中可以不写abstract关键字。
类实现(implements)接口时,需要去实现接口中所有的抽象方法,否则需要将该类设置为抽象类。
接口中可以设置常量,默认public static final。
default:接口中的默认方法,可以带方法体。default void a(){}
接口中的静态方法也可以带方法体。
接口也可以实现继承,并且可以多继承
接口和抽象类的区别
1.抽象类只能继承一次,但是可以实现多个接口
2.接口和抽象类必须实现其中所有的方法,抽象类中如果有未实现的抽象方法,那么子类也需要定义为抽象类。抽象类中可以有非抽象的方法
3.接口中的变量必须用 public static final 修饰,并且需要给出初始值。所以实现类不能重新定义,也不能改变其值。
4.接口中的方法默认是 public abstract,也只能是这个类型。不能是 static,接口中的方法也不允许子类覆写,抽象类中允许有static 的方法
内部类获取内部类对象实例,方式1:new 外部类.new 内部类。
方式2:外部类对象.new 内部类。
内部类可以直接使用外部类的成员。
可以使用外部类.this.成员的方式,访问外部类中同名的信息。
静态内部类中,只能直接访问玩不累的静态方法,如果需要调用非静态成员,可以通过对象实例。new 外部类.成员
可以通过外部类.内部类.静态成员的方式,访问内部类中的静态成员
匿名内部类匿名内部类适合创建那种只需要一次使用的类。最常用的创建匿名内部类的方式是创建某个接口类型的对象。
八、包装类和异常
所有包装类都用final修饰不能被继承
基本数据类型和包装类的转换
装箱
//1、自动装箱
int t1=6;
Interger t2=t1;
//2、手动装箱
Interger t3=new Interger(t1);
拆箱
自动拆箱
int t4=t2;
//手动拆箱
int t5=t2.intValue();
double t6=t2.doubleValue();
基本数据类型和字符串
int t1=2;
String t2=Interger.toString(t1);//基本类型转换字符类型
int t3=Interger.parseInt(t2);//字符串转为基本数据类型
int t4=Interger.valueOf(t2);//字符串转为基本数据类型
异常
ArithmeticException:数学运算异常,出现了除以零这样的运算
NumberFormatExeption:数字格式化异常,出现字符串不能转数字
ArrayIndexOutOfBoundsException:数组下标越界异常,超出数组下标
NullPointerException:空指针异常,使用了不存在或者未初始化的对象
ClassCastException:类型转换异常,进行向下转型时,无法完成正常转换
ArrayStoreException:数组中包含不兼容的值
InputMismatchException:输入格式错误异常
FileBotFoundException:文件未找到异常
异常概念
System.exit(1);//无条件终止程序运行
throw:有程序员主动抛出某种特定的异常
throws:表示通知方法调用者,使用该方法时,可能会发生那些异常
throw抛出异常对象的处理方案:
1.通过try…catch包含throw语句–自己抛自己处理
2.通过throws在方法声明出抛出异常异常类型–调用者自己处理,也可以继续上抛
自定义异常
定义一个类,去继承Throwable类或者其他的子类。
和通过throw new Exception(描述信息)不一样,这种操作适合临时或者应用频率不高的异常处理情况,而自定义异常是结合业务产生的类型,并设置其描述信息,更适合在项目中相对频繁出现的场景
九、字符串(String)
String常用方法
==和equals,hashcode区别
byte,short,char,int,long,float,double,boolean 他们之间的比较,应用双等号(==),比较的是他们的值。
在字符串中,==比较的是对象地址是否相等,equals()比较的是内容是否相等,hashcode比较的是hash值是否相等
字符串数组互转
String str=new String("JAVA编程基础");
//将字符串改为byte数组
byte[] arrs=str.getBytes("UTF-8");
//将数组转换为字符串
String str1=nwe String(arrs,"UTF-8");
StringBuilder
String和StringBuilder的区别:
String具有不可变性,而StringBuilder不具备,所以当频繁操作字符串时,使用StringBuilder
StringBuffer和StringBuilder的区别:
二者基本相似,StringBuffer是线程安全,StringBuilder则没有,所以性能略高
StringBuilder常用方法
//拼接字符串
StringBuilder str=new StringBuilder("你好");
/*方法一
str.append(',');
str.append("imooc!");
System.out.println(str);
*/
//方法二
System.out.println(str.append(',').append("imooc!"));
//替换字符串
System.out.println(str.replace(4,8,"MOOC"));//方法一,直接替换
System.out.println(str.delete(4,8).insert(4,"MOOC"));//方法二,先删除后插入
十、集合
集合和数组的区别:
数组的长度固定,集合长度可以动态扩展
数组只能存相同的类型的数据,集合可以存不同类型的数据
ArrayList常用方法:
List list=new ArrayList();
list.add("java");//添加元素
list.add("c++");
list.get(0);//输出元素jvaa,下标从0开始
list.size();//列表元素个数
list.remove(1);//删除下标为1的c++,另一种方式:list.remove("c++);
案例演示:
//Notice类四个属性int id;String title;String creator;Date createTime
Notice notice1=new Notice(1,"欢迎","管理员",new Date());//生成公告
Notice notice2=new Notice(2,"在线","老师",new Date());
ArrayList list=new ArrayList();
list.add(notice1);//添加第一条公告信息,存储公告信息
//显示第一条公告的title的内容
System.out.println(((Notice)(list.get(0))).getTitle());
//在第一条公告后面插入一条新的公告
list.add(1,notice2);//1表示在下标为1的地方插入
notice1.setTitle("编辑");//修改title内容
list.set(0,notice1);//用set方法修改之前的noticel1的内容
HashSet常用方法和泛型的应用
Set set=new HashSet();
set.add("bule");//向集合添加元素
set.add("red");
Iterator it=set.iterator();//迭代器,专门遍历集合来使用
while(it.hasNxt()){//hasNex()检测集合是否还有下一个元素
System.out.println(it.next());//返回集合中的下一个元素
}
案例演示:
//以下的代码可以加入泛型规定必须是Cat类型的。
//加入泛型的作用是用来代替类型的强制转换
//Cat类中三个属性String name;int month;String species
Cat huahua=new Cat("花花",12,"短毛猫");
Cat fanfan=new Cat("凡凡",3,"田园猫");
Set set=new HashSet();//Set<Cat> set=new HashSet<Cat>();
set.add(huahua);//添加到HashSet中
set.add(fanfan);
//加入泛型Iterator it=set.iterator();
Iterator it=set.iterator();//迭代器,专门遍历集合来使用
//遍历猫的信息,需要在Cat类中重写toString()方法,否则输出的是引用地址
while(it.hasNxt()){
System.out.println(it.next());
}
/*************************查找信息*******************************/
boolean flag=false;
Cat c;
it=set.iterator();//每遍历一次最好重新定义一次
while(it.hasNext()){
c=(Cat)it.next();//如果假如了泛型就不用强转为Cat,直接c=it.next();
if(c.getName().equals("花花")){
flag=true;
break;
}
}
if(flag){
System.out.println(c);//输出花花的信息
}else{
System.out.println(”没找到“);
}
/************************************************************/
//删除花花的数据,下面的方法需要加入泛型后才能使用
for(Cat cat:set){//遍历set集合的元素,把set中的元素存到cat中
//花花在cat.getName()中
if("花花".equals(cat.getName())){
set.remove(cat);
//因为在遍历查找数据时不允许删除数据,所以如果找到花花删除后就要跳出循环
break;
}
}
//遍历set集合
for(Cat cat:set){
System.out.println(set);
}
/************************************************************/
//删除多条数据
Set<Cat> set1=new HashSet<Cat>();
for(Cat cat:set){
//删除年龄小于5的数据
if(cat.getMonth()<5){
set1.add(cat);//把年龄小于5的数据存到set1集合中
}
}
set.removeAll(set1);
/************************************************************/
//删除所有set集合的元素
boolean flag1=set.removeAll(set);//删除所有信息,返回true
if(flag1){
System.out.println("全部删了");
}else{
System.out.println("删除失败");
}
}
}
自己定义类时,如果要判断添加的信息是否重复需要重写hashCode()和equals()
Map和HashMap
LinkedHashMap和hashMap和TreeMap的区别
共同点:
HashMap,LinkedHashMap,TreeMap都属于Map;Map 主要用于存储键(key)值(value)对,根据键得到值,因此键不允许键重复,但允许值重复。
不同点:
- HashMap里面存入的键值对在取出的时候是随机的,也是我们最常用的一个Map.它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。在Map中插入、删除和定位元素,HashMap 是最好的选择。
- TreeMap取出来的是排序后的键值对。但如果您要按自然顺序(字典顺序),那么TreeMap会更好。
- LinkedHashMap 是HashMap的一个子类,如果需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现。
- LinkedHashMap是线程不安全的。
演示案例一:
/*********输入三组单词对应的注释存储到HashMap中****************/
Map<String,String> animal=new HashMap<String,String>();
Scanner scanner=new Scanner(System.in);//创建键盘输入对象
int i=0;
while(i<3){
System.out.println("输入key值(单词)");
String key=scanner.next();//对象调用方法执行输入
System.out.println("输入value值(注释)");
String value=scanner.next();
animal.put(key,value);//把输入的值传入HashMap集合中
i++;
}
/*********************遍历输出value(注释)的值***********************/
Iterrator<String> it=animal.values().itertator();
while(it.hasNext()){
System.out.println(it.next()+" ");
}
/**********遍历输出key(单词)和value(注释)的值*******************/
//Set类有泛型,Entry类也有泛型
Set<Enty<String,String>> entrySet=animal.entrySet();
for(Enty<String,String> entry:entrySet){
System.out.print(entry.getKey());
System.out.println(entry.getValue());
}
/**************通过key(单词)找到value(注释)的值****************/
String strSearch=scanner.next();//输入查找的key
//用keySet()方法把animal对象的所有key值取出来存到keySet集合中
Set<String> keySet=animal.keySet();
for(String key:keySet){//遍历keySet集合的值依次存到key中
//如果输入的值等于key的值
if(sreSearch.equals(key)){
//get()方法:返回到指定键所映射的值
System.out.print("找到了"+key+animal.get(key));
}
}
演示案例二:商品信息管理
/****Goods类中有三个属性:String id;String name;double price;*********/
public String toString(){
//Goods类中重写了toString()方法,其他类new Goods类时自动执行toString()
return "商品编号"+id+",商品名称"+name+",商品价格"+price;
}
/**************在主方法中添加商品信息****************/
Map<String,Goods> goodsMap=new HashMap<String,Goods>();
String goodsId="s00001";
String goodsName="冰箱"
String goodsPrice="5000";
Goods goods=new Goods(goodsId,goodsName,goodsPrice);
goodsMap.put(goodsId,goods);//将商品信息存到HashMpa集合中
//遍历goodsMap集合中的所有value值,输出商品信息
Iterator<Goods> itGoods=goodsMap.values().iterator();
while(itGoods.hasNext()){
System.out.print(itGoods.next());
}
Iterator(迭代器)
Iterrator接口以统一的方式对各种集合元素进行遍历
//调用集合的iterator方法得到Iterator对象
Iterator<String> it=set.iterator();
while(it.hasNext()){//判断迭代器中是否还有下一个内容
System.out.print(it.next()+" ")
}
List集合排序
使用Collections.sort()对list中的数据排序
List<Integer> list=new ArrayList<Integer>();
list.add(2);list.add(6);list.add(1);
Collections.sort(list);//排序
for(int a:list){
System.out.println(n+" ");}
用比较器Comparator对类型的名字进行排序
//比较器
//创建类NameComparator实现接口Comparator<T>,实现方法compare
public int compare(Car o1,Cat o2){
//按名字升序排序,调用cat类中的getName方法
String naem1=o1.getName();
String name2=02.getName();
//比较,name1>name2返回正整数,name1=name2返回0,name1<name2返回负数
int n=name1.compareTo(name2);//降序则改变name1和name2的位置
return n;
//上面是对String类型排序。如果对int类型的年龄排序,获取年龄后相减
int age1=o1.getMonth();int age2=o1.getMonth();
return age1-age2;//升序排序,降序排序改为age2-age1
}
//cat类有姓名,年龄,品种三个属性。在主函数中测试
Cat huahua =new Cat("huahua",5,"英国短毛猫");
Cat fanfan=new Cat("fanfan",2,"中华田园猫");
Cat maomao=new Cat("maomao",3,"英国短毛猫");
List<Cat> list=new ArrayList<Cat>();
list.add(huahua);list.add(fanfan);list.add(maomao);
Collections.sort(list,new NameComparator());
for(Cat cat:list){ System.out.println(cat);}
Comparable接口排序
此接口强行对实现他的每个类的对象进行整体排序。
这种排序被称为类的自然排序,类的compareTo方法称为自然比较方法。
集合调用Collection.sort进行排序,数组调用Array.sort排序。
//商品类实现了Comparable<商品类>接口,有编号,名称,价格属性。且实现了接口中的compareTo方法
public int compareTo(商品类 o){
//获取商品价格
double p1=this.getPrice();double p2=o.getPrice();
//取出double的整型部分
int n=new Double(p1-p2).intValue();
return n;
}
//在测试类中添加数据后,直接调用Collection.sort();传入商品的集合对象即可排序
Comparator和Comparable的区别
TreeSet
TreeSet<>是一个有序的集合,他支持自然排序和根据实现Comparator或Comparable接口进行排序。默认按字母升序排序。
当TreeSet中添加自定义类的对象时,实现Comparator
TreeSet a=new TreeSet<>(new 实现Comparator的类());
a.add(对象);之后添加对象后会自动排序
泛型
优点:不用进行强制类型转换,避免运行时异常的安全隐患
//泛型作为参数,想把泛型的子类也传进来
public void sellGoods(List<? extends Goods> goods){}
//父类引用
public void sellGoods(List<? super Goods> goods){}
十一、线程
进程:进程是资源分配的基本单位,它是程序执行时的一个实例,在程序运行时创建。
线程:比进程还要小的运行单位,一个进程包含多个线程。
创建线程方式一:继承Thread类
继承Thread类,重写run()方法来创建线程
注意:线程开启后不一定立即执行,由cpu调度执行
创建线程方式二:实现Runnable接口
- 只有一个方法run()
- Runnable是java中用以实现线程的接口
- 任何线程功能的类都必须实现该接口
实现Runnable接口,重写run()方法来创建线程
注意:启动实现Runnable接口的类需要创建Thread对象调用start()方法启动,也就是需要三步。
下面启动方法可以直接:new Thread(new TextThread3()).start();
推荐使用方法二:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
创建线程方式三:实现callable接口
实现callable接口,重写call()方法,call作为线程的执行体,具有放回值,并且可以对异常进行申明和抛出,使用start()方法来启动线程。使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
线程的生命周期
线程的状态:新建,可运行,运行,阻塞,终止。五种关系的相互转换状态称为生命周期
Thread类常用方法的使用⬇
sleep方法
try{
//在指定的毫秒内让正在执行的线程休眠(暂停执行)
Thread.sleep(1000);//静态方法类名直接调用,停止1000毫秒
}catch(){}
join方法
调用该方法的线程结束后其他线程才能执行
//线程MyThread继承了Thread类
MyThread mt=new MyThread();
try{
//join(1)里面可以加入毫秒参数,代表此线程执行一毫秒后其他线程才能执行
mt.join();
}catch(){}
线程的优先级
一、优先级可以用1-10表示,数字越大优先级越高,超过范围会抛出异常。
二、主线程也就是main()方法默认优先级为5。
三、设置优先级并不一定优先级高的先执行
优先级常量:
线程最高优先级10:MAX_PRIORITY
线程最低优先级1:MIN_PRIORITY
线程默认优先级5:NORM_PRIORITY
//线程MyThread继承了Thread类
MyThread mt=new MyThread();
//currentThread()获取当前线程,getPriority()获取线程的优先级
Thread.currentThread().getPriority();
//设置线程优先级为10也可以写成mt.setPriority(Thread.MAX_PRIORITY);
mt.setPriority(10);
mt.getPriority();//获取mt线程的优先级
同步(synchronized)
使用关键字synchronized来进行同步,不允许其他线程对此方法进行打断.
语句块的使用例子:
void abc(){
synchronized(this){//方法体内执行过程中不允许被打断
方法体
}
}
十二、多线程
为什么要使用线程池?
1.反复创建线程开销大
2.过多的线程会占用太多的内存
线程池的重要性:
1.用少量的线程——避免内存占用过多
2.让这部分线程都保持工作,且可以反复执行任务——避免生命周期的损耗
3.加快响应速度
4.合理利用CPU和内存
5.统一管理
线程池适合应用的场景:
服务器接受到大量请求时,使用线程池技术非常的合适,它可以大大减少线程的创建和销毁次数,提高服务器的工作效率
实际上,在开发中,如果需要创建5个以上的线程,那么就可以使用线程池来管理
等待队列也已经排满了,再也塞不下新任务了同时,线程池中的max线程也达到了,无法继续为新任务服务。
这时候我们就需要拒绝策略机制合理的处理这个问题。
JDK拒绝策略:new ThreadPoolExecutor.AbortPolicy()
- AbortPolicy(默认):直接抛出 RejectedExecutionException异常阻止系统正常运知。
- CallerRunsPolicy:"调用者运行"一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
- DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。
- DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。
以上内置拒绝策略均实现了RejectedExecutionHandler接口。
workQueue工作队列
直接交接:SynchronousQueue没有队列
无界队列:LinkedBlockingQueue无限队列
有界队列:ArryBlockingQueue经典队列
延迟队列:DelayedWordQueue
子任务队列:workStealingPool 1.8后加入
添加线程的规则
jdk线程池对比
创建线程池:Executors.线程池
newFixedThreadPool:核心线程自定义固定,使用无限队列,容易造成大量内存占用
newSingleThreadExecutor:核心线程数固定为1,和上面类似
newCachedTreadPool:核心线程为零,最大线程数无限,60s自动回收线程。会导致创建非常多的线程
newScheduledThreadPool:支持定时及周期性任务执行的线程池
正确的创建线程池的方法:根据不同的业务场景创建
线程池里的线程设定为多少比较合适?
- CPU密集型(加密、计算hash等):最佳线程数为CPU核心数的1-2倍左右。
- 耗时IO型(读写数据库、文件、网络读写等):最佳线程数一般会大于CPU核心数很多倍
- 参考Brain Goetz计算方法:线程数=CPU核心数*(1+平均等待时间/平均工作时间)
停止、暂停、恢复线程池的方法
- executtorService.shutdown()停止线程池,但仍然会执行线程,直到所有线程执行完毕。停止状态。此时提交新任务到线程池会报错
- executtorService。isShutdown()返回ture和false,判断线程池是否在停止状态
- executtorService.isTerminated()返回ture和false,判断线程池是否彻底停止,所有线程已执行完毕
- executtorService.awaitTerminnation(时间),判断在一定时间内线程池是否已彻底停止,返回ture和false
- executtorService。shutdownNow()立刻彻底关闭线程池且会返回队列中的线程
线程池状态
线程池组成部分:线程池管理器,工作队列,工作线程,任务接口
自定义线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolExecutorDemo {
public static void doSomething(ExecutorService executorService, int numOfRequest) {
try {
System.out.println(((ThreadPoolExecutor)executorService).getRejectedExecutionHandler().getClass() + ":");
TimeUnit.SECONDS.sleep(1);
for (int i = 0; i < numOfRequest; i++) {
final int tempInt = i;
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + "\t 给用户:" + tempInt + " 办理业务");
});
}
TimeUnit.SECONDS.sleep(1);
System.out.println("\n\n");
} catch (Exception e) {
System.err.println(e);
} finally {
executorService.shutdown();
}
}
//创建线程池ThreadPoolExecutor
public static ExecutorService newMyThreadPoolExecutor(int corePoolSize,
int maximumPoolSize, int blockingQueueSize, RejectedExecutionHandler handler){
return new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
1,//keepAliveTime
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(blockingQueueSize),
Executors.defaultThreadFactory(),
handler);
}
public static void main(String[] args) {
doSomething(newMyThreadPoolExecutor(2, 5, 3, new ThreadPoolExecutor.AbortPolicy()), 10);
doSomething(newMyThreadPoolExecutor(2, 5, 3, new ThreadPoolExecutor.CallerRunsPolicy()), 20);
doSomething(newMyThreadPoolExecutor(2, 5, 3, new ThreadPoolExecutor.DiscardOldestPolicy()), 10);
doSomething(newMyThreadPoolExecutor(2, 5, 3, new ThreadPoolExecutor.DiscardPolicy()), 10);
}
}
```c
class java.util.concurrent.ThreadPoolExecutor$AbortPolicy:
pool-1-thread-1 给用户:0 办理业务
pool-1-thread-3 给用户:5 办理业务java.util.concurrent.RejectedExecutionException: Task com.lun.concurrency.MyThreadPoolExecutorDemo$$Lambda$1/303563356@eed1f14 rejected from java.util.concurrent.ThreadPoolExecutor@7229724f[Running, pool size = 5, active threads = 0, queued tasks = 0, completed tasks = 8]
pool-1-thread-2 给用户:1 办理业务
pool-1-thread-5 给用户:7 办理业务
pool-1-thread-3 给用户:3 办理业务
pool-1-thread-4 给用户:6 办理业务
pool-1-thread-1 给用户:2 办理业务
pool-1-thread-2 给用户:4 办理业务
class java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy:
pool-2-thread-1 给用户:0 办理业务
pool-2-thread-2 给用户:1 办理业务
pool-2-thread-1 给用户:2 办理业务
pool-2-thread-3 给用户:5 办理业务
pool-2-thread-3 给用户:7 办理业务
pool-2-thread-3 给用户:9 办理业务
pool-2-thread-4 给用户:6 办理业务
pool-2-thread-2 给用户:3 办理业务
pool-2-thread-5 给用户:8 办理业务
main 给用户:10 办理业务
pool-2-thread-1 给用户:4 办理业务
pool-2-thread-3 给用户:11 办理业务
pool-2-thread-4 给用户:13 办理业务
main 给用户:14 办理业务
pool-2-thread-1 给用户:12 办理业务
pool-2-thread-5 给用户:15 办理业务
pool-2-thread-2 给用户:17 办理业务
main 给用户:18 办理业务
pool-2-thread-3 给用户:16 办理业务
pool-2-thread-4 给用户:19 办理业务
class java.util.concurrent.ThreadPoolExecutor$DiscardOldestPolicy:
pool-3-thread-1 给用户:0 办理业务
pool-3-thread-2 给用户:1 办理业务
pool-3-thread-1 给用户:2 办理业务
pool-3-thread-2 给用户:3 办理业务
pool-3-thread-3 给用户:5 办理业务
pool-3-thread-5 给用户:8 办理业务
pool-3-thread-2 给用户:7 办理业务
pool-3-thread-4 给用户:6 办理业务
pool-3-thread-1 给用户:4 办理业务
pool-3-thread-3 给用户:9 办理业务
class java.util.concurrent.ThreadPoolExecutor$DiscardPolicy:
pool-4-thread-1 给用户:0 办理业务
pool-4-thread-2 给用户:1 办理业务
pool-4-thread-1 给用户:2 办理业务
pool-4-thread-2 给用户:3 办理业务
pool-4-thread-3 给用户:5 办理业务
pool-4-thread-3 给用户:9 办理业务
pool-4-thread-1 给用户:4 办理业务
pool-4-thread-5 给用户:8 办理业务
pool-4-thread-4 给用户:6 办理业务
pool-4-thread-2 给用户:7 办理业务
## ThreadLocal
1.2个线程分别用自己的SimpleDateFormat,这没问题
2.后来延伸出10个
3.但是当需求变成了1000个,那么必然要用线程池。每个线程都new开销也大
4.所有线程都共用一个SimpleDateFormat对象,这是线程不安全的出现了并发安全问题
5.可以选择加锁,但是效率低
6.更好的解决方案式使用ThreadLocal,线程安全,每个线程独有自己的对象,不需要加锁
![在这里插入图片描述](https://img-blog.csdnimg.cn/02ec84a3d6c9417c9bb07440f83112a6.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/079f689ae51148c5ad562516ef29691a.png)
**需要注意,线程或者线程池没关闭ThreadLocal创建的对象会一直存在,会造成内存泄漏**
约定:使用完ThreadLocal之后应该调用remove方法
## 十三、反射
反射是再运行时动态访问类的属性和方法的技术。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200525160948560.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyMDEyODUz,size_16,color_FFFFFF,t_70)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200514091740272.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyMDEyODUz,size_16,color_FFFFFF,t_70)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200514092536188.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyMDEyODUz,size_16,color_FFFFFF,t_70)
**Constructor类(操作构造方法)**
![在这里插入图片描述](https://img-blog.csdnimg.cn/2020052516270755.png)
**Field类(操作属性)**
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200525164256480.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyMDEyODUz,size_16,color_FFFFFF,t_70)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200514092438541.png)
System.out.print(c5.getName());//获得包名加类名
System.out.print(c5.getSimpleName());//获得类名
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200514092710614.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyMDEyODUz,size_16,color_FFFFFF,t_70)
## 十四、javaIO流
file类常用方法:
file.isDirectory()//判断是否是目录,返回true或false
file.isFile()//判断是否是文件,返回true或false
三种创建File对象的方法
```java
/****************三种创建File对象的方法**************************/
File file=new File("c:\\imooc\\io\\student.txt");//方式一
File file=new File("c:\\imooc","io\\student.txt");//方式二
File file=new File("c:\\imooc");
File file1=new File(file,"io\\student.txt");
/**************** 创建目录**************************/
File file=new File("c:\\imooc\\io\\set\\HashSet");
if(!file.exists()){//exists方法判断文件是否存在,如果不存在就新建
file.mkdirs();//mkdirs()创建多级目录,mkdir()创建一级目录
}
/*****创建文件************/
if(!file.exists()){//判断是否存在文件
file.createNewFile();//创建文件,需要处理异常
}
FileInputStream(文件输入流)
FileInputStream(文件输入流):从文件系统的某个文件中获得输入字节流。
为了保证close()方法一定执行,通常把关闭流的操作写在finally代码块中
try{
FileInputStream fis=nwe FileInputStream("imooc.txt");//找到文件保存到fis
int n=0;
//方法一:遍历文件内容,如果==-1代表遍历输出完毕
while((n=fis.read())!=-1){
System.out.println((char)n);//因为n是int型需要强转为char型,否则输出的是数字
}
}
catch(FileNotFoundExcption e){
e.printStackTrace();}catch(IOExcepion e){e.printStackTrace();}
fis.close();//记得关闭输入流
/****************方法二**************************/
FileInputStream fis=nwe FileInputStream("imooc.txt");
byte[] b=new byte[100];
fis.read(b);
System.out.println(new String(b));//强转为字符串
fis.colse();
FileOutputStream(文件输出流):
//输出信息到文件
try{
FileOutputStream fos=nwe FileOutputStream("imooc.txt");//覆盖内容
FileOutputStream fos=nwe FileOutputStream("imooc.txt",true);
fos.write('a');
fos.close();
}
catch(FileNotFoundExcption e){
e.printStackTrace();
}catch(IOExcepion e){e.printStackTrace();}
文件拷贝,复制图片
缓冲输入输出流(BufferedOutputStream和BufferedInputStream)
字节字符转换流
其他字符流(复制)
对象序列化
十五、JVM
java源码编译后变成.class文件
new出来的对象地址不同,但是class loader都是相同的
双亲委派机制:类名和java环境自带的类名相同,那么优先执行自带的类。作用是安全
new Robot()可以实现鼠标按下和键盘的操作
native:有native标识的类表示本地方法栈,说明java的作用范围达不到了。会调用本地方法接口(JNI)
JNI:扩展java的使用,融合不同的编程语言为java所用
什么是反射?
java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能叫做java语言的反射机制
什么是ClassLoader?
ClassLoader的作用是从系统外部获得Class二进制数据流装载进系统,然后交给Java虚拟机进行连接、初始化等操作。它是java的核心组件,所有的Class都是由他进行加载的。
ClassLoader的种类:
1.BootStrapClassLoader:C++编写,加载核心库java.*
2.ExtClassLoder:java编写,加载扩展库javax.*
3.AppClassLoader:java编写,加载程序所在目录
4.自定义ClssLoader:定制化加载
十六、Lambda表达式
函数式接口中有方法需要重写才能使用可以使用lambda表达式。
函数式接口:有且只有一个抽象方法得接口
//返回a*b结果,重写了opcratc方法
//如果参数只有一个可以省略小括号
MathOperation mult=(a,b)->a*b+0f;
System.out.println(mult.opcratc(5,3));
函数式编程
函数式编程是基于函数式接口并使用lambda表达式的编程方式
函数式编程理念是将代码作为可重用数据代入到程序运行中
jdk8后提供了一系列的函数式接口,位于java.utl.function
编写函数式接口可以添加@FunctionalInterface通知编译器这是函数式接口,进行抽象方法检查。起到检查无其他作用
下面方法中Predicate是新增的函数式接口,用于测试传入的数据是否满足判断要求。test方法返回的是boolean值
例子
Stream流式处理
//获取集合中最大的偶数
Optional<Integer> op=Arrays.asList(1,2,3,4,5,6).stream()
.filter(x->x%2==0) //获取偶数
.sorted((a,b)->b-a) //排序,从大到小
.findFirst(); //取第一个
例子
十七、注解和反射
元注解的作用就是负责注解其他注解
- @Target:描述注解使用范围(被描述的注解可以用在什么地方)
- @Retention:表示需要在什么级别保存该注释信息,用于描述注解生命周期
- @Documented:说明该注解将生成在javadoc中
- @Inherited:表示子类可以继承父类的注解
自定义注解:
- 使用@interface用来声明一个注解public @interface ForUpdate {自定义内容}
- 其中的每一个方法实际上申明了一个配置参数
- 方法名称就是参数的名称,返回值类型就是方法的类型(返回值只能是基本类型,Class,String,enum)
- 注解元素必须有值,可以通过default来声明参数的默认值,如果只有一个参数成员,一般参数名为value。
反射(Reflection)
- 反射机制允许程序在执行期通过反射api取得任何类的内部信息,并能直接操作任意对象的内部属性及方法
- 一个类在内存中只有一个class对象
- 一个类被加载后,类的整个结构都会被封装在class对象中
常量因为加载是就已经初始化到方法区中,所以不需要初始化类去调用
反射加自定义注解记录更改日志
package com.zrantech.config;
import java.lang.annotation.*;
/**
* 描述:自定义注解标记那些字段需要记录日志
*/
@Target(ElementType.FIELD) //注解范围为属性
@Retention(RetentionPolicy.RUNTIME) //作用在程序运行时
@Documented //在javadoc文档中记录
public @interface ForUpdate {
//默认为空字符串,只有一个字段推荐名字fieldName改为value
String fieldName() default "";
}
package com.zrantech.utils;
import com.zrantech.config.ForUpdate;
import com.zrantech.hospital.entity.ChangeLog;
import javax.persistence.Table;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 描述:实现记录Bean修改明细,判断是否需要记录日志
*/
public class BeanUpdateUtils {
private static Pattern humpPattern = Pattern.compile("[A-Z]");
/**
* 描述:驼峰转下划线
* @Author: GXY
* @date 2022/4/24
**/
public static String changeUpperToUnderLetter(String para){
String regExp="([A-Z]{1})"; // 匹配单个字符
Pattern pattern = Pattern.compile(regExp);
Matcher matcher = pattern.matcher(para);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
// 把大写的 改成 _小写的内容。匹配大写后改成小写的,前面加一个下划线
matcher.appendReplacement(sb, "_"+matcher.group().toLowerCase());
}
matcher.appendTail(sb);
return sb.toString();
}
/**
* 获取变更内容,返回需要保存的日志
* @param newBean 更改前的Bean
* @param oldBean 更改后的Bean
* @param userId 修改人id
* @param id 字段唯一主键
* @param <T>
* @return list
*/
public static <T> List getChangedFields(T newBean, T oldBean,String userId,String id,int userType){
List list=new ArrayList<>();
//获取newBean的所有属性存到数组中
Field[] fields = newBean.getClass().getDeclaredFields();
ChangeLog changeLog;
for(Field field : fields) {
//设置可以获取变量名 和 变量的值.
field.setAccessible(true);
//判断注释ForUpdate是否在此field字段上
if (field.isAnnotationPresent(ForUpdate.class)) {
try {
//获取属性值
Object newValue = field.get(newBean);
Object oldValue = field.get(oldBean);
//判断两个对象值是否相等,不相等才保存
if(!Objects.equals(newValue, oldValue)) {
//保存
changeLog=new ChangeLog();
changeLog.setTableName(newBean.getClass().getAnnotation(Table.class).name());
changeLog.setUpdateTime(new Date());
changeLog.setChangeField(changeUpperToUnderLetter(field.getName()));
changeLog.setBeforeChange(newValue.toString()); //更改前
changeLog.setAfterChange(oldValue.toString());//更改后
changeLog.setPrimaryKey(id);
changeLog.setUpdateUserId(userId);
changeLog.setUserIdType(userType);
changeLog.setText(field.getAnnotation(ForUpdate.class).fieldName());
list.add(changeLog);
}
} catch (Exception e) {
System.out.println(e);
}
}
}
return list;
}
}
package com.zrantech.hospital.entity;
import com.zrantech.base.comment.Comment;
import com.zrantech.base.entity.BaseEntity;
import com.zrantech.config.ForUpdate;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
/**
* 病人建档实体类
* @author zuguo
*/
@Entity
@Table(name = "his_patient")
@Data
@NoArgsConstructor
@Comment("患者建档表")
public class Patient extends BaseEntity {
@Id
@Comment("主键")
@GeneratedValue(generator = "system-uuid")
@Column(length = 35)
private String id;
@Comment("建档类型_见字典_1-成人 2-儿童")
@Column(length = 50)
private String setupType;
@Comment("证件类别_见字典 证件类型")
@Column(length = 50)
private String idType;
@Comment("证件号_为空时是临时卡")
@Column(length = 20, unique = true)
private String idNo;
@Comment("门诊号,患者医院唯一编号,his返回")
@Column(length = 50, unique = true)
private String patientId;
@Comment("病人类别_见字典_自费,医保")
@Column(length = 50)
private String patientType;
@Comment("姓名")
@Column(length = 20)
private String patientName;
@Comment("民族_见字典")
@Column(length = 50)
private String nation;
@Comment("婚姻_见字典")
@Column(length = 50)
private String marriage;
@Comment("性别_1男,2女,3其他")
@Column(length = 50)
private String sex;
@Comment("出生日期_格式:yyyy-MM-dd")
@Column(length = 12)
private String birthday;
@ForUpdate(fieldName = "联系电话")
@Comment("联系电话")
@Column(length = 15)
private String phone;
@Comment("年龄")
@Column(length = 3)
private Integer age;
@ForUpdate(fieldName = "详细地址")
@Comment("详细地址")
private String address;
@ForUpdate(fieldName = "家庭地址")
@Comment("家庭地址")
private String homeAddress;
@Comment("家庭地址编码")
@ForUpdate(fieldName = "家庭地址编码")
private String homeAddressCode;
@Comment("监护人姓名")
private String guardianName;
@Comment("监护人身份证号")
private String guardianIdNo;
@Comment("监护人联系电话")
private String guardianPhone;
@Comment("指派的客服id,关联sys_user表的id,非会员")
private String customerId;
@Comment("指派的健康管家id,关联sys_user表的id,会员")
private String managerId;
@Comment("指派的健康管家所属的组id")
private String groupId;
@Comment("是否能评价")
@Column(columnDefinition = "bit DEFAULT 0")
private boolean evaluate;
@Comment("证件类型,字典")
private String cardType;
}
十八、代码规范
1.Map/Set的key为自定义对象时,必须重写hashCode和equals
解释:Set hashSet = new HashSet();
hashSet.add(new ObjDemo(“1”,“对象1”));
hashSet.add(new ObjDemo(“1”,“对象1”));
我们逻辑上放进去的是一个对象,因为id、name都相等。但是会发现在hashSet中却有两个对象,因为这两个对象的内存地址是不同的。所以要重写equals()和hashCode()方法。
@Override
public boolean equals(Object o) {
if (o instanceof ObjDemo){
ObjDemo obj = (ObjDemo)o;
return id.equals(obj.id);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(id);
}